All checks were successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
227 lines
6.4 KiB
TypeScript
227 lines
6.4 KiB
TypeScript
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();
|
|
});
|
|
});
|