Files
JWeBot/tests/test_sdk_contracts.py
T
JWeBot Contributors a74147e7f1
check / local-gate (push) Failing after 40s
Release alpha 0.6.6 autochess flow
2026-10-15 17:10:28 +08:00

113 lines
4.1 KiB
Python

#!/usr/bin/env python3
import sys
import unittest
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[1]
SDK_SRC = PROJECT_ROOT / "packages" / "jwebot-sdk" / "src"
sys.path.insert(0, str(SDK_SRC))
from jwebot_sdk import ( # noqa: E402
ContractViolation,
Event,
ExtensionResult,
Fact,
PlanKind,
ReplyPlan,
)
class EventContractTests(unittest.TestCase):
def event(self, **overrides):
value = {
"version": 1,
"event_id": "event-1",
"observed_at": "2026-10-14T10:00:00+08:00",
"sender": "Alice",
"text": "@JWeBot hello",
"confidence": 0.95,
"sender_confidence": 0.96,
"body_confidence": 0.97,
"side": "incoming",
"mention_bot": True,
"group_fingerprint": "group-a",
"source": "rapidocr-local",
}
value.update(overrides)
return value
def test_event_round_trip_preserves_normalized_fields(self):
event = Event.from_mapping(self.event())
self.assertEqual(event.to_mapping(), self.event())
def test_event_rejects_outgoing_messages(self):
with self.assertRaisesRegex(ContractViolation, "incoming"):
Event.from_mapping(self.event(side="outgoing"))
def test_event_rejects_out_of_range_confidence(self):
with self.assertRaisesRegex(ContractViolation, "between 0 and 1"):
Event.from_mapping(self.event(confidence=1.1))
def test_event_rejects_unsupported_version(self):
with self.assertRaisesRegex(ContractViolation, "unsupported event version"):
Event.from_mapping(self.event(version=2))
class PlanContractTests(unittest.TestCase):
def test_text_plan_has_a_bounded_serialized_shape(self):
plan = ReplyPlan.text("hello")
self.assertEqual(plan.kind, PlanKind.TEXT)
self.assertEqual(plan.to_mapping(), {"kind": "text", "text": "hello"})
def test_multi_text_plan_has_bounded_parts(self):
plan = ReplyPlan.multi_text(["one", "two"])
self.assertEqual(plan.kind, PlanKind.MULTI_TEXT)
self.assertEqual(
plan.to_mapping(),
{"kind": "multi_text", "texts": ["one", "two"]},
)
restored = ReplyPlan.from_mapping(plan.to_mapping())
self.assertEqual(restored, plan)
def test_no_reply_plan_cannot_contain_text(self):
with self.assertRaisesRegex(ContractViolation, "cannot contain text"):
ReplyPlan(kind=PlanKind.NO_REPLY, message="unexpected")
def test_result_freezes_metadata_and_serializes_facts(self):
result = ExtensionResult(
reply=ReplyPlan.text("hello"),
facts=(Fact(kind="topic", text="likes servers", confidence=0.8),),
metadata={"source": "test"},
)
self.assertEqual(result.to_mapping()["metadata"], {"source": "test"})
with self.assertRaises(TypeError):
result.metadata["source"] = "changed" # type: ignore[index]
def test_result_rejects_unbounded_fact_count(self):
facts = tuple(Fact(kind="topic", text=f"fact-{index}") for index in range(13))
with self.assertRaisesRegex(ContractViolation, "exceeds 12 facts"):
ExtensionResult(facts=facts)
def test_result_round_trip_preserves_reply_facts_and_metadata(self):
result = ExtensionResult(
reply=ReplyPlan.text("hello"),
facts=(Fact(kind="topic", text="likes servers", confidence=0.8),),
metadata={"source": "test"},
)
restored = ExtensionResult.from_mapping(result.to_mapping())
self.assertEqual(restored.reply, result.reply)
self.assertEqual(restored.facts, result.facts)
self.assertEqual(dict(restored.metadata), dict(result.metadata))
def test_result_rejects_wrong_api_version(self):
value = ExtensionResult(reply=ReplyPlan.text("hello")).to_mapping()
value["api_version"] = "2"
with self.assertRaisesRegex(ContractViolation, "API version"):
ExtensionResult.from_mapping(value)
if __name__ == "__main__":
unittest.main()