import { describe, it, expect, beforeEach, vi, afterEach } from "vitest"; import { EmbeddingService } from "./embedding.service"; import { PrismaService } from "../../prisma/prisma.service"; // Mock OpenAI with a proper class const mockEmbeddingsCreate = vi.fn(); vi.mock("openai", () => { return { default: class MockOpenAI { embeddings = { create: mockEmbeddingsCreate, }; }, }; }); describe("EmbeddingService", () => { let service: EmbeddingService; let prismaService: PrismaService; let originalEnv: string | undefined; beforeEach(() => { // Store original env originalEnv = process.env.OPENAI_API_KEY; prismaService = { $executeRaw: vi.fn(), knowledgeEmbedding: { deleteMany: vi.fn(), }, } as unknown as PrismaService; // Clear mock call history vi.clearAllMocks(); }); afterEach(() => { // Restore original env if (originalEnv) { process.env.OPENAI_API_KEY = originalEnv; } else { delete process.env.OPENAI_API_KEY; } }); describe("constructor", () => { it("should not instantiate OpenAI client when API key is missing", () => { delete process.env.OPENAI_API_KEY; service = new EmbeddingService(prismaService); // Verify service is not configured (client is null) expect(service.isConfigured()).toBe(false); }); it("should instantiate OpenAI client when API key is provided", () => { process.env.OPENAI_API_KEY = "test-api-key"; service = new EmbeddingService(prismaService); // Verify service is configured (client is not null) expect(service.isConfigured()).toBe(true); }); }); // Default service setup (without API key) for remaining tests function createServiceWithoutKey(): EmbeddingService { delete process.env.OPENAI_API_KEY; return new EmbeddingService(prismaService); } describe("isConfigured", () => { it("should return false when OPENAI_API_KEY is not set", () => { service = createServiceWithoutKey(); expect(service.isConfigured()).toBe(false); }); it("should return true when OPENAI_API_KEY is set", () => { process.env.OPENAI_API_KEY = "test-key"; service = new EmbeddingService(prismaService); expect(service.isConfigured()).toBe(true); }); }); describe("prepareContentForEmbedding", () => { beforeEach(() => { service = createServiceWithoutKey(); }); it("should combine title and content with title weighting", () => { const title = "Test Title"; const content = "Test content goes here"; const result = service.prepareContentForEmbedding(title, content); expect(result).toContain(title); expect(result).toContain(content); // Title should appear twice for weighting expect(result.split(title).length - 1).toBe(2); }); it("should handle empty content", () => { const title = "Test Title"; const content = ""; const result = service.prepareContentForEmbedding(title, content); expect(result).toBe(`${title}\n\n${title}`); }); }); describe("generateAndStoreEmbedding", () => { it("should skip generation when not configured", async () => { service = createServiceWithoutKey(); await service.generateAndStoreEmbedding("test-id", "test content"); expect(prismaService.$executeRaw).not.toHaveBeenCalled(); }); }); describe("deleteEmbedding", () => { beforeEach(() => { service = createServiceWithoutKey(); }); it("should delete embedding for entry", async () => { const entryId = "test-entry-id"; await service.deleteEmbedding(entryId); expect(prismaService.knowledgeEmbedding.deleteMany).toHaveBeenCalledWith({ where: { entryId }, }); }); }); describe("batchGenerateEmbeddings", () => { it("should return 0 when not configured", async () => { service = createServiceWithoutKey(); const entries = [ { id: "1", content: "content 1" }, { id: "2", content: "content 2" }, ]; const result = await service.batchGenerateEmbeddings(entries); expect(result).toBe(0); }); }); describe("generateEmbedding", () => { it("should throw error when not configured", async () => { service = createServiceWithoutKey(); await expect(service.generateEmbedding("test text")).rejects.toThrow( "OPENAI_API_KEY not configured" ); }); }); });