dotb_ai-platform/tests/test_webhook_contract_cli.py
2026-09-14 09:57:12 +07:00

172 lines
6.7 KiB
Python

import json
from pathlib import Path
import pytest
from pydantic import ValidationError
from lead_scoring.cli.webhook_contract import (
TEST_SIGNING_SECRET,
canonical_json,
generate_contract_files,
run_webhook_contract_cli,
sign_webhook,
)
from lead_scoring.schemas.review import (
REVIEW_EVENT_TYPE,
LeadScoringReviewedEvent,
ReviewDecision,
)
from lead_scoring.schemas.scoring_run import ScoringRunStatus
from lead_scoring.schemas.webhook import LeadScoringTerminalEvent, WebhookIngestErrorResponse
def test_contract_files_are_deterministic_and_validate_against_models():
files = generate_contract_files()
assert files == generate_contract_files()
assert set(files) == {
"contract.json",
"lead-scoring-terminal-event.schema.json",
"lead-scoring-reviewed-event.schema.json",
"webhook-ingest-response.schema.json",
"webhook-ingest-error-response.schema.json",
"fixtures/completed.json",
"fixtures/tenant-unavailable.json",
"fixtures/review_required.json",
"fixtures/worker_interrupted.json",
"fixtures/reviewed_approve.json",
"fixtures/reviewed_override.json",
"fixtures/reviewed_reevaluate.json",
"signature-vectors.json",
}
manifest = json.loads(files["contract.json"])
assert manifest["contract_pack_version"] == "0.1.0"
assert manifest["endpoint_path"] == "/api/v1/internal/lead-scoring/events"
assert manifest["event_types"] == [
"lead_scoring.run.terminal",
"lead_scoring.run.reviewed",
]
assert manifest["supported_event_types"] == [
"lead_scoring.run.terminal",
"lead_scoring.run.reviewed",
]
assert manifest["method"] == "POST"
assert manifest["replay_window_seconds"] == 300
assert manifest["signature_algorithm"] == "HMAC-SHA256"
assert manifest["signature_version"] == "v1"
terminal_schema = json.loads(files["lead-scoring-terminal-event.schema.json"])
assert "event_type" in terminal_schema["required"]
assert "event_version" in terminal_schema["required"]
reviewed_schema = json.loads(files["lead-scoring-reviewed-event.schema.json"])
assert "event_type" in reviewed_schema["required"]
assert "event_version" in reviewed_schema["required"]
completed = LeadScoringTerminalEvent.model_validate(
json.loads(files["fixtures/completed.json"])
)
review_required = LeadScoringTerminalEvent.model_validate(
json.loads(files["fixtures/review_required.json"])
)
worker_interrupted = LeadScoringTerminalEvent.model_validate(
json.loads(files["fixtures/worker_interrupted.json"])
)
assert completed.data.status is ScoringRunStatus.COMPLETED
assert review_required.data.status is ScoringRunStatus.COMPLETED
assert worker_interrupted.data.status is ScoringRunStatus.FAILED
assert worker_interrupted.data.error_code == "worker_interrupted"
assert worker_interrupted.data.retryable is True
reviewed_appr = LeadScoringReviewedEvent.model_validate(
json.loads(files["fixtures/reviewed_approve.json"])
)
assert reviewed_appr.event_type == REVIEW_EVENT_TYPE
assert reviewed_appr.data.decision is ReviewDecision.APPROVE
assert reviewed_appr.data.effective_score == 61
assert reviewed_appr.data.version == 2
assert reviewed_appr.data.reviewed_by == "agent_10"
reviewed_over = LeadScoringReviewedEvent.model_validate(
json.loads(files["fixtures/reviewed_override.json"])
)
assert reviewed_over.event_type == REVIEW_EVENT_TYPE
assert reviewed_over.data.decision is ReviewDecision.OVERRIDE
assert reviewed_over.data.calculated_score == 61
assert reviewed_over.data.effective_score == 90
assert reviewed_over.data.version == 2
assert reviewed_over.data.reviewed_by == "agent_10"
reviewed_reeval = LeadScoringReviewedEvent.model_validate(
json.loads(files["fixtures/reviewed_reevaluate.json"])
)
assert reviewed_reeval.event_type == REVIEW_EVENT_TYPE
assert reviewed_reeval.data.status == "superseded"
assert reviewed_reeval.data.decision is ReviewDecision.RE_EVALUATE
assert reviewed_reeval.data.calculated_score == 82
assert reviewed_reeval.data.effective_score == 82
assert reviewed_reeval.data.version == 2
assert reviewed_reeval.data.reviewed_by == "agent_10"
def test_tenant_unavailable_error_contract_is_exact():
files = generate_contract_files()
response_body = json.loads(files["fixtures/tenant-unavailable.json"])
response = WebhookIngestErrorResponse.model_validate(response_body)
assert response_body == {
"error_code": "subscriber_tenant_unavailable",
"detail": "Webhook destination is not available",
}
assert response.model_dump(mode="json") == response_body
schema = json.loads(files["webhook-ingest-error-response.schema.json"])
assert schema["additionalProperties"] is False
assert schema["required"] == ["error_code", "detail"]
with pytest.raises(ValidationError):
WebhookIngestErrorResponse.model_validate({**response_body, "tenant_id": "hidden"})
with pytest.raises(ValidationError):
WebhookIngestErrorResponse.model_validate(
{**response_body, "error_code": "tenant_not_registered"}
)
def test_signature_vectors_match_fixture_bytes():
files = generate_contract_files()
vectors = json.loads(files["signature-vectors.json"])
assert vectors["replay_window_seconds"] == 300
assert vectors["signature_version"] == "v1"
assert vectors["test_secret"] == TEST_SIGNING_SECRET
for vector in vectors["vectors"]:
body = files[vector["body_file"]].encode()
assert vector["signature"] == sign_webhook(
TEST_SIGNING_SECRET,
vector["event_id"],
vector["timestamp"],
body,
)
def test_webhook_contract_cli_generates_and_checks_contract_pack(tmp_path: Path):
output_dir = tmp_path / "contract"
assert run_webhook_contract_cli(["--output", str(output_dir)]) == 0
assert run_webhook_contract_cli(["--output", str(output_dir), "--check"]) == 0
def test_webhook_contract_cli_rejects_stale_or_unexpected_artifacts(tmp_path: Path):
output_dir = tmp_path / "contract"
assert run_webhook_contract_cli(["--output", str(output_dir)]) == 0
fixture = output_dir / "fixtures" / "completed.json"
fixture.write_text(canonical_json({"stale": True}), encoding="utf-8")
assert run_webhook_contract_cli(["--output", str(output_dir), "--check"]) == 1
assert run_webhook_contract_cli(["--output", str(output_dir)]) == 0
(output_dir / "unexpected.json").write_text("{}\n", encoding="utf-8")
assert run_webhook_contract_cli(["--output", str(output_dir), "--check"]) == 1