|
| 1 | +import { readdirSync } from "node:fs"; |
| 2 | +import { join } from "node:path"; |
| 3 | +import { describe, expect, it } from "vitest"; |
| 4 | +import { ENV_PAGE_SEGMENTS } from "./deeplinkPages"; |
| 5 | + |
| 6 | +// Flat-route prefix for every page that renders inside an environment. The trailing dot matters: |
| 7 | +// it excludes the layout route itself (`…env.$envParam`), which has no segment of its own. |
| 8 | +const ENV_ROUTE_PREFIX = "_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam."; |
| 9 | + |
| 10 | +/** |
| 11 | + * Segments that are route files but not deeplink targets: |
| 12 | + * - `_index` is the environment root, which is already where an unrecognised deeplink lands. |
| 13 | + * - `queues_` is Remix's "opt out of the parent layout" spelling of `queues`, not a distinct URL. |
| 14 | + */ |
| 15 | +const NOT_DEEPLINKABLE = new Set(["_index", "queues_"]); |
| 16 | + |
| 17 | +/** The first path segment of every environment page, read off the route filenames. */ |
| 18 | +function envRouteSegments(): Set<string> { |
| 19 | + const entries = readdirSync(join(__dirname, "../routes")); |
| 20 | + const segments = new Set<string>(); |
| 21 | + |
| 22 | + for (const entry of entries) { |
| 23 | + if (!entry.startsWith(ENV_ROUTE_PREFIX)) continue; |
| 24 | + // `metrics.$dashboardKey.ts` -> `metrics`, `agents` -> `agents`, `errors._index` -> `errors` |
| 25 | + const segment = entry.slice(ENV_ROUTE_PREFIX.length).split(/[./]/)[0]; |
| 26 | + // Guards against a future `…env.$envParam.tsx` contributing its extension as a segment. |
| 27 | + if (!segment || segment === "ts" || segment === "tsx") continue; |
| 28 | + if (NOT_DEEPLINKABLE.has(segment)) continue; |
| 29 | + segments.add(segment); |
| 30 | + } |
| 31 | + |
| 32 | + return segments; |
| 33 | +} |
| 34 | + |
| 35 | +describe("deeplink allowlist", () => { |
| 36 | + it("matches the environment layout's route segments", () => { |
| 37 | + // Sorted arrays rather than sets so a mismatch names the segment that drifted. |
| 38 | + expect([...ENV_PAGE_SEGMENTS].sort()).toEqual([...envRouteSegments()].sort()); |
| 39 | + }); |
| 40 | + |
| 41 | + it("found the routes directory", () => { |
| 42 | + // Guards the test itself: an empty derived set would make the assertion above vacuous |
| 43 | + // if the allowlist were ever emptied too. |
| 44 | + expect(envRouteSegments().size).toBeGreaterThan(20); |
| 45 | + }); |
| 46 | + |
| 47 | + it("excludes the environment root and the layout-opt-out spelling", () => { |
| 48 | + expect(ENV_PAGE_SEGMENTS.has("_index")).toBe(false); |
| 49 | + expect(ENV_PAGE_SEGMENTS.has("queues_")).toBe(false); |
| 50 | + // `queues` itself is still reachable — it is the real URL segment. |
| 51 | + expect(ENV_PAGE_SEGMENTS.has("queues")).toBe(true); |
| 52 | + }); |
| 53 | + |
| 54 | + it("includes the pages that ENV_PAGE_META omits", () => { |
| 55 | + // These have no entry in ENV_PAGE_META (their icon/label is special-cased when resolving |
| 56 | + // page metadata), which is why the allowlist is derived from routes and not from that map. |
| 57 | + for (const segment of ["tasks", "agents", "settings"]) { |
| 58 | + expect(ENV_PAGE_SEGMENTS.has(segment)).toBe(true); |
| 59 | + } |
| 60 | + }); |
| 61 | +}); |
0 commit comments