From 5b1df707b9fde73606918ce7c8133434da4cee20 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 03:02:29 +0000 Subject: [PATCH] ref(dashboard): split oversized app and route tests Move shell rendering out of the dashboard app entry and split the route tests so both stay under the file length limit. Co-Authored-By: David Cramer --- packages/junior-dashboard/src/app.ts | 342 +----------------- packages/junior-dashboard/src/shell.ts | 319 ++++++++++++++++ .../tests/auth-config.test.ts | 74 +++- .../tests/dashboard-routes.test.ts | 314 +--------------- .../tests/dashboard-shell-routes.test.ts | 292 +++++++++++++++ 5 files changed, 704 insertions(+), 637 deletions(-) create mode 100644 packages/junior-dashboard/src/shell.ts create mode 100644 packages/junior-dashboard/tests/dashboard-shell-routes.test.ts diff --git a/packages/junior-dashboard/src/app.ts b/packages/junior-dashboard/src/app.ts index 87306693f7..55606800fe 100644 --- a/packages/junior-dashboard/src/app.ts +++ b/packages/junior-dashboard/src/app.ts @@ -1,6 +1,4 @@ import { Hono, type Context, type Next } from "hono"; -import { existsSync, readFileSync } from "node:fs"; -import path from "node:path"; import { authenticatePersonalToken, createJuniorApi, @@ -23,12 +21,6 @@ import { dashboardIdentitySchema, dashboardProfileUpdateSchema, } from "./api/schema"; -import { - dashboardAvatarHeaderAsset, - dashboardClientAsset, - dashboardInstallIconAsset, - dashboardTailwindAsset, -} from "./assets"; import { createDashboardAuth, dashboardBearerSession, @@ -40,20 +32,26 @@ import { type DashboardAuth, type DashboardSession, } from "./auth"; -import { dashboardRainbowProgressClass } from "./dashboardLoader"; import { isAuthenticatedPath } from "./authenticated-routes"; import { createMockReportingApi } from "./mock-reporting/routes"; -import { resolveDashboardBaseURL } from "./url"; +import { + DASHBOARD_AVATAR_HEADER_PATH, + DASHBOARD_CLIENT_PATH, + DASHBOARD_INSTALL_ICON_PATH, + DASHBOARD_MANIFEST_PATH, + dashboardPagePaths, + readDashboardAvatarHeader, + readDashboardClient, + renderDashboard, + renderFavicon, + renderForbiddenPage, + renderInstallIcon, + renderManifest, +} from "./shell"; +import { normalizeDashboardPath, resolveDashboardBaseURL } from "./url"; const DEFAULT_BASE_PATH = "/"; const DEFAULT_AUTH_PATH = "/api/auth"; -const DASHBOARD_CLIENT_VERSION = Date.now().toString(36); -const DASHBOARD_CLIENT_PATH = "/_junior/dashboard/client.js"; -const DASHBOARD_AVATAR_HEADER_PATH = "/_junior/dashboard/avatar.png"; -const DASHBOARD_INSTALL_ICON_PATH = "/_junior/dashboard/icon-512.png"; -const DASHBOARD_MANIFEST_PATH = "/_junior/dashboard/manifest.webmanifest"; -const DASHBOARD_THEME_COLOR = "#000000"; -const DASHBOARD_BACKGROUND_COLOR = "#000000"; const LOGIN_NEXT_PARAM = "next"; const LOCAL_VIEWER_EMAIL = "dev@example.com"; /** Process-local display names for mock reporting only. */ @@ -94,29 +92,6 @@ function hasSentryConversationLinks(): boolean { ); } -function normalizePath(path: string, fallback: string): string { - const value = path.trim() || fallback; - const withSlash = value.startsWith("/") ? value : `/${value}`; - return stripTrailingSlashes(withSlash); -} - -function stripTrailingSlashes(value: string): string { - let end = value.length; - while (end > 1 && value.charCodeAt(end - 1) === 47) { - end -= 1; - } - return end === value.length ? value : value.slice(0, end); -} - -function escapeHtml(value: string): string { - return value - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); -} - function normalizeValues(values: string[] | undefined): string[] { return [ ...new Set( @@ -334,34 +309,7 @@ function unauthorized( function forbidden(request: Request, agentName: string): Response { if (!isJsonRoute(new URL(request.url).pathname)) { - return new Response( - ` - - - - - ${escapeHtml(agentName)} access denied - - - -
-
-

Access denied

-

Your Google account is authenticated, but it is not allowed to use this ${escapeHtml(agentName)} dashboard.

-
-
- -`, - { - headers: { - "cache-control": "no-store", - "content-type": "text/html; charset=utf-8", - }, - status: 403, - }, - ); + return renderForbiddenPage(agentName); } return jsonResponse(apiErrorSchema, { error: "forbidden" }, { status: 403 }); } @@ -391,264 +339,10 @@ function mockViewerFromSession(session: DashboardSession) { }; } -function readAssetUrl(url: URL): string { - if (!existsSync(url)) { - return ""; - } - return readFileSync(url, "utf8"); -} - -function readWorkspaceAsset(fileName: string): string { - const assetPath = path.join( - process.cwd(), - "node_modules", - "@sentry", - "junior-dashboard", - "dist", - fileName, - ); - if (!existsSync(assetPath)) { - return ""; - } - return readFileSync(assetPath, "utf8"); -} - -function readDashboardClient(): string { - const client = - dashboardClientAsset || - readAssetUrl(new URL("./client.js", import.meta.url)) || - readAssetUrl(new URL("../dist/client.js", import.meta.url)) || - readWorkspaceAsset("client.js"); - if (!client) { - throw new Error("Junior dashboard client bundle was not found"); - } - return client; -} - function dashboardTimeZone(): string { return process.env.JUNIOR_TIMEZONE || "America/Los_Angeles"; } -function readDashboardTailwind(): string { - return ( - dashboardTailwindAsset || - readAssetUrl(new URL("./tailwind.css", import.meta.url)) || - readAssetUrl(new URL("../dist/tailwind.css", import.meta.url)) || - readWorkspaceAsset("tailwind.css") - ); -} - -function readDashboardColorIcon(): ArrayBuffer { - const embeddedAsset = dashboardInstallIconAsset || dashboardAvatarHeaderAsset; - if (embeddedAsset) { - return Uint8Array.from(Buffer.from(embeddedAsset, "base64")).buffer; - } - - const assetUrl = new URL("./assets/junior-avatar.png", import.meta.url); - if (!existsSync(assetUrl)) { - throw new Error("Junior dashboard color icon was not found"); - } - return Uint8Array.from(readFileSync(assetUrl)).buffer; -} - -function readDashboardAvatarHeader(): ArrayBuffer { - return readDashboardColorIcon(); -} - -function readDashboardInstallIcon(): ArrayBuffer { - return readDashboardColorIcon(); -} - -/** Use the exact registered dashboard base path so installed launches do not 404. */ -function dashboardStartUrl(basePath: string): string { - return basePath; -} - -function dashboardPagePaths( - basePath: string, - options: { componentGallery?: boolean } = {}, -): Array<{ nested?: boolean; path: string }> { - const paths: Array<{ nested?: boolean; path: string }> = [ - { path: basePath }, - { - path: basePath === "/" ? "/code" : `${basePath}/code`, - }, - { - nested: true, - path: basePath === "/" ? "/conversations" : `${basePath}/conversations`, - }, - { - nested: true, - path: basePath === "/" ? "/people" : `${basePath}/people`, - }, - { - nested: true, - path: basePath === "/" ? "/locations" : `${basePath}/locations`, - }, - { - nested: true, - path: basePath === "/" ? "/system" : `${basePath}/system`, - }, - { - nested: true, - path: basePath === "/" ? "/tasks" : `${basePath}/tasks`, - }, - { - nested: true, - path: basePath === "/" ? "/memories" : `${basePath}/memories`, - }, - { - nested: true, - path: basePath === "/" ? "/settings" : `${basePath}/settings`, - }, - { - nested: true, - path: basePath === "/" ? "/plugins" : `${basePath}/plugins`, - }, - ]; - if (options.componentGallery) { - paths.push({ - nested: true, - path: basePath === "/" ? "/dev" : `${basePath}/dev`, - }); - } - return paths; -} - -function renderManifest(basePath: string, agentName: string): Response { - const startUrl = dashboardStartUrl(basePath); - return new Response( - JSON.stringify({ - background_color: DASHBOARD_BACKGROUND_COLOR, - description: `${agentName} dashboard`, - display: "standalone", - icons: [ - { - purpose: "any", - sizes: "512x512", - src: DASHBOARD_INSTALL_ICON_PATH, - type: "image/png", - }, - ], - name: agentName, - scope: startUrl, - short_name: agentName, - start_url: startUrl, - theme_color: DASHBOARD_THEME_COLOR, - }), - { - headers: { - "cache-control": "public, max-age=0, must-revalidate", - "content-type": "application/manifest+json", - }, - }, - ); -} - -function renderInstallIcon(): Response { - return new Response(readDashboardInstallIcon(), { - headers: { - "cache-control": "public, max-age=0, must-revalidate", - "content-type": "image/png", - }, - }); -} - -function renderDashboard(basePath: string, agentName: string): Response { - const encodedAgentName = JSON.stringify(agentName).replace(/ - - - - - - - - - - - - ${escapeHtml(agentName)} - - - -
-
-
-
Jr
-
-
Loading ${escapeHtml(agentName)}
-
-
-
-
-
- - - -`, - { - headers: { - "cache-control": "no-store", - "content-type": "text/html; charset=utf-8", - }, - }, - ); -} - -function renderFavicon(): Response { - return new Response( - `Jr`, - { headers: { "content-type": "image/svg+xml" } }, - ); -} - function pluginRoutePrefix(pluginName: string): string { return `/api/plugins/${pluginName}`; } @@ -691,11 +385,11 @@ export function createDashboardApp( initSentry(); } - const basePath = normalizePath( + const basePath = normalizeDashboardPath( options.basePath ?? DEFAULT_BASE_PATH, DEFAULT_BASE_PATH, ); - const authPath = normalizePath( + const authPath = normalizeDashboardPath( options.authPath ?? DEFAULT_AUTH_PATH, DEFAULT_AUTH_PATH, ); diff --git a/packages/junior-dashboard/src/shell.ts b/packages/junior-dashboard/src/shell.ts new file mode 100644 index 0000000000..f90ddcb278 --- /dev/null +++ b/packages/junior-dashboard/src/shell.ts @@ -0,0 +1,319 @@ +import { existsSync, readFileSync } from "node:fs"; +import path from "node:path"; +import { + dashboardAvatarHeaderAsset, + dashboardClientAsset, + dashboardInstallIconAsset, + dashboardTailwindAsset, +} from "./assets"; +import { dashboardRainbowProgressClass } from "./dashboardLoader"; + +const DASHBOARD_CLIENT_VERSION = Date.now().toString(36); +export const DASHBOARD_CLIENT_PATH = "/_junior/dashboard/client.js"; +export const DASHBOARD_AVATAR_HEADER_PATH = "/_junior/dashboard/avatar.png"; +export const DASHBOARD_INSTALL_ICON_PATH = "/_junior/dashboard/icon-512.png"; +export const DASHBOARD_MANIFEST_PATH = "/_junior/dashboard/manifest.webmanifest"; +const DASHBOARD_THEME_COLOR = "#000000"; +const DASHBOARD_BACKGROUND_COLOR = "#000000"; + +function escapeHtml(value: string): string { + return value + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} + +function readAssetUrl(url: URL): string { + if (!existsSync(url)) { + return ""; + } + return readFileSync(url, "utf8"); +} + +function readWorkspaceAsset(fileName: string): string { + const assetPath = path.join( + process.cwd(), + "node_modules", + "@sentry", + "junior-dashboard", + "dist", + fileName, + ); + if (!existsSync(assetPath)) { + return ""; + } + return readFileSync(assetPath, "utf8"); +} + +/** Load the dashboard browser bundle from the package build output. */ +export function readDashboardClient(): string { + const client = + dashboardClientAsset || + readAssetUrl(new URL("./client.js", import.meta.url)) || + readAssetUrl(new URL("../dist/client.js", import.meta.url)) || + readWorkspaceAsset("client.js"); + if (!client) { + throw new Error("Junior dashboard client bundle was not found"); + } + return client; +} + +function readDashboardTailwind(): string { + return ( + dashboardTailwindAsset || + readAssetUrl(new URL("./tailwind.css", import.meta.url)) || + readAssetUrl(new URL("../dist/tailwind.css", import.meta.url)) || + readWorkspaceAsset("tailwind.css") + ); +} + +function readDashboardColorIcon(): ArrayBuffer { + const embeddedAsset = dashboardInstallIconAsset || dashboardAvatarHeaderAsset; + if (embeddedAsset) { + return Uint8Array.from(Buffer.from(embeddedAsset, "base64")).buffer; + } + + const assetUrl = new URL("./assets/junior-avatar.png", import.meta.url); + if (!existsSync(assetUrl)) { + throw new Error("Junior dashboard color icon was not found"); + } + return Uint8Array.from(readFileSync(assetUrl)).buffer; +} + +/** Load the dashboard header avatar image. */ +export function readDashboardAvatarHeader(): ArrayBuffer { + return readDashboardColorIcon(); +} + +function readDashboardInstallIcon(): ArrayBuffer { + return readDashboardColorIcon(); +} + +/** Use the exact registered dashboard base path so installed launches do not 404. */ +function dashboardStartUrl(basePath: string): string { + return basePath; +} + +/** List the browser page paths served by the authenticated dashboard shell. */ +export function dashboardPagePaths( + basePath: string, + options: { componentGallery?: boolean } = {}, +): Array<{ nested?: boolean; path: string }> { + const paths: Array<{ nested?: boolean; path: string }> = [ + { path: basePath }, + { + path: basePath === "/" ? "/code" : `${basePath}/code`, + }, + { + nested: true, + path: basePath === "/" ? "/conversations" : `${basePath}/conversations`, + }, + { + nested: true, + path: basePath === "/" ? "/people" : `${basePath}/people`, + }, + { + nested: true, + path: basePath === "/" ? "/locations" : `${basePath}/locations`, + }, + { + nested: true, + path: basePath === "/" ? "/system" : `${basePath}/system`, + }, + { + nested: true, + path: basePath === "/" ? "/tasks" : `${basePath}/tasks`, + }, + { + nested: true, + path: basePath === "/" ? "/memories" : `${basePath}/memories`, + }, + { + nested: true, + path: basePath === "/" ? "/settings" : `${basePath}/settings`, + }, + { + nested: true, + path: basePath === "/" ? "/plugins" : `${basePath}/plugins`, + }, + ]; + if (options.componentGallery) { + paths.push({ + nested: true, + path: basePath === "/" ? "/dev" : `${basePath}/dev`, + }); + } + return paths; +} + +/** Serve the installable web app manifest for the dashboard shell. */ +export function renderManifest(basePath: string, agentName: string): Response { + const startUrl = dashboardStartUrl(basePath); + return new Response( + JSON.stringify({ + background_color: DASHBOARD_BACKGROUND_COLOR, + description: `${agentName} dashboard`, + display: "standalone", + icons: [ + { + purpose: "any", + sizes: "512x512", + src: DASHBOARD_INSTALL_ICON_PATH, + type: "image/png", + }, + ], + name: agentName, + scope: startUrl, + short_name: agentName, + start_url: startUrl, + theme_color: DASHBOARD_THEME_COLOR, + }), + { + headers: { + "cache-control": "public, max-age=0, must-revalidate", + "content-type": "application/manifest+json", + }, + }, + ); +} + +/** Serve the install icon used by the dashboard shell. */ +export function renderInstallIcon(): Response { + return new Response(readDashboardInstallIcon(), { + headers: { + "cache-control": "public, max-age=0, must-revalidate", + "content-type": "image/png", + }, + }); +} + +/** Render the authenticated dashboard HTML shell. */ +export function renderDashboard(basePath: string, agentName: string): Response { + const encodedAgentName = JSON.stringify(agentName).replace(/ + + + + + + + + + + + + ${escapeHtml(agentName)} + + + +
+
+
+
Jr
+
+
Loading ${escapeHtml(agentName)}
+
+
+
+
+
+ + + +`, + { + headers: { + "cache-control": "no-store", + "content-type": "text/html; charset=utf-8", + }, + }, + ); +} + +/** Serve the dashboard favicon. */ +export function renderFavicon(): Response { + return new Response( + `Jr`, + { headers: { "content-type": "image/svg+xml" } }, + ); +} + +/** Render a browser-readable access denied page for blocked dashboard users. */ +export function renderForbiddenPage(agentName: string): Response { + return new Response( + ` + + + + + ${escapeHtml(agentName)} access denied + + + +
+
+

Access denied

+

Your Google account is authenticated, but it is not allowed to use this ${escapeHtml(agentName)} dashboard.

+
+
+ +`, + { + headers: { + "cache-control": "no-store", + "content-type": "text/html; charset=utf-8", + }, + status: 403, + }, + ); +} diff --git a/packages/junior-dashboard/tests/auth-config.test.ts b/packages/junior-dashboard/tests/auth-config.test.ts index b7d0e10b62..5ef211bed3 100644 --- a/packages/junior-dashboard/tests/auth-config.test.ts +++ b/packages/junior-dashboard/tests/auth-config.test.ts @@ -1,5 +1,6 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { dashboardIdentitySchema } from "../src/api/schema"; +import { resetDashboardEnv } from "./dashboard-test-helpers"; describe("dashboard auth config", () => { afterEach(() => { @@ -65,3 +66,74 @@ describe("dashboard auth config", () => { expect(capturedOptions).not.toHaveProperty("database"); }); }); + +describe("dashboard auth setup", () => { + beforeEach(() => { + resetDashboardEnv(); + }); + + afterEach(() => { + resetDashboardEnv(); + }); + + it("uses JUNIOR_SECRET as the default Better Auth secret", async () => { + process.env.JUNIOR_SECRET = "junior-secret"; + const { createDashboardAuth } = await import("../src/auth"); + + expect(() => + createDashboardAuth({ + authPath: "/api/auth", + trustedOrigins: [], + }), + ).toThrow("GOOGLE_CLIENT_ID is required for Junior dashboard auth"); + }); + + it("defaults dashboard auth to the local development URL", async () => { + process.env.JUNIOR_SECRET = "junior-secret"; + process.env.GOOGLE_CLIENT_ID = "google-client-id"; + process.env.GOOGLE_CLIENT_SECRET = "google-client-secret"; + const { createDashboardAuth } = await import("../src/auth"); + + expect(() => + createDashboardAuth({ + authPath: "/api/auth", + trustedOrigins: [], + }), + ).not.toThrow(); + }); + + it("derives the Better Auth base URL from Junior deployment env", async () => { + process.env.JUNIOR_SECRET = "junior-secret"; + process.env.GOOGLE_CLIENT_ID = "google-client-id"; + process.env.GOOGLE_CLIENT_SECRET = "google-client-secret"; + process.env.JUNIOR_BASE_URL = "https://junior.example.com"; + const { createDashboardAuth } = await import("../src/auth"); + + expect(() => + createDashboardAuth({ + authPath: "/api/auth", + trustedOrigins: [], + }), + ).not.toThrow(); + }); + + it("preserves the Better Auth OAuth state cookie during Google sign-in", async () => { + const { createDashboardAuth } = await import("../src/auth"); + const auth = createDashboardAuth({ + authPath: "/api/auth", + googleClientId: "google-client-id", + googleClientSecret: "google-client-secret", + secret: "0123456789abcdef0123456789abcdef", + trustedOrigins: [], + }); + + const response = await auth.signInWithGoogle( + new Request("http://localhost/auth/login"), + "http://localhost/", + ); + + expect(response.status).toBe(302); + expect(response.headers.get("location")).toContain("accounts.google.com"); + expect(response.headers.get("set-cookie")).toContain("oauth_state"); + }); +}); diff --git a/packages/junior-dashboard/tests/dashboard-routes.test.ts b/packages/junior-dashboard/tests/dashboard-routes.test.ts index fc3c329722..cf371f9fc8 100644 --- a/packages/junior-dashboard/tests/dashboard-routes.test.ts +++ b/packages/junior-dashboard/tests/dashboard-routes.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createDashboardApp } from "../src/app"; -import { createDashboardAuth, type DashboardSession } from "../src/auth"; +import type { DashboardSession } from "../src/auth"; import { auth, resetDashboardEnv } from "./dashboard-test-helpers"; const { resolveViewerUser, updateViewerDisplayName } = vi.hoisted(() => ({ @@ -86,6 +86,7 @@ describe("dashboard routes", () => { expect(location.searchParams.get("next")).toBe(path); } }); + it("uses the requested dashboard path as the Google sign-in callback", async () => { let callbackURL: string | undefined; const app = createDashboardApp({ @@ -436,99 +437,6 @@ describe("dashboard routes", () => { expect(response.status).toBe(403); }); - it("renders the authenticated ops deck shell", async () => { - const app = dashboard({ - user: { - email: "person@sentry.io", - emailVerified: true, - }, - }); - - const response = await app.fetch(new Request("http://localhost/")); - - expect(response.status).toBe(200); - expect(response.headers.get("cache-control")).toBe("no-store"); - expect(response.headers.get("content-type")).toContain("text/html"); - const html = await response.text(); - expect(html).toContain("Junior"); - expect(html).toContain("Loading Junior"); - expect(html).toContain("junior-rainbow-flow"); - expect(html).toMatch(/\/_junior\/dashboard\/client\.js\?v=[a-z0-9]+/); - expect(html).toContain("__JUNIOR_DASHBOARD_BASE_PATH__"); - expect(html).toContain( - 'content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover, interactive-widget=resizes-content"', - ); - expect(html).toContain('name="theme-color" content="#000000"'); - expect(html).toContain('href="/_junior/dashboard/manifest.webmanifest"'); - expect(html).toContain('href="/_junior/dashboard/icon-512.png"'); - }); - - it("renders the configured agent name from the dashboard shell", async () => { - const app = createDashboardApp({ agentName: "Marky", authRequired: false }); - - const shell = await app.fetch(new Request("http://localhost/")); - const html = await shell.text(); - expect(html).toContain("Marky"); - expect(html).toContain("Loading Marky"); - expect(html).toContain('__JUNIOR_DASHBOARD_AGENT_NAME__ = "Marky"'); - }); - - it("escapes the configured agent name in HTML and inline JavaScript", async () => { - const app = createDashboardApp({ - agentName: '', - authRequired: false, - }); - - const response = await app.fetch(new Request("http://localhost/")); - const html = await response.text(); - - expect(html).not.toContain(''); - expect(html).toContain( - "</script><script>alert("xss")</script>", - ); - expect(html).toContain("\\u003c/script>\\u003cscript>alert"); - }); - - it("renders React Router dashboard page routes", async () => { - const app = dashboard({ - user: { - email: "person@sentry.io", - emailVerified: true, - }, - }); - - for (const path of [ - "/code", - "/conversations", - "/conversations/slack%3AC1%3A123", - "/locations", - "/locations/destination-1", - "/people", - "/people/person%40sentry.io", - "/system", - "/system/plugins/github", - "/tasks", - "/tasks/task-1", - "/tasks/scheduled/task-1/executions", - "/memories", - "/memories/memory-1", - "/settings", - "/settings/api-tokens", - "/system/workspaces", - "/system/workspaces/new", - "/system/workspaces/11111111-1111-4111-8111-111111111111", - "/plugins/memory/memories", - "/plugins/memory/memories/library", - ]) { - const response = await app.fetch(new Request(`http://localhost${path}`)); - - expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toContain("text/html"); - const html = await response.text(); - expect(html).toContain("Junior"); - } - }); - it("updates the signed-in viewer display name", async () => { const app = dashboard({ user: { @@ -622,131 +530,6 @@ describe("dashboard routes", () => { }); }); - it("does not serve retired dashboard page routes", async () => { - const app = dashboard({ - user: { - email: "person@sentry.io", - emailVerified: true, - }, - }); - - for (const path of ["/chat/legacy-id"]) { - const response = await app.fetch(new Request(`http://localhost${path}`)); - expect(response.status).toBe(404); - } - }); - - it("serves the dashboard client bundle without browser caching", async () => { - const app = dashboard({ - user: { - email: "person@sentry.io", - emailVerified: true, - }, - }); - - const response = await app.fetch( - new Request("http://localhost/_junior/dashboard/client.js"), - ); - - expect(response.status).toBe(200); - expect(response.headers.get("cache-control")).toBe("no-store"); - expect(response.headers.get("content-type")).toContain( - "application/javascript", - ); - expect(await response.text()).not.toMatch(/\bfrom\s*["']lucide-react["']/); - }); - - it("serves the official dashboard avatar with revalidation", async () => { - const app = dashboard({ - user: { - email: "person@sentry.io", - emailVerified: true, - }, - }); - - const response = await app.fetch( - new Request("http://localhost/_junior/dashboard/avatar.png"), - ); - const installIcon = await app.fetch( - new Request("http://localhost/_junior/dashboard/icon-512.png"), - ); - - expect(response.status).toBe(200); - expect(response.headers.get("cache-control")).toBe( - "public, max-age=0, must-revalidate", - ); - expect(response.headers.get("content-type")).toBe("image/png"); - const avatarBytes = new Uint8Array(await response.arrayBuffer()); - const installBytes = new Uint8Array(await installIcon.arrayBuffer()); - expect(avatarBytes.byteLength).toBeGreaterThan(1_000); - expect(avatarBytes).toEqual(installBytes); - }); - - it("serves the dashboard favicon without auth noise", async () => { - const app = dashboard(null); - - const response = await app.fetch( - new Request("http://localhost/favicon.ico"), - ); - - expect(response.status).toBe(200); - expect(response.headers.get("content-type")).toContain("image/svg+xml"); - }); - - it("serves the installable shell manifest without auth", async () => { - const app = createDashboardApp({ - agentName: "Marky", - allowedEmails: ["admin@example.com"], - auth: auth(null), - basePath: "/ops", - }); - - const response = await app.fetch( - new Request("http://localhost/_junior/dashboard/manifest.webmanifest"), - ); - - expect(response.status).toBe(200); - expect(response.headers.get("cache-control")).toBe( - "public, max-age=0, must-revalidate", - ); - expect(response.headers.get("content-type")).toBe( - "application/manifest+json", - ); - expect(await response.json()).toEqual({ - background_color: "#000000", - description: "Marky dashboard", - display: "standalone", - icons: [ - { - purpose: "any", - sizes: "512x512", - src: "/_junior/dashboard/icon-512.png", - type: "image/png", - }, - ], - name: "Marky", - scope: "/ops", - short_name: "Marky", - start_url: "/ops", - theme_color: "#000000", - }); - }); - - it("serves the install icon without auth", async () => { - const app = dashboard(null); - - const response = await app.fetch( - new Request("http://localhost/_junior/dashboard/icon-512.png"), - ); - - expect(response.status).toBe(200); - expect(response.headers.get("cache-control")).toBe( - "public, max-age=0, must-revalidate", - ); - expect(response.headers.get("content-type")).toBe("image/png"); - expect((await response.arrayBuffer()).byteLength).toBeGreaterThan(1_000); - }); - it("returns the signed-in identity", async () => { const app = dashboard({ session: { @@ -793,26 +576,6 @@ describe("dashboard routes", () => { expect(callbackURL).toBe("http://localhost/dev?fixture=charts"); }); - it("serves the component gallery only when enabled", async () => { - const disabled = createDashboardApp({ authRequired: false }); - const enabled = createDashboardApp({ - authRequired: false, - componentGallery: true, - }); - - expect( - (await disabled.fetch(new Request("http://localhost/dev"))).status, - ).toBe(404); - expect( - (await enabled.fetch(new Request("http://localhost/dev"))).status, - ).toBe(200); - expect( - await ( - await enabled.fetch(new Request("http://localhost/api/config")) - ).json(), - ).toMatchObject({ componentGallery: true }); - }); - it("returns safe dashboard config signals", async () => { process.env.SENTRY_DSN = "https://public@example.ingest.sentry.io/1"; process.env.SENTRY_ORG_SLUG = "sentry"; @@ -857,23 +620,6 @@ describe("dashboard routes", () => { expect(await response.json()).toEqual({ error: "forbidden" }); }); - it("renders a browser-readable forbidden page for denied dashboard routes", async () => { - const app = dashboard({ - user: { - email: "person@example.com", - emailVerified: true, - }, - }); - - const response = await app.fetch(new Request("http://localhost/")); - - expect(response.status).toBe(403); - expect(response.headers.get("content-type")).toContain("text/html"); - const html = await response.text(); - expect(html).toContain("