Some checks failed
ci/woodpecker/push/api Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
150 lines
4.7 KiB
TypeScript
150 lines
4.7 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
import { Test, TestingModule } from "@nestjs/testing";
|
|
import { ConflictException, NotFoundException } from "@nestjs/common";
|
|
import { ConversationArchiveService } from "./conversation-archive.service";
|
|
import { PrismaService } from "../prisma/prisma.service";
|
|
import { EmbeddingService } from "../knowledge/services/embedding.service";
|
|
|
|
const mockPrisma = {
|
|
conversationArchive: {
|
|
findUnique: vi.fn(),
|
|
create: vi.fn(),
|
|
count: vi.fn(),
|
|
findMany: vi.fn(),
|
|
findFirst: vi.fn(),
|
|
},
|
|
$queryRaw: vi.fn(),
|
|
$executeRaw: vi.fn(),
|
|
};
|
|
|
|
const mockEmbedding = {
|
|
isConfigured: vi.fn(),
|
|
generateEmbedding: vi.fn(),
|
|
};
|
|
|
|
describe("ConversationArchiveService", () => {
|
|
let service: ConversationArchiveService;
|
|
|
|
beforeEach(async () => {
|
|
vi.clearAllMocks();
|
|
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
ConversationArchiveService,
|
|
{ provide: PrismaService, useValue: mockPrisma },
|
|
{ provide: EmbeddingService, useValue: mockEmbedding },
|
|
],
|
|
}).compile();
|
|
|
|
service = module.get<ConversationArchiveService>(ConversationArchiveService);
|
|
});
|
|
|
|
describe("ingest", () => {
|
|
const workspaceId = "ws-1";
|
|
const dto = {
|
|
sessionId: "sess-abc",
|
|
agentId: "agent-xyz",
|
|
messages: [
|
|
{ role: "user", content: "Hello" },
|
|
{ role: "assistant", content: "Hi there!" },
|
|
],
|
|
summary: "A greeting conversation",
|
|
startedAt: "2026-02-28T10:00:00Z",
|
|
};
|
|
|
|
it("creates a conversation archive and returns id", async () => {
|
|
mockPrisma.conversationArchive.findUnique.mockResolvedValue(null);
|
|
mockPrisma.conversationArchive.create.mockResolvedValue({ id: "conv-1" });
|
|
mockEmbedding.isConfigured.mockReturnValue(false);
|
|
|
|
const result = await service.ingest(workspaceId, dto);
|
|
|
|
expect(result).toEqual({ id: "conv-1" });
|
|
expect(mockPrisma.conversationArchive.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
data: expect.objectContaining({
|
|
workspaceId,
|
|
sessionId: dto.sessionId,
|
|
agentId: dto.agentId,
|
|
messageCount: 2,
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
|
|
it("throws ConflictException when session already exists", async () => {
|
|
mockPrisma.conversationArchive.findUnique.mockResolvedValue({ id: "existing" });
|
|
|
|
await expect(service.ingest(workspaceId, dto)).rejects.toThrow(ConflictException);
|
|
});
|
|
});
|
|
|
|
describe("findAll", () => {
|
|
const workspaceId = "ws-1";
|
|
|
|
it("returns paginated list", async () => {
|
|
mockPrisma.conversationArchive.count.mockResolvedValue(5);
|
|
mockPrisma.conversationArchive.findMany.mockResolvedValue([
|
|
{ id: "conv-1", sessionId: "sess-1" },
|
|
]);
|
|
|
|
const result = await service.findAll(workspaceId, { page: 1, limit: 10 });
|
|
|
|
expect(result.pagination.total).toBe(5);
|
|
expect(result.data).toHaveLength(1);
|
|
});
|
|
|
|
it("uses default pagination when not provided", async () => {
|
|
mockPrisma.conversationArchive.count.mockResolvedValue(0);
|
|
mockPrisma.conversationArchive.findMany.mockResolvedValue([]);
|
|
|
|
const result = await service.findAll(workspaceId, {});
|
|
|
|
expect(result.pagination.page).toBe(1);
|
|
expect(result.pagination.limit).toBe(20);
|
|
});
|
|
});
|
|
|
|
describe("findOne", () => {
|
|
const workspaceId = "ws-1";
|
|
|
|
it("returns record when found", async () => {
|
|
const record = { id: "conv-1", workspaceId, sessionId: "sess-1" };
|
|
mockPrisma.conversationArchive.findFirst.mockResolvedValue(record);
|
|
|
|
const result = await service.findOne(workspaceId, "conv-1");
|
|
|
|
expect(result).toEqual(record);
|
|
});
|
|
|
|
it("throws NotFoundException when record does not exist", async () => {
|
|
mockPrisma.conversationArchive.findFirst.mockResolvedValue(null);
|
|
|
|
await expect(service.findOne(workspaceId, "missing")).rejects.toThrow(NotFoundException);
|
|
});
|
|
});
|
|
|
|
describe("search", () => {
|
|
it("throws ConflictException when embedding is not configured", async () => {
|
|
mockEmbedding.isConfigured.mockReturnValue(false);
|
|
|
|
await expect(service.search("ws-1", { query: "test query" })).rejects.toThrow(
|
|
ConflictException
|
|
);
|
|
});
|
|
|
|
it("performs vector search when configured", async () => {
|
|
mockEmbedding.isConfigured.mockReturnValue(true);
|
|
mockEmbedding.generateEmbedding.mockResolvedValue(new Array(1536).fill(0.1));
|
|
mockPrisma.$queryRaw
|
|
.mockResolvedValueOnce([{ id: "conv-1", similarity: 0.9 }])
|
|
.mockResolvedValueOnce([{ count: BigInt(1) }]);
|
|
|
|
const result = await service.search("ws-1", { query: "greetings" });
|
|
|
|
expect(result.data).toHaveLength(1);
|
|
expect(result.pagination.total).toBe(1);
|
|
});
|
|
});
|
|
});
|