import { describe, it, expect, vi, beforeEach } from "vitest"; import { initTRPC, TRPCError } from "@trpc/server"; import { wrap } from "@typeschema/valibot"; import { CreateEnrollmentSchema, EnrollAdditionalSampleSchema, DeleteEnrollmentSchema, AnalyzeAudioSchema, AnalysisFilterSchema, AnalysisResultSchema, AnalysisFeedbackSchema, JobStatusSchema, } from "../schemas/voiceprint"; vi.mock("~/server/services/voiceprint.service", () => ({ getEnrollments: vi.fn(), createEnrollment: vi.fn(), enrollAdditionalSample: vi.fn(), deleteEnrollment: vi.fn(), analyzeAudio: vi.fn(), reportAnalysisFeedback: vi.fn(), getAnalyses: vi.fn(), getAnalysisResult: vi.fn(), getJobStatus: vi.fn(), getUsageStats: vi.fn(), })); import * as voiceprintService from "~/server/services/voiceprint.service"; const mockGetEnrollments = vi.mocked(voiceprintService.getEnrollments); const mockCreateEnrollment = vi.mocked(voiceprintService.createEnrollment); const mockEnrollAdditionalSample = vi.mocked(voiceprintService.enrollAdditionalSample); const mockDeleteEnrollment = vi.mocked(voiceprintService.deleteEnrollment); const mockAnalyzeAudio = vi.mocked(voiceprintService.analyzeAudio); const mockReportAnalysisFeedback = vi.mocked(voiceprintService.reportAnalysisFeedback); const mockGetAnalyses = vi.mocked(voiceprintService.getAnalyses); const mockGetAnalysisResult = vi.mocked(voiceprintService.getAnalysisResult); const mockGetJobStatus = vi.mocked(voiceprintService.getJobStatus); const mockGetUsageStats = vi.mocked(voiceprintService.getUsageStats); 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().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({ getEnrollments: t.procedure.use(isAuthed).query(async ({ ctx }) => { return mockGetEnrollments(ctx.user.id); }), createEnrollment: t.procedure.use(isAuthed) .input(wrap(CreateEnrollmentSchema)) .mutation(async ({ ctx, input }) => { return mockCreateEnrollment(ctx.user.id, input.name, input.audioBase64); }), enrollAdditionalSample: t.procedure.use(isAuthed) .input(wrap(EnrollAdditionalSampleSchema)) .mutation(async ({ ctx, input }) => { return mockEnrollAdditionalSample(ctx.user.id, input.enrollmentId, input.audioBase64); }), deleteEnrollment: t.procedure.use(isAuthed) .input(wrap(DeleteEnrollmentSchema)) .mutation(async ({ ctx, input }) => { return mockDeleteEnrollment(ctx.user.id, input.enrollmentId); }), analyzeAudio: t.procedure.use(isAuthed) .input(wrap(AnalyzeAudioSchema)) .mutation(async ({ ctx, input }) => { return mockAnalyzeAudio(ctx.user.id, input.audioBase64, input.enrollmentId); }), reportAnalysisFeedback: t.procedure.use(isAuthed) .input(wrap(AnalysisFeedbackSchema)) .mutation(async ({ ctx, input }) => { return mockReportAnalysisFeedback(ctx.user.id, input.analysisId, { isFalsePositive: input.isFalsePositive, notes: input.notes, }); }), getAnalyses: t.procedure.use(isAuthed) .input(wrap(AnalysisFilterSchema)) .query(async ({ ctx, input }) => { return mockGetAnalyses(ctx.user.id, input); }), getAnalysisResult: t.procedure.use(isAuthed) .input(wrap(AnalysisResultSchema)) .query(async ({ ctx, input }) => { return mockGetAnalysisResult(ctx.user.id, input.analysisId); }), getJobStatus: t.procedure.use(isAuthed) .input(wrap(JobStatusSchema)) .query(async ({ ctx, input }) => { return mockGetJobStatus(ctx.user.id, input.jobId); }), getUsageStats: t.procedure.use(isAuthed).query(async ({ ctx }) => { return mockGetUsageStats(ctx.user.id); }), }); 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 { return { ...baseUser, ...overrides }; } beforeEach(() => { vi.clearAllMocks(); }); describe("voiceprint.getEnrollments", () => { it("returns enrollments for authenticated user", async () => { const items = [{ id: "enr-1", name: "My Voice" }]; mockGetEnrollments.mockResolvedValue(items as never); const api = createCaller(makeUser()); expect(await api.getEnrollments()).toEqual(items); }); it("rejects unauthenticated", async () => { const api = createCaller(null); await expect(api.getEnrollments()).rejects.toThrow(TRPCError); }); }); describe("voiceprint.createEnrollment", () => { it("creates an enrollment", async () => { const enrollment = { id: "enr-1", name: "My Voice" }; mockCreateEnrollment.mockResolvedValue(enrollment as never); const api = createCaller(makeUser()); const result = await api.createEnrollment({ name: "My Voice", audioBase64: "dGVzdA==" }); expect(result).toEqual(enrollment); }); it("rejects empty name", async () => { const api = createCaller(makeUser()); await expect( api.createEnrollment({ name: "", audioBase64: "dGVzdA==" }), ).rejects.toThrow(); }); }); describe("voiceprint.enrollAdditionalSample", () => { it("enrolls an additional audio sample", async () => { const result = { id: "enr-1", enrollmentsCount: 3, enrollmentStatus: "Enrolled" }; mockEnrollAdditionalSample.mockResolvedValue(result as never); const api = createCaller(makeUser()); const res = await api.enrollAdditionalSample({ enrollmentId: "enr-1", audioBase64: "bW9yZS1hdWRpbw==", }); expect(res.enrollmentsCount).toBe(3); }); it("rejects missing enrollmentId", async () => { const api = createCaller(makeUser()); await expect( api.enrollAdditionalSample({ enrollmentId: "", audioBase64: "dGVzdA==" }), ).rejects.toThrow(); }); }); describe("voiceprint.deleteEnrollment", () => { it("deletes enrollment", async () => { mockDeleteEnrollment.mockResolvedValue({ id: "enr-1", isActive: false } as never); const api = createCaller(makeUser()); const result = await api.deleteEnrollment({ enrollmentId: "enr-1" }); expect(result.isActive).toBe(false); }); }); describe("voiceprint.analyzeAudio", () => { it("analyzes audio and returns result", async () => { const result = { id: "ana-1", verdict: "NATURAL", confidence: 0.95 }; mockAnalyzeAudio.mockResolvedValue(result as never); const api = createCaller(makeUser()); const res = await api.analyzeAudio({ audioBase64: "dGVzdA==" }); expect(res.verdict).toBe("NATURAL"); }); it("accepts optional enrollmentId", async () => { mockAnalyzeAudio.mockResolvedValue({ id: "ana-1" } as never); const api = createCaller(makeUser()); await api.analyzeAudio({ audioBase64: "dGVzdA==", enrollmentId: "enr-1" }); expect(mockAnalyzeAudio).toHaveBeenCalledWith("user-1", "dGVzdA==", "enr-1"); }); }); describe("voiceprint.reportAnalysisFeedback", () => { it("submits feedback on analysis", async () => { const result = { id: "ana-1", userFeedback: { isFalsePositive: true } }; mockReportAnalysisFeedback.mockResolvedValue(result as never); const api = createCaller(makeUser()); const res = await api.reportAnalysisFeedback({ analysisId: "ana-1", isFalsePositive: true, notes: "Not synthetic", }); expect((res.userFeedback as { isFalsePositive: boolean }).isFalsePositive).toBe(true); }); }); describe("voiceprint.getAnalyses", () => { it("returns paginated analyses", async () => { const data = { items: [], total: 0, page: 1, limit: 20, totalPages: 0 }; mockGetAnalyses.mockResolvedValue(data); const api = createCaller(makeUser()); const result = await api.getAnalyses({ page: 1, limit: 20 }); expect(result.total).toBe(0); }); it("passes verdict filter", async () => { mockGetAnalyses.mockResolvedValue({ items: [], total: 0, page: 1, limit: 20, totalPages: 0 }); const api = createCaller(makeUser()); await api.getAnalyses({ verdict: "SYNTHETIC" }); expect(mockGetAnalyses).toHaveBeenCalledWith("user-1", { verdict: "SYNTHETIC", page: 1, limit: 20 }); }); }); describe("voiceprint.getAnalysisResult", () => { it("returns analysis details", async () => { const analysis = { id: "ana-1", verdict: "NATURAL", confidence: 0.95 }; mockGetAnalysisResult.mockResolvedValue(analysis as never); const api = createCaller(makeUser()); const result = await api.getAnalysisResult({ analysisId: "ana-1" }); expect(result.id).toBe("ana-1"); }); }); describe("voiceprint.getJobStatus", () => { it("returns job status", async () => { const job = { id: "job-1", status: "RUNNING", result: null }; mockGetJobStatus.mockResolvedValue(job as never); const api = createCaller(makeUser()); const result = await api.getJobStatus({ jobId: "job-1" }); expect(result.status).toBe("RUNNING"); }); }); describe("voiceprint.getUsageStats", () => { it("returns usage statistics", async () => { const stats = { analysesThisMonth: 5, activeEnrollments: 2 }; mockGetUsageStats.mockResolvedValue(stats); const api = createCaller(makeUser()); const result = await api.getUsageStats(); expect(result.analysesThisMonth).toBe(5); expect(result.activeEnrollments).toBe(2); }); });