Files
aios/tests/test_patch_openclaw_weixin_sender_identity.py
T

208 lines
8.0 KiB
Python

import importlib.util
import json
from pathlib import Path
import pytest
SCRIPT = Path(__file__).parents[1] / "fixes" / "patch_openclaw_weixin_sender_identity.py"
SPEC = importlib.util.spec_from_file_location("sender_identity_patch", SCRIPT)
MODULE = importlib.util.module_from_spec(SPEC)
assert SPEC.loader is not None
SPEC.loader.exec_module(MODULE)
def plugin_fixture(tmp_path: Path) -> Path:
root = tmp_path / "plugin"
(root / "src/messaging").mkdir(parents=True)
(root / "dist/src/messaging").mkdir(parents=True)
(root / "src/monitor").mkdir(parents=True)
(root / "dist/src/monitor").mkdir(parents=True)
(root / "package.json").write_text(json.dumps({"name": "@tencent-weixin/openclaw-weixin"}), encoding="utf-8")
(root / "src/messaging/inbound.ts").write_text(
"const ctx = {\n From: from_user_id,\n To: from_user_id,\n};\n", encoding="utf-8"
)
(root / "dist/src/messaging/inbound.js").write_text(
"const ctx = {\n From: from_user_id,\n To: from_user_id,\n};\n", encoding="utf-8"
)
(root / "src/messaging/process-message.ts").write_text(
'''// AIOS Hermes image follow-up helpers end
export async function processOneMessage(
) {
await deps.channelRuntime.session.recordInboundSession({
storePath,
sessionKey: route.sessionKey,
ctx: finalized as Parameters<typeof deps.channelRuntime.session.recordInboundSession>[0]["ctx"],
updateLastRoute: {
sessionKey: route.mainSessionKey,
channel: "openclaw-weixin",
to: ctx.To,
accountId: deps.accountId,
},
onRecordError: (err) => deps.errLog(`recordInboundSession: ${String(err)}`),
});
try {
await deps.channelRuntime.reply.withReplyDispatcher({
dispatcher,
run: () =>
deps.channelRuntime.reply.dispatchReplyFromConfig({
ctx: finalized,
cfg: deps.config,
dispatcher,
replyOptions: {
...replyOptions,
...(replyProgressSender?.replyOptions ?? {}),
disableBlockStreaming: true,
},
}),
});
} catch (err) {}
}
''', encoding="utf-8"
)
(root / "dist/src/messaging/process-message.js").write_text(
'''// AIOS Hermes image follow-up helpers end
export async function processOneMessage(full, deps) {
await deps.channelRuntime.session.recordInboundSession({
storePath,
sessionKey: route.sessionKey,
ctx: finalized,
updateLastRoute: {
sessionKey: route.mainSessionKey,
channel: "openclaw-weixin",
to: ctx.To,
accountId: deps.accountId,
},
onRecordError: (err) => deps.errLog(`recordInboundSession: ${String(err)}`),
});
try {
await deps.channelRuntime.reply.withReplyDispatcher({
dispatcher,
run: () => deps.channelRuntime.reply.dispatchReplyFromConfig({
ctx: finalized,
cfg: deps.config,
dispatcher,
replyOptions: {
...replyOptions,
...(replyProgressSender?.replyOptions ?? {}),
disableBlockStreaming: true,
},
}),
});
}
catch (err) { }
}
''', encoding="utf-8"
)
(root / "src/monitor/monitor.ts").write_text(
'''import { processOneMessage } from "../messaging/process-message.js";
const RETRY_DELAY_MS = 2_000;
/**
* Long-poll loop: getUpdates -> normalize -> recordInboundSession -> dispatchReplyFromConfig.
*/
async function fixture() {
await processOneMessage(full, {
accountId,
config,
channelRuntime,
baseUrl,
cdnBaseUrl,
token,
typingTicket: cachedConfig.typingTicket,
log: opts.runtime?.log ?? (() => {}),
errLog,
});
}
''', encoding="utf-8"
)
(root / "dist/src/monitor/monitor.js").write_text(
'''import { processOneMessage } from "../messaging/process-message.js";
const RETRY_DELAY_MS = 2_000;
/**
* Long-poll loop: getUpdates -> normalize -> recordInboundSession -> dispatchReplyFromConfig.
*/
async function fixture() {
await processOneMessage(full, {
accountId,
config,
channelRuntime,
baseUrl,
cdnBaseUrl,
token,
typingTicket: cachedConfig.typingTicket,
log: opts.runtime?.log ?? (() => { }),
errLog,
});
}
''', encoding="utf-8"
)
return root
def test_check_is_non_mutating_and_reports_all_files(tmp_path: Path):
root = plugin_fixture(tmp_path)
result = MODULE.patch_plugin(root, apply=False)
assert result["status"] == "CHECKED"
assert result["changed"] is True
assert all(item["changed"] for item in result["files"].values())
assert "SenderId" not in (root / "src/messaging/inbound.ts").read_text(encoding="utf-8")
def test_apply_updates_source_and_dist_with_a_hashable_backup(tmp_path: Path):
root = plugin_fixture(tmp_path)
backup = tmp_path / "backup"
result = MODULE.patch_plugin(root, apply=True, backup_dir=backup)
assert result["status"] == "APPLIED"
for relative in MODULE.PATCHES:
assert result["files"][relative]["before_sha256"] == result["files"][relative]["backup_sha256"]
assert result["files"][relative]["after_sha256"] != result["files"][relative]["before_sha256"]
assert "SenderId: from_user_id" in (root / "src/messaging/inbound.ts").read_text(encoding="utf-8")
source_monitor = (root / "src/monitor/monitor.ts").read_text(encoding="utf-8")
dist_monitor = (root / "dist/src/monitor/monitor.js").read_text(encoding="utf-8")
assert "processOneMessageWithReplySessionRetry" in source_monitor
assert "processOneMessageWithReplySessionRetry" in dist_monitor
assert "resolveReplySessionInitRetryDelayMs" in source_monitor
assert "resolveReplySessionInitRetryDelayMs" in dist_monitor
assert "REPLY_SESSION_INIT_RETRY_DELAYS_MS" in source_monitor
assert "REPLY_SESSION_INIT_RETRY_DELAYS_MS" in dist_monitor
assert "}, abortSignal);" in source_monitor
assert "}, abortSignal);" in dist_monitor
source_process = (root / "src/messaging/process-message.ts").read_text(encoding="utf-8")
dist_process = (root / "dist/src/messaging/process-message.js").read_text(encoding="utf-8")
assert "dispatchWithSessionInitConflictRetry" in source_process
assert "dispatchWithSessionInitConflictRetry" in dist_process
assert "resolveDispatchSessionInitRetryDelayMs" in source_process
assert "resolveDispatchSessionInitRetryDelayMs" in dist_process
assert "const sessionMetaTasks: Promise<unknown>[] = [];" in source_process
assert "trackSessionMetaTask: (task) => sessionMetaTasks.push(task)" in source_process
assert "await Promise.all(sessionMetaTasks);" in source_process
assert "const sessionMetaTasks = [];" in dist_process
assert "trackSessionMetaTask: (task) => sessionMetaTasks.push(task)" in dist_process
assert "await Promise.all(sessionMetaTasks);" in dist_process
def test_second_check_is_idempotent(tmp_path: Path):
root = plugin_fixture(tmp_path)
MODULE.patch_plugin(root, apply=True, backup_dir=tmp_path / "backup")
result = MODULE.patch_plugin(root, apply=False)
assert result["changed"] is False
def test_shape_drift_fails_closed(tmp_path: Path):
root = plugin_fixture(tmp_path)
(root / "src/messaging/inbound.ts").write_text("unexpected", encoding="utf-8")
with pytest.raises(RuntimeError, match="target_shape_mismatch"):
MODULE.patch_plugin(root, apply=False)
def test_apply_requires_new_backup_directory(tmp_path: Path):
root = plugin_fixture(tmp_path)
with pytest.raises(RuntimeError, match="backup_dir_required"):
MODULE.patch_plugin(root, apply=True)
existing = tmp_path / "existing"
existing.mkdir()
with pytest.raises(FileExistsError):
MODULE.patch_plugin(root, apply=True, backup_dir=existing)