dotb_ai-platform/tests/test_app_credential_application.py
hungtran24 b1f6643796 refactor: score synchronously and drop webhook/background execution
POST /v1/lead-scoring/score now evaluates in the request, applies an overall
AI deadline, and stores the completed run only after scoring. Idempotency is
kept by (app_id, account_id, request_id) with a body fingerprint.

Removes the webhook outbox, scheduler/recovery loop, app lead_scoring
permission, credential audit events, seed_demo and generate_secret scripts,
and the tests that covered them. Trims tests to behavior of the new flow.
2026-10-09 02:44:24 +07:00

98 lines
3.3 KiB
Python

from dataclasses import replace
from datetime import UTC, datetime
import pytest
from ai_platform.platform.application.app_credentials import (
AppCredentialConflict,
AppCredentialNotFound,
AppCredentialRecord,
AppCredentialUseCases,
)
from ai_platform.platform.security import digest
class Repository:
def __init__(self):
self.record = AppCredentialRecord("omni-local", True, datetime.now(UTC), datetime.now(UTC))
self.verifier = digest("initial-key")
self.error: Exception | None = None
async def list(self):
return [self.record]
async def active_app_for_verifier(self, verifier):
return self.record if self.record.active and verifier == self.verifier else None
async def create(self, app_id, verifier):
if self.error:
raise self.error
self.record = replace(self.record, app_id=app_id)
self.verifier = verifier
async def rotate(self, app_id, verifier):
if self.error:
raise self.error
self.verifier = verifier
self.record = replace(self.record, active=True)
async def revoke(self, app_id):
if self.error:
raise self.error
self.record = replace(self.record, active=False)
return self.record
class Transaction:
def __init__(self):
self.commits = 0
self.rollbacks = 0
self.error: Exception | None = None
async def commit(self):
self.commits += 1
if self.error:
raise self.error
async def rollback(self):
self.rollbacks += 1
async def test_lifecycle_preserves_identity_and_stores_only_verifier():
repo, tx = Repository(), Transaction()
keys = iter(["created-key", "rotated-key", "reactivated-key"])
cases = AppCredentialUseCases(repo, tx.commit, tx.rollback, lambda: next(keys))
assert await cases.create("omni-local") == "created-key"
assert repo.verifier == digest("created-key")
assert await cases.authenticate("created-key") == "omni-local"
assert await cases.rotate("omni-local") == "rotated-key"
assert await cases.authenticate("created-key") is None
assert await cases.authenticate("rotated-key") == "omni-local"
assert not (await cases.revoke("omni-local")).active
assert await cases.authenticate("rotated-key") is None
await cases.rotate("omni-local")
assert await cases.authenticate("reactivated-key") == "omni-local"
assert tx.commits == 4 and tx.rollbacks == 0
@pytest.mark.parametrize("operation", ["create", "rotate", "revoke"])
@pytest.mark.parametrize(
"error", [AppCredentialConflict(), AppCredentialNotFound(), RuntimeError("storage")]
)
async def test_repository_error_rolls_back(operation, error):
repo, tx = Repository(), Transaction()
repo.error = error
cases = AppCredentialUseCases(repo, tx.commit, tx.rollback, lambda: "key")
with pytest.raises(type(error)):
await getattr(cases, operation)("omni-local")
assert tx.commits == 0 and tx.rollbacks == 1
async def test_failed_commit_rolls_back():
repo, tx = Repository(), Transaction()
tx.error = RuntimeError("commit failed")
cases = AppCredentialUseCases(repo, tx.commit, tx.rollback, lambda: "key")
with pytest.raises(RuntimeError, match="commit failed"):
await cases.rotate("omni-local")
assert tx.commits == 1 and tx.rollbacks == 1