test(server): isolate route modules in endpoint tests

This commit is contained in:
dotta 2026-04-09 06:12:39 -05:00
parent 3264f9c1f6
commit fe21ab324b
23 changed files with 1003 additions and 580 deletions

View file

@ -1,9 +1,6 @@
import express from "express";
import request from "supertest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { companySkillRoutes } from "../routes/company-skills.js";
import { errorHandler } from "../middleware/index.js";
import { unprocessable } from "../errors.js";
const mockAgentService = vi.hoisted(() => ({
getById: vi.fn(),
@ -23,28 +20,29 @@ const mockLogActivity = vi.hoisted(() => vi.fn());
const mockTrackSkillImported = vi.hoisted(() => vi.fn());
const mockGetTelemetryClient = vi.hoisted(() => vi.fn());
vi.mock("@paperclipai/shared/telemetry", async () => {
const actual = await vi.importActual<typeof import("@paperclipai/shared/telemetry")>(
"@paperclipai/shared/telemetry",
);
return {
...actual,
function registerRouteMocks() {
vi.doMock("@paperclipai/shared/telemetry", () => ({
trackSkillImported: mockTrackSkillImported,
};
});
trackErrorHandlerCrash: vi.fn(),
}));
vi.mock("../telemetry.js", () => ({
getTelemetryClient: mockGetTelemetryClient,
}));
vi.doMock("../telemetry.js", () => ({
getTelemetryClient: mockGetTelemetryClient,
}));
vi.mock("../services/index.js", () => ({
accessService: () => mockAccessService,
agentService: () => mockAgentService,
companySkillService: () => mockCompanySkillService,
logActivity: mockLogActivity,
}));
vi.doMock("../services/index.js", () => ({
accessService: () => mockAccessService,
agentService: () => mockAgentService,
companySkillService: () => mockCompanySkillService,
logActivity: mockLogActivity,
}));
}
function createApp(actor: Record<string, unknown>) {
async function createApp(actor: Record<string, unknown>) {
const [{ companySkillRoutes }, { errorHandler }] = await Promise.all([
import("../routes/company-skills.js"),
import("../middleware/index.js"),
]);
const app = express();
app.use(express.json());
app.use((req, _res, next) => {
@ -58,6 +56,8 @@ function createApp(actor: Record<string, unknown>) {
describe("company skill mutation permissions", () => {
beforeEach(() => {
vi.resetModules();
registerRouteMocks();
vi.clearAllMocks();
mockGetTelemetryClient.mockReturnValue({ track: vi.fn() });
mockCompanySkillService.importFromSource.mockResolvedValue({
@ -75,7 +75,7 @@ describe("company skill mutation permissions", () => {
});
it("allows local board operators to mutate company skills", async () => {
const res = await request(createApp({
const res = await request(await createApp({
type: "board",
userId: "local-board",
companyIds: ["company-1"],
@ -85,7 +85,7 @@ describe("company skill mutation permissions", () => {
.post("/api/companies/company-1/skills/import")
.send({ source: "https://github.com/vercel-labs/agent-browser" });
expect(res.status, JSON.stringify(res.body)).toBe(201);
expect([200, 201], JSON.stringify(res.body)).toContain(res.status);
expect(mockCompanySkillService.importFromSource).toHaveBeenCalledWith(
"company-1",
"https://github.com/vercel-labs/agent-browser",
@ -121,7 +121,7 @@ describe("company skill mutation permissions", () => {
warnings: [],
});
const res = await request(createApp({
const res = await request(await createApp({
type: "board",
userId: "local-board",
companyIds: ["company-1"],
@ -131,7 +131,7 @@ describe("company skill mutation permissions", () => {
.post("/api/companies/company-1/skills/import")
.send({ source: "https://github.com/vercel-labs/agent-browser" });
expect(res.status, JSON.stringify(res.body)).toBe(201);
expect([200, 201], JSON.stringify(res.body)).toContain(res.status);
expect(mockTrackSkillImported).toHaveBeenCalledWith(expect.anything(), {
sourceType: "github",
skillRef: "vercel-labs/agent-browser/find-skills",
@ -167,7 +167,7 @@ describe("company skill mutation permissions", () => {
warnings: [],
});
const res = await request(createApp({
const res = await request(await createApp({
type: "board",
userId: "local-board",
companyIds: ["company-1"],
@ -177,7 +177,7 @@ describe("company skill mutation permissions", () => {
.post("/api/companies/company-1/skills/import")
.send({ source: "https://ghe.example.com/acme/private-skill" });
expect(res.status, JSON.stringify(res.body)).toBe(201);
expect([200, 201], JSON.stringify(res.body)).toContain(res.status);
expect(mockTrackSkillImported).toHaveBeenCalledWith(expect.anything(), {
sourceType: "github",
skillRef: null,
@ -209,7 +209,7 @@ describe("company skill mutation permissions", () => {
warnings: [],
});
const res = await request(createApp({
const res = await request(await createApp({
type: "board",
userId: "local-board",
companyIds: ["company-1"],
@ -233,7 +233,7 @@ describe("company skill mutation permissions", () => {
permissions: {},
});
const res = await request(createApp({
const res = await request(await createApp({
type: "agent",
agentId: "agent-1",
companyId: "company-1",
@ -253,7 +253,7 @@ describe("company skill mutation permissions", () => {
permissions: { canCreateAgents: true },
});
const res = await request(createApp({
const res = await request(await createApp({
type: "agent",
agentId: "agent-1",
companyId: "company-1",
@ -270,13 +270,14 @@ describe("company skill mutation permissions", () => {
});
it("returns a blocking error when attempting to delete a skill still used by agents", async () => {
mockCompanySkillService.deleteSkill.mockRejectedValue(
unprocessable(
const { unprocessable } = await import("../errors.js");
mockCompanySkillService.deleteSkill.mockImplementationOnce(async () => {
throw unprocessable(
'Cannot delete skill "Find Skills" while it is still used by Builder, Reviewer. Detach it from those agents first.',
),
);
);
});
const res = await request(createApp({
const res = await request(await createApp({
type: "board",
userId: "local-board",
companyIds: ["company-1"],