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

94 lines
3.8 KiB
TypeScript

// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
// What a loader does with a refusal on TanStack Start (F10): a real 302, 404 or 403, set before anything
// streams. Start has no `forbidden()`, so the 403 is a not-found with `data.forbidden` plus the header the
// server entry turns into the status.
import { AppError } from "@conusai-kit/core"
import { isNotFound, isRedirect } from "@tanstack/react-router"
import { beforeEach, describe, expect, it, vi } from "vitest"
const start = vi.hoisted(() => ({
setResponseStatus: vi.fn<(status: number) => void>(),
setResponseHeader: vi.fn<(name: string, value: string) => void>(),
}))
vi.mock("@tanstack/react-start/server", () => start)
const { orFallback, PAGE_STATUS_HEADER, pageForbidden, pageNotFound, signInHref, toSignIn } = await import(
"../apps/web/src/server/guard"
)
const refusal = (status: number): AppError =>
new AppError({ type: "/problems/refused", code: "refused", title: "Refused", status })
const thrownBy = async (run: () => unknown): Promise<unknown> => {
try {
await run()
} catch (error) {
return error
}
throw new Error("it did not throw")
}
beforeEach(() => {
start.setResponseStatus.mockClear()
start.setResponseHeader.mockClear()
})
describe("signInHref", () => {
it("names where to come back to, and nothing for the home page", () => {
expect(signInHref("/acme/general/demo/notes?x=1")).toBe(
"/auth/sign-in?return_to=%2Facme%2Fgeneral%2Fdemo%2Fnotes%3Fx%3D1",
)
expect(signInHref("/")).toBe("/auth/sign-in")
expect(signInHref("/", "denied")).toBe("/auth/sign-in?error=denied")
})
})
describe("the status pages", () => {
it("answers a signed-out visitor with a 302 to sign in and back", async () => {
const error = (await thrownBy(() => toSignIn("/a/b/c/notes"))) as { options: { href: string; statusCode: number } }
expect(isRedirect(error)).toBe(true)
expect(error.options.href).toBe("/auth/sign-in?return_to=%2Fa%2Fb%2Fc%2Fnotes")
expect(error.options.statusCode).toBe(302)
})
it("answers not found with 404, in the status and in the header the server entry applies", async () => {
expect(isNotFound(await thrownBy(() => pageNotFound()))).toBe(true)
expect(start.setResponseStatus).toHaveBeenCalledWith(404)
expect(start.setResponseHeader).toHaveBeenCalledWith(PAGE_STATUS_HEADER, "404")
})
it("answers forbidden with 403 and marks the not-found as the forbidden page", async () => {
const error = (await thrownBy(() => pageForbidden())) as { data: unknown }
expect(isNotFound(error)).toBe(true)
expect(error.data).toEqual({ forbidden: true })
expect(start.setResponseHeader).toHaveBeenCalledWith(PAGE_STATUS_HEADER, "403")
})
})
describe("orFallback", () => {
it("passes an answer through", async () => {
await expect(orFallback("/here", async () => "rows")).resolves.toBe("rows")
expect(start.setResponseStatus).not.toHaveBeenCalled()
})
it.each([
[404, "404"],
[403, "403"],
])("turns a procedure's %i into that page", async (status, header) => {
const error = await thrownBy(() => orFallback("/here", () => Promise.reject(refusal(status))))
expect(isNotFound(error)).toBe(true)
expect(start.setResponseHeader).toHaveBeenCalledWith(PAGE_STATUS_HEADER, header)
})
it("turns a procedure's 401 into the way to sign in", async () => {
expect(isRedirect(await thrownBy(() => orFallback("/here", () => Promise.reject(refusal(401)))))).toBe(true)
})
it("leaves anything else to the error boundary", async () => {
const fault = new Error("database down")
expect(await thrownBy(() => orFallback("/here", () => Promise.reject(fault)))).toBe(fault)
expect(await thrownBy(() => orFallback("/here", () => Promise.reject(refusal(500))))).toBeInstanceOf(AppError)
expect(start.setResponseStatus).not.toHaveBeenCalled()
})
})