mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-16 01:05:42 -04:00
122 lines
3.8 KiB
TypeScript
122 lines
3.8 KiB
TypeScript
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);
|
|
});
|
|
});
|