Lumi/plugins/lumi_overlay/tests/verify.js
2026-07-26 22:06:34 +02:00

129 lines
5.5 KiB
JavaScript

"use strict";
const assert = require("assert");
const { WebSocket } = require("ws");
const { parseClientMessage, sanitizeChat, sanitizeEvent, MAX_MESSAGE_BYTES } = require("../backend/protocol");
const { OverlayFeed, MAX_QUEUE, matchesSelectedSource } = require("../backend/feed");
const { isReadableTextChannel } = require("../backend/sources");
function run() {
verifyProtocol();
verifyCapabilityGate();
verifyFilteringAndNoBacklog();
verifyDiscordReadability();
verifyBoundedQueueAndDeduplication();
verifySanitization();
console.log("Lumi native overlay verification passed: protocol limits, server-side filtering, no backlog, reconnect-safe deduplication, bounded queues, and payload sanitization.");
}
function verifyDiscordReadability() {
const discord = { user: { id: "bot" } };
const channel = {
guild: { id: "guild" },
isTextBased: () => true,
permissionsFor: () => ({ has: (flag) => flag === 1024n })
};
assert.equal(isReadableTextChannel(channel, discord), true);
assert.equal(isReadableTextChannel({ ...channel, permissionsFor: () => ({ has: () => false }) }, discord), false);
assert.equal(isReadableTextChannel({ ...channel, isTextBased: () => false }, discord), false);
}
function verifyCapabilityGate() {
const feed = Object.create(OverlayFeed.prototype);
let response = "";
const socket = {
write(value) { response += value; },
destroy() {}
};
feed.devices = { authenticate: (_header, capability) => {
assert.equal(capability, "overlay.read.v1");
return { allowed: false, reason: "missing_credentials" };
} };
feed.upgrade({ headers: {}, socket: {} }, socket, Buffer.alloc(0));
assert.match(response, /^HTTP\/1.1 401/);
response = "";
feed.devices = { authenticate: () => ({ allowed: false, reason: "capability_revoked" }) };
feed.upgrade({ headers: { authorization: "redacted" }, socket: {} }, socket, Buffer.alloc(0));
assert.match(response, /^HTTP\/1.1 403/);
}
function verifyProtocol() {
assert.deepEqual(parseClientMessage(JSON.stringify({
type: "subscribe",
sources: ["twitch:lumi", "twitch:lumi", "discord:guild:channel"],
events: ["twitch.follow"]
})), {
type: "subscribe",
sources: ["twitch:lumi", "discord:guild:channel"],
events: ["twitch.follow"]
});
assert.throws(() => parseClientMessage(JSON.stringify({ type: "history" })), /Unsupported/);
assert.throws(() => parseClientMessage(Buffer.alloc(MAX_MESSAGE_BYTES + 1)), /too large/);
}
function verifyFilteringAndNoBacklog() {
const feed = Object.create(OverlayFeed.prototype);
feed.sessions = new Set();
feed.sequence = 0;
feed.publishedSeen = new Map();
feed.publishChat({ id: "before", platform: "twitch", channel: { name: "lumi" } });
const sent = [];
const session = sessionFor(sent, ["twitch:lumi"]);
feed.sessions.add(session);
assert.equal(sent.length, 0, "opening/subscribing must not replay a pre-connection message");
feed.publishChat({
id: "selected", platform: "twitch", text: "hello", timestamp: 1,
channel: { id: "1", name: "lumi", key: "lumi" },
author: { id: "viewer", name: "Viewer", badges: [] }, emotes: []
});
feed.publishChat({
id: "selected", platform: "twitch", text: "duplicate", timestamp: 3,
channel: { id: "1", name: "lumi", key: "lumi" },
author: { id: "viewer", name: "Viewer", badges: [] }, emotes: []
});
feed.publishChat({
id: "other", platform: "twitch", text: "hidden", timestamp: 2,
channel: { id: "2", name: "other", key: "other" },
author: { id: "viewer", name: "Viewer", badges: [] }, emotes: []
});
assert.equal(sent.length, 1, "provider duplicates must remain suppressed across feed sessions/reconnects");
assert.equal(JSON.parse(sent[0]).message.id, "selected");
assert.equal(matchesSelectedSource(new Set(["discord:guild:channel"]), "discord:guild:*"), true);
}
function verifyBoundedQueueAndDeduplication() {
const feed = Object.create(OverlayFeed.prototype);
feed.sequence = 0;
const session = sessionFor([], ["twitch:lumi"], WebSocket.CLOSED);
for (let index = 0; index < MAX_QUEUE + 40; index += 1)
feed.enqueue(session, "chat", { index }, `message:${index}`);
assert.equal(session.queue.length, MAX_QUEUE);
assert.equal(session.queue[0].index, 40, "overflow must deterministically drop the oldest queued item");
feed.enqueue(session, "chat", { index: 999 }, `message:${MAX_QUEUE + 39}`);
assert.equal(session.queue.length, MAX_QUEUE, "a duplicate event must not enter the queue twice");
}
function verifySanitization() {
const chat = sanitizeChat({
id: "id", platform: "twitch", text: "<script>plain text</script>",
channel: {}, author: { id: "user", name: "Viewer", avatar: "http://insecure.test/avatar.png" },
emotes: [{ start: 0, end: 1, image: "https://cdn.test/emote.png" }]
});
assert.equal(chat.text, "<script>plain text</script>", "content remains plain data and is not interpreted server-side");
assert.equal(chat.author.avatar, null);
assert.equal(chat.emotes[0].image, "https://cdn.test/emote.png");
const event = sanitizeEvent({ id: "event", type: "twitch.follow", payload: { user_name: "Viewer", access_token: "secret" } });
assert.equal(event.payload.access_token, undefined);
assert.equal(event.payload.user_name, "Viewer");
}
function sessionFor(sent, sources, state = WebSocket.OPEN) {
return {
socket: { readyState: state, send(value) { sent.push(value); } },
device: {}, sources: new Set(sources), events: new Set(), subscribed: true,
queue: [], draining: false, seen: new Map(), lastPong: Date.now()
};
}
run();