from __future__ import annotations from typing import Any import pytest from sqlalchemy import inspect, text from sqlalchemy.engine import Engine from sqlalchemy.exc import DBAPIError, IntegrityError from app.models.savings import ( ProfileBaselineSnapshot, SavingsEvent, SavingsEvidenceLink, SavingsOpportunity, SavingsRealization, ) def _assert_savings_head_schema(engine: Engine) -> None: inspector = inspect(engine) for model in ( ProfileBaselineSnapshot, SavingsOpportunity, SavingsRealization, SavingsEvidenceLink, SavingsEvent, ): table = model.__table__ live_columns = { str(column["name"]): bool(column["nullable"]) for column in inspector.get_columns(table.name, schema="public") } declared_columns = { column.name: bool(column.nullable) for column in table.columns } assert live_columns == declared_columns live_constraint_names = { str(item["name"]) for loader in ( inspector.get_unique_constraints, inspector.get_check_constraints, inspector.get_foreign_keys, ) for item in loader(table.name, schema="public") if item.get("name") } declared_constraint_names = { str(constraint.name) for constraint in table.constraints if constraint.name is not None } assert live_constraint_names == declared_constraint_names live_index_names = { str(item["name"]) for item in inspector.get_indexes(table.name, schema="public") if not item.get("duplicates_constraint") } declared_index_names = {str(index.name) for index in table.indexes} assert live_index_names == declared_index_names for table_name in ("savings_opportunities", "savings_realizations"): constrained_columns = { str(column) for foreign_key in inspector.get_foreign_keys(table_name, schema="public") for column in foreign_key["constrained_columns"] } assert "claim_id" not in constrained_columns assert "claim_item_id" not in constrained_columns with engine.connect() as connection: canonical_index = str( connection.scalar( text( "SELECT indexdef FROM pg_indexes " "WHERE schemaname = 'public' " "AND tablename = 'savings_realizations' " "AND indexname = " "'uq_savings_realizations_actual_canonical_benefit'" ) ) or "" ).lower() trigger_count = int( connection.scalar( text( "SELECT COUNT(*) FROM pg_trigger trigger " "JOIN pg_class relation ON relation.oid = trigger.tgrelid " "WHERE relation.relname = 'savings_events' " "AND trigger.tgname = 'trg_savings_events_append_only' " "AND NOT trigger.tgisinternal" ) ) or 0 ) assert "unique index" in canonical_index assert "realization_type" in canonical_index assert "actual" in canonical_index assert "dedupe_status" in canonical_index assert "canonical" in canonical_index assert trigger_count == 1 def _assert_savings_runtime_invariants(engine: Engine) -> None: opportunity_insert = text( """ INSERT INTO savings_opportunities ( id, tenant_id, opportunity_key, benefit_key, expense_case_id, claim_id, claim_no_snapshot, source_type, source_id, category, value_kind, title, description, exposure_amount, baseline_snapshot_id, baseline_amount, target_amount, estimated_gross, estimated_cost, estimated_net, estimated_low, estimated_high, confidence, currency, reporting_currency, attribution_method, suggested_action, owner_id, owner_name, owner_role ) VALUES ( :id, :tenant_id, :opportunity_key, :benefit_key, :expense_case_id, 'claim-soft-ref', 'CLM-SNAPSHOT', 'policy_adjustment', :id, 'lodging', 'cash', '住宿标准优化', '服务器政策反事实', 20, :baseline_snapshot_id, 100, 80, 20, 0, 20, 15, 25, 1, 'CNY', 'CNY', 'policy_counterfactual', '按职级标准重算', 'finance-owner', '财务负责人', 'finance' ) """ ) realization_insert = text( """ INSERT INTO savings_realizations ( id, tenant_id, realization_key, opportunity_id, expense_case_id, claim_id, realization_type, reversal_of_realization_id, realized_at, recorded_by_id, recorded_by_name, actual_gross, incremental_cost, actual_net, original_currency, reporting_amount, reporting_currency, fx_rate, fx_source, fx_date, fx_version, attribution_method, attribution_ratio, benefit_key, dedupe_status, canonical_realization_id, status ) VALUES ( :id, 'tenant-a', :id, :opportunity_id, 'case-a', 'claim-soft-ref', :realization_type, :reversal_of_realization_id, now(), 'recorder-a', '填报人', :actual_gross, 0, :actual_net, 'CNY', :reporting_amount, 'CNY', 1, 'system-fixed', current_date, 'fx-cny-v1', 'direct', 1, :benefit_key, :dedupe_status, :canonical_realization_id, 'pending_confirmation' ) """ ) def execute_rejected( connection: Any, statement: Any, parameters: dict[str, Any], error_type: type[DBAPIError] = IntegrityError, ) -> None: savepoint = connection.begin_nested() try: with pytest.raises(error_type): connection.execute(statement, parameters) finally: if savepoint.is_active: savepoint.rollback() with engine.connect() as connection: transaction = connection.begin() try: connection.execute( text( """ INSERT INTO expense_cases ( id, tenant_id, case_no, scene_code, title, current_stage, status ) VALUES ( 'case-a', 'tenant-a', 'CASE-A', 'expense_reimbursement', '迁移约束探针', 'payment', 'active' ) """ ) ) connection.execute( text( """ INSERT INTO profile_baseline_snapshots ( id, tenant_id, baseline_key, baseline_type, dimension_type, dimension_id, metric_key, unit, original_currency, baseline_value, sample_count, method, query_fingerprint, data_quality_status, data_quality_score, algorithm_version, policy_version, policy_effective_from, target_resource_type, target_resource_id, frozen_at, frozen_by ) VALUES ( 'baseline-a', 'tenant-a', 'baseline-a', 'policy_counterfactual', 'claim_item', 'item-soft-ref', 'approved_amount', 'currency', 'CNY', 100, 0, 'server_policy', 'query-fingerprint-a', 'complete', 1, 'policy-calculator-v1', 'policy-v1', current_date, 'claim_item', 'item-soft-ref', now(), 'system' ) """ ) ) connection.execute( opportunity_insert, { "id": "opportunity-a", "tenant_id": "tenant-a", "opportunity_key": "opportunity-key-a", "benefit_key": "benefit-a", "expense_case_id": "case-a", "baseline_snapshot_id": "baseline-a", }, ) connection.execute( opportunity_insert, { "id": "opportunity-b", "tenant_id": "tenant-a", "opportunity_key": "opportunity-key-b", "benefit_key": "benefit-b", "expense_case_id": "case-a", "baseline_snapshot_id": "baseline-a", }, ) execute_rejected( connection, opportunity_insert, { "id": "cross-tenant-opportunity", "tenant_id": "tenant-b", "opportunity_key": "cross-tenant-opportunity", "benefit_key": "cross-tenant-benefit", "expense_case_id": "case-a", "baseline_snapshot_id": "baseline-a", }, ) common_actual = { "opportunity_id": "opportunity-a", "realization_type": "actual", "reversal_of_realization_id": None, "actual_gross": 20, "actual_net": 20, "reporting_amount": 20, "canonical_realization_id": None, } connection.execute( realization_insert, { **common_actual, "id": "realization-a", "benefit_key": "benefit-a", "dedupe_status": "pending_review", }, ) connection.execute( realization_insert, { **common_actual, "id": "realization-canonical", "benefit_key": "benefit-canonical", "dedupe_status": "canonical", }, ) execute_rejected( connection, realization_insert, { **common_actual, "id": "realization-canonical-duplicate", "benefit_key": "benefit-canonical", "dedupe_status": "canonical", }, ) execute_rejected( connection, realization_insert, { **common_actual, "id": "realization-wrong-canonical", "benefit_key": "other-benefit", "dedupe_status": "duplicate", "canonical_realization_id": "realization-a", }, ) execute_rejected( connection, realization_insert, { "id": "cross-opportunity-reversal", "opportunity_id": "opportunity-b", "realization_type": "reversal", "reversal_of_realization_id": "realization-a", "actual_gross": -5, "actual_net": -5, "reporting_amount": -5, "benefit_key": "benefit-a", "dedupe_status": "pending_review", "canonical_realization_id": None, }, ) execute_rejected( connection, text( """ UPDATE savings_realizations SET status = 'finance_confirmed', finance_confirmer_id = recorded_by_id, finance_confirmer_name = '同一填报人', confirmed_at = now(), confirmation_note = '不应通过' WHERE id = 'realization-canonical' """ ), {}, ) connection.execute( text( """ UPDATE savings_realizations SET status = 'finance_confirmed', finance_confirmer_id = 'finance-b', finance_confirmer_name = '独立财务', confirmed_at = now(), confirmation_note = '证据完整' WHERE id = 'realization-canonical' """ ) ) assert connection.scalar( text( "SELECT status FROM savings_realizations " "WHERE id = 'realization-canonical'" ) ) == "finance_confirmed" connection.execute( realization_insert, { "id": "valid-reversal", "opportunity_id": "opportunity-a", "realization_type": "reversal", "reversal_of_realization_id": "realization-a", "actual_gross": -5, "actual_net": -5, "reporting_amount": -5, "benefit_key": "benefit-a", "dedupe_status": "canonical", "canonical_realization_id": None, }, ) connection.execute( text( """ UPDATE savings_realizations SET status = 'finance_confirmed', finance_confirmer_id = recorded_by_id, finance_confirmer_name = '冲回财务', confirmed_at = now(), confirmation_note = '全额冲回' WHERE id = 'valid-reversal' """ ) ) connection.execute( text( """ INSERT INTO savings_events ( id, tenant_id, aggregate_type, aggregate_id, opportunity_id, action, actor_id, actor_name, actor_type, request_id, expected_version, result_version, payload_fingerprint ) VALUES ( 'event-a', 'tenant-a', 'opportunity', 'opportunity-a', 'opportunity-a', 'created', 'system', '系统', 'system', 'request-a', 0, 1, 'fingerprint-a' ) """ ) ) execute_rejected( connection, text("UPDATE savings_events SET action = 'tampered' WHERE id = 'event-a'"), {}, DBAPIError, ) execute_rejected( connection, text("DELETE FROM savings_events WHERE id = 'event-a'"), {}, DBAPIError, ) finally: transaction.rollback()