dotb_ai-platform/tests/test_snapshot_contract.py
2026-09-25 09:55:25 +07:00

150 lines
5.0 KiB
Python

import json
from copy import deepcopy
import pytest
from pydantic import ValidationError
from ai_platform.presentation.http.schemas import RunRequest
def test_snapshot_preserves_known_omni_metadata_and_array_order(valid_run_payload):
request = RunRequest.model_validate(valid_run_payload)
snapshot = request.snapshot
assert snapshot.version == "1.0.0"
assert snapshot.scope == "linked_contact_history"
assert snapshot.identity_context is not None
assert (
snapshot.identity_context.model_dump() == valid_run_payload["snapshot"]["identity_context"]
)
assert snapshot.trigger_metadata is not None
assert (
snapshot.trigger_metadata.model_dump() == valid_run_payload["snapshot"]["trigger_metadata"]
)
assert snapshot.conversations[0].channel == "webchat"
assert [message.message_id for message in snapshot.conversations[0].messages] == [
"message-2",
"message-1",
]
def test_message_order_remains_significant_to_canonical_fingerprint(valid_run_payload):
original = RunRequest.model_validate(valid_run_payload).model_dump(mode="json")
reordered_payload = deepcopy(valid_run_payload)
reordered_payload["snapshot"]["conversations"][0]["messages"].reverse()
reordered = RunRequest.model_validate(reordered_payload).model_dump(mode="json")
assert json.dumps(original, sort_keys=True, separators=(",", ":")) != json.dumps(
reordered, sort_keys=True, separators=(",", ":")
)
def test_conversation_order_is_preserved(valid_run_payload):
conversations = valid_run_payload["snapshot"]["conversations"]
conversations.append(
{
"conversation_id": "conversation-1",
"messages": [
{
"message_id": "older-message",
"sender_type": "contact",
"content": "Earlier in time, later in the array.",
"sent_at": "2025-12-30T10:00:00Z",
}
],
}
)
snapshot = RunRequest.model_validate(valid_run_payload).snapshot
assert [conversation.conversation_id for conversation in snapshot.conversations] == [
"conversation-2",
"conversation-1",
]
@pytest.mark.parametrize(
("change_snapshot", "error"),
[
(
lambda snapshot: snapshot.update(
conversations=[
{"conversation_id": f"conversation-{index}", "messages": []}
for index in range(11)
]
),
"10",
),
(
lambda snapshot: snapshot["conversations"][0].update(
messages=[
{
"message_id": f"message-{index}",
"sender_type": "contact",
"content": "x",
"sent_at": "2025-12-31T10:00:00Z",
}
for index in range(51)
]
),
"50",
),
(
lambda snapshot: snapshot["conversations"][0]["messages"][0].update(
content="x" * 20_001
),
"20000",
),
(
lambda snapshot: snapshot["conversations"][0]["messages"][0].update(
sent_at="2025-12-31T10:00:00"
),
"timezone-aware",
),
(
lambda snapshot: snapshot["conversations"][0]["messages"][0].update(
sent_at="2025-09-30T12:00:00Z"
),
"90-day",
),
],
)
def test_snapshot_rejects_invalid_evidence(valid_run_payload, change_snapshot, error):
data = deepcopy(valid_run_payload)
change_snapshot(data["snapshot"])
with pytest.raises(ValidationError, match=error):
RunRequest.model_validate(data)
@pytest.mark.parametrize(
("target", "extra"),
[
("snapshot", "unsupported_snapshot_field"),
("lead_profile", "unsupported_profile_field"),
("conversation", "unsupported_conversation_field"),
("message", "unsupported_message_field"),
],
)
def test_snapshot_rejects_unmodeled_fields(valid_run_payload, target, extra):
snapshot = deepcopy(valid_run_payload)["snapshot"]
if target == "snapshot":
snapshot[extra] = "not silently discarded"
elif target == "lead_profile":
snapshot["lead_profile"][extra] = "not silently discarded"
elif target == "conversation":
snapshot["conversations"][0][extra] = "not silently discarded"
else:
snapshot["conversations"][0]["messages"][0][extra] = "not silently discarded"
with pytest.raises(ValidationError, match="Extra inputs are not permitted"):
RunRequest.model_validate({**valid_run_payload, "snapshot": snapshot})
def test_snapshot_rejects_naive_capture_timestamp(valid_run_payload):
data = deepcopy(valid_run_payload)
data["snapshot"]["captured_at"] = "2026-01-01T12:00:00"
with pytest.raises(ValidationError, match="timezone-aware"):
RunRequest.model_validate(data)