Files
Veskslop-t/poc/src/test-communication.ts
T

219 lines
7.2 KiB
TypeScript

/*
* Veskslop — unit tests for the pure communication logic.
*
* Usage: pnpm test:comm
*/
import assert from "node:assert/strict";
import {
MessageLog,
TRUSTED_LOG_LIMIT,
UNTRUSTED_LOG_LIMIT,
assembleContext,
compareSnowflakes,
extractMentionIds,
formatContextLine,
hasTier,
isTrusted,
parseCommand,
parsePoll,
parseTier,
redactSecrets,
stripMentions,
toCachedMessage,
type CachedMessage,
type PuppetMessage
} from "./communication.js";
import { PACING, planSend } from "./pacing.js";
function cached(id: number, content = `m${id}`): CachedMessage {
return { id: String(id), authorId: "1", authorName: "a", content, timestamp: "" };
}
function entry(id: number, trusted: boolean): PuppetMessage {
return {
id: String(id),
channelId: "c",
authorId: trusted ? "owner" : "stranger",
authorName: "tester",
content: `msg ${id}`,
timestamp: new Date().toISOString(),
trusted
};
}
// ring buffer limits
{
const log = new MessageLog();
for (let i = 0; i < 150; i++) log.add(entry(i, true));
assert.equal(log.trusted.length, TRUSTED_LOG_LIMIT);
assert.equal(log.trusted[0].id, "50");
for (let i = 0; i < 40; i++) log.add(entry(i, false));
assert.equal(log.untrusted.length, UNTRUSTED_LOG_LIMIT);
assert.equal(log.untrusted[0].id, "10");
}
// mentions
{
const ids = extractMentionIds("<@!42> hi <@7>", [{ id: "9" }]);
assert.deepEqual([...ids].sort(), ["42", "7", "9"]);
assert.equal(stripMentions("<@!42> whitelist list", new Set(["42"])), "whitelist list");
assert.equal(stripMentions("<@42> <@99> hey", new Set(["42"])), "<@99> hey");
}
// commands
{
assert.deepEqual(parseCommand(" WHITELIST add <@123> "), { name: "whitelist", args: ["add", "<@123>"] });
assert.deepEqual(parseCommand("whitelist list"), { name: "whitelist", args: ["list"] });
assert.equal(parseCommand(" "), undefined);
}
// trust
{
assert.equal(isTrusted("owner", "owner", []), true);
assert.equal(isTrusted("trusted", "owner", ["trusted"]), true);
assert.equal(isTrusted("stranger", "owner", ["trusted"]), false);
}
// pacing: fast first send in a burst, 500-2800 ms afterwards, variable reset
{
for (let i = 0; i < 200; i++) {
const first = planSend(1_000_000, 0);
assert.ok(
first.delay >= PACING.firstMinMs && first.delay <= PACING.firstMaxMs,
`first delay out of range: ${first.delay}`
);
assert.ok(
first.burstUntil - 1_000_000 >= PACING.idleMinMs && first.burstUntil - 1_000_000 <= PACING.idleMaxMs,
`burst window out of range: ${first.burstUntil}`
);
const followUp = planSend(1_000_000, 1_001_000);
assert.ok(
followUp.delay >= PACING.burstMinMs && followUp.delay <= PACING.burstMaxMs,
`burst delay out of range: ${followUp.delay}`
);
// A fresh burst after the window expired is fast again.
const resumed = planSend(2_000_000, 1_000_000);
assert.ok(
resumed.delay >= PACING.firstMinMs && resumed.delay <= PACING.firstMaxMs,
`resumed delay out of range: ${resumed.delay}`
);
}
}
// context assembly
{
const messages = Array.from({ length: 20 }, (_, i) => cached(100 + i));
const byCount = assembleContext(messages, { maxMessages: 15, maxChars: 1_000_000 });
assert.equal(byCount.messages.length, 15);
assert.equal(byCount.messages[0].id, "105");
assert.equal(byCount.truncated, true);
const long = Array.from({ length: 10 }, (_, i) => cached(i, "y".repeat(50)));
const byChars = assembleContext(long, { maxMessages: 15, maxChars: 120 });
assert.equal(byChars.messages.length, 2);
assert.ok(byChars.chars <= 120);
assert.equal(byChars.truncated, true);
// the newest message is always kept, even when it exceeds the budget
const single = assembleContext([cached(1, "z".repeat(5000))], { maxChars: 100 });
assert.equal(single.messages.length, 1);
// the trigger message can be excluded
const excluded = assembleContext([cached(1), cached(2), cached(3)], { excludeId: "3" });
assert.deepEqual(
excluded.messages.map(m => m.id),
["1", "2"]
);
}
// context formatting and snowflake compare
{
const withFile: CachedMessage = { ...cached(1, "look"), attachments: ["clip.mp4"] };
assert.equal(formatContextLine(withFile), "a: look [file: clip.mp4]");
assert.equal(formatContextLine({ ...cached(2, ""), attachments: ["a.png"] }), "a: [file: a.png]");
assert.equal(compareSnowflakes("10", "9"), 1);
assert.equal(compareSnowflakes("9", "10"), -1);
assert.equal(compareSnowflakes("5", "5"), 0);
}
// raw message conversion keeps file names only
{
const converted = toCachedMessage({
id: 5,
author: { id: 7, username: "u" },
content: "see this",
timestamp: "t",
attachments: [{ filename: "a.png" }, { filename: "b.mp4" }]
});
assert.deepEqual(converted, {
id: "5",
authorId: "7",
authorName: "u",
content: "see this",
timestamp: "t",
attachments: ["a.png", "b.mp4"]
});
}
// timestamps in context lines
{
const withTime: CachedMessage = {
...cached(1, "hi"),
timestamp: "2026-09-11T14:03:00.000Z"
};
assert.match(formatContextLine(withTime), /^\[\d\d:\d\d\] a: hi$/);
assert.equal(formatContextLine(cached(2, "no time")), "a: no time");
}
// secret redaction
{
const fakeToken = `${"x".repeat(24)}.${"abcde"}.${"y".repeat(25)}`;
assert.equal(redactSecrets(`token ${fakeToken} here`), "token [redacted] here");
assert.equal(redactSecrets("mfa." + "z".repeat(30)), "[redacted]");
assert.equal(redactSecrets("nothing to hide"), "nothing to hide");
}
// trust tiers
{
assert.equal(hasTier("admin", "admin"), true);
assert.equal(hasTier("trusted", "admin"), false);
assert.equal(hasTier("trusted", "user"), true);
assert.equal(hasTier(undefined, "user"), false);
assert.equal(parseTier("trusted"), "trusted");
assert.equal(parseTier("bogus"), undefined);
}
// log lookup by ID
{
const log = new MessageLog();
log.add({ ...entry(1, true), context: [] });
assert.equal(log.find("1")?.id, "1");
assert.equal(log.find("nope"), undefined);
}
// poll parser
{
assert.deepEqual(parsePoll("Lunch? | Pizza | Sushi"), { question: "Lunch?", options: ["Pizza", "Sushi"] });
assert.deepEqual(parsePoll("Q | A | B | C | D"), { question: "Q", options: ["A", "B", "C", "D"] });
assert.deepEqual(parsePoll('"What country is best" Sweden // Denmark // Madagaskar'), {
question: "What country is best",
options: ["Sweden", "Denmark", "Madagaskar"]
});
// the whitespace guard keeps URLs together
assert.deepEqual(parsePoll('"Links?" https://a.example // https://b.example'), {
question: "Links?",
options: ["https://a.example", "https://b.example"]
});
assert.equal(parsePoll("Q | A"), undefined);
assert.equal(parsePoll("Q | A | B | C | D | E"), undefined);
assert.equal(parsePoll(`Q | ${"x".repeat(100)} | B`), undefined);
}
console.log("all communication tests passed");