Release beta 2.0.0.1021
check / local-gate (push) Failing after 1m38s

This commit is contained in:
JWeBot Contributors
2026-10-21 17:05:09 +08:00
parent 5a3c4fb94c
commit e4365841ed
79 changed files with 1572 additions and 1840 deletions
+236
View File
@@ -0,0 +1,236 @@
#!/usr/bin/env python3
import hashlib
import json
import sys
import tempfile
import unittest
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[1]
sys.path[:0] = [
str(PROJECT_ROOT / "packages" / "jwebot-config" / "src"),
str(PROJECT_ROOT / "packages" / "jwebot-sdk" / "src"),
str(PROJECT_ROOT / "packages" / "jwebot-official-extensions" / "src"),
str(PROJECT_ROOT / "packages" / "jwebot-kernel" / "src"),
]
from jwebot_kernel.conversation_scope import ( # noqa: E402
ConversationConfigError,
ConversationKind,
ConversationTarget,
ShadowConversationRegistry,
build_shadow_event,
)
def fingerprint(label: str) -> str:
return hashlib.sha256(label.encode()).hexdigest()
class ConversationTargetTests(unittest.TestCase):
def test_group_requires_an_explicit_mention(self):
target = ConversationTarget(
key="main-group",
kind=ConversationKind.GROUP,
title_fingerprint=fingerprint("main-group"),
)
self.assertEqual(
target.question("@JWeBot hello", bot_mention="JWeBot"),
"hello",
)
self.assertEqual(target.question("hello", bot_mention="JWeBot"), "")
def test_direct_chat_accepts_only_commands_or_wake_words(self):
target = ConversationTarget(
key="test-direct",
kind=ConversationKind.DIRECT,
title_fingerprint=fingerprint("test-direct"),
sender="Test User",
)
self.assertEqual(
target.question("/状态", bot_mention="JWeBot"),
"/状态",
)
self.assertEqual(
target.question("JWeBot: hello", bot_mention="JWeBot"),
"hello",
)
self.assertEqual(target.question("普通聊天", bot_mention="JWeBot"), "")
def test_direct_chat_requires_a_configured_sender(self):
with self.assertRaisesRegex(ConversationConfigError, "direct sender"):
ConversationTarget(
key="test-direct",
kind=ConversationKind.DIRECT,
title_fingerprint=fingerprint("test-direct"),
)
def test_title_identity_must_be_a_sha256_digest(self):
with self.assertRaisesRegex(ConversationConfigError, "SHA-256"):
ConversationTarget(
key="test-direct",
kind=ConversationKind.DIRECT,
title_fingerprint="visible-contact-name",
sender="Test User",
)
class ShadowRegistryTests(unittest.TestCase):
def test_registry_is_disabled_without_an_explicit_path(self):
registry = ShadowConversationRegistry.from_environment({})
self.assertFalse(registry.enabled)
self.assertEqual(registry.targets, ())
def test_disabled_registry_never_matches_targets(self):
digest = fingerprint("test-direct")
registry = ShadowConversationRegistry.from_mapping(
{
"format_version": 1,
"enabled": False,
"targets": [
{
"key": "test-direct",
"kind": "direct",
"title_fingerprint": digest,
"sender": "Test User",
}
],
}
)
self.assertIsNone(registry.target_for_fingerprint(digest))
def test_explicit_config_loads_only_shadow_targets(self):
digest = fingerprint("test-direct")
config = {
"format_version": 1,
"enabled": True,
"targets": [
{
"key": "test-direct",
"kind": "direct",
"title_fingerprint": digest,
"sender": "Test User",
}
],
}
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "shadow.json"
path.write_text(json.dumps(config), encoding="utf-8")
registry = ShadowConversationRegistry.from_environment(
{"JWEBOT_CONVERSATION_SHADOW_CONFIG": str(path)}
)
self.assertTrue(registry.enabled)
self.assertEqual(registry.target_for_fingerprint(digest), registry.targets[0])
def test_duplicate_title_fingerprints_are_rejected(self):
digest = fingerprint("duplicate")
with self.assertRaisesRegex(ConversationConfigError, "fingerprints"):
ShadowConversationRegistry.from_mapping(
{
"format_version": 1,
"enabled": True,
"targets": [
{
"key": "direct-a",
"kind": "direct",
"title_fingerprint": digest,
"sender": "Test A",
},
{
"key": "direct-b",
"kind": "direct",
"title_fingerprint": digest,
"sender": "Test B",
},
],
}
)
def test_group_targets_are_rejected_in_private_shadow_phase(self):
with self.assertRaisesRegex(ConversationConfigError, "direct targets only"):
ShadowConversationRegistry.from_mapping(
{
"format_version": 1,
"enabled": True,
"targets": [
{
"key": "main-group",
"kind": "group",
"title_fingerprint": fingerprint("main-group"),
}
],
}
)
def test_unknown_delivery_field_is_rejected(self):
with self.assertRaisesRegex(ConversationConfigError, "unknown fields"):
ShadowConversationRegistry.from_mapping(
{
"format_version": 1,
"enabled": True,
"delivery_enabled": True,
"targets": [],
}
)
class ShadowEventTests(unittest.TestCase):
def test_direct_event_uses_verified_conversation_identity(self):
digest = fingerprint("test-direct")
target = ConversationTarget(
key="test-direct",
kind=ConversationKind.DIRECT,
title_fingerprint=digest,
sender="Test User",
)
event = build_shadow_event(
target,
observed_at="2026-10-20T12:00:00+08:00",
text="/状态",
body_confidence=0.98,
visual_signature="visual-1",
bot_mention="JWeBot",
)
self.assertEqual(event["sender"], "Test User")
self.assertEqual(event["sender_confidence"], 1.0)
self.assertEqual(event["question"], "/状态")
self.assertTrue(event["mention_bot"])
self.assertEqual(event["group_fingerprint"], digest)
self.assertEqual(event["conversation_kind"], "direct")
self.assertTrue(event["shadow_only"])
self.assertFalse(event["dispatch_allowed"])
def test_unaddressed_direct_message_is_observed_but_not_triggered(self):
target = ConversationTarget(
key="test-direct",
kind=ConversationKind.DIRECT,
title_fingerprint=fingerprint("test-direct"),
sender="Test User",
)
event = build_shadow_event(
target,
observed_at="2026-10-20T12:00:00+08:00",
text="普通聊天",
body_confidence=0.98,
visual_signature="visual-2",
bot_mention="JWeBot",
)
self.assertEqual(event["question"], "")
self.assertFalse(event["mention_bot"])
self.assertTrue(event["shadow_only"])
self.assertFalse(event["dispatch_allowed"])
if __name__ == "__main__":
unittest.main()