test(server): add test harness and auth coverage for every procedure

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Opus 5.5 committed 2026-10-04 23:14:56 -04:00
1 parent 810bbecf78
commit 6620f0b76f
6 files changed
+266 -2

No files matched your search

+2 -1
View File
@@ -9,7 +9,8 @@
"lint": "oxlint", "lint": "oxlint",
"format": "oxfmt --config ../../.oxfmtrc.json", "format": "oxfmt --config ../../.oxfmtrc.json",
"format:check": "oxfmt --check --config ../../.oxfmtrc.json", "format:check": "oxfmt --check --config ../../.oxfmtrc.json",
"check-types": "tsc --noEmit" "check-types": "tsc --noEmit",
"test": "vitest run"
}, },
"dependencies": { "dependencies": {
"@orpc/contract": "catalog:", "@orpc/contract": "catalog:",
+121
View File
@@ -0,0 +1,121 @@
import { call } from "@orpc/server";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { auth } from "@sofa/auth/server";
import { importJobs } from "@sofa/db/schema";
import { clearAllTables, insertUser, testDb } from "@sofa/test/db";
import { implementedRouter } from "../src/orpc/router";
vi.mock("@sofa/auth/server", () => ({
auth: {
api: { getSession: vi.fn<() => void>(), updateUser: vi.fn<() => void>() },
handler: vi.fn<() => void>(),
},
}));
const getSession = vi.mocked(auth.api.getSession);
const ctx = { context: { headers: new Headers() } };
function sessionFor(userId: string, role: "user" | "admin" = "user") {
return {
session: { id: `session-${userId}`, userId },
user: { id: userId, role, name: "U", email: `${userId}@example.com` },
} as never;
}
function codeOf(error: unknown): unknown {
return (error as { code?: unknown } | undefined)?.code;
}
async function rejection(promise: Promise<unknown>): Promise<unknown> {
try {
await promise;
} catch (error) {
return error;
}
return undefined;
}
function createJob(userId: string): string {
const id = `job-${Math.random().toString(36).slice(2, 10)}`;
testDb
.insert(importJobs)
.values({
id,
userId,
source: "trakt",
status: "pending",
payload: "{}",
importWatches: true,
importWatchlist: true,
importRatings: true,
createdAt: new Date(),
})
.run();
return id;
}
beforeEach(() => {
clearAllTables();
getSession.mockReset();
});
describe("admin middleware", () => {
test("rejects a missing session with UNAUTHORIZED", async () => {
getSession.mockResolvedValue(null);
const error = await rejection(call(implementedRouter.admin.settings.get, undefined, ctx));
expect(codeOf(error)).toBe("UNAUTHORIZED");
});
test("rejects a non-admin user with FORBIDDEN", async () => {
getSession.mockResolvedValue(sessionFor("user-1", "user"));
const error = await rejection(call(implementedRouter.admin.settings.get, undefined, ctx));
expect(codeOf(error)).toBe("FORBIDDEN");
});
test("bypasses the cookie cache and lets admins through", async () => {
getSession.mockResolvedValue(sessionFor("admin-1", "admin"));
const error = await rejection(call(implementedRouter.admin.settings.get, undefined, ctx));
expect(getSession).toHaveBeenCalledWith(
expect.objectContaining({ query: { disableCookieCache: true } }),
);
// The call may resolve or fail for unrelated reasons; it must not be an auth rejection.
expect(["UNAUTHORIZED", "FORBIDDEN"]).not.toContain(codeOf(error));
});
});
describe("authed middleware", () => {
test("rejects a missing session with UNAUTHORIZED", async () => {
getSession.mockResolvedValue(null);
const error = await rejection(call(implementedRouter.titles.similar, { id: "anything" }, ctx));
expect(codeOf(error)).toBe("UNAUTHORIZED");
});
});
describe("import job ownership", () => {
test("another user's job is FORBIDDEN for getJob and cancelJob", async () => {
insertUser("user-a");
insertUser("user-b");
const jobId = createJob("user-a");
getSession.mockResolvedValue(sessionFor("user-b"));
const getError = await rejection(call(implementedRouter.imports.getJob, { id: jobId }, ctx));
expect(codeOf(getError)).toBe("FORBIDDEN");
const cancelError = await rejection(
call(implementedRouter.imports.cancelJob, { id: jobId }, ctx),
);
expect(codeOf(cancelError)).toBe("FORBIDDEN");
});
test("the owner can read their own job", async () => {
insertUser("user-a");
insertUser("user-b");
const jobId = createJob("user-a");
getSession.mockResolvedValue(sessionFor("user-a"));
const job = await call(implementedRouter.imports.getJob, { id: jobId }, ctx);
expect(job.id).toBe(jobId);
});
});
+48
View File
@@ -0,0 +1,48 @@
import { isProcedure } from "@orpc/server";
import { describe, expect, test } from "vitest";
import { admin, authed } from "../src/orpc/middleware";
import { implementedRouter } from "../src/orpc/router";
// Procedures that are intentionally callable without a session.
const PUBLIC_PROCEDURES = new Set(["system.publicInfo"]);
function collectProcedures(
node: unknown,
path: string[] = [],
): { path: string; middlewares: readonly unknown[] }[] {
if (isProcedure(node)) {
return [{ path: path.join("."), middlewares: node["~orpc"].middlewares }];
}
return Object.entries(node as Record<string, unknown>).flatMap(([key, child]) =>
collectProcedures(child, [...path, key]),
);
}
const procedures = collectProcedures(implementedRouter);
describe("router authorization", () => {
test("finds every procedure", () => {
expect(procedures.length).toBeGreaterThanOrEqual(50);
});
test.each(procedures.filter((p) => !PUBLIC_PROCEDURES.has(p.path)))(
"$path requires a session",
({ middlewares }) => {
expect(middlewares.includes(authed) || middlewares.includes(admin)).toBe(true);
},
);
test.each(procedures.filter((p) => p.path.startsWith("admin.")))(
"$path requires an admin",
({ middlewares }) => {
expect(middlewares.includes(admin)).toBe(true);
},
);
test("public procedures have no auth middleware", () => {
for (const proc of procedures.filter((p) => PUBLIC_PROCEDURES.has(p.path))) {
expect(proc.middlewares.includes(authed) || proc.middlewares.includes(admin)).toBe(false);
}
});
});
+86
View File
@@ -0,0 +1,86 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
import { auth } from "@sofa/auth/server";
import { clearAllTables } from "@sofa/test/db";
import avatars from "../src/routes/avatars";
import backups from "../src/routes/backups";
import exportRoute from "../src/routes/export";
import lists from "../src/routes/lists";
import webhooks from "../src/routes/webhooks";
vi.mock("@sofa/auth/server", () => ({
auth: {
api: { getSession: vi.fn<() => void>(), updateUser: vi.fn<() => void>() },
handler: vi.fn<() => void>(),
},
}));
const getSession = vi.mocked(auth.api.getSession);
function userSession(role: "user" | "admin" = "user") {
return {
session: { id: "s1", userId: "user-1" },
user: { id: "user-1", role, name: "U", email: "u@example.com" },
} as never;
}
beforeEach(() => {
clearAllTables();
getSession.mockReset();
});
describe("backups route", () => {
test("rejects a missing session with 401", async () => {
getSession.mockResolvedValue(null);
const res = await backups.request("/sofa-backup.db");
expect(res.status).toBe(401);
expect(await res.json()).toEqual({ error: "Unauthorized" });
});
test("rejects a non-admin with 403 and bypasses the cookie cache", async () => {
getSession.mockResolvedValue(userSession("user"));
const res = await backups.request("/sofa-backup.db");
expect(res.status).toBe(403);
expect(await res.json()).toEqual({ error: "Forbidden" });
expect(getSession).toHaveBeenCalledWith(
expect.objectContaining({ query: { disableCookieCache: true } }),
);
});
});
describe("export route", () => {
test("rejects a missing session with 401", async () => {
getSession.mockResolvedValue(null);
const res = await exportRoute.request("/user-data");
expect(res.status).toBe(401);
});
});
describe("avatars route", () => {
test("rejects a missing session with 401", async () => {
getSession.mockResolvedValue(null);
const res = await avatars.request("/user-1");
expect(res.status).toBe(401);
});
});
describe("webhooks route", () => {
test("returns 200 { ok: true } for an unknown token", async () => {
const res = await webhooks.request("/not-a-real-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({}),
});
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ ok: true });
});
});
describe("lists route", () => {
test("returns 200 [] for an unknown token", async () => {
const res = await lists.request("/not-a-real-token");
expect(res.status).toBe(200);
expect(await res.json()).toEqual([]);
});
});
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"extends": "@sofa/tsconfig/base.json", "extends": "@sofa/tsconfig/base.json",
"include": ["src"] "include": ["src", "test"]
} }
+8
View File
@@ -0,0 +1,8 @@
import { defineProject } from "vitest/config";
export default defineProject({
test: {
include: ["test/**/*.test.ts"],
setupFiles: ["@sofa/test/setup"],
},
});