test: add rule-case validator for no-reply and 🔚 injection paths
This commit is contained in:
54
docs/rule-cases.json
Normal file
54
docs/rule-cases.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"enabled": true,
|
||||||
|
"discordOnly": true,
|
||||||
|
"bypassUserIds": ["561921120408698910"],
|
||||||
|
"endSymbols": ["。", "!", "?", ".", "!", "?"]
|
||||||
|
},
|
||||||
|
"cases": [
|
||||||
|
{
|
||||||
|
"name": "non-discord skips gate",
|
||||||
|
"channel": "telegram",
|
||||||
|
"senderId": "u1",
|
||||||
|
"content": "hello",
|
||||||
|
"expect": {
|
||||||
|
"shouldUseNoReply": false,
|
||||||
|
"reason": "non_discord",
|
||||||
|
"injectEndMarker": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bypass sender injects end marker",
|
||||||
|
"channel": "discord",
|
||||||
|
"senderId": "561921120408698910",
|
||||||
|
"content": "hello",
|
||||||
|
"expect": {
|
||||||
|
"shouldUseNoReply": false,
|
||||||
|
"reason": "bypass_sender",
|
||||||
|
"injectEndMarker": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ending punctuation injects end marker",
|
||||||
|
"channel": "discord",
|
||||||
|
"senderId": "u2",
|
||||||
|
"content": "你好!",
|
||||||
|
"expect": {
|
||||||
|
"shouldUseNoReply": false,
|
||||||
|
"reason": "end_symbol:!",
|
||||||
|
"injectEndMarker": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "no ending punctuation triggers no-reply override",
|
||||||
|
"channel": "discord",
|
||||||
|
"senderId": "u2",
|
||||||
|
"content": "继续",
|
||||||
|
"expect": {
|
||||||
|
"shouldUseNoReply": true,
|
||||||
|
"reason": "rule_match_no_end_symbol",
|
||||||
|
"injectEndMarker": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "WhisperGate OpenClaw plugin",
|
"description": "WhisperGate OpenClaw plugin",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"check": "node ../scripts/check-plugin-files.mjs"
|
"check": "node ../scripts/check-plugin-files.mjs",
|
||||||
|
"check:rules": "node ../scripts/validate-rules.mjs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
scripts/validate-rules.mjs
Normal file
64
scripts/validate-rules.mjs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
function getLastChar(input) {
|
||||||
|
const t = (input || "").trim();
|
||||||
|
return t.length ? t[t.length - 1] : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function evaluateDecision({ config, channel, senderId, content }) {
|
||||||
|
if (config.enabled === false) {
|
||||||
|
return { shouldUseNoReply: false, reason: "disabled" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ch = (channel || "").toLowerCase();
|
||||||
|
if (config.discordOnly !== false && ch !== "discord") {
|
||||||
|
return { shouldUseNoReply: false, reason: "non_discord" };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (senderId && (config.bypassUserIds || []).includes(senderId)) {
|
||||||
|
return { shouldUseNoReply: false, reason: "bypass_sender" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const last = getLastChar(content || "");
|
||||||
|
if (last && (config.endSymbols || []).includes(last)) {
|
||||||
|
return { shouldUseNoReply: false, reason: `end_symbol:${last}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { shouldUseNoReply: true, reason: "rule_match_no_end_symbol" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldInjectEndMarker(reason) {
|
||||||
|
return reason === "bypass_sender" || String(reason).startsWith("end_symbol:");
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixturePath = path.join(process.cwd(), "docs", "rule-cases.json");
|
||||||
|
const payload = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
||||||
|
|
||||||
|
let ok = true;
|
||||||
|
for (const c of payload.cases || []) {
|
||||||
|
const d = evaluateDecision({
|
||||||
|
config: payload.config,
|
||||||
|
channel: c.channel,
|
||||||
|
senderId: c.senderId,
|
||||||
|
content: c.content,
|
||||||
|
});
|
||||||
|
|
||||||
|
const inject = shouldInjectEndMarker(d.reason);
|
||||||
|
const pass =
|
||||||
|
d.shouldUseNoReply === c.expect.shouldUseNoReply &&
|
||||||
|
d.reason === c.expect.reason &&
|
||||||
|
inject === c.expect.injectEndMarker;
|
||||||
|
|
||||||
|
if (!pass) {
|
||||||
|
ok = false;
|
||||||
|
console.error(`FAIL ${c.name}`);
|
||||||
|
console.error(` got: ${JSON.stringify({ ...d, injectEndMarker: inject })}`);
|
||||||
|
console.error(` expect: ${JSON.stringify(c.expect)}`);
|
||||||
|
} else {
|
||||||
|
console.log(`OK ${c.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ok) process.exit(1);
|
||||||
|
console.log("all rule cases passed");
|
||||||
Reference in New Issue
Block a user