136 lines
4.7 KiB
JavaScript
136 lines
4.7 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import test from "node:test";
|
|
import {
|
|
SessionProjection,
|
|
buildBrowseUrl,
|
|
buildStreamUrl,
|
|
chooseSnapshotTransport,
|
|
safeText,
|
|
} from "../../src/FinalFactory.Rendezvous.Server/Diagnostics/Assets/app.mjs";
|
|
|
|
const cursor = "rvs1.test-cursor";
|
|
|
|
function session(id, name = `Host ${id}`) {
|
|
return {
|
|
contractVersion: 1,
|
|
listingId: `00000000-0000-0000-0000-${String(id).padStart(12, "0")}`,
|
|
gameId: "space-game",
|
|
environmentId: "smoke",
|
|
regionId: "local",
|
|
protocolVersion: 1,
|
|
buildVersion: "1.0.0",
|
|
displayName: name,
|
|
visibility: "public",
|
|
publisherTrustMode: "managedDedicated",
|
|
capacity: { currentPlayers: 1, maximumPlayers: 8 },
|
|
metadata: { mode: "online-coop" },
|
|
};
|
|
}
|
|
|
|
function event(kind, values = {}) {
|
|
return {
|
|
contractVersion: 1,
|
|
kind,
|
|
cursor: values.cursor ?? cursor,
|
|
session: values.session ?? null,
|
|
listingId: values.listingId ?? null,
|
|
};
|
|
}
|
|
|
|
test("snapshot and ordered deltas match the final public projection", () => {
|
|
const projection = new SessionProjection(10);
|
|
assert.equal(projection.replace([session(1)], cursor), "applied");
|
|
assert.equal(projection.apply(event("sessionUpsert", {
|
|
cursor: `${cursor}-2`,
|
|
session: session(2),
|
|
})), "applied");
|
|
assert.equal(projection.apply(event("sessionUpsert", {
|
|
cursor: `${cursor}-3`,
|
|
session: session(1, "Updated host"),
|
|
})), "applied");
|
|
assert.equal(projection.apply(event("sessionRemove", {
|
|
cursor: `${cursor}-4`,
|
|
listingId: session(2).listingId,
|
|
})), "applied");
|
|
|
|
assert.deepEqual(
|
|
projection.sessions().map((entry) => entry.session.displayName),
|
|
["Updated host"],
|
|
);
|
|
assert.equal(projection.cursor, `${cursor}-4`);
|
|
});
|
|
|
|
test("reset, malformed events, and a partial snapshot fail closed", () => {
|
|
const projection = new SessionProjection(2);
|
|
assert.equal(projection.replace([session(1)], cursor, true), "applied");
|
|
assert.equal(projection.apply(event("sessionRemove", {
|
|
listingId: session(1).listingId,
|
|
})), "refresh");
|
|
assert.equal(projection.apply(event("reset")), "reset");
|
|
assert.equal(projection.apply({ kind: "sessionUpsert" }), "invalid");
|
|
assert.equal(projection.replace([session(1), session(2), session(3)], cursor), "overflow");
|
|
});
|
|
|
|
test("bursts coalesce by listing identity and memory stays bounded", () => {
|
|
const projection = new SessionProjection(2);
|
|
assert.equal(projection.replace([], cursor), "applied");
|
|
for (let index = 0; index < 1_000; index += 1) {
|
|
assert.equal(projection.apply(event("sessionUpsert", {
|
|
cursor: `${cursor}-${index}`,
|
|
session: session(1, `Host ${index}`),
|
|
})), "applied");
|
|
}
|
|
assert.equal(projection.sessions().length, 1);
|
|
assert.equal(projection.sessions()[0].session.displayName, "Host 999");
|
|
assert.equal(projection.apply(event("sessionUpsert", { session: session(2) })), "applied");
|
|
assert.equal(projection.apply(event("sessionUpsert", { session: session(3) })), "overflow");
|
|
assert.equal(projection.sessions().length, 2);
|
|
});
|
|
|
|
test("requests are fixed same-origin paths with an exact configured filter", () => {
|
|
const filter = {
|
|
gameId: "space-game",
|
|
environmentId: "smoke",
|
|
protocolVersion: 1,
|
|
regionId: "local",
|
|
excludeFull: true,
|
|
};
|
|
const browse = buildBrowseUrl(filter);
|
|
const stream = buildStreamUrl(filter, cursor);
|
|
assert.match(browse, /^\/v1\/sessions\?/u);
|
|
assert.match(stream, /^\/v1\/sessions\/stream\?/u);
|
|
assert.match(browse, /gameId=space-game/u);
|
|
assert.match(browse, /environmentId=smoke/u);
|
|
assert.match(stream, /streamCursor=rvs1.test-cursor/u);
|
|
assert.doesNotMatch(browse, /https?:/u);
|
|
assert.doesNotMatch(stream, /https?:/u);
|
|
});
|
|
|
|
test("snapshot transport preserves deliberate polling and bounds partial snapshots", () => {
|
|
assert.equal(chooseSnapshotTransport(false, false), "stream");
|
|
assert.equal(chooseSnapshotTransport(true, false), "boundedPolling");
|
|
assert.equal(chooseSnapshotTransport(false, true), "pollingOnly");
|
|
assert.equal(chooseSnapshotTransport(true, true), "pollingOnly");
|
|
});
|
|
|
|
test("hostile display data remains literal text and no unsafe DOM sink exists", async () => {
|
|
const payload = `<img src=x onerror=alert(1)><style>body{display:none}</style><a href=//evil>go</a>`;
|
|
assert.equal(safeText(payload, 200), payload);
|
|
const source = await readFile(new URL(
|
|
"../../src/FinalFactory.Rendezvous.Server/Diagnostics/Assets/app.mjs",
|
|
import.meta.url,
|
|
), "utf8");
|
|
for (const forbidden of [
|
|
"innerHTML",
|
|
"outerHTML",
|
|
"insertAdjacentHTML",
|
|
"document.write",
|
|
"eval(",
|
|
"new Function",
|
|
"window.location",
|
|
]) {
|
|
assert.equal(source.includes(forbidden), false, `unsafe browser sink: ${forbidden}`);
|
|
}
|
|
});
|