| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import { debugReport, describeError, developerCookie, readDeveloperMode, wantsServerErrors } from "./developer-mode.ts"; |
| 5 | |
| 6 | const request = (cookie: string | null, authorization?: string) => |
| 7 | new Request("https://g1t.sh/acme", { headers: { ...(cookie ? { cookie } : {}), ...(authorization ? { authorization } : {}) } }); |
| 8 | |
| 9 | test("developer mode is on only for the cookie's own value", () => { |
| 10 | assert.equal(readDeveloperMode("1"), true); |
| 11 | for (const junk of ["", "true", "0", null, undefined]) assert.equal(readDeveloperMode(junk), false); |
| 12 | }); |
| 13 | |
| 14 | test("the cookie keeps the choice for a year, and turning it off forgets it", () => { |
| 15 | assert.equal(developerCookie(true, true), "g1t_dev=1; Path=/; Max-Age=31536000; SameSite=Lax; Secure"); |
| 16 | assert.equal(developerCookie(false, false), "g1t_dev=; Path=/; Max-Age=0; SameSite=Lax"); |
| 17 | }); |
| 18 | |
| 19 | test("the server's own errors go only to someone signed in who turned it on", () => { |
| 20 | assert.equal(wantsServerErrors(request("g1t_session=abc; g1t_dev=1")), true); |
| 21 | assert.equal(wantsServerErrors(request("g1t_dev=1; g1t_theme=dark", "Bearer g1t_x")), true); |
| 22 | assert.equal(wantsServerErrors(request("g1t_dev=1")), false); |
| 23 | assert.equal(wantsServerErrors(request("g1t_session=abc")), false); |
| 24 | assert.equal(wantsServerErrors(request("g1t_session=abc; g1t_dev=10")), false); |
| 25 | assert.equal(wantsServerErrors(request("g1t_session=abc; not_g1t_dev=1")), false); |
| 26 | }); |
| 27 | |
| 28 | test("whatever was thrown is described", () => { |
| 29 | const thrown = describeError(new TypeError("x is undefined")); |
| 30 | assert.equal(thrown.kind, "TypeError"); |
| 31 | assert.equal(thrown.message, "x is undefined"); |
| 32 | const response = describeError({ status: 500, statusText: "Internal Server Error", data: "the sidebar could not be read" }); |
| 33 | assert.deepEqual([response.kind, response.status, response.message, response.data], ["Response", 500, "500 Internal Server Error", "the sidebar could not be read"]); |
| 34 | assert.deepEqual([describeError("plain").kind, describeError("plain").message], ["string", "plain"]); |
| 35 | }); |
| 36 | |
| 37 | test("one report says what failed, where, when and on which build", () => { |
| 38 | const error = new Error("Cannot read properties of undefined (reading 'slug')"); |
| 39 | error.stack = "Error: Cannot read properties of undefined\n at Integrations (integrations.tsx:10:5)"; |
| 40 | const report = debugReport({ |
| 41 | url: "https://g1t.sh/flagon-io/-/integrations", |
| 42 | at: new Date("2026-10-12T23:00:00Z"), |
| 43 | error, |
| 44 | routes: ["root", "routes/workspace/integrations"], |
| 45 | build: "app-Ab12Cd.css", |
| 46 | userAgent: "Chrome", |
| 47 | desktop: "0.3.6", |
| 48 | navigation: "client", |
| 49 | }); |
| 50 | for (const line of [ |
| 51 | "URL: https://g1t.sh/flagon-io/-/integrations", |
| 52 | "Time: 2026-10-12T23:00:00.000Z", |
| 53 | "Loaded: a navigation in the browser", |
| 54 | "Routes: root > routes/workspace/integrations", |
| 55 | "Desktop app: 0.3.6", |
| 56 | "Error: Cannot read properties of undefined (reading 'slug')", |
| 57 | " at Integrations (integrations.tsx:10:5)", |
| 58 | ]) { |
| 59 | assert.ok(report.includes(line), `${line} in:\n${report}`); |
| 60 | } |
| 61 | assert.ok(debugReport({ url: "/", at: null, error, routes: [], build: "b", userAgent: "", navigation: "document" }).includes("Time: (not yet in the browser)")); |
| 62 | }); |