from __future__ import annotations import uuid from datetime import UTC, datetime, timedelta from decimal import Decimal import pytest from commercial_runtime_testkit import seed_meter, seed_run, seed_tool_call from sqlalchemy import create_engine, select from sqlalchemy.orm import Session, sessionmaker from sqlalchemy.pool import StaticPool import app.models # noqa: F401 - 注册完整 metadata from app.db.base_class import Base from app.models.commercial import UsageMeterEvent from app.models.commercial_runtime import CommercialRuntimeReservation from app.services.agent_runs import AgentRunService from app.services.commercial_entitlements import CommercialEntitlementService from app.services.commercial_runtime_bridge import CommercialRuntimeBridge from app.services.commercial_runtime_reconciler import CommercialRuntimeReconciler @pytest.fixture() def db() -> Session: engine = create_engine( "sqlite+pysqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool, ) Base.metadata.create_all(engine) factory = sessionmaker(bind=engine, expire_on_commit=False) with factory() as session: yield session Base.metadata.drop_all(engine) engine.dispose() def test_reserved_success_settles_real_usage_and_replays_idempotently( db: Session, monkeypatch: pytest.MonkeyPatch, ) -> None: now = datetime.now(UTC) _, entitlement = seed_meter(db, "tenant-a", now, basis="call", hard_limit=Decimal("2")) run = seed_run(db, now, route_json={"tenant_id": "tenant-a"}) tool_call_id = str(uuid.uuid4()) bridge = CommercialRuntimeBridge(db) permit = bridge.reserve_tool( run.run_id, tool_call_id=tool_call_id, tool_type="llm", tool_name="chat.completions", ) reservation = db.get(CommercialRuntimeReservation, permit.reservation_id) assert permit.gate.allowed is True assert reservation is not None and reservation.status == "reserved" assert db.query(UsageMeterEvent).count() == 0 run_service = AgentRunService(db) monkeypatch.setattr(run_service, "_ensure_ready", lambda: None) run_service.record_tool_call( run_id=run.run_id, tool_call_id=tool_call_id, tool_type="llm", tool_name="chat.completions", status="succeeded", ) replay = bridge.sync_tool_call(tool_call_id) db.refresh(reservation) quota = CommercialEntitlementService(db).get_account_for_tenant("tenant-a").quotas[0] assert reservation.status == "committed" assert Decimal(reservation.actual_quantity or 0) == Decimal("1") assert replay.status == "replayed" assert db.query(UsageMeterEvent).count() == 1 assert quota.entitlement.id == entitlement.id assert quota.used_quantity == Decimal("1") assert quota.reserved_quantity == Decimal("0") @pytest.mark.parametrize( ("tool_status", "business_occurred"), [("failed", True), ("blocked", False)], ) def test_failed_or_blocked_tool_releases_reservation_without_usage( db: Session, monkeypatch: pytest.MonkeyPatch, tool_status: str, business_occurred: bool, ) -> None: now = datetime.now(UTC) seed_meter(db, "tenant-a", now, basis="call", hard_limit=Decimal("1")) run = seed_run(db, now, route_json={"tenant_id": "tenant-a"}) tool_call_id = str(uuid.uuid4()) permit = CommercialRuntimeBridge(db).reserve_tool( run.run_id, tool_call_id=tool_call_id, tool_type="llm", tool_name="chat.completions", ) run_service = AgentRunService(db) monkeypatch.setattr(run_service, "_ensure_ready", lambda: None) run_service.record_tool_call( run_id=run.run_id, tool_call_id=tool_call_id, tool_type="llm", tool_name="chat.completions", status=tool_status, ) result = CommercialRuntimeBridge(db).sync_tool_call(tool_call_id) reservation = db.get(CommercialRuntimeReservation, permit.reservation_id) assert reservation is not None and reservation.status == "released" assert result.reason_code == "tool_call_not_billable" assert result.business_call_occurred is business_occurred assert db.query(UsageMeterEvent).count() == 0 def test_active_reservation_holds_hard_quota_before_execution(db: Session) -> None: now = datetime.now(UTC) seed_meter(db, "tenant-a", now, basis="call", hard_limit=Decimal("1")) run = seed_run(db, now, route_json={"tenant_id": "tenant-a"}) bridge = CommercialRuntimeBridge(db) first = bridge.reserve_tool( run.run_id, tool_call_id=str(uuid.uuid4()), tool_type="llm", tool_name="chat.completions", ) second = bridge.reserve_tool( run.run_id, tool_call_id=str(uuid.uuid4()), tool_type="llm", tool_name="chat.completions", ) quota = CommercialEntitlementService(db).get_account_for_tenant("tenant-a").quotas[0] assert first.reservation_id is not None assert second.reservation_id is None assert second.gate.allowed is False assert quota.reserved_quantity == Decimal("1") assert quota.hard_limit_remaining == Decimal("0") def test_same_reservation_request_replays_without_consuming_its_own_quota( db: Session, ) -> None: now = datetime.now(UTC) seed_meter(db, "tenant-a", now, basis="call", hard_limit=Decimal("1")) run = seed_run(db, now, route_json={"tenant_id": "tenant-a"}) bridge = CommercialRuntimeBridge(db) tool_call_id = str(uuid.uuid4()) first = bridge.reserve_tool( run.run_id, tool_call_id=tool_call_id, tool_type="llm", tool_name="chat.completions", ) replay = bridge.reserve_tool( run.run_id, tool_call_id=tool_call_id, tool_type="llm", tool_name="chat.completions", ) assert replay.gate.allowed is True assert replay.gate.reason_code == "reservation_replayed" assert replay.reservation_id == first.reservation_id assert db.query(CommercialRuntimeReservation).count() == 1 def test_variable_quantity_requires_hard_max_and_refuses_actual_over_reservation( db: Session, monkeypatch: pytest.MonkeyPatch, ) -> None: now = datetime.now(UTC) seed_meter(db, "tenant-a", now, basis="duration_ms", hard_limit=Decimal("1000")) run = seed_run(db, now, route_json={"tenant_id": "tenant-a"}) bridge = CommercialRuntimeBridge(db) rejected = bridge.reserve_tool( run.run_id, tool_call_id=str(uuid.uuid4()), tool_type="llm", tool_name="chat.completions", requested_quantity=Decimal("500"), ) assert rejected.gate.reason_code == "hard_max_required" tool_call_id = str(uuid.uuid4()) permit = bridge.reserve_tool( run.run_id, tool_call_id=tool_call_id, tool_type="llm", tool_name="chat.completions", requested_quantity=Decimal("500"), hard_max_confirmed=True, ) run_service = AgentRunService(db) monkeypatch.setattr(run_service, "_ensure_ready", lambda: None) run_service.record_tool_call( run_id=run.run_id, tool_call_id=tool_call_id, tool_type="llm", tool_name="chat.completions", status="succeeded", duration_ms=501, ) failed = bridge.sync_tool_call(tool_call_id) reservation = db.get(CommercialRuntimeReservation, permit.reservation_id) assert failed.status == "error" assert failed.requires_reconciliation is True assert reservation is not None and reservation.status == "reserved" assert db.query(UsageMeterEvent).count() == 0 run_service.update_tool_call(tool_call_id, duration_ms=400, status="succeeded") db.refresh(reservation) assert reservation.status == "committed" assert Decimal(db.scalars(select(UsageMeterEvent)).one().quantity) == Decimal("400") def test_expired_historical_meter_does_not_enable_runtime_enforcement(db: Session) -> None: now = datetime.now(UTC) subscription, _ = seed_meter(db, "tenant-a", now, basis="call") subscription.current_period_start = now - timedelta(days=31) subscription.current_period_end = now - timedelta(days=1) db.commit() run = seed_run(db, now, route_json={"tenant_id": "tenant-a"}) permit = CommercialRuntimeBridge(db).reserve_tool( run.run_id, tool_call_id=str(uuid.uuid4()), tool_type="llm", tool_name="chat.completions", ) assert permit.gate.enforced is False assert permit.gate.allowed is True assert permit.gate.reason_code == "runtime_meter_not_configured" assert permit.reservation_id is None def test_legacy_run_without_tenant_skips_bridge_without_reconciliation(db: Session) -> None: now = datetime.now(UTC) _, tool_call = seed_tool_call(db, now, route_json={}) result = CommercialRuntimeBridge(db).sync_tool_call(tool_call.id) assert result.status == "skipped" assert result.reason_code == "legacy_run_without_tenant" assert result.requires_reconciliation is False assert db.query(CommercialRuntimeReservation).count() == 0 def test_direct_call_on_suspended_contract_is_persisted_for_reconciliation( db: Session, monkeypatch: pytest.MonkeyPatch, ) -> None: now = datetime.now(UTC) subscription, _ = seed_meter( db, "tenant-a", now, basis="call", ) subscription.status = "suspended" db.flush() run = seed_run(db, now, route_json={"tenant_id": "tenant-a"}) run_service = AgentRunService(db) monkeypatch.setattr(run_service, "_ensure_ready", lambda: None) call = run_service.record_tool_call( run_id=run.run_id, tool_type="llm", tool_name="chat.completions", status="succeeded", ) reservation = db.scalars(select(CommercialRuntimeReservation)).one() assert reservation.tool_call_id == call.id assert reservation.status == "reconciliation_required" assert db.query(UsageMeterEvent).count() == 0 def test_expired_reconciler_settles_releases_and_preserves_uncertain_holds( db: Session, ) -> None: now = datetime.now(UTC) seed_meter(db, "tenant-a", now, basis="call", hard_limit=Decimal("10")) bridge = CommercialRuntimeBridge(db) reservations: dict[str, CommercialRuntimeReservation] = {} for scenario, run_status in ( ("success", "running"), ("failure", "running"), ("terminal_without_call", "failed"), ("uncertain", "running"), ): run = seed_run( db, now, route_json={"tenant_id": "tenant-a"}, status=run_status, ) tool_call_id = str(uuid.uuid4()) permit = bridge.reserve_tool( run.run_id, tool_call_id=tool_call_id, tool_type="llm", tool_name="chat.completions", ) reservation = db.get(CommercialRuntimeReservation, permit.reservation_id) assert reservation is not None reservation.created_at = now - timedelta(hours=1) reservation.expires_at = now - timedelta(minutes=30) reservations[scenario] = reservation if scenario == "success": seed_tool_call( db, now - timedelta(minutes=45), route_json={"tenant_id": "tenant-a"}, run=run, status="succeeded", )[1].id = tool_call_id elif scenario == "failure": seed_tool_call( db, now - timedelta(minutes=45), route_json={"tenant_id": "tenant-a"}, run=run, status="failed", )[1].id = tool_call_id db.commit() batch = CommercialRuntimeReconciler(db).reconcile_expired(as_of=now, limit=10) for row in reservations.values(): db.refresh(row) assert batch.settled == 1 assert batch.released == 2 assert batch.deferred == 1 assert batch.errors == 0 assert reservations["success"].status == "committed" assert reservations["failure"].status == "released" assert reservations["terminal_without_call"].status == "expired" assert reservations["uncertain"].status == "reserved" assert db.query(UsageMeterEvent).count() == 1