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