Files
ttrpg-tools/src/shared/session-id.test.ts
T
hypercross 68b976575a feat(session): add shared session id validation
Session ids become directory names server-side and topic levels on the broker, so they must reject path separators, ./.., whitespace and Windows reserved device names. toSessionId derives a safe id from a display name; isValidSessionId is the boundary check shared by the CLI and the web client.
2026-09-13 13:36:33 +08:00

74 lines
1.8 KiB
TypeScript

import {
SESSION_ID_MAX_LENGTH,
isValidSessionId,
toSessionId,
} from "./session-id";
describe("isValidSessionId", () => {
test.each(["default", "a", "A1", "my-game", "under_score", "-"])(
"accepts %p",
(id) => {
expect(isValidSessionId(id)).toBe(true);
},
);
test.each([
"",
".",
"..",
"../evil",
"..\\..\\evil",
"a/b",
"a\\b",
"a b",
"a.b",
"a/b/../c",
])("rejects %p", (id) => {
expect(isValidSessionId(id)).toBe(false);
});
test("rejects non-strings and over-long ids", () => {
expect(isValidSessionId(null)).toBe(false);
expect(isValidSessionId(undefined)).toBe(false);
expect(isValidSessionId(42)).toBe(false);
expect(isValidSessionId("a".repeat(SESSION_ID_MAX_LENGTH + 1))).toBe(false);
});
test("rejects Windows reserved device names", () => {
expect(isValidSessionId("con")).toBe(false);
expect(isValidSessionId("COM1")).toBe(false);
expect(isValidSessionId("Nul")).toBe(false);
});
});
describe("toSessionId", () => {
test("slugifies names", () => {
expect(toSessionId("My Game")).toBe("my-game");
expect(toSessionId(" Curse of Strahd!! ")).toBe("curse-of-strahd");
expect(toSessionId("")).toBe("session");
expect(toSessionId("!!!")).toBe("session");
});
test("avoids Windows reserved device names", () => {
expect(toSessionId("CON")).toBe("con-session");
expect(isValidSessionId(toSessionId("CON"))).toBe(true);
});
test("always yields a valid id, including for hostile input", () => {
const names = [
"My Game",
"",
"..",
"../etc/passwd",
"a/b\\c",
"CON",
"Ünïcödé",
"x".repeat(200),
"\0\0",
];
for (const name of names) {
expect(isValidSessionId(toSessionId(name))).toBe(true);
}
});
});