mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 03:51:58 +00:00
56 lines
2.8 KiB
Python
56 lines
2.8 KiB
Python
"""口径调用审计(app/ledger/usage.py):谁在什么时候用了哪一版、带什么参数。
|
||
|
||
- 参数只记指纹和字段名,不记值;
|
||
- composed(问数据里 use_caliber)和 issued(按口径正式出具)各记一条 caliber.use;
|
||
- 同一组参数写法不同(键顺序、空值)指纹相同;参数不同指纹不同。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import uuid
|
||
|
||
from sqlalchemy import select
|
||
|
||
from app.db.base import SessionLocal
|
||
from app.db.models import AuditEvent
|
||
from app.ledger.usage import audit_composed_uses, params_fingerprint, record_use
|
||
|
||
|
||
def test_params_fingerprint_ignores_order_and_blanks_but_not_values():
|
||
first, fields = params_fingerprint({"start": "2026-09-01", "end": "2026-09-08", "store": ""})
|
||
second, _ = params_fingerprint({"end": "2026-09-08", "start": "2026-09-01", "store": None})
|
||
assert first == second and fields == ["end", "start"]
|
||
other, _ = params_fingerprint({"start": "2026-09-01", "end": "2026-09-07"})
|
||
assert other != first
|
||
|
||
|
||
async def test_record_use_keeps_a_fingerprint_not_the_values():
|
||
wf = f"usage{uuid.uuid4().hex[:10]}"
|
||
async with SessionLocal() as session:
|
||
await record_use(session, workflow_id=wf, version=3, how="issued", card="card", metrics=["gmv", "gmv"],
|
||
params={"start": "2026-09-01", "end": "2026-09-08"}, run_id="run-1", auto=True,
|
||
actor="王敏")
|
||
await session.commit()
|
||
[event] = (await session.execute(select(AuditEvent).where(
|
||
AuditEvent.action == "caliber.use", AuditEvent.subject_id == f"{wf}@3"))).scalars().all()
|
||
assert event.actor == "王敏" and event.subject_kind == "caliber"
|
||
assert event.data["how"] == "issued" and event.data["metrics"] == ["gmv"]
|
||
assert event.data["run_id"] == "run-1" and event.data["auto"] is True
|
||
assert event.data["param_fields"] == ["end", "start"] and event.data["params_hash"]
|
||
assert "2026-09-01" not in str(event.data) and "2026-09-08" not in str(event.data)
|
||
|
||
|
||
async def test_composed_uses_are_written_as_one_audit_each():
|
||
wf = f"usage{uuid.uuid4().hex[:10]}"
|
||
count = await audit_composed_uses([
|
||
{"workflow_id": wf, "version": 1, "card": "card", "metrics": ["gmv"],
|
||
"inputs": {"start": "2026-09-01", "end": "2026-09-08"}},
|
||
{"workflow_id": wf, "version": 2, "card": "card2", "metrics": ["orders"], "inputs": {}},
|
||
], conversation_id="conv-1")
|
||
assert count == 2
|
||
async with SessionLocal() as session:
|
||
rows = list((await session.execute(select(AuditEvent).where(
|
||
AuditEvent.action == "caliber.use", AuditEvent.subject_kind == "caliber")
|
||
.where(AuditEvent.subject_id.in_([f"{wf}@1", f"{wf}@2"])))).scalars())
|
||
assert {r.subject_id for r in rows} == {f"{wf}@1", f"{wf}@2"}
|
||
assert all(r.data["how"] == "composed" and r.data["conversation_id"] == "conv-1" for r in rows)
|