release: public alpha 0.6.0 baseline
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
SDK_SRC = PROJECT_ROOT / "packages" / "jwebot-sdk" / "src"
|
||||
KERNEL_SRC = PROJECT_ROOT / "packages" / "jwebot-kernel" / "src"
|
||||
HOST_SRC = PROJECT_ROOT / "packages" / "jwebot-extension-host" / "src"
|
||||
OFFICIAL_SRC = (
|
||||
PROJECT_ROOT
|
||||
/ "packages"
|
||||
/ "jwebot-official-extensions"
|
||||
/ "src"
|
||||
)
|
||||
try:
|
||||
import tomli
|
||||
|
||||
TOML_EXTRA = [str(Path(tomli.__file__).resolve().parent.parent)]
|
||||
except ModuleNotFoundError:
|
||||
TOML_EXTRA = []
|
||||
sys.path[:0] = [
|
||||
str(SDK_SRC),
|
||||
str(OFFICIAL_SRC),
|
||||
str(KERNEL_SRC),
|
||||
]
|
||||
|
||||
from jwebot_kernel import ( # noqa: E402
|
||||
ExtensionHostClient,
|
||||
build_release,
|
||||
install_release,
|
||||
sign_release_record,
|
||||
)
|
||||
from jwebot_official_extensions import LOCAL_EXTENSIONS # noqa: E402
|
||||
from tests.test_jwebot_edge import MODULE, config, event # noqa: E402
|
||||
|
||||
|
||||
PUBLISHER = "test-publisher"
|
||||
|
||||
|
||||
@unittest.skipUnless(
|
||||
Path("/usr/bin/ssh-keygen").is_file(),
|
||||
"/usr/bin/ssh-keygen is unavailable",
|
||||
)
|
||||
class HostedCommandParityTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.workspace = tempfile.TemporaryDirectory()
|
||||
cls.root = Path(cls.workspace.name)
|
||||
cls.private_key = cls.root / "publisher"
|
||||
result = subprocess.run(
|
||||
[
|
||||
"/usr/bin/ssh-keygen",
|
||||
"-q",
|
||||
"-t",
|
||||
"ed25519",
|
||||
"-N",
|
||||
"",
|
||||
"-f",
|
||||
str(cls.private_key),
|
||||
],
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError("cannot generate test publisher key")
|
||||
cls.allowed_signers = cls.root / "allowed_signers"
|
||||
cls.allowed_signers.write_text(
|
||||
f"{PUBLISHER} "
|
||||
+ cls.private_key.with_suffix(".pub").read_text(
|
||||
encoding="utf-8"
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
cls.wrapper = cls.root / "host-wrapper.py"
|
||||
cls.wrapper.write_text(
|
||||
"\n".join(
|
||||
(
|
||||
"import runpy, sys",
|
||||
"sys.path[:0] = [",
|
||||
f" {str(SDK_SRC)!r},",
|
||||
f" {str(KERNEL_SRC)!r},",
|
||||
f" {str(HOST_SRC)!r},",
|
||||
*(
|
||||
f" {path!r},"
|
||||
for path in TOML_EXTRA
|
||||
),
|
||||
"]",
|
||||
"runpy.run_module('jwebot_extension_host', run_name='__main__')",
|
||||
)
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
cls.state_root = cls.root / "extensions"
|
||||
health_client = cls.client()
|
||||
for descriptor in LOCAL_EXTENSIONS:
|
||||
source = (
|
||||
PROJECT_ROOT
|
||||
/ "extensions"
|
||||
/ descriptor.directory
|
||||
/ descriptor.extension_id
|
||||
)
|
||||
artifact, record_path, _ = build_release(
|
||||
source,
|
||||
cls.root / "dist" / descriptor.extension_id,
|
||||
)
|
||||
signature_path, _ = sign_release_record(
|
||||
record_path,
|
||||
cls.private_key,
|
||||
PUBLISHER,
|
||||
)
|
||||
install_release(
|
||||
artifact,
|
||||
record_path,
|
||||
cls.state_root / descriptor.extension_id,
|
||||
signature=signature_path,
|
||||
allowed_signers=cls.allowed_signers,
|
||||
expected_publisher=PUBLISHER,
|
||||
health_check=lambda release, client=health_client: client.invoke(
|
||||
release,
|
||||
{"op": "health"},
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.workspace.cleanup()
|
||||
|
||||
@classmethod
|
||||
def client(cls):
|
||||
return ExtensionHostClient(
|
||||
command=(sys.executable, str(cls.wrapper)),
|
||||
timeout_seconds=5,
|
||||
allowed_signers=cls.allowed_signers,
|
||||
expected_publisher=PUBLISHER,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def factory(cls, plugin_id, release_dir):
|
||||
del plugin_id
|
||||
client = cls.client()
|
||||
return lambda request, service: client.invoke(
|
||||
release_dir,
|
||||
request,
|
||||
service_handler=service,
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self.hosted_temp = tempfile.TemporaryDirectory()
|
||||
self.compat_temp = tempfile.TemporaryDirectory()
|
||||
self.hosted = MODULE.EdgeApplication(
|
||||
config(
|
||||
Path(self.hosted_temp.name),
|
||||
extension_mode="host-only",
|
||||
extension_transport="process",
|
||||
extension_state_root=self.state_root,
|
||||
extension_allowed_signers=self.allowed_signers,
|
||||
extension_publisher=PUBLISHER,
|
||||
),
|
||||
host_invoker_factory=self.factory,
|
||||
)
|
||||
self.compat = MODULE.EdgeApplication(
|
||||
config(
|
||||
Path(self.compat_temp.name),
|
||||
local_extensions_enabled=False,
|
||||
)
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.hosted.close()
|
||||
self.compat.close()
|
||||
self.hosted_temp.cleanup()
|
||||
self.compat_temp.cleanup()
|
||||
|
||||
def assert_command_equal(
|
||||
self,
|
||||
event_id,
|
||||
text,
|
||||
*,
|
||||
sender="Alice",
|
||||
):
|
||||
incoming = event(event_id, text, sender=sender)
|
||||
hosted = self.hosted.accept(dict(incoming))
|
||||
compat = self.compat.accept(dict(incoming))
|
||||
self.assertEqual(hosted["route"], compat["route"])
|
||||
self.assertEqual(hosted["text"], compat["text"])
|
||||
return hosted
|
||||
|
||||
def test_all_plugin_families_match_direct_compatibility_path(self):
|
||||
stateless = (
|
||||
("dice", "@JWeBot /丢骰子5d20"),
|
||||
("coin", "@JWeBot /翻硬币"),
|
||||
("random", "@JWeBot /随机数 -10 100"),
|
||||
("lot", "@JWeBot /抽签"),
|
||||
("fortune", "@JWeBot /今日运势"),
|
||||
("memory-help", "@JWeBot /记忆帮助"),
|
||||
("scan-empty", "@JWeBot /扫码排行"),
|
||||
("repeat-empty", "@JWeBot /复读榜"),
|
||||
("history-empty", "@JWeBot /查记录 测试"),
|
||||
)
|
||||
for event_id, text in stateless:
|
||||
with self.subTest(command=text):
|
||||
self.assert_command_equal(event_id, text)
|
||||
|
||||
memory = (
|
||||
("remember", "@JWeBot /记住 我喜欢家庭服务器"),
|
||||
("my-memory", "@JWeBot /我的记忆"),
|
||||
("person-memory", "@JWeBot /人物记忆 Alice"),
|
||||
("forget-memory", "@JWeBot /忘记 全部"),
|
||||
)
|
||||
for event_id, text in memory:
|
||||
with self.subTest(command=text):
|
||||
self.assert_command_equal(event_id, text)
|
||||
|
||||
challenge = (
|
||||
("challenge-start", "@JWeBot /丢骰子挑战 开始 5d20", "Alice"),
|
||||
("challenge-alice", "@JWeBot /丢骰子挑战", "Alice"),
|
||||
("challenge-gb", "@JWeBot /丢骰子挑战", "Bob"),
|
||||
("challenge-state", "@JWeBot /丢骰子挑战 状态", "Alice"),
|
||||
("challenge-settle", "@JWeBot /丢骰子挑战 结算", "Alice"),
|
||||
)
|
||||
for event_id, text, sender in challenge:
|
||||
with self.subTest(command=text):
|
||||
self.assert_command_equal(event_id, text, sender=sender)
|
||||
|
||||
self.assert_command_equal(
|
||||
"guess-start",
|
||||
"@JWeBot /猜数字 开始 50",
|
||||
)
|
||||
target = self.hosted.ledger.local_game(
|
||||
"group-a",
|
||||
"guess_number",
|
||||
)["target"]
|
||||
self.assertEqual(
|
||||
target,
|
||||
self.compat.ledger.local_game(
|
||||
"group-a",
|
||||
"guess_number",
|
||||
)["target"],
|
||||
)
|
||||
self.assert_command_equal("guess-status", "@JWeBot /猜数字 状态")
|
||||
self.assert_command_equal("guess-win", f"@JWeBot /猜 {target}")
|
||||
|
||||
rps = (
|
||||
("rps-start", "@JWeBot /石头剪刀布 开始", "Alice"),
|
||||
("rps-join", "@JWeBot /石头剪刀布 加入", "Bob"),
|
||||
("rps-first", "@JWeBot /石头剪刀布 出 石头", "Alice"),
|
||||
("rps-second", "@JWeBot /石头剪刀布 出 剪刀", "Bob"),
|
||||
("rps-end", "@JWeBot /石头剪刀布 结束", "Alice"),
|
||||
)
|
||||
for event_id, text, sender in rps:
|
||||
with self.subTest(command=text):
|
||||
self.assert_command_equal(event_id, text, sender=sender)
|
||||
|
||||
for application in (self.hosted, self.compat):
|
||||
application.local.steam = mock.Mock()
|
||||
application.local.steam.render.return_value = (
|
||||
"Steam:yr 正在玩 Dota 2"
|
||||
)
|
||||
self.assert_command_equal("steam", "@JWeBot /steam")
|
||||
hosted_row = self.hosted.ledger.row("steam")
|
||||
compat_row = self.compat.ledger.row("steam")
|
||||
self.assertEqual(
|
||||
self.hosted.generate_steam_reply(hosted_row),
|
||||
self.compat.generate_steam_reply(compat_row),
|
||||
)
|
||||
|
||||
self.assert_command_equal(
|
||||
"restart-guess",
|
||||
"@JWeBot /猜数字 开始 30",
|
||||
)
|
||||
expected_target = self.hosted.ledger.local_game(
|
||||
"group-a",
|
||||
"guess_number",
|
||||
)["target"]
|
||||
self.hosted.close()
|
||||
self.hosted = MODULE.EdgeApplication(
|
||||
config(
|
||||
Path(self.hosted_temp.name),
|
||||
extension_mode="host-only",
|
||||
extension_transport="process",
|
||||
extension_state_root=self.state_root,
|
||||
extension_allowed_signers=self.allowed_signers,
|
||||
extension_publisher=PUBLISHER,
|
||||
),
|
||||
host_invoker_factory=self.factory,
|
||||
)
|
||||
restored = self.hosted.ledger.local_game(
|
||||
"group-a",
|
||||
"guess_number",
|
||||
)
|
||||
self.assertEqual(restored["target"], expected_target)
|
||||
self.assertEqual(restored["upper"], 30)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user