141 lines
5.6 KiB
TypeScript
141 lines
5.6 KiB
TypeScript
// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0
|
|
// A module's pages above a project (F10): `/<org>/<workspace>/modules/<module>/...`. Signed out is a redirect,
|
|
// a workspace the person cannot see is the same 404 as one that does not exist, a missing permission is a 403
|
|
// before `load` runs, and `load` receives the query string. `e2e/workspace-pages.spec.ts` proves the same four
|
|
// answers over a build on a real Postgres; these say which line gives each one.
|
|
|
|
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: "reports" }, { id: "unpaged" }],
|
|
nav: [{ module: "reports", scope: "workspace", href: "reports/a/b", label: "workspace.overview" }],
|
|
},
|
|
env: { deployment: {} },
|
|
}),
|
|
}))
|
|
vi.mock("../apps/web/src/server/tenancy-calls", () => ({ mayCreateOrganization: () => false }))
|
|
vi.mock("../apps/web/src/server/platform-theme", () => ({ organizationThemeStyle: async () => undefined }))
|
|
vi.mock("@conusai-kit/host/http", () => ({ csrfSessionId: () => "session", csrfToken: () => "token" }))
|
|
vi.mock("@app/modules/pages", () => ({
|
|
modulePages: {},
|
|
workspacePages: {
|
|
reports: async () => ({ permission: "reports.report.read", load: mocks.load }),
|
|
},
|
|
}))
|
|
|
|
const { workspacePageData } = 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 request = (): Request =>
|
|
new Request("http://app.test/a/b/modules/reports", { headers: { "x-path": "/a/b/modules/reports" } })
|
|
const params = { org: "a", workspace: "b", module: "reports" }
|
|
const refusal = (status: number): AppError =>
|
|
new AppError({ type: "/problems/refused", code: "refused", title: "Refused", status })
|
|
const thrown = (run: Promise<unknown>): Promise<unknown> =>
|
|
run.then(
|
|
() => "resolved",
|
|
(error: unknown) => error,
|
|
)
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mocks.callByName.mockResolvedValue({ permissions: ["reports.report.read"], revision: "r1" })
|
|
mocks.load.mockResolvedValue({ title: "Reports" })
|
|
})
|
|
|
|
describe("a workspace page's loader", () => {
|
|
it("sends a signed-out visitor to sign in, before reading anything", async () => {
|
|
mocks.requestContext.mockResolvedValue({ caller: {}, container: {}, principal: null })
|
|
expect(isRedirect(await thrown(workspacePageData(params, request())))).toBe(true)
|
|
expect(mocks.load).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it("hands `load` the workspace, the path below the module and the query string", async () => {
|
|
signedIn()
|
|
const data = await workspacePageData({ ...params, _splat: "a/b", search: "?q=one&tab=two&q=three" }, request())
|
|
expect(mocks.load).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
slugs: { org: "a", workspace: "b" },
|
|
segments: ["a", "b"],
|
|
search: { q: "three", tab: "two" },
|
|
}),
|
|
)
|
|
expect(data).toMatchObject({
|
|
module: "reports",
|
|
title: "Workspace overview · Example App",
|
|
segments: ["a", "b"],
|
|
locale: "en",
|
|
data: { title: "Reports" },
|
|
scope: { org: "a", workspace: "b" },
|
|
seed: { permissions: ["reports.report.read"] },
|
|
})
|
|
})
|
|
|
|
it("answers 404 for a workspace of another organization, before `load`", async () => {
|
|
signedIn()
|
|
mocks.callByName.mockRejectedValue(refusal(404))
|
|
expect(isNotFound(await thrown(workspacePageData(params, request())))).toBe(true)
|
|
expect(mocks.load).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it("answers 403 to a member without the page's permission, before `load`", async () => {
|
|
signedIn()
|
|
mocks.callByName.mockResolvedValue({ permissions: [], revision: "r1" })
|
|
const error = (await thrown(workspacePageData(params, request()))) as { data?: unknown }
|
|
expect(isNotFound(error)).toBe(true)
|
|
expect(error.data).toEqual({ forbidden: true })
|
|
expect(mocks.setResponseHeader).toHaveBeenCalledWith("x-conusai-page-status", "403")
|
|
expect(mocks.load).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it.each([
|
|
["no registered module", { ...params, module: "nothing" }],
|
|
["a registered module with no workspace page", { ...params, module: "unpaged" }],
|
|
["a name every object has", { ...params, module: "constructor" }],
|
|
])("answers 404 for %s", async (_name, wanted) => {
|
|
signedIn()
|
|
expect(isNotFound(await thrown(workspacePageData(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, expected) => {
|
|
signedIn()
|
|
mocks.load.mockRejectedValue(refusal(status))
|
|
const error = (await thrown(workspacePageData(params, request()))) as { data?: unknown }
|
|
expect(isNotFound(error)).toBe(true)
|
|
expect(error.data).toEqual(expected)
|
|
})
|
|
})
|