203 lines
7.2 KiB
TypeScript
203 lines
7.2 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { initTRPC, TRPCError } from "@trpc/server";
|
|
import { wrap } from "@typeschema/valibot";
|
|
import {
|
|
AddWatchlistItemSchema,
|
|
RemoveWatchlistItemSchema,
|
|
ExposureFilterSchema,
|
|
ExposureDetailsSchema,
|
|
RunScanSchema,
|
|
ReportFilterSchema,
|
|
} from "../schemas/darkwatch";
|
|
|
|
vi.mock("~/server/services/darkwatch.service", () => ({
|
|
getWatchlistItems: vi.fn(),
|
|
addWatchlistItem: vi.fn(),
|
|
removeWatchlistItem: vi.fn(),
|
|
getExposures: vi.fn(),
|
|
getExposureDetails: vi.fn(),
|
|
runScan: vi.fn(),
|
|
getScanStatus: vi.fn(),
|
|
getReports: vi.fn(),
|
|
}));
|
|
|
|
import * as darkwatchService from "~/server/services/darkwatch.service";
|
|
|
|
const mockGetWatchlistItems = vi.mocked(darkwatchService.getWatchlistItems);
|
|
const mockAddWatchlistItem = vi.mocked(darkwatchService.addWatchlistItem);
|
|
const mockRemoveWatchlistItem = vi.mocked(darkwatchService.removeWatchlistItem);
|
|
const mockGetExposures = vi.mocked(darkwatchService.getExposures);
|
|
const mockGetExposureDetails = vi.mocked(darkwatchService.getExposureDetails);
|
|
const mockRunScan = vi.mocked(darkwatchService.runScan);
|
|
const mockGetScanStatus = vi.mocked(darkwatchService.getScanStatus);
|
|
const mockGetReports = vi.mocked(darkwatchService.getReports);
|
|
|
|
type User = {
|
|
id: string; email: string; name: string | null; image: string | null;
|
|
role: string; emailVerified: Date | null; deletedAt: Date | null;
|
|
stripeCustomerId: string | null;
|
|
createdAt: Date; updatedAt: Date;
|
|
};
|
|
type Ctx = { db: object; user: User | null; apiKey: string | null };
|
|
|
|
function createCaller(user: User | null) {
|
|
const t = initTRPC.context<Ctx>().create();
|
|
const isAuthed = t.middleware(({ ctx, next }) => {
|
|
if (!ctx.user) throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
return next({ ctx: { ...ctx, user: ctx.user } });
|
|
});
|
|
|
|
const router = t.router({
|
|
getWatchlist: t.procedure.use(isAuthed).query(async ({ ctx }) => {
|
|
return mockGetWatchlistItems(ctx.user.id);
|
|
}),
|
|
addWatchlistItem: t.procedure.use(isAuthed)
|
|
.input(wrap(AddWatchlistItemSchema))
|
|
.mutation(async ({ ctx, input }) => {
|
|
return mockAddWatchlistItem(ctx.user.id, input.type, input.value);
|
|
}),
|
|
removeWatchlistItem: t.procedure.use(isAuthed)
|
|
.input(wrap(RemoveWatchlistItemSchema))
|
|
.mutation(async ({ ctx, input }) => {
|
|
return mockRemoveWatchlistItem(ctx.user.id, input.itemId);
|
|
}),
|
|
getExposures: t.procedure.use(isAuthed)
|
|
.input(wrap(ExposureFilterSchema))
|
|
.query(async ({ ctx, input }) => {
|
|
return mockGetExposures(ctx.user.id, input);
|
|
}),
|
|
getExposureDetails: t.procedure.use(isAuthed)
|
|
.input(wrap(ExposureDetailsSchema))
|
|
.query(async ({ ctx, input }) => {
|
|
return mockGetExposureDetails(ctx.user.id, input.exposureId);
|
|
}),
|
|
runScan: t.procedure.use(isAuthed)
|
|
.input(wrap(RunScanSchema))
|
|
.mutation(async ({ ctx }) => {
|
|
return mockRunScan(ctx.user.id);
|
|
}),
|
|
getScanStatus: t.procedure.use(isAuthed).query(async ({ ctx }) => {
|
|
return mockGetScanStatus(ctx.user.id);
|
|
}),
|
|
getReports: t.procedure.use(isAuthed)
|
|
.input(wrap(ReportFilterSchema))
|
|
.query(async ({ ctx, input }) => {
|
|
return mockGetReports(ctx.user.id, input);
|
|
}),
|
|
});
|
|
|
|
const caller = t.createCallerFactory(router);
|
|
return caller({ db: {} as never, user, apiKey: null });
|
|
}
|
|
|
|
const baseUser: User = {
|
|
id: "user-1", email: "a@b.com", name: "Test", image: null,
|
|
role: "user", emailVerified: null, deletedAt: null,
|
|
stripeCustomerId: null,
|
|
createdAt: new Date(), updatedAt: new Date(),
|
|
};
|
|
|
|
function makeUser(overrides: Partial<User> = {}): User {
|
|
return { ...baseUser, ...overrides };
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe("darkwatch.getWatchlist", () => {
|
|
it("returns watchlist items for authenticated user", async () => {
|
|
const items = [{ id: "w1", type: "email", value: "test@example.com" }];
|
|
mockGetWatchlistItems.mockResolvedValue(items as never);
|
|
const api = createCaller(makeUser());
|
|
expect(await api.getWatchlist()).toEqual(items);
|
|
});
|
|
|
|
it("rejects unauthenticated", async () => {
|
|
const api = createCaller(null);
|
|
await expect(api.getWatchlist()).rejects.toThrow(TRPCError);
|
|
});
|
|
});
|
|
|
|
describe("darkwatch.addWatchlistItem", () => {
|
|
it("adds a watchlist item", async () => {
|
|
const item = { id: "w1", type: "email", value: "test@example.com" };
|
|
mockAddWatchlistItem.mockResolvedValue(item as never);
|
|
const api = createCaller(makeUser());
|
|
const result = await api.addWatchlistItem({ type: "email", value: "test@example.com" });
|
|
expect(result).toEqual(item);
|
|
});
|
|
|
|
it("rejects invalid type", async () => {
|
|
const api = createCaller(makeUser());
|
|
await expect(
|
|
api.addWatchlistItem({ type: "invalid" as never, value: "test" }),
|
|
).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe("darkwatch.removeWatchlistItem", () => {
|
|
it("removes a watchlist item", async () => {
|
|
mockRemoveWatchlistItem.mockResolvedValue({ id: "w1", isActive: false } as never);
|
|
const api = createCaller(makeUser());
|
|
const result = await api.removeWatchlistItem({ itemId: "w1" });
|
|
expect(result.isActive).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("darkwatch.getExposures", () => {
|
|
it("returns exposures with pagination", async () => {
|
|
const data = { items: [], total: 0, page: 1, limit: 20, totalPages: 0 };
|
|
mockGetExposures.mockResolvedValue(data);
|
|
const api = createCaller(makeUser());
|
|
const result = await api.getExposures({ page: 1, limit: 20 });
|
|
expect(result.total).toBe(0);
|
|
});
|
|
|
|
it("passes severity filter", async () => {
|
|
const data = { items: [], total: 0, page: 1, limit: 20, totalPages: 0 };
|
|
mockGetExposures.mockResolvedValue(data);
|
|
const api = createCaller(makeUser());
|
|
await api.getExposures({ severity: "critical" });
|
|
expect(mockGetExposures).toHaveBeenCalledWith("user-1", { severity: "critical", page: 1, limit: 20 });
|
|
});
|
|
});
|
|
|
|
describe("darkwatch.getExposureDetails", () => {
|
|
it("returns exposure details", async () => {
|
|
const exposure = { id: "e1", identifier: "test@example.com", watchlistItem: null };
|
|
mockGetExposureDetails.mockResolvedValue(exposure as never);
|
|
const api = createCaller(makeUser());
|
|
const result = await api.getExposureDetails({ exposureId: "e1" });
|
|
expect(result.id).toBe("e1");
|
|
});
|
|
});
|
|
|
|
describe("darkwatch.runScan", () => {
|
|
it("triggers a scan", async () => {
|
|
mockRunScan.mockResolvedValue({ scanId: "s1", queued: false });
|
|
const api = createCaller(makeUser());
|
|
const result = await api.runScan({});
|
|
expect(result.scanId).toBe("s1");
|
|
});
|
|
});
|
|
|
|
describe("darkwatch.getScanStatus", () => {
|
|
it("returns scan status", async () => {
|
|
mockGetScanStatus.mockResolvedValue({ status: "idle", scanId: null, startedAt: null, completedAt: null, progress: 0, currentSource: null, error: null });
|
|
const api = createCaller(makeUser());
|
|
const result = await api.getScanStatus();
|
|
expect(result.status).toBe("idle");
|
|
});
|
|
});
|
|
|
|
describe("darkwatch.getReports", () => {
|
|
it("returns reports", async () => {
|
|
const data = { items: [], total: 0, page: 1, limit: 20, totalPages: 0 };
|
|
mockGetReports.mockResolvedValue(data);
|
|
const api = createCaller(makeUser());
|
|
const result = await api.getReports({ page: 1, limit: 20 });
|
|
expect(result.total).toBe(0);
|
|
});
|
|
});
|