Files
template-app/test/start-page.test.ts
T
2026-11-14 09:56:45 +00:00

194 lines
7.5 KiB
TypeScript

// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// What a route loader reads (F10). `pageContext` and `permissionsIn` are kept per request, because React's
// `cache` memoizes nothing outside server components, and `modulePageData` is the one module route's
// loader: signed out is a redirect, every refusal is a real status, and a module's data passes through.
import { AppError } from "@conusai-kit/core"
import { isNotFound, isRedirect } from "@tanstack/react-router"
import { beforeEach, describe, expect, it, vi } from "vitest"
const mocks = vi.hoisted(() => ({
requestContext: vi.fn(),
callByName: vi.fn(),
setResponseHeader: vi.fn<(name: string, value: string) => void>(),
load: vi.fn(),
call: vi.fn(),
}))
vi.mock("@tanstack/react-start/server", () => ({
getRequest: () => {
throw new Error("the tests pass their request")
},
setResponseStatus: vi.fn(),
setResponseHeader: mocks.setResponseHeader,
}))
vi.mock("../apps/web/src/server/request", () => ({ requestContext: mocks.requestContext }))
vi.mock("../apps/web/src/server/app", () => ({
webRuntime: () => ({
observe: { config: { errors: false } },
selfServiceOrganizations: "members",
registry: {
modules: [{ id: "notes" }, { id: "unpaged" }],
nav: [{ module: "notes", scope: "project", href: "notes", label: "workspace.projects" }],
},
env: { deployment: {} },
}),
}))
vi.mock("../apps/web/src/server/tenancy-calls", () => ({ mayCreateOrganization: () => false }))
vi.mock("../apps/web/src/server/platform-theme", () => ({
organizationThemeStyle: async () => undefined,
organizationIdentity: async () => ({}),
}))
vi.mock("@conusai-kit/host/http", () => ({ csrfSessionId: () => "session", csrfToken: () => "token" }))
vi.mock("@app/modules/pages", () => ({ modulePages: { notes: async () => ({ load: mocks.load }) } }))
const { modulePageData, pageContext, permissionsIn, projectLayoutData, shellData } = await import(
"../apps/web/src/server/page"
)
const person = { actor: { kind: "user" } }
const signedIn = () =>
mocks.requestContext.mockResolvedValue({
caller: { call: mocks.call, callByName: mocks.callByName },
container: {},
principal: person,
user: { email: "ada@example.test", displayName: "Ada", locale: "en" },
})
const signedOut = () =>
mocks.requestContext.mockResolvedValue({
caller: { call: mocks.call, callByName: mocks.callByName },
container: {},
principal: null,
})
const request = (): Request => new Request("http://app.test/a/b/c/notes", { headers: { "x-path": "/a/b/c/notes" } })
const params = { org: "a", workspace: "b", project: "c", module: "notes" }
const refusal = (status: number): AppError =>
new AppError({ type: "/problems/refused", code: "refused", title: "Refused", status })
beforeEach(() => {
vi.clearAllMocks()
mocks.callByName.mockResolvedValue({ permissions: ["notes.note.read"], revision: "r1" })
mocks.call.mockResolvedValue({})
mocks.load.mockResolvedValue({ kind: "list", title: "Notes" })
})
describe("the page context", () => {
it("is built once per request, and again for the next one", async () => {
signedIn()
const first = request()
await Promise.all([pageContext(first), pageContext(first)])
expect(mocks.requestContext).toHaveBeenCalledOnce()
await pageContext(request())
expect(mocks.requestContext).toHaveBeenCalledTimes(2)
})
it("reads the permissions of a scope once per request", async () => {
signedIn()
const current = request()
await Promise.all([permissionsIn({ org: "a" }, current), permissionsIn({ org: "a" }, current)])
expect(mocks.callByName).toHaveBeenCalledOnce()
await permissionsIn({ org: "a", workspace: "b" }, current)
expect(mocks.callByName).toHaveBeenCalledTimes(2)
})
})
describe("the shell's loader data", () => {
it("is plain values with its labels translated, and nothing a browser must not hold", async () => {
vi.stubEnv("CONUSAI_PUBLIC_URL", "https://app.example.test")
signedIn()
const shell = await shellData(request())
expect(JSON.parse(JSON.stringify(shell))).toEqual(shell)
expect(shell.signedIn).toBe(true)
expect(shell.labels.signIn.length).toBeGreaterThan(0)
expect(shell.brand.style).toBeTypeOf("object")
expect(shell.publicUrl).toBe("https://app.example.test")
expect(JSON.stringify(shell)).not.toContain("ada@example.test")
})
})
describe("the module route's loader", () => {
const thrown = (run: Promise<unknown>): Promise<unknown> =>
run.then(
() => "resolved",
(error: unknown) => error,
)
it("sends a signed-out visitor to sign in, before reading anything", async () => {
signedOut()
expect(isRedirect(await thrown(modulePageData(params, request())))).toBe(true)
expect(mocks.call).not.toHaveBeenCalled()
expect(mocks.load).not.toHaveBeenCalled()
})
it.each([
["no registered module", { ...params, module: "nothing" }],
["a registered module with no page", { ...params, module: "unpaged" }],
["a name every object has", { ...params, module: "constructor" }],
])("answers 404 for %s", async (_name, wanted) => {
signedIn()
expect(isNotFound(await thrown(modulePageData(wanted, request())))).toBe(true)
expect(mocks.load).not.toHaveBeenCalled()
})
it.each([
[404, undefined],
[403, { forbidden: true }],
])("turns the module's own %i into that page", async (status, data) => {
signedIn()
mocks.load.mockRejectedValue(refusal(status))
const error = (await thrown(modulePageData(params, request()))) as { data?: unknown }
expect(isNotFound(error)).toBe(true)
expect(error.data).toEqual(data)
expect(mocks.setResponseHeader).toHaveBeenCalledWith("x-conusai-page-status", String(status))
})
it("leaves a fault to the error boundary", async () => {
signedIn()
const fault = new Error("database down")
mocks.load.mockRejectedValue(fault)
expect(await thrown(modulePageData(params, request()))).toBe(fault)
})
it("runs the module's loader with the slugs and the rest of the path", async () => {
signedIn()
const page = await modulePageData({ ...params, _splat: "note-1/" }, request())
expect(mocks.load.mock.calls[0]?.[0]).toMatchObject({
slugs: { org: "a", workspace: "b", project: "c" },
segments: ["note-1"],
})
expect(page).toMatchObject({
module: "notes",
title: "Projects · Example App",
segments: ["note-1"],
data: { kind: "list", title: "Notes" },
})
})
})
describe("the project route's loader", () => {
const slugs = { org: "a", workspace: "b", project: "c" }
const refused = (run: Promise<unknown>): Promise<unknown> => run.catch((error: unknown) => error)
it("sends a signed-out visitor to sign in, before it reads anything", async () => {
signedOut()
expect(isRedirect(await refused(projectLayoutData(slugs, request())))).toBe(true)
expect(mocks.call).not.toHaveBeenCalled()
})
it("answers 404 for a project of another tenant", async () => {
signedIn()
mocks.call.mockRejectedValue(refusal(404))
expect(isNotFound(await refused(projectLayoutData(slugs, request())))).toBe(true)
expect(mocks.setResponseHeader).toHaveBeenCalledWith("x-conusai-page-status", "404")
})
it("returns the project's scope and the person's permissions there, the seed of useCan", async () => {
signedIn()
expect(await projectLayoutData(slugs, request())).toMatchObject({
scope: slugs,
seed: expect.objectContaining({ permissions: ["notes.note.read"] }),
project: { id: slugs.project },
})
})
})