dotb_ai-platform/old_repo/tests/test_contract_pack_cli.py

220 lines
8.7 KiB
Python

import json
from pathlib import Path
from typing import Literal
from uuid import UUID
from pydantic import BaseModel, ConfigDict
from lead_scoring.cli.contract_pack import (
API_MAJOR_VERSION,
CONTRACT_PACK_VERSION,
MARKER_CLOSE,
MARKER_OPEN,
canonical_json,
generate_contract_files,
run_contract_pack_cli,
validate_fixture_corpus,
)
from lead_scoring.cli.webhook_contract import generate_contract_files as generate_webhook_files
from lead_scoring.schemas.errors import ErrorTaxonomy
from lead_scoring.schemas.lineage import ScoringRunLineage
from lead_scoring.schemas.outcomes import OutcomeTaxonomy
from lead_scoring.schemas.review import LeadScoringReviewedEvent, ReviewResponse
from lead_scoring.schemas.scoring_config import ScoringConfig, ScoringConfigVersionSelection
from lead_scoring.schemas.scoring_run import (
CreateScoringRunRequest,
CreateScoringRunResponse,
ScoringRunResponse,
)
from lead_scoring.schemas.snapshot import CanonicalSnapshot
from lead_scoring.schemas.webhook import (
LeadScoringTerminalEvent,
WebhookIngestErrorResponse,
)
class LegacyTerminalEvent(BaseModel):
"""Consumer model tối giản mô phỏng reader cũ tương thích additive."""
model_config = ConfigDict(extra="ignore")
event_id: UUID
event_type: Literal["lead_scoring.run.terminal"]
event_version: Literal["1.0.0"]
tenant_id: str
subject_ref: str
scoring_config_version: str
run_id: UUID
run_created_at: str
occurred_at: str
data: dict
def test_contract_pack_is_deterministic_and_versioned():
files = generate_contract_files()
assert files == generate_contract_files()
assert json.loads(files["contract.json"])["contract_pack_version"] == CONTRACT_PACK_VERSION
manifest = json.loads(files["contract.json"])
assert manifest["api_major_version"] == API_MAJOR_VERSION
assert manifest["release_status"] == "pre-release"
assert set(manifest["profiles"]) == {
"canonical_snapshot",
"scoring_config",
"admission",
"polling",
"review",
"lineage",
"webhook",
"errors",
}
assert all(profile["version"] == "1.0.0" for profile in manifest["profiles"].values())
assert "openapi.json" in files
assert "SHA256SUMS" in files
assert (
files["fixtures/tenant-unavailable.json"]
== generate_webhook_files()["fixtures/tenant-unavailable.json"]
)
def test_contract_schemas_and_fixture_corpus_use_pydantic_models():
files = generate_contract_files()
assert json.loads(files["schemas/canonical-snapshot.schema.json"]) == (
CanonicalSnapshot.model_json_schema()
)
assert (
json.loads(files["schemas/scoring-config.schema.json"]) == ScoringConfig.model_json_schema()
)
assert json.loads(files["schemas/scoring-config-selection.schema.json"]) == (
ScoringConfigVersionSelection.model_json_schema()
)
assert json.loads(files["schemas/admission-request.schema.json"]) == (
CreateScoringRunRequest.model_json_schema()
)
assert json.loads(files["schemas/admission-response.schema.json"]) == (
CreateScoringRunResponse.model_json_schema()
)
assert json.loads(files["schemas/polling-response.schema.json"]) == (
ScoringRunResponse.model_json_schema()
)
assert (
json.loads(files["schemas/review-response.schema.json"])
== ReviewResponse.model_json_schema()
)
assert json.loads(files["schemas/lineage.schema.json"]) == ScoringRunLineage.model_json_schema()
assert json.loads(files["schemas/terminal-event.schema.json"]) == (
LeadScoringTerminalEvent.model_json_schema()
)
assert json.loads(files["schemas/reviewed-event.schema.json"]) == (
LeadScoringReviewedEvent.model_json_schema()
)
ErrorTaxonomy.model_validate(json.loads(files["error-taxonomy.json"]))
OutcomeTaxonomy.model_validate(json.loads(files["outcome-taxonomy.json"]))
validate_fixture_corpus(files)
assert json.loads(files["fixtures/snapshot.json"])["version"] == "1.0.0"
assert json.loads(files["fixtures/admission-request.json"])["subject_ref"] == "ems-lead-1001"
ScoringConfigVersionSelection.model_validate(
json.loads(files["fixtures/scoring-config-selection.json"])
)
assert json.loads(files["fixtures/review-override-request.json"])["override_score"] == 90
WebhookIngestErrorResponse.model_validate(json.loads(files["fixtures/tenant-unavailable.json"]))
def test_marker_fixtures_cover_shared_merge_cases():
files = generate_contract_files()
names = {
"plain-description-append",
"empty-description-append",
"single-valid-block-replace",
"orphan-opening",
"orphan-closing",
"nested-block",
"overlapping-block",
"multiple-complete-blocks",
"unicode-multiline-preservation",
"deleted-block-followed-by-append",
"failed-run-no-render",
}
fixtures = {name: json.loads(files[f"fixtures/marker/{name}.json"]) for name in names}
assert MARKER_OPEN in fixtures["plain-description-append"]["expected_description"]
assert MARKER_CLOSE in fixtures["plain-description-append"]["expected_description"]
assert fixtures["empty-description-append"]["current_description"] == ""
assert fixtures["empty-description-append"]["expected_description"].startswith(MARKER_OPEN)
assert fixtures["unicode-multiline-preservation"]["writeback"] is True
assert fixtures["unicode-multiline-preservation"]["current_description"].startswith(
"Khách hàng: Nguyễn Ánh 😊"
)
malformed = [
fixtures[name]
for name in (
"orphan-opening",
"orphan-closing",
"nested-block",
"overlapping-block",
"multiple-complete-blocks",
)
]
assert all(item["expected_error"] == "marker_block_malformed" for item in malformed)
assert all(item["expected_description"] is None for item in malformed)
assert all(item["writeback"] is False for item in malformed)
assert fixtures["failed-run-no-render"]["marker_input"] is None
assert fixtures["failed-run-no-render"]["writeback"] is False
def test_fixture_index_declares_shared_provider_consumer_corpus():
files = generate_contract_files()
index = json.loads(files["fixture-index.json"])
assert index["provider_validation"] == "validate_fixture_corpus"
assert index["consumer_validation"]
assert all(item["role"] == "provider_consumer" for item in index["positive"])
assert {item["path"] for item in index["marker"]} == {
"fixtures/marker/deleted-block-followed-by-append.json",
"fixtures/marker/empty-description-append.json",
"fixtures/marker/failed-run-no-render.json",
"fixtures/marker/multiple-complete-blocks.json",
"fixtures/marker/nested-block.json",
"fixtures/marker/orphan-closing.json",
"fixtures/marker/orphan-opening.json",
"fixtures/marker/overlapping-block.json",
"fixtures/marker/plain-description-append.json",
"fixtures/marker/single-valid-block-replace.json",
"fixtures/marker/unicode-multiline-preservation.json",
}
assert all(item["role"] == "provider_consumer" for item in index["marker"])
assert {item["path"] for item in index["negative"]} == {
"fixtures/negative/snapshot-future-message.json",
"fixtures/negative/admission-subject-mismatch.json",
"fixtures/negative/review-override-out-of-range.json",
"fixtures/negative/event-missing-version.json",
}
def test_additive_fixture_remains_compatible_with_previous_consumer():
files = generate_contract_files()
current = json.loads(files["fixtures/webhook/completed.json"])
legacy = LegacyTerminalEvent.model_validate({**current, "new_optional_field": "ignored"})
assert legacy.event_type == "lead_scoring.run.terminal"
assert legacy.run_id == UUID(current["run_id"])
def test_contract_pack_cli_generates_checks_and_rejects_drift(tmp_path: Path):
output_dir = tmp_path / "contract-pack"
assert run_contract_pack_cli(["--output", str(output_dir)]) == 0
assert run_contract_pack_cli(["--output", str(output_dir), "--check"]) == 0
assert run_contract_pack_cli(["--output", str(output_dir), "--validate-fixtures"]) == 0
fixture = output_dir / "fixtures" / "snapshot.json"
fixture.write_text(canonical_json({"stale": True}), encoding="utf-8")
assert run_contract_pack_cli(["--output", str(output_dir), "--check"]) == 1
assert run_contract_pack_cli(["--output", str(output_dir)]) == 0
(output_dir / "unexpected.json").write_text("{}\n", encoding="utf-8")
assert run_contract_pack_cli(["--output", str(output_dir), "--check"]) == 1