dotb_ai-platform/scripts/export_contract_fixtures.py
hungtran24 2268b6a77a feat: replace tenant auth with app-scoped scoring access
Add app credentials, account-scoped idempotency and per-app terminal notifications. Consolidate the unreleased migration baseline, preserve rule descriptions as AI context, and regenerate provider contracts with regression coverage.
2026-10-02 10:56:58 +07:00

104 lines
3.6 KiB
Python

"""Generate response and signed webhook examples from provider serializers."""
import argparse
import asyncio
import json
from datetime import UTC, datetime
from pathlib import Path
import httpx
from ai_platform.infrastructure.webhook import OmniWebhook
from ai_platform.lead_scoring.application.notifications import Notification
from ai_platform.lead_scoring.application.repository import ScoringRunRecord
from ai_platform.lead_scoring.application.run_use_cases import to_run_detail
from ai_platform.lead_scoring.application.scoring import calculate_run
from ai_platform.presentation.http.routes import to_detail_json, to_json
from ai_platform.presentation.http.run_responses import RunDetail, RunSummary
from ai_platform.presentation.http.schemas import RunRequest
FIXTURES = Path(__file__).resolve().parents[1] / "contracts/lead-scoring/v2/fixtures"
NOW = datetime(2026, 9, 28, tzinfo=UTC)
SECRET = "test-only-webhook-secret-32-characters-long"
RUN_ID = "22222222-2222-4222-8222-222222222222"
EVENT_ID = "11111111-1111-4111-8111-111111111111"
def write_json(name: str, value: dict, *, check: bool) -> None:
path = FIXTURES / name
expected = json.dumps(value, ensure_ascii=False, indent=2) + "\n"
if check:
if not path.exists() or path.read_text() != expected:
raise SystemExit(f"Contract fixture drift: {name}; regenerate provider examples")
else:
path.write_text(expected)
async def main(*, check: bool = False) -> None:
request = RunRequest.model_validate_json(
(FIXTURES / "run-request.json").read_text()
).model_dump(mode="json")
rule_score, breakdown = calculate_run(request)
row = ScoringRunRecord(
id=RUN_ID,
request_id=request["request_id"],
app_id="omni-local",
account_id=request["account_id"],
status="completed",
fingerprint="0" * 64,
profile_version="profile-example",
request=request,
rule_score=rule_score,
ai_score=None,
final_score=rule_score,
degraded=False,
result_mode="rules_only",
error_code=None,
breakdown=breakdown,
created_at=NOW,
completed_at=NOW,
ai_confidence=None,
ai_reasoning=None,
ai_evidence=None,
)
write_json(
"run-summary.json",
RunSummary.model_validate(to_json(row)).model_dump(mode="json"),
check=check,
)
write_json(
"run-detail.json",
RunDetail.model_validate(to_detail_json(to_run_detail(row))).model_dump(mode="json"),
check=check,
)
event = Notification(
EVENT_ID, RUN_ID, row.app_id, row.account_id, row.request_id, "completed", NOW, 0
)
def capture(request: httpx.Request) -> httpx.Response:
write_json(
"webhook-delivery.json",
{
"method": request.method,
"content_type": request.headers["Content-Type"],
"timestamp": request.headers["X-Webhook-Timestamp"],
"body": request.content.decode(),
"signature": request.headers["X-Webhook-Signature"],
},
check=check,
)
return httpx.Response(202)
async with httpx.AsyncClient(transport=httpx.MockTransport(capture)) as client:
assert await OmniWebhook(
client, "https://omni.example.invalid/events", SECRET, lambda: NOW
).send(event)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--check", action="store_true", help="Reject drift without writing artifacts"
)
asyncio.run(main(check=parser.parse_args().check))