"""终态费用事实与一次外发边界;使用内存事务替身,不访问数据库或模型。""" import asyncio import hashlib from contextlib import contextmanager from copy import deepcopy from decimal import Decimal from types import SimpleNamespace import pytest import muse.任务运行.调用结算 as 结算模块 from muse.任务运行.接口 import 模型结果, 模型请求 from muse.任务运行.模型 import 状态冲突 from muse.任务运行.调用治理 import 受控发送 from muse.任务运行.调用结算 import 结算调用, 调用结算请求 from muse.共享.调用身份 import 用途 class 游标: def __init__(self, row): self.row = row def fetchone(self): return self.row class 账本: 用途 = 用途.评测 def __init__(self): self.task = { "author_id": "author", "state": "cancelled", "last_sequence": 8, "accounting_revision": 2, } self.call = { "call_id": "call", "task_id": "task", "attempt_id": "attempt", "account_id": "account", "state": "unknown", "actual_amount": None, } self.receipts = {} self.statements = [] @contextmanager def 连接(self): yield self @contextmanager def transaction(self): 原 = deepcopy((self.task, self.call, self.receipts)) try: yield self except Exception: self.task, self.call, self.receipts = 原 raise class 存储替身: def __init__(self, db, purpose): self.db = db def 任务行(self, task, **kwargs): assert task == "task" return self.db.task def 查询(self, sql, args=()): self.db.statements.append(sql) if sql.startswith("SELECT request_hash"): return 游标(self.db.receipts.get(args[1])) if "SELECT * FROM {s}.muse_budget_reservation" in sql: return 游标(self.db.call if args[1] == "call" else None) if sql.startswith("SELECT accounting_revision"): return 游标(self.db.task) if sql.startswith("INSERT INTO {s}.muse_call_settlement"): self.db.receipts[args[1]] = {"request_hash": args[3], "receipt": args[4].obj} return 游标(None) raise AssertionError(sql) class 预算替身: def __init__(self, db, account): self.db = db def 结算于(self, conn, call, amount, *, 回执ID): if self.db.call["state"] != "unknown": raise 状态冲突("不能改写已结费用") self.db.call.update(state="settled", actual_amount=amount, receipt_id=回执ID) self.db.task["accounting_revision"] += 1 def 请求(**changes): statement = "作者核对账单2026-09-17第12行,实际费用0.15美元。" return 调用结算请求( **{ "command_id": "settle-1", "expected_revision": 2, "amount": "0.15", "currency": "USD", "source_reference": "账单第12行", "source_statement": statement, "source_hash": hashlib.sha256(statement.encode()).hexdigest(), **changes, } ) @pytest.mark.case_id("NC-O02-SETTLE-TERMINAL") def test_取消任务结费幂等且不修改执行状态(monkeypatch): db = 账本() monkeypatch.setattr(结算模块, "任务存储", 存储替身) monkeypatch.setattr(结算模块, "预算管理", 预算替身) receipt = 结算调用(db, "task", "call", "author", 请求()) assert receipt["author_id"] == "author" assert receipt["accounting_revision"] == 3 and receipt["execution_final_sequence"] == 8 assert db.task["state"] == "cancelled" and db.task["last_sequence"] == 8 assert db.call["actual_amount"] == Decimal("0.15") assert 结算调用(db, "task", "call", "author", 请求()) == receipt with pytest.raises(状态冲突, match="不能改变"): 结算调用(db, "task", "call", "author", 请求(amount="0.16")) assert not any( "muse_attempt" in s or "muse_step" in s or "muse_task_event" in s for s in db.statements ) @pytest.mark.case_id("NC-O02-SETTLE-SCOPE") def test_错作者错调用和旧账目版本不能结算(monkeypatch): db = 账本() monkeypatch.setattr(结算模块, "任务存储", 存储替身) monkeypatch.setattr(结算模块, "预算管理", 预算替身) for call, author, req in [ ("call", "other", 请求()), ("other", "author", 请求()), ("call", "author", 请求(expected_revision=1)), ]: with pytest.raises(状态冲突): 结算调用(db, "task", call, author, req) assert db.call["state"] == "unknown" and not db.receipts @pytest.mark.case_id("NC-O02-SETTLE-EVIDENCE") def test_零费用缺明确确认和伪造来源哈希均拒绝(): with pytest.raises(状态冲突, match="零费用"): 请求(amount="0") with pytest.raises(状态冲突, match="来源哈希"): 请求(source_hash="0" * 64) with pytest.raises(状态冲突, match="提供方认证"): 请求(source_kind="provider_receipt") assert 请求(amount="0", zero_cost_confirmed=True).amount == "0.000000" class 发送预算: def __init__(self, 允许=True): self.允许 = 允许 self.events = [] def 预留(self, *args, **kwargs): self.events.append("reserved") def 标记已发送(self, *args, **kwargs): kwargs["发送复检"](None) self.events.append("sent") return SimpleNamespace(允许外发=self.允许) def 取消预留(self, *args): self.events.append("release") def 结算(self, call, amount, *, 回执ID): self.events.append(("settle", amount, 回执ID)) return SimpleNamespace(状态="unknown" if amount is None else "settled") class 宿主: def __init__(self, failure=None): self.calls = 0 self.failure = failure def 准备(self, request): return self async def 调用(self): self.calls += 1 if self.failure: raise self.failure return 模型结果("completed", '{"ok": true}', "model", None, "response") def _模型请求(): return 模型请求("call", "provider", "model", "probe", "probe", {}, 64, 60, thinking="off") @pytest.mark.case_id("NC-O02-GOVERNED-CANCEL") def test_发送取消不推定免费(): budget, host = 发送预算(), 宿主(asyncio.CancelledError()) pricing = SimpleNamespace(金额=lambda _: Decimal("0.1")) with pytest.raises(asyncio.CancelledError): asyncio.run( 受控发送(budget, None, _模型请求(), "extractor", host, pricing, 发送复检=lambda _: None) ) assert host.calls == 1 and budget.events[-1] == ("settle", None, "call") @pytest.mark.case_id("NC-O02-GOVERNED-ONCE") def test_已发送调用不会再次外发(): budget, host = 发送预算(False), 宿主() with pytest.raises(状态冲突): asyncio.run( 受控发送(budget, None, _模型请求(), "extractor", host, None, 发送复检=lambda _: None) ) assert host.calls == 0 @pytest.mark.case_id("NC-O02-GOVERNED-PRICING") def test_计价失败仍登记未知费用(): budget, host = 发送预算(), 宿主() def broken(_): raise ValueError("rate missing") result, settlement = asyncio.run( 受控发送( budget, None, _模型请求(), "extractor", host, SimpleNamespace(金额=broken), 发送复检=lambda _: None, ) ) assert result.响应ID == "response" and settlement.状态 == "unknown" assert budget.events[-1] == ("settle", None, "response") @pytest.mark.case_id("NC-O02-HTTP-CONTRACT") def test_HTTP结算验证材料哈希并返回认证作者(monkeypatch): from dataclasses import asdict from fastapi import FastAPI from fastapi.testclient import TestClient from muse.任务运行.接口 import 任务服务 from muse.接入.http.作者会话 import 要求作者 from muse.接入.http.路由.任务运行 import 路由 db = 账本() monkeypatch.setattr(结算模块, "任务存储", 存储替身) monkeypatch.setattr(结算模块, "预算管理", 预算替身) service = 任务服务(db, None) app = FastAPI() app.state.装配 = SimpleNamespace(任务运行=service) app.dependency_overrides[要求作者] = lambda: "author" app.include_router(路由) with TestClient(app) as client: body = asdict(请求()) response = client.post("/api/v1/tasks/task/calls/call/settlements", json=body) assert response.status_code == 200, response.text assert response.json()["author_id"] == "author" assert response.json()["amount"] == "0.150000" assert response.json()["state"] == "cancelled" assert ( client.post( "/api/v1/tasks/task/calls/call/settlements", json={**body, "author_id": "other"} ).status_code == 422 )