- Skip client initialization when OPENAI_API_KEY not configured - Set openai property to null instead of creating with dummy key - Methods return gracefully when embeddings not available - Updated tests to verify client is not instantiated without key Refs #338 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
165 lines
4.4 KiB
TypeScript
165 lines
4.4 KiB
TypeScript
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"
|
|
);
|
|
});
|
|
});
|
|
});
|