94 lines
3.8 KiB
TypeScript
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()
|
|
})
|
|
})
|