+149
-15
@@ -310,7 +310,11 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(first["text"], MODULE.FAST_ACKNOWLEDGEMENT)
|
||||
self.assertEqual(second["text"], MODULE.FAST_ACKNOWLEDGEMENT)
|
||||
self.assertEqual(observed_context, [("你好", "第一条回复喵")])
|
||||
self.assertEqual(len(observed_context), 1)
|
||||
self.assertEqual(observed_context[0]["speaker"], "Alice")
|
||||
self.assertEqual(observed_context[0]["user_text"], "你好")
|
||||
self.assertEqual(observed_context[0]["taffy_text"], "第一条回复喵")
|
||||
self.assertIn("same_sender", observed_context[0]["tags"])
|
||||
self.assertEqual(
|
||||
self.application.ledger.row("event-2")["response_text"],
|
||||
"第二条回复喵",
|
||||
@@ -335,12 +339,14 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
||||
self.application.process_direct_chat_once()
|
||||
|
||||
self.assertEqual(
|
||||
observed["group-second"],
|
||||
[("群里的第一条", "group-first 回复喵")],
|
||||
[(item["speaker"], item["user_text"], item["taffy_text"])
|
||||
for item in observed["group-second"]],
|
||||
[("Alice", "群里的第一条", "group-first 回复喵")],
|
||||
)
|
||||
self.assertEqual(
|
||||
observed["direct-second"],
|
||||
[("私聊里的第一条", "direct-first 回复喵")],
|
||||
[(item["speaker"], item["user_text"], item["taffy_text"])
|
||||
for item in observed["direct-second"]],
|
||||
[("Alice", "私聊里的第一条", "direct-first 回复喵")],
|
||||
)
|
||||
|
||||
def test_shadow_event_is_rejected_before_routing_or_persistence(self):
|
||||
@@ -371,15 +377,143 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
||||
self.application.accept(event("chat-context", "@JWeBot 刚才是什么结果"))
|
||||
self.application.process_direct_chat_once()
|
||||
|
||||
self.assertIn(
|
||||
(
|
||||
"[本地命令结果][发送者:Alice][类型:coin] /翻硬币",
|
||||
local["text"],
|
||||
),
|
||||
observed_context,
|
||||
self.assertTrue(
|
||||
any(
|
||||
item["route"] == "local"
|
||||
and item["route_reason"] == "local_coin"
|
||||
and item["speaker"] == "Alice"
|
||||
and item["user_text"] == "/翻硬币"
|
||||
and item["taffy_text"] == local["text"]
|
||||
for item in observed_context
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
all(question != "/version" for question, _reply in observed_context)
|
||||
all(item["user_text"] != "/version" for item in observed_context)
|
||||
)
|
||||
|
||||
def test_self_referential_context_does_not_cross_senders(self):
|
||||
observed_context = []
|
||||
|
||||
def complete(**kwargs):
|
||||
observed_context[:] = list(kwargs["context"])
|
||||
return "我只看你的喵"
|
||||
|
||||
self.application.deepseek.complete = complete
|
||||
self.application.deliver = mock.Mock()
|
||||
self.application.accept(event("alice-first", "@JWeBot Alice 的事", sender="Alice"))
|
||||
self.application.process_direct_chat_once()
|
||||
self.application.accept(event("bob-first", "@JWeBot Bob 的事", sender="Bob"))
|
||||
self.application.process_direct_chat_once()
|
||||
self.application.accept(event("bob-follow", "@JWeBot 我刚才说啥", sender="Bob"))
|
||||
self.application.process_direct_chat_once()
|
||||
|
||||
self.assertTrue(observed_context)
|
||||
self.assertEqual(
|
||||
{item["speaker"] for item in observed_context},
|
||||
{"Bob"},
|
||||
)
|
||||
self.assertTrue(
|
||||
all("Alice 的事" not in item["user_text"] for item in observed_context)
|
||||
)
|
||||
|
||||
def test_mentioned_person_context_can_cross_senders(self):
|
||||
observed_context = []
|
||||
|
||||
def complete(**kwargs):
|
||||
observed_context[:] = list(kwargs["context"])
|
||||
return "看 Bob 那句喵"
|
||||
|
||||
self.application.deepseek.complete = complete
|
||||
self.application.deliver = mock.Mock()
|
||||
self.application.accept(event("bob-mentioned", "@JWeBot Bob 的事", sender="Bob"))
|
||||
self.application.process_direct_chat_once()
|
||||
self.application.accept(
|
||||
event("alice-asks-bob", "@JWeBot Bob 刚才说啥", sender="Alice")
|
||||
)
|
||||
self.application.process_direct_chat_once()
|
||||
|
||||
self.assertTrue(
|
||||
any(
|
||||
item["speaker"] == "Bob"
|
||||
and item["user_text"] == "Bob 的事"
|
||||
and "mentioned_person" in item["tags"]
|
||||
for item in observed_context
|
||||
)
|
||||
)
|
||||
|
||||
def test_low_confidence_sender_does_not_receive_identity_memory(self):
|
||||
captured = {}
|
||||
|
||||
def complete(**kwargs):
|
||||
captured.update(kwargs)
|
||||
return "先确认是谁喵"
|
||||
|
||||
self.application.deepseek.complete = complete
|
||||
self.application.deliver = mock.Mock()
|
||||
self.application.accept(
|
||||
{
|
||||
**event("low-conf-chat", "@JWeBot 我是谁", sender="alice"),
|
||||
"sender_confidence": 0.42,
|
||||
"body_confidence": 0.98,
|
||||
"source": "rapidocr-local",
|
||||
}
|
||||
)
|
||||
self.application.process_direct_chat_once()
|
||||
|
||||
self.assertEqual(captured["person_memory"], ())
|
||||
self.assertIsNone(
|
||||
self.application.ledger.person_profile(
|
||||
"group-a",
|
||||
MODULE.person_scope_key("group-a", "Alice"),
|
||||
)
|
||||
)
|
||||
|
||||
def test_low_confidence_identity_bound_local_command_is_rejected(self):
|
||||
response = self.application.accept(
|
||||
{
|
||||
**event("low-conf-draw", "@JWeBot /抽奖 10", sender="alice"),
|
||||
"sender_confidence": 0.42,
|
||||
"body_confidence": 0.98,
|
||||
"source": "rapidocr-local",
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(response["route"], "local")
|
||||
self.assertIn("没认稳", response["text"])
|
||||
self.assertEqual(
|
||||
self.application.ledger.row("low-conf-draw")["status"],
|
||||
"local_rejected",
|
||||
)
|
||||
|
||||
def test_delivered_taffy_broadcast_is_available_to_context(self):
|
||||
self.application.ledger.reserve_clock_broadcast(
|
||||
group_fingerprint="group-a",
|
||||
slot_key="hourly:2026-10-21T17",
|
||||
local_date="2026-10-21",
|
||||
kind="ambient_chat",
|
||||
message="我刚刚路过说了一句喵",
|
||||
)
|
||||
self.assertTrue(
|
||||
self.application.ledger.complete_clock_broadcast(
|
||||
"group-a",
|
||||
"hourly:2026-10-21T17",
|
||||
)
|
||||
)
|
||||
|
||||
context = self.application.ledger.conversation_context(
|
||||
"group-a",
|
||||
exclude_event_id="current",
|
||||
sender="Alice",
|
||||
question="Taffy 刚刚说了什么",
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
any(
|
||||
item["speaker"] == "Taffy"
|
||||
and item["taffy_text"] == "我刚刚路过说了一句喵"
|
||||
and "taffy_broadcast" in item["tags"]
|
||||
for item in context
|
||||
)
|
||||
)
|
||||
|
||||
def test_route_and_ack_telemetry_is_privacy_safe(self):
|
||||
@@ -1081,9 +1215,9 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
||||
self.assertEqual(version["route"], "local")
|
||||
self.assertIn("JWeBot 版本", version["text"])
|
||||
self.assertIn("kernel", version["text"])
|
||||
self.assertIn("最新版本:beta 2.0.0.1021", version["text"])
|
||||
self.assertIn("更新时间:2026-10-21 16:57:39", version["text"])
|
||||
self.assertIn("更新摘要:Beta 2.0 架构更新完成", version["text"])
|
||||
self.assertIn("最新版本:beta 2.0.1.1021", version["text"])
|
||||
self.assertIn("更新时间:2026-10-21 17:32:46", version["text"])
|
||||
self.assertIn("更新摘要:上下文管理小版本更新完成", version["text"])
|
||||
self.assertEqual(debug["route"], "local")
|
||||
self.assertIn("调试埋点", debug["text"])
|
||||
self.assertEqual(balance["route"], "local")
|
||||
|
||||
@@ -20,7 +20,8 @@ SPEC.loader.exec_module(MODULE)
|
||||
|
||||
|
||||
class ReleaseUpdateTests(unittest.TestCase):
|
||||
release = "beta 2.0.0.1021"
|
||||
release = "beta 2.0.1.1021"
|
||||
major_release = "beta 2.0.0.1021"
|
||||
|
||||
def setUp(self):
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
@@ -111,7 +112,8 @@ class ReleaseUpdateTests(unittest.TestCase):
|
||||
self.assertEqual(summary, "第一行更新摘要, 同一条摘要可以换行。")
|
||||
announcement = MODULE.build_announcement(self.release, summary)
|
||||
self.assertIn(f"{self.release} 更新现已完成", announcement)
|
||||
self.assertIn("更新补偿:摩拉 x400,原石 x2", announcement)
|
||||
self.assertIn("更新补偿:摩拉 x200", announcement)
|
||||
self.assertNotIn("原石 x", announcement)
|
||||
self.assertNotIn("第二条", announcement)
|
||||
|
||||
def test_policy_release_announces_primogem(self):
|
||||
@@ -126,9 +128,9 @@ class ReleaseUpdateTests(unittest.TestCase):
|
||||
self.assertIn("更新补偿:摩拉 x200,原石 x1", announcement)
|
||||
|
||||
def test_major_release_announces_larger_compensation(self):
|
||||
self.assertEqual(MODULE.mora_compensation_amount(self.release), 400)
|
||||
self.assertEqual(MODULE.primogem_compensation_amount(self.release), 2)
|
||||
announcement = MODULE.build_announcement(self.release, "架构更新完成。")
|
||||
self.assertEqual(MODULE.mora_compensation_amount(self.major_release), 400)
|
||||
self.assertEqual(MODULE.primogem_compensation_amount(self.major_release), 2)
|
||||
announcement = MODULE.build_announcement(self.major_release, "架构更新完成。")
|
||||
self.assertIn("更新补偿:摩拉 x400,原石 x2", announcement)
|
||||
|
||||
def test_patch_release_without_policy_does_not_announce_primogem(self):
|
||||
@@ -157,14 +159,22 @@ class ReleaseUpdateTests(unittest.TestCase):
|
||||
self.assertEqual(delivered[0][1], "group-a")
|
||||
self.assertEqual(
|
||||
delivered[0][2],
|
||||
(
|
||||
"Taffy 准备停机更新,本次更新是架构级大版本更新,"
|
||||
f"预期版本{self.release},离线的时候保持耐心迎接Taffy的归来哦~"
|
||||
),
|
||||
f"Taffy 准备停机更新到 {self.release},会短暂离线一下;别慌,我很快回来喵",
|
||||
)
|
||||
self.assertIn("shutdown", delivered[0][3])
|
||||
self.assertEqual([state["balance"] for state in self.account_states()], [1000, 50])
|
||||
|
||||
def test_major_release_shutdown_message_is_ceremonial(self):
|
||||
message = MODULE.build_lifecycle_message(self.major_release, "shutdown")
|
||||
|
||||
self.assertEqual(
|
||||
message,
|
||||
(
|
||||
"Taffy 准备停机更新,本次更新是架构级大版本更新,"
|
||||
f"预期版本{self.major_release},离线的时候保持耐心迎接Taffy的归来哦~"
|
||||
),
|
||||
)
|
||||
|
||||
def test_release_update_grants_once_and_announces_once(self):
|
||||
delivered = []
|
||||
|
||||
@@ -197,28 +207,18 @@ class ReleaseUpdateTests(unittest.TestCase):
|
||||
self.assertEqual(len(delivered), 1)
|
||||
self.assertEqual(delivered[0][1], "group-a")
|
||||
self.assertIn("更新摩拉账户并修复重复回复问题", delivered[0][2])
|
||||
self.assertEqual([state["balance"] for state in self.account_states()], [1400, 450])
|
||||
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
||||
for state in self.account_states():
|
||||
self.assertEqual(state["primogems"], 2)
|
||||
self.assertEqual(state["primogems"], 0)
|
||||
matches = [
|
||||
event
|
||||
for event in state["coin_events"]
|
||||
if event["delta"] == 400
|
||||
if event["delta"] == 200
|
||||
and event["reason"] == f"{self.release} 版本更新补偿"
|
||||
]
|
||||
self.assertEqual(len(matches), 1)
|
||||
self.assertEqual(matches[0]["created_at"], applied_at.timestamp())
|
||||
primogem_matches = [
|
||||
event
|
||||
for event in state["primogem_events"]
|
||||
if event["reason"] == f"{self.release} 版本更新补偿"
|
||||
]
|
||||
self.assertEqual(len(primogem_matches), 1)
|
||||
self.assertEqual(primogem_matches[0]["delta"], 2)
|
||||
self.assertEqual(
|
||||
primogem_matches[0]["created_at"],
|
||||
applied_at.timestamp(),
|
||||
)
|
||||
self.assertEqual(state["primogem_events"], [])
|
||||
self.assertEqual(self.announcement_marker()["state"], "delivered")
|
||||
self.assertEqual(len(list(self.backups.glob("*.sqlite3"))), 1)
|
||||
|
||||
@@ -235,8 +235,8 @@ class ReleaseUpdateTests(unittest.TestCase):
|
||||
delivery_helper=self.delivery_helper,
|
||||
deliverer=uncertain,
|
||||
)
|
||||
self.assertEqual([state["balance"] for state in self.account_states()], [1400, 450])
|
||||
self.assertEqual([state["primogems"] for state in self.account_states()], [2, 2])
|
||||
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
||||
self.assertEqual([state["primogems"] for state in self.account_states()], [0, 0])
|
||||
self.assertEqual(self.announcement_marker()["state"], "uncertain")
|
||||
|
||||
called = []
|
||||
@@ -266,8 +266,8 @@ class ReleaseUpdateTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(result["compensation"], "already_applied")
|
||||
self.assertEqual(len(called), 1)
|
||||
self.assertEqual([state["balance"] for state in self.account_states()], [1400, 450])
|
||||
self.assertEqual([state["primogems"] for state in self.account_states()], [2, 2])
|
||||
self.assertEqual([state["balance"] for state in self.account_states()], [1200, 250])
|
||||
self.assertEqual([state["primogems"] for state in self.account_states()], [0, 0])
|
||||
self.assertEqual(self.announcement_marker()["state"], "delivered")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user