104 lines
3.2 KiB
Python
104 lines
3.2 KiB
Python
import json
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from ai_platform.lead_scoring.application.repository import AIProfileRecord
|
|
from ai_platform.lead_scoring.application.scoring_execution import ScoringExecutionUseCases
|
|
from ai_platform.lead_scoring.application.scoring_execution_repository import (
|
|
Evidence,
|
|
Scoring,
|
|
ScoringExecutionRun,
|
|
ScoringResult,
|
|
)
|
|
|
|
FIXTURE = Path(__file__).parent / "fixtures" / "mock_omni_run.json"
|
|
|
|
|
|
class RunRepository:
|
|
def __init__(self, run: ScoringExecutionRun, profile: AIProfileRecord):
|
|
self.run = run
|
|
self.profile = profile
|
|
self.result: ScoringResult | None = None
|
|
|
|
async def find_run(self, run_id: str):
|
|
return self.run
|
|
|
|
async def find_profile(self, version: str):
|
|
assert version == self.run.profile_version
|
|
return self.profile
|
|
|
|
async def mark_running(self, run_id: str):
|
|
pass
|
|
|
|
async def complete(self, run_id: str, result: ScoringResult, completed_at: datetime):
|
|
self.result = result
|
|
|
|
async def fail(self, run_id: str, completed_at: datetime, error_code: str):
|
|
pytest.fail(f"execution failed: {error_code}")
|
|
|
|
async def mark_interrupted(self, completed_at: datetime):
|
|
pytest.fail("unexpected recovery")
|
|
|
|
|
|
class StubProvider:
|
|
def __init__(self):
|
|
self.models: list[str] = []
|
|
self.keys: list[str] = []
|
|
|
|
async def extract_evidence(self, *, model, snapshot, vkey, timeout):
|
|
self.models.append(model)
|
|
self.keys.append(vkey)
|
|
return [Evidence("interest", "Asked about enrollment", "mock-message-001")]
|
|
|
|
async def score(self, *, model, system_prompt, user_prompt, vkey, timeout):
|
|
self.models.append(model)
|
|
self.keys.append(vkey)
|
|
return Scoring(80, 0.8, "Evidence supports intent")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execution_uses_frozen_profile_provider_and_never_forwards_vkey_to_openai():
|
|
payload = json.loads(FIXTURE.read_text())
|
|
payload["ai_profile"] = "medium"
|
|
payload["scoring_config"].update(rule_weight=0.5, ai_weight=0.5)
|
|
run = ScoringExecutionRun("run-1", "pending", payload, "old-profile")
|
|
profile = AIProfileRecord(
|
|
"old-profile",
|
|
"gemini-3.5-flash-lite",
|
|
"prompt",
|
|
30,
|
|
{},
|
|
False,
|
|
"openai_compatible",
|
|
"http://127.0.0.1:8318",
|
|
)
|
|
repository = RunRepository(run, profile)
|
|
old_provider = StubProvider()
|
|
active_aihub_provider = StubProvider()
|
|
|
|
async def no_op():
|
|
pass
|
|
|
|
selected_urls: list[str] = []
|
|
|
|
def provider_for_url(url: str) -> StubProvider:
|
|
selected_urls.append(url)
|
|
return old_provider
|
|
|
|
cases = ScoringExecutionUseCases(
|
|
repository,
|
|
{"aihub": active_aihub_provider},
|
|
no_op,
|
|
no_op,
|
|
lambda: datetime(2026, 9, 28, tzinfo=UTC),
|
|
provider_for_url,
|
|
)
|
|
await cases.execute(run.id, "caller-vkey-should-not-reach-openai")
|
|
assert repository.result is not None and repository.result.result_mode == "hybrid"
|
|
assert selected_urls == ["http://127.0.0.1:8318"]
|
|
assert old_provider.models == ["gemini-3.5-flash-lite"] * 2
|
|
assert old_provider.keys == ["", ""]
|
|
assert active_aihub_provider.models == []
|