test(api): add integration tests for MS22 knowledge layer modules (MS22-TEST-001)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
This commit is contained in:
198
apps/api/src/agent-memory/agent-memory.integration.spec.ts
Normal file
198
apps/api/src/agent-memory/agent-memory.integration.spec.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, afterAll } from "vitest";
|
||||
import { randomUUID as uuid } from "crypto";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { NotFoundException } from "@nestjs/common";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { AgentMemoryService } from "./agent-memory.service";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
|
||||
const shouldRunDbIntegrationTests =
|
||||
process.env.RUN_DB_TESTS === "true" && Boolean(process.env.DATABASE_URL);
|
||||
const describeFn = shouldRunDbIntegrationTests ? describe : describe.skip;
|
||||
|
||||
async function createWorkspace(
|
||||
prisma: PrismaClient,
|
||||
label: string
|
||||
): Promise<{ workspaceId: string; ownerId: string }> {
|
||||
const workspace = await prisma.workspace.create({
|
||||
data: {
|
||||
name: `${label} ${Date.now()}`,
|
||||
owner: {
|
||||
create: {
|
||||
email: `${label.toLowerCase().replace(/\s+/g, "-")}-${Date.now()}@example.com`,
|
||||
name: `${label} Owner`,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
workspaceId: workspace.id,
|
||||
ownerId: workspace.ownerId,
|
||||
};
|
||||
}
|
||||
|
||||
describeFn("AgentMemoryService Integration", () => {
|
||||
let moduleRef: TestingModule;
|
||||
let prisma: PrismaClient;
|
||||
let service: AgentMemoryService;
|
||||
let setupComplete = false;
|
||||
|
||||
let workspaceAId: string;
|
||||
let workspaceAOwnerId: string;
|
||||
let workspaceBId: string;
|
||||
let workspaceBOwnerId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
prisma = new PrismaClient();
|
||||
await prisma.$connect();
|
||||
|
||||
const workspaceA = await createWorkspace(prisma, "Agent Memory Integration A");
|
||||
workspaceAId = workspaceA.workspaceId;
|
||||
workspaceAOwnerId = workspaceA.ownerId;
|
||||
|
||||
const workspaceB = await createWorkspace(prisma, "Agent Memory Integration B");
|
||||
workspaceBId = workspaceB.workspaceId;
|
||||
workspaceBOwnerId = workspaceB.ownerId;
|
||||
|
||||
moduleRef = await Test.createTestingModule({
|
||||
providers: [
|
||||
AgentMemoryService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prisma,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = moduleRef.get<AgentMemoryService>(AgentMemoryService);
|
||||
setupComplete = true;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
await prisma.agentMemory.deleteMany({
|
||||
where: {
|
||||
workspaceId: {
|
||||
in: [workspaceAId, workspaceBId],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (!prisma) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceIds = [workspaceAId, workspaceBId].filter(
|
||||
(id): id is string => typeof id === "string"
|
||||
);
|
||||
const ownerIds = [workspaceAOwnerId, workspaceBOwnerId].filter(
|
||||
(id): id is string => typeof id === "string"
|
||||
);
|
||||
|
||||
if (workspaceIds.length > 0) {
|
||||
await prisma.agentMemory.deleteMany({
|
||||
where: {
|
||||
workspaceId: {
|
||||
in: workspaceIds,
|
||||
},
|
||||
},
|
||||
});
|
||||
await prisma.workspace.deleteMany({ where: { id: { in: workspaceIds } } });
|
||||
}
|
||||
|
||||
if (ownerIds.length > 0) {
|
||||
await prisma.user.deleteMany({ where: { id: { in: ownerIds } } });
|
||||
}
|
||||
|
||||
if (moduleRef) {
|
||||
await moduleRef.close();
|
||||
}
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
it("upserts and lists memory entries", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const agentId = `agent-${uuid()}`;
|
||||
|
||||
const entry = await service.upsert(workspaceAId, agentId, "session-context", {
|
||||
value: { intent: "create-tests", depth: "integration" },
|
||||
});
|
||||
|
||||
expect(entry.workspaceId).toBe(workspaceAId);
|
||||
expect(entry.agentId).toBe(agentId);
|
||||
expect(entry.key).toBe("session-context");
|
||||
|
||||
const listed = await service.findAll(workspaceAId, agentId);
|
||||
|
||||
expect(listed).toHaveLength(1);
|
||||
expect(listed[0]?.id).toBe(entry.id);
|
||||
expect(listed[0]?.value).toMatchObject({ intent: "create-tests" });
|
||||
});
|
||||
|
||||
it("updates existing key via upsert without creating duplicates", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const agentId = `agent-${uuid()}`;
|
||||
|
||||
const first = await service.upsert(workspaceAId, agentId, "preferences", {
|
||||
value: { model: "fast" },
|
||||
});
|
||||
|
||||
const second = await service.upsert(workspaceAId, agentId, "preferences", {
|
||||
value: { model: "accurate" },
|
||||
});
|
||||
|
||||
expect(second.id).toBe(first.id);
|
||||
expect(second.value).toMatchObject({ model: "accurate" });
|
||||
|
||||
const rowCount = await prisma.agentMemory.count({
|
||||
where: {
|
||||
workspaceId: workspaceAId,
|
||||
agentId,
|
||||
key: "preferences",
|
||||
},
|
||||
});
|
||||
|
||||
expect(rowCount).toBe(1);
|
||||
});
|
||||
|
||||
it("lists keys in sorted order and isolates by workspace", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const agentId = `agent-${uuid()}`;
|
||||
|
||||
await service.upsert(workspaceAId, agentId, "beta", { value: { v: 2 } });
|
||||
await service.upsert(workspaceAId, agentId, "alpha", { value: { v: 1 } });
|
||||
await service.upsert(workspaceBId, agentId, "alpha", { value: { v: 99 } });
|
||||
|
||||
const workspaceAEntries = await service.findAll(workspaceAId, agentId);
|
||||
const workspaceBEntries = await service.findAll(workspaceBId, agentId);
|
||||
|
||||
expect(workspaceAEntries.map((row) => row.key)).toEqual(["alpha", "beta"]);
|
||||
expect(workspaceBEntries).toHaveLength(1);
|
||||
expect(workspaceBEntries[0]?.value).toMatchObject({ v: 99 });
|
||||
});
|
||||
|
||||
it("throws NotFoundException when requesting unknown key", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
await expect(service.findOne(workspaceAId, `agent-${uuid()}`, "missing")).rejects.toThrow(
|
||||
NotFoundException
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,239 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, afterAll, vi } from "vitest";
|
||||
import { randomUUID as uuid } from "crypto";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { ConflictException } from "@nestjs/common";
|
||||
import { PrismaClient, Prisma } from "@prisma/client";
|
||||
import { EMBEDDING_DIMENSION } from "@mosaic/shared";
|
||||
import { ConversationArchiveService } from "./conversation-archive.service";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import { EmbeddingService } from "../knowledge/services/embedding.service";
|
||||
|
||||
const shouldRunDbIntegrationTests =
|
||||
process.env.RUN_DB_TESTS === "true" && Boolean(process.env.DATABASE_URL);
|
||||
const describeFn = shouldRunDbIntegrationTests ? describe : describe.skip;
|
||||
|
||||
function vector(value: number): number[] {
|
||||
return Array.from({ length: EMBEDDING_DIMENSION }, () => value);
|
||||
}
|
||||
|
||||
function toVectorLiteral(input: number[]): string {
|
||||
return `[${input.join(",")}]`;
|
||||
}
|
||||
|
||||
describeFn("ConversationArchiveService Integration", () => {
|
||||
let moduleRef: TestingModule;
|
||||
let prisma: PrismaClient;
|
||||
let service: ConversationArchiveService;
|
||||
let workspaceId: string;
|
||||
let ownerId: string;
|
||||
let setupComplete = false;
|
||||
|
||||
const embeddingServiceMock = {
|
||||
isConfigured: vi.fn(),
|
||||
generateEmbedding: vi.fn(),
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
prisma = new PrismaClient();
|
||||
await prisma.$connect();
|
||||
|
||||
const workspace = await prisma.workspace.create({
|
||||
data: {
|
||||
name: `Conversation Archive Integration ${Date.now()}`,
|
||||
owner: {
|
||||
create: {
|
||||
email: `conversation-archive-integration-${Date.now()}@example.com`,
|
||||
name: "Conversation Archive Integration Owner",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
workspaceId = workspace.id;
|
||||
ownerId = workspace.ownerId;
|
||||
|
||||
moduleRef = await Test.createTestingModule({
|
||||
providers: [
|
||||
ConversationArchiveService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prisma,
|
||||
},
|
||||
{
|
||||
provide: EmbeddingService,
|
||||
useValue: embeddingServiceMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = moduleRef.get<ConversationArchiveService>(ConversationArchiveService);
|
||||
setupComplete = true;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
embeddingServiceMock.isConfigured.mockReturnValue(false);
|
||||
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
await prisma.conversationArchive.deleteMany({ where: { workspaceId } });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (!prisma) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (workspaceId) {
|
||||
await prisma.conversationArchive.deleteMany({ where: { workspaceId } });
|
||||
await prisma.workspace.deleteMany({ where: { id: workspaceId } });
|
||||
}
|
||||
if (ownerId) {
|
||||
await prisma.user.deleteMany({ where: { id: ownerId } });
|
||||
}
|
||||
|
||||
if (moduleRef) {
|
||||
await moduleRef.close();
|
||||
}
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
it("ingests a conversation log", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = `session-${uuid()}`;
|
||||
|
||||
const result = await service.ingest(workspaceId, {
|
||||
sessionId,
|
||||
agentId: "agent-conversation-ingest",
|
||||
messages: [
|
||||
{ role: "user", content: "Can you summarize deployment issues?" },
|
||||
{ role: "assistant", content: "Yes, three retries timed out in staging." },
|
||||
],
|
||||
summary: "Deployment retry failures discussed",
|
||||
startedAt: "2026-02-28T21:00:00.000Z",
|
||||
endedAt: "2026-02-28T21:05:00.000Z",
|
||||
metadata: { source: "integration-test" },
|
||||
});
|
||||
|
||||
expect(result.id).toBeDefined();
|
||||
|
||||
const stored = await prisma.conversationArchive.findUnique({
|
||||
where: {
|
||||
id: result.id,
|
||||
},
|
||||
});
|
||||
|
||||
expect(stored).toBeTruthy();
|
||||
expect(stored?.workspaceId).toBe(workspaceId);
|
||||
expect(stored?.sessionId).toBe(sessionId);
|
||||
expect(stored?.messageCount).toBe(2);
|
||||
expect(stored?.summary).toBe("Deployment retry failures discussed");
|
||||
});
|
||||
|
||||
it("rejects duplicate session ingest per workspace", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = `session-${uuid()}`;
|
||||
const dto = {
|
||||
sessionId,
|
||||
agentId: "agent-conversation-duplicate",
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
summary: "simple conversation",
|
||||
startedAt: "2026-02-28T22:00:00.000Z",
|
||||
};
|
||||
|
||||
await service.ingest(workspaceId, dto);
|
||||
|
||||
await expect(service.ingest(workspaceId, dto)).rejects.toThrow(ConflictException);
|
||||
});
|
||||
|
||||
it("rejects semantic search when embeddings are disabled", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
embeddingServiceMock.isConfigured.mockReturnValue(false);
|
||||
|
||||
await expect(
|
||||
service.search(workspaceId, {
|
||||
query: "deployment retries",
|
||||
})
|
||||
).rejects.toThrow(ConflictException);
|
||||
});
|
||||
|
||||
it("searches archived conversations by vector similarity", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const near = vector(0.02);
|
||||
const far = vector(0.85);
|
||||
|
||||
const matching = await prisma.conversationArchive.create({
|
||||
data: {
|
||||
workspaceId,
|
||||
sessionId: `session-search-${uuid()}`,
|
||||
agentId: "agent-conversation-search-a",
|
||||
messages: [
|
||||
{ role: "user", content: "What caused deployment retries?" },
|
||||
{ role: "assistant", content: "A connection pool timeout." },
|
||||
] as unknown as Prisma.InputJsonValue,
|
||||
messageCount: 2,
|
||||
summary: "Deployment retries caused by connection pool timeout",
|
||||
startedAt: new Date("2026-02-28T23:00:00.000Z"),
|
||||
metadata: { channel: "cli" } as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
|
||||
const nonMatching = await prisma.conversationArchive.create({
|
||||
data: {
|
||||
workspaceId,
|
||||
sessionId: `session-search-${uuid()}`,
|
||||
agentId: "agent-conversation-search-b",
|
||||
messages: [
|
||||
{ role: "user", content: "How is billing configured?" },
|
||||
] as unknown as Prisma.InputJsonValue,
|
||||
messageCount: 1,
|
||||
summary: "Billing and quotas conversation",
|
||||
startedAt: new Date("2026-02-28T23:10:00.000Z"),
|
||||
metadata: { channel: "cli" } as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.$executeRaw`
|
||||
UPDATE conversation_archives
|
||||
SET embedding = ${toVectorLiteral(near)}::vector(${EMBEDDING_DIMENSION})
|
||||
WHERE id = ${matching.id}::uuid
|
||||
`;
|
||||
await prisma.$executeRaw`
|
||||
UPDATE conversation_archives
|
||||
SET embedding = ${toVectorLiteral(far)}::vector(${EMBEDDING_DIMENSION})
|
||||
WHERE id = ${nonMatching.id}::uuid
|
||||
`;
|
||||
|
||||
embeddingServiceMock.isConfigured.mockReturnValue(true);
|
||||
embeddingServiceMock.generateEmbedding.mockResolvedValue(near);
|
||||
|
||||
const result = await service.search(workspaceId, {
|
||||
query: "deployment retries timeout",
|
||||
agentId: "agent-conversation-search-a",
|
||||
similarityThreshold: 0,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
const rows = result.data as Array<{ id: string; agent_id: string; similarity: number }>;
|
||||
|
||||
expect(result.pagination.total).toBe(1);
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0]?.id).toBe(matching.id);
|
||||
expect(rows[0]?.agent_id).toBe("agent-conversation-search-a");
|
||||
expect(rows[0]?.similarity).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
226
apps/api/src/findings/findings.integration.spec.ts
Normal file
226
apps/api/src/findings/findings.integration.spec.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, afterAll, vi } from "vitest";
|
||||
import { randomUUID as uuid } from "crypto";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { BadRequestException, NotFoundException } from "@nestjs/common";
|
||||
import { PrismaClient, Prisma } from "@prisma/client";
|
||||
import { FindingsService } from "./findings.service";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import { EmbeddingService } from "../knowledge/services/embedding.service";
|
||||
|
||||
const shouldRunDbIntegrationTests =
|
||||
process.env.RUN_DB_TESTS === "true" && Boolean(process.env.DATABASE_URL);
|
||||
const describeFn = shouldRunDbIntegrationTests ? describe : describe.skip;
|
||||
|
||||
const EMBEDDING_DIMENSION = 1536;
|
||||
|
||||
function vector(value: number): number[] {
|
||||
return Array.from({ length: EMBEDDING_DIMENSION }, () => value);
|
||||
}
|
||||
|
||||
function toVectorLiteral(input: number[]): string {
|
||||
return `[${input.join(",")}]`;
|
||||
}
|
||||
|
||||
describeFn("FindingsService Integration", () => {
|
||||
let moduleRef: TestingModule;
|
||||
let prisma: PrismaClient;
|
||||
let service: FindingsService;
|
||||
let workspaceId: string;
|
||||
let ownerId: string;
|
||||
let setupComplete = false;
|
||||
|
||||
const embeddingServiceMock = {
|
||||
isConfigured: vi.fn(),
|
||||
generateEmbedding: vi.fn(),
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
prisma = new PrismaClient();
|
||||
await prisma.$connect();
|
||||
|
||||
const workspace = await prisma.workspace.create({
|
||||
data: {
|
||||
name: `Findings Integration ${Date.now()}`,
|
||||
owner: {
|
||||
create: {
|
||||
email: `findings-integration-${Date.now()}@example.com`,
|
||||
name: "Findings Integration Owner",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
workspaceId = workspace.id;
|
||||
ownerId = workspace.ownerId;
|
||||
|
||||
moduleRef = await Test.createTestingModule({
|
||||
providers: [
|
||||
FindingsService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prisma,
|
||||
},
|
||||
{
|
||||
provide: EmbeddingService,
|
||||
useValue: embeddingServiceMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = moduleRef.get<FindingsService>(FindingsService);
|
||||
setupComplete = true;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
embeddingServiceMock.isConfigured.mockReturnValue(false);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (!prisma) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (workspaceId) {
|
||||
await prisma.finding.deleteMany({ where: { workspaceId } });
|
||||
await prisma.workspace.deleteMany({ where: { id: workspaceId } });
|
||||
}
|
||||
if (ownerId) {
|
||||
await prisma.user.deleteMany({ where: { id: ownerId } });
|
||||
}
|
||||
|
||||
if (moduleRef) {
|
||||
await moduleRef.close();
|
||||
}
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
it("creates, lists, fetches, and deletes findings", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const created = await service.create(workspaceId, {
|
||||
agentId: "agent-findings-crud",
|
||||
type: "security",
|
||||
title: "Unescaped SQL fragment",
|
||||
data: { severity: "high" },
|
||||
summary: "Potential injection risk in dynamic query path.",
|
||||
});
|
||||
|
||||
expect(created.id).toBeDefined();
|
||||
expect(created.workspaceId).toBe(workspaceId);
|
||||
expect(created.taskId).toBeNull();
|
||||
|
||||
const listed = await service.findAll(workspaceId, {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
agentId: "agent-findings-crud",
|
||||
});
|
||||
|
||||
expect(listed.meta.total).toBeGreaterThanOrEqual(1);
|
||||
expect(listed.data.some((row) => row.id === created.id)).toBe(true);
|
||||
|
||||
const found = await service.findOne(created.id, workspaceId);
|
||||
expect(found.id).toBe(created.id);
|
||||
expect(found.title).toBe("Unescaped SQL fragment");
|
||||
|
||||
await expect(service.findOne(created.id, uuid())).rejects.toThrow(NotFoundException);
|
||||
|
||||
await expect(service.remove(created.id, workspaceId)).resolves.toEqual({
|
||||
message: "Finding deleted successfully",
|
||||
});
|
||||
|
||||
await expect(service.findOne(created.id, workspaceId)).rejects.toThrow(NotFoundException);
|
||||
});
|
||||
|
||||
it("rejects create when taskId does not exist in workspace", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
await expect(
|
||||
service.create(workspaceId, {
|
||||
taskId: uuid(),
|
||||
agentId: "agent-findings-missing-task",
|
||||
type: "bug",
|
||||
title: "Invalid task id",
|
||||
data: { source: "integration-test" },
|
||||
summary: "Should fail when task relation is not found.",
|
||||
})
|
||||
).rejects.toThrow(NotFoundException);
|
||||
});
|
||||
|
||||
it("rejects vector search when embeddings are disabled", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
embeddingServiceMock.isConfigured.mockReturnValue(false);
|
||||
|
||||
await expect(
|
||||
service.search(workspaceId, {
|
||||
query: "security issue",
|
||||
})
|
||||
).rejects.toThrow(BadRequestException);
|
||||
});
|
||||
|
||||
it("searches findings by vector similarity with filters", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const near = vector(0.01);
|
||||
const far = vector(0.9);
|
||||
|
||||
const matchedFinding = await prisma.finding.create({
|
||||
data: {
|
||||
workspaceId,
|
||||
agentId: "agent-findings-search-a",
|
||||
type: "incident",
|
||||
title: "Authentication bypass",
|
||||
data: { score: 0.9 } as Prisma.InputJsonValue,
|
||||
summary: "Bypass risk found in login checks.",
|
||||
},
|
||||
});
|
||||
|
||||
const otherFinding = await prisma.finding.create({
|
||||
data: {
|
||||
workspaceId,
|
||||
agentId: "agent-findings-search-b",
|
||||
type: "incident",
|
||||
title: "Retry timeout",
|
||||
data: { score: 0.2 } as Prisma.InputJsonValue,
|
||||
summary: "Timeout issue in downstream retries.",
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.$executeRaw`
|
||||
UPDATE findings
|
||||
SET embedding = ${toVectorLiteral(near)}::vector(1536)
|
||||
WHERE id = ${matchedFinding.id}::uuid
|
||||
`;
|
||||
await prisma.$executeRaw`
|
||||
UPDATE findings
|
||||
SET embedding = ${toVectorLiteral(far)}::vector(1536)
|
||||
WHERE id = ${otherFinding.id}::uuid
|
||||
`;
|
||||
|
||||
embeddingServiceMock.isConfigured.mockReturnValue(true);
|
||||
embeddingServiceMock.generateEmbedding.mockResolvedValue(near);
|
||||
|
||||
const result = await service.search(workspaceId, {
|
||||
query: "authentication bypass risk",
|
||||
agentId: "agent-findings-search-a",
|
||||
limit: 10,
|
||||
similarityThreshold: 0,
|
||||
});
|
||||
|
||||
expect(result.query).toBe("authentication bypass risk");
|
||||
expect(result.meta.total).toBe(1);
|
||||
expect(result.data).toHaveLength(1);
|
||||
expect(result.data[0]?.id).toBe(matchedFinding.id);
|
||||
expect(result.data[0]?.agentId).toBe("agent-findings-search-a");
|
||||
expect(result.data.find((row) => row.id === otherFinding.id)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
162
apps/api/src/tasks/tasks.assigned-agent.integration.spec.ts
Normal file
162
apps/api/src/tasks/tasks.assigned-agent.integration.spec.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, afterAll, vi } from "vitest";
|
||||
import { randomUUID as uuid } from "crypto";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { TasksService } from "./tasks.service";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import { ActivityService } from "../activity/activity.service";
|
||||
|
||||
const shouldRunDbIntegrationTests =
|
||||
process.env.RUN_DB_TESTS === "true" && Boolean(process.env.DATABASE_URL);
|
||||
const describeFn = shouldRunDbIntegrationTests ? describe : describe.skip;
|
||||
|
||||
describeFn("TasksService assignedAgent Integration", () => {
|
||||
let moduleRef: TestingModule;
|
||||
let prisma: PrismaClient;
|
||||
let service: TasksService;
|
||||
let workspaceId: string;
|
||||
let ownerId: string;
|
||||
let setupComplete = false;
|
||||
|
||||
const activityServiceMock = {
|
||||
logTaskCreated: vi.fn().mockResolvedValue(undefined),
|
||||
logTaskUpdated: vi.fn().mockResolvedValue(undefined),
|
||||
logTaskDeleted: vi.fn().mockResolvedValue(undefined),
|
||||
logTaskCompleted: vi.fn().mockResolvedValue(undefined),
|
||||
logTaskAssigned: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
prisma = new PrismaClient();
|
||||
await prisma.$connect();
|
||||
|
||||
const workspace = await prisma.workspace.create({
|
||||
data: {
|
||||
name: `Tasks Assigned Agent Integration ${Date.now()}`,
|
||||
owner: {
|
||||
create: {
|
||||
email: `tasks-assigned-agent-integration-${Date.now()}@example.com`,
|
||||
name: "Tasks Assigned Agent Integration Owner",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
workspaceId = workspace.id;
|
||||
ownerId = workspace.ownerId;
|
||||
|
||||
moduleRef = await Test.createTestingModule({
|
||||
providers: [
|
||||
TasksService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: prisma,
|
||||
},
|
||||
{
|
||||
provide: ActivityService,
|
||||
useValue: activityServiceMock,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = moduleRef.get<TasksService>(TasksService);
|
||||
setupComplete = true;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
await prisma.task.deleteMany({ where: { workspaceId } });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (!prisma) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (workspaceId) {
|
||||
await prisma.task.deleteMany({ where: { workspaceId } });
|
||||
await prisma.workspace.deleteMany({ where: { id: workspaceId } });
|
||||
}
|
||||
if (ownerId) {
|
||||
await prisma.user.deleteMany({ where: { id: ownerId } });
|
||||
}
|
||||
|
||||
if (moduleRef) {
|
||||
await moduleRef.close();
|
||||
}
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
it("persists assignedAgent on create", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const task = await service.create(workspaceId, ownerId, {
|
||||
title: `Assigned agent create ${uuid()}`,
|
||||
assignedAgent: "fleet-worker-1",
|
||||
});
|
||||
|
||||
expect(task.assignedAgent).toBe("fleet-worker-1");
|
||||
|
||||
const stored = await prisma.task.findUnique({
|
||||
where: {
|
||||
id: task.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
assignedAgent: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(stored).toMatchObject({
|
||||
id: task.id,
|
||||
assignedAgent: "fleet-worker-1",
|
||||
});
|
||||
|
||||
const listed = await service.findAll({ workspaceId, page: 1, limit: 10 }, ownerId);
|
||||
const listedTask = listed.data.find((row) => row.id === task.id);
|
||||
|
||||
expect(listedTask?.assignedAgent).toBe("fleet-worker-1");
|
||||
});
|
||||
|
||||
it("updates and clears assignedAgent", async () => {
|
||||
if (!setupComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
const created = await service.create(workspaceId, ownerId, {
|
||||
title: `Assigned agent update ${uuid()}`,
|
||||
});
|
||||
|
||||
expect(created.assignedAgent).toBeNull();
|
||||
|
||||
const updated = await service.update(created.id, workspaceId, ownerId, {
|
||||
assignedAgent: "fleet-worker-2",
|
||||
});
|
||||
|
||||
expect(updated.assignedAgent).toBe("fleet-worker-2");
|
||||
|
||||
const cleared = await service.update(created.id, workspaceId, ownerId, {
|
||||
assignedAgent: null,
|
||||
});
|
||||
|
||||
expect(cleared.assignedAgent).toBeNull();
|
||||
|
||||
const stored = await prisma.task.findUnique({
|
||||
where: {
|
||||
id: created.id,
|
||||
},
|
||||
select: {
|
||||
assignedAgent: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(stored?.assignedAgent).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user