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.
208 lines
6.4 KiB
Python
208 lines
6.4 KiB
Python
from datetime import UTC, datetime
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from ai_platform.lead_scoring.application.admin import (
|
|
AdminScoringUseCases,
|
|
AIProfileVersionConflict,
|
|
AIProviderUnavailable,
|
|
)
|
|
from ai_platform.lead_scoring.application.repository import (
|
|
AIProfileRecord,
|
|
NewAIProfile,
|
|
)
|
|
|
|
|
|
class FakeScoringRepository:
|
|
def __init__(self) -> None:
|
|
self.profiles: dict[str, AIProfileRecord] = {}
|
|
self.audit_events: list[tuple[str, str, str, datetime]] = []
|
|
|
|
async def active_profile_version(self) -> str | None:
|
|
return "profile-v1"
|
|
|
|
async def list_profiles(self):
|
|
return list(self.profiles.values())
|
|
|
|
async def find_profile(self, version: str):
|
|
return self.profiles.get(version)
|
|
|
|
async def activate_profile(self, profile: NewAIProfile):
|
|
for version, current in list(self.profiles.items()):
|
|
self.profiles[version] = AIProfileRecord(
|
|
version=current.version,
|
|
model=current.model,
|
|
system_prompt=current.system_prompt,
|
|
timeout_seconds=current.timeout_seconds,
|
|
limits=current.limits,
|
|
active=False,
|
|
provider=current.provider,
|
|
base_url=current.base_url,
|
|
)
|
|
active = AIProfileRecord(
|
|
version=profile.version,
|
|
model=profile.model,
|
|
system_prompt=profile.system_prompt,
|
|
timeout_seconds=profile.timeout_seconds,
|
|
limits=profile.limits,
|
|
active=True,
|
|
provider=profile.provider,
|
|
base_url=profile.base_url,
|
|
)
|
|
self.profiles[profile.version] = active
|
|
return active
|
|
|
|
async def audit_profile_activation(self, actor, version, provider, occurred_at):
|
|
self.audit_events.append((actor, version, provider, occurred_at))
|
|
|
|
async def list_recent_runs(self, limit: int):
|
|
return []
|
|
|
|
async def find_run(self, run_id: str):
|
|
return None
|
|
|
|
async def find_by_request(self, *_: Any, **__: Any):
|
|
return None
|
|
|
|
async def save_run(self, *_: Any, **__: Any):
|
|
raise NotImplementedError
|
|
|
|
|
|
class Transaction:
|
|
def __init__(self) -> None:
|
|
self.commits = 0
|
|
self.rollbacks = 0
|
|
|
|
async def commit(self) -> None:
|
|
self.commits += 1
|
|
|
|
async def rollback(self) -> None:
|
|
self.rollbacks += 1
|
|
|
|
|
|
async def test_admin_profile_versions_are_immutable():
|
|
repository = FakeScoringRepository()
|
|
repository.profiles["profile-v1"] = AIProfileRecord(
|
|
version="profile-v1",
|
|
model="model-old",
|
|
system_prompt="prompt",
|
|
timeout_seconds=30,
|
|
limits={},
|
|
active=True,
|
|
)
|
|
transaction = Transaction()
|
|
use_cases = AdminScoringUseCases(
|
|
repository,
|
|
transaction.commit,
|
|
transaction.rollback,
|
|
frozenset({"aihub", "openai_compatible"}),
|
|
lambda: datetime(2026, 9, 25, tzinfo=UTC),
|
|
)
|
|
|
|
with pytest.raises(AIProfileVersionConflict):
|
|
await use_cases.activate_profile(
|
|
NewAIProfile(
|
|
version="profile-v1",
|
|
model="model-new",
|
|
system_prompt="prompt",
|
|
timeout_seconds=30,
|
|
limits={},
|
|
),
|
|
actor="admin@example.invalid",
|
|
)
|
|
|
|
assert transaction.commits == 0
|
|
assert transaction.rollbacks == 1
|
|
assert repository.profiles["profile-v1"].model == "model-old"
|
|
|
|
|
|
async def test_admin_profile_activation_commits_through_use_case():
|
|
repository = FakeScoringRepository()
|
|
transaction = Transaction()
|
|
use_cases = AdminScoringUseCases(
|
|
repository,
|
|
transaction.commit,
|
|
transaction.rollback,
|
|
frozenset({"aihub", "openai_compatible"}),
|
|
lambda: datetime(2026, 9, 25, tzinfo=UTC),
|
|
)
|
|
requested = NewAIProfile(
|
|
version="profile-v2",
|
|
model="model",
|
|
system_prompt="prompt",
|
|
timeout_seconds=30,
|
|
limits={"max_tokens": 100},
|
|
)
|
|
|
|
activated = await use_cases.activate_profile(requested, actor="admin@example.invalid")
|
|
|
|
assert activated.active is True
|
|
assert activated.version == requested.version
|
|
assert repository.audit_events == [
|
|
("admin@example.invalid", requested.version, "aihub", datetime(2026, 9, 25, tzinfo=UTC))
|
|
]
|
|
assert transaction.commits == 1
|
|
assert transaction.rollbacks == 0
|
|
|
|
|
|
async def test_default_openai_url_is_frozen_in_profile_version():
|
|
repository = FakeScoringRepository()
|
|
transaction = Transaction()
|
|
cases = AdminScoringUseCases(
|
|
repository,
|
|
transaction.commit,
|
|
transaction.rollback,
|
|
frozenset({"openai_compatible"}),
|
|
lambda: datetime(2026, 9, 28, tzinfo=UTC),
|
|
openai_base_url="http://127.0.0.1:8317",
|
|
)
|
|
requested = NewAIProfile("v1", "model", "prompt", 30, {}, "openai_compatible")
|
|
|
|
activated = await cases.activate_profile(requested, "admin@example.invalid")
|
|
|
|
assert activated.base_url == "http://127.0.0.1:8317"
|
|
assert repository.profiles["v1"].base_url == activated.base_url
|
|
|
|
|
|
async def test_provider_change_requires_new_profile_version_and_configured_adapter():
|
|
repository = FakeScoringRepository()
|
|
transaction = Transaction()
|
|
use_cases = AdminScoringUseCases(
|
|
repository,
|
|
transaction.commit,
|
|
transaction.rollback,
|
|
frozenset({"aihub"}),
|
|
lambda: datetime(2026, 9, 28, tzinfo=UTC),
|
|
)
|
|
requested = NewAIProfile(
|
|
"v1",
|
|
"gemini-3.5-flash-lite",
|
|
"prompt",
|
|
30,
|
|
{},
|
|
"openai_compatible",
|
|
"http://127.0.0.1:8317",
|
|
)
|
|
with pytest.raises(AIProviderUnavailable):
|
|
await use_cases.activate_profile(requested, "admin@example.invalid")
|
|
assert transaction.commits == 0
|
|
assert repository.audit_events == []
|
|
|
|
use_cases = AdminScoringUseCases(
|
|
repository,
|
|
transaction.commit,
|
|
transaction.rollback,
|
|
frozenset({"aihub", "openai_compatible"}),
|
|
lambda: datetime(2026, 9, 28, tzinfo=UTC),
|
|
)
|
|
await use_cases.activate_profile(requested, "admin@example.invalid")
|
|
with pytest.raises(AIProfileVersionConflict):
|
|
await use_cases.activate_profile(
|
|
NewAIProfile("v1", requested.model, requested.system_prompt, 30, {}, "aihub"),
|
|
"admin@example.invalid",
|
|
)
|
|
assert transaction.commits == 1
|
|
assert len(repository.audit_events) == 1
|
|
assert repository.profiles["v1"].provider == "openai_compatible"
|