@apart-tech/intelligence-core 1.11.5 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/ability.d.ts +4 -4
- package/dist/auth/ability.d.ts.map +1 -1
- package/dist/auth/ability.js +17 -11
- package/dist/auth/ability.js.map +1 -1
- package/dist/auth/ability.test.js +25 -12
- package/dist/auth/ability.test.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +5 -1
- package/dist/config/index.js.map +1 -1
- package/dist/db/tenant.d.ts.map +1 -1
- package/dist/db/tenant.js +8 -0
- package/dist/db/tenant.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/services/__tests__/chunk-service.test.d.ts +2 -0
- package/dist/services/__tests__/chunk-service.test.d.ts.map +1 -0
- package/dist/services/__tests__/chunk-service.test.js +111 -0
- package/dist/services/__tests__/chunk-service.test.js.map +1 -0
- package/dist/services/__tests__/chunker.test.d.ts +2 -0
- package/dist/services/__tests__/chunker.test.d.ts.map +1 -0
- package/dist/services/__tests__/chunker.test.js +113 -0
- package/dist/services/__tests__/chunker.test.js.map +1 -0
- package/dist/services/__tests__/node-service.test.d.ts +2 -0
- package/dist/services/__tests__/node-service.test.d.ts.map +1 -0
- package/dist/services/__tests__/node-service.test.js +207 -0
- package/dist/services/__tests__/node-service.test.js.map +1 -0
- package/dist/services/__tests__/pii-detector-service.test.js +51 -0
- package/dist/services/__tests__/pii-detector-service.test.js.map +1 -1
- package/dist/services/__tests__/pii-encryption-service.test.js +37 -0
- package/dist/services/__tests__/pii-encryption-service.test.js.map +1 -1
- package/dist/services/__tests__/search-service.test.d.ts +2 -0
- package/dist/services/__tests__/search-service.test.d.ts.map +1 -0
- package/dist/services/__tests__/search-service.test.js +163 -0
- package/dist/services/__tests__/search-service.test.js.map +1 -0
- package/dist/services/backfill-chunks.d.ts +30 -0
- package/dist/services/backfill-chunks.d.ts.map +1 -0
- package/dist/services/backfill-chunks.js +55 -0
- package/dist/services/backfill-chunks.js.map +1 -0
- package/dist/services/chunk-service.d.ts +45 -0
- package/dist/services/chunk-service.d.ts.map +1 -0
- package/dist/services/chunk-service.js +111 -0
- package/dist/services/chunk-service.js.map +1 -0
- package/dist/services/chunker.d.ts +32 -0
- package/dist/services/chunker.d.ts.map +1 -0
- package/dist/services/chunker.js +289 -0
- package/dist/services/chunker.js.map +1 -0
- package/dist/services/context-service.d.ts +3 -1
- package/dist/services/context-service.d.ts.map +1 -1
- package/dist/services/context-service.js +17 -1
- package/dist/services/context-service.js.map +1 -1
- package/dist/services/node-service.d.ts +12 -1
- package/dist/services/node-service.d.ts.map +1 -1
- package/dist/services/node-service.js +54 -11
- package/dist/services/node-service.js.map +1 -1
- package/dist/services/pii-detector-service.d.ts +1 -0
- package/dist/services/pii-detector-service.d.ts.map +1 -1
- package/dist/services/pii-detector-service.js +95 -2
- package/dist/services/pii-detector-service.js.map +1 -1
- package/dist/services/pii-encryption-service.d.ts +10 -0
- package/dist/services/pii-encryption-service.d.ts.map +1 -1
- package/dist/services/pii-encryption-service.js +32 -0
- package/dist/services/pii-encryption-service.js.map +1 -1
- package/dist/services/search-service.d.ts +30 -1
- package/dist/services/search-service.d.ts.map +1 -1
- package/dist/services/search-service.js +262 -45
- package/dist/services/search-service.js.map +1 -1
- package/dist/services/tag-service.d.ts +78 -0
- package/dist/services/tag-service.d.ts.map +1 -0
- package/dist/services/tag-service.js +639 -0
- package/dist/services/tag-service.js.map +1 -0
- package/dist/services/tag-service.test.d.ts +2 -0
- package/dist/services/tag-service.test.d.ts.map +1 -0
- package/dist/services/tag-service.test.js +448 -0
- package/dist/services/tag-service.test.js.map +1 -0
- package/dist/services/user-service.d.ts +1 -0
- package/dist/services/user-service.d.ts.map +1 -1
- package/dist/services/user-service.test.js +72 -1
- package/dist/services/user-service.test.js.map +1 -1
- package/dist/services/workspace-service.d.ts +2 -0
- package/dist/services/workspace-service.d.ts.map +1 -1
- package/dist/services/workspace-service.js +7 -1
- package/dist/services/workspace-service.js.map +1 -1
- package/dist/types/index.d.ts +67 -2
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/prisma/schema.prisma +180 -3
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { ChunkService } from "../chunk-service.js";
|
|
3
|
+
import { PiiDetectorService } from "../pii-detector-service.js";
|
|
4
|
+
import { PiiEncryptionService } from "../pii-encryption-service.js";
|
|
5
|
+
function createMockDb() {
|
|
6
|
+
const rawCalls = [];
|
|
7
|
+
return {
|
|
8
|
+
$executeRaw: vi.fn((...args) => {
|
|
9
|
+
rawCalls.push(args);
|
|
10
|
+
return Promise.resolve(1);
|
|
11
|
+
}),
|
|
12
|
+
$queryRaw: vi.fn().mockResolvedValue([]),
|
|
13
|
+
$queryRawUnsafe: vi.fn().mockResolvedValue([]),
|
|
14
|
+
_rawCalls: rawCalls,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function createMockEmbeddings() {
|
|
18
|
+
return {
|
|
19
|
+
lastBatchInput: null,
|
|
20
|
+
embed: vi.fn(async () => [0.1, 0.2, 0.3]),
|
|
21
|
+
embedBatch: vi.fn(async function (texts) {
|
|
22
|
+
this.lastBatchInput = texts;
|
|
23
|
+
return texts.map(() => [0.1, 0.2, 0.3]);
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
describe("ChunkService", () => {
|
|
28
|
+
const detector = new PiiDetectorService();
|
|
29
|
+
const piiEncryption = new PiiEncryptionService(detector);
|
|
30
|
+
const tenantCtx = { organizationId: "org-1" };
|
|
31
|
+
let db;
|
|
32
|
+
let embeddings;
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
db = createMockDb();
|
|
35
|
+
embeddings = createMockEmbeddings();
|
|
36
|
+
});
|
|
37
|
+
describe("chunkAndEmbed", () => {
|
|
38
|
+
it("calls embedBatch with heading-prefixed texts", async () => {
|
|
39
|
+
const service = new ChunkService(db, embeddings, tenantCtx);
|
|
40
|
+
const content = "# Overview\n\nThis is a paragraph about the overview topic.";
|
|
41
|
+
await service.chunkAndEmbed("node-1", "Test Title", content);
|
|
42
|
+
expect(embeddings.embedBatch).toHaveBeenCalled();
|
|
43
|
+
const batchTexts = embeddings.embedBatch.mock.calls[0][0];
|
|
44
|
+
// At least one text should have heading prefix
|
|
45
|
+
expect(batchTexts.some((t) => t.includes("Overview:"))).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
it("deletes existing chunks before inserting", async () => {
|
|
48
|
+
const service = new ChunkService(db, embeddings, tenantCtx);
|
|
49
|
+
await service.chunkAndEmbed("node-1", "Title", "Some content here.");
|
|
50
|
+
// $executeRaw should be called for DELETE
|
|
51
|
+
expect(db.$executeRaw).toHaveBeenCalled();
|
|
52
|
+
const deleteCall = db.$executeRaw.mock.calls.find((call) => {
|
|
53
|
+
const tpl = call[0];
|
|
54
|
+
return tpl.raw.join("").includes("DELETE FROM node_chunks");
|
|
55
|
+
});
|
|
56
|
+
expect(deleteCall).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
it("strips PII before chunking in encrypt mode", async () => {
|
|
59
|
+
const service = new ChunkService(db, embeddings, tenantCtx, piiEncryption, { mode: "encrypt", orgKey: Buffer.alloc(32) });
|
|
60
|
+
await service.chunkAndEmbed("node-1", "Contact", "Email john@example.com for info");
|
|
61
|
+
expect(embeddings.embedBatch).toHaveBeenCalled();
|
|
62
|
+
const batchTexts = embeddings.embedBatch.mock.calls[0][0];
|
|
63
|
+
// PII should be stripped
|
|
64
|
+
for (const text of batchTexts) {
|
|
65
|
+
expect(text).not.toContain("john@example.com");
|
|
66
|
+
expect(text).toContain("[EMAIL]");
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
it("sets embedding_pii_mode on chunks", async () => {
|
|
70
|
+
const service = new ChunkService(db, embeddings, tenantCtx, piiEncryption, { mode: "encrypt", orgKey: Buffer.alloc(32) });
|
|
71
|
+
await service.chunkAndEmbed("node-1", "Title", "Some content for testing.");
|
|
72
|
+
// $queryRawUnsafe should be called with INSERT containing pii mode
|
|
73
|
+
expect(db.$queryRawUnsafe).toHaveBeenCalled();
|
|
74
|
+
const insertCall = db.$queryRawUnsafe.mock.calls[0];
|
|
75
|
+
const sql = insertCall[0];
|
|
76
|
+
expect(sql).toContain("INSERT INTO node_chunks");
|
|
77
|
+
// One of the params should be the pii mode
|
|
78
|
+
expect(insertCall).toContain("encrypt");
|
|
79
|
+
});
|
|
80
|
+
it("does nothing for empty content", async () => {
|
|
81
|
+
const service = new ChunkService(db, embeddings, tenantCtx);
|
|
82
|
+
await service.chunkAndEmbed("node-1", "", "");
|
|
83
|
+
expect(embeddings.embedBatch).not.toHaveBeenCalled();
|
|
84
|
+
expect(db.$queryRawUnsafe).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe("deleteByNodeId", () => {
|
|
88
|
+
it("executes DELETE via $executeRaw", async () => {
|
|
89
|
+
const service = new ChunkService(db, embeddings, tenantCtx);
|
|
90
|
+
await service.deleteByNodeId("node-1");
|
|
91
|
+
expect(db.$executeRaw).toHaveBeenCalled();
|
|
92
|
+
const call = db.$executeRaw.mock.calls[0];
|
|
93
|
+
const tpl = call[0];
|
|
94
|
+
const sql = tpl.raw.join("");
|
|
95
|
+
expect(sql).toContain("DELETE FROM node_chunks");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe("getByNodeId", () => {
|
|
99
|
+
it("queries with tenant filter and orders by chunk_index", async () => {
|
|
100
|
+
const service = new ChunkService(db, embeddings, tenantCtx);
|
|
101
|
+
await service.getByNodeId("node-1");
|
|
102
|
+
expect(db.$queryRaw).toHaveBeenCalled();
|
|
103
|
+
const call = db.$queryRaw.mock.calls[0];
|
|
104
|
+
const tpl = call[0];
|
|
105
|
+
const sql = tpl.raw.join("");
|
|
106
|
+
expect(sql).toContain("node_chunks");
|
|
107
|
+
expect(sql).toContain("ORDER BY chunk_index ASC");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
//# sourceMappingURL=chunk-service.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/chunk-service.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker.test.d.ts","sourceRoot":"","sources":["../../../src/services/__tests__/chunker.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { chunkText, estimateTokens, MAX_CHUNK_TOKENS, } from "../chunker.js";
|
|
3
|
+
describe("estimateTokens", () => {
|
|
4
|
+
it("estimates ~4 chars per token", () => {
|
|
5
|
+
expect(estimateTokens("abcd")).toBe(1);
|
|
6
|
+
expect(estimateTokens("abcdefgh")).toBe(2);
|
|
7
|
+
expect(estimateTokens("a")).toBe(1); // ceil(1/4) = 1
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
describe("chunkText", () => {
|
|
11
|
+
it("returns empty array for empty content", () => {
|
|
12
|
+
expect(chunkText("")).toEqual([]);
|
|
13
|
+
expect(chunkText(" ")).toEqual([]);
|
|
14
|
+
});
|
|
15
|
+
it("returns single chunk for short text", () => {
|
|
16
|
+
const text = "This is a short paragraph.";
|
|
17
|
+
const chunks = chunkText(text);
|
|
18
|
+
expect(chunks).toHaveLength(1);
|
|
19
|
+
expect(chunks[0].content).toBe(text);
|
|
20
|
+
expect(chunks[0].chunkIndex).toBe(0);
|
|
21
|
+
expect(chunks[0].overlapPrev).toBe(0);
|
|
22
|
+
expect(chunks[0].tokenCount).toBeGreaterThan(0);
|
|
23
|
+
});
|
|
24
|
+
it("splits long text at paragraph boundaries", () => {
|
|
25
|
+
// Create text with multiple paragraphs that exceed TARGET_CHUNK_TOKENS
|
|
26
|
+
const para = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(20);
|
|
27
|
+
const text = `${para}\n\n${para}\n\n${para}`;
|
|
28
|
+
const chunks = chunkText(text);
|
|
29
|
+
expect(chunks.length).toBeGreaterThan(1);
|
|
30
|
+
// Each chunk should be within bounds
|
|
31
|
+
for (const chunk of chunks) {
|
|
32
|
+
expect(chunk.tokenCount).toBeLessThanOrEqual(MAX_CHUNK_TOKENS);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
it("carries heading context into chunks", () => {
|
|
36
|
+
const text = `# Introduction
|
|
37
|
+
|
|
38
|
+
First paragraph under intro.
|
|
39
|
+
|
|
40
|
+
# Details
|
|
41
|
+
|
|
42
|
+
Detail paragraph here with some content.`;
|
|
43
|
+
const chunks = chunkText(text);
|
|
44
|
+
expect(chunks.length).toBeGreaterThanOrEqual(2);
|
|
45
|
+
const introChunk = chunks.find((c) => c.content.includes("First paragraph"));
|
|
46
|
+
expect(introChunk?.headingContext).toBe("Introduction");
|
|
47
|
+
const detailChunk = chunks.find((c) => c.content.includes("Detail paragraph"));
|
|
48
|
+
expect(detailChunk?.headingContext).toBe("Details");
|
|
49
|
+
});
|
|
50
|
+
it("does not split code blocks", () => {
|
|
51
|
+
const codeBlock = "```javascript\n" + "const x = 1;\n".repeat(50) + "```";
|
|
52
|
+
const text = `Some intro text.\n\n${codeBlock}\n\nSome outro text.`;
|
|
53
|
+
const chunks = chunkText(text);
|
|
54
|
+
// Find the chunk containing the code block
|
|
55
|
+
const codeChunk = chunks.find((c) => c.content.includes("```javascript"));
|
|
56
|
+
expect(codeChunk).toBeDefined();
|
|
57
|
+
expect(codeChunk.content).toContain("const x = 1;");
|
|
58
|
+
expect(codeChunk.content).toContain("```");
|
|
59
|
+
});
|
|
60
|
+
it("has overlap = 0 at structural boundaries", () => {
|
|
61
|
+
// Multiple short paragraphs that fit in separate chunks due to accumulation
|
|
62
|
+
const para = "Short paragraph. ".repeat(40); // ~160 tokens each
|
|
63
|
+
const text = `${para}\n\n${para}\n\n${para}`;
|
|
64
|
+
const chunks = chunkText(text);
|
|
65
|
+
// Structural boundaries (paragraph breaks) should have overlap = 0
|
|
66
|
+
for (const chunk of chunks) {
|
|
67
|
+
if (chunk.chunkIndex === 0 || chunk.content.startsWith("Short paragraph")) {
|
|
68
|
+
expect(chunk.overlapPrev).toBe(0);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
it("produces multiple chunks for forced mid-paragraph splits of long text", () => {
|
|
73
|
+
// Single huge paragraph that forces mid-paragraph splitting
|
|
74
|
+
const longPara = "This is a sentence that keeps going. ".repeat(100);
|
|
75
|
+
const chunks = chunkText(longPara);
|
|
76
|
+
expect(chunks.length).toBeGreaterThan(1);
|
|
77
|
+
// Each chunk should respect the max token limit
|
|
78
|
+
for (const chunk of chunks) {
|
|
79
|
+
expect(chunk.tokenCount).toBeLessThanOrEqual(MAX_CHUNK_TOKENS);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
it("produces correct offsets", () => {
|
|
83
|
+
const text = "First paragraph.\n\nSecond paragraph.\n\nThird paragraph.";
|
|
84
|
+
const chunks = chunkText(text);
|
|
85
|
+
// All chunks should have valid offset ranges
|
|
86
|
+
for (const chunk of chunks) {
|
|
87
|
+
expect(chunk.startOffset).toBeGreaterThanOrEqual(0);
|
|
88
|
+
expect(chunk.endOffset).toBeGreaterThan(chunk.startOffset);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
it("assigns sequential chunk indices", () => {
|
|
92
|
+
const para = "Content paragraph with some text. ".repeat(30);
|
|
93
|
+
const text = `# Section 1\n\n${para}\n\n# Section 2\n\n${para}`;
|
|
94
|
+
const chunks = chunkText(text);
|
|
95
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
96
|
+
expect(chunks[i].chunkIndex).toBe(i);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
it("handles text with no headings", () => {
|
|
100
|
+
const text = "Just plain text without any markdown headings.\n\nAnother paragraph here.";
|
|
101
|
+
const chunks = chunkText(text);
|
|
102
|
+
expect(chunks.length).toBeGreaterThanOrEqual(1);
|
|
103
|
+
expect(chunks[0].headingContext).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
it("handles text with only headings", () => {
|
|
106
|
+
const text = "# Heading 1\n\n## Heading 2";
|
|
107
|
+
const chunks = chunkText(text);
|
|
108
|
+
// Headings are parsed as structure, remaining text is minimal
|
|
109
|
+
// The exact count depends on how headings are parsed, but should be small
|
|
110
|
+
expect(chunks.length).toBeLessThanOrEqual(1);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=chunker.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/chunker.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-service.test.d.ts","sourceRoot":"","sources":["../../../src/services/__tests__/node-service.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { NodeService } from "../node-service.js";
|
|
3
|
+
import { PiiDetectorService } from "../pii-detector-service.js";
|
|
4
|
+
import { PiiEncryptionService } from "../pii-encryption-service.js";
|
|
5
|
+
function createMockDb() {
|
|
6
|
+
const rawCalls = [];
|
|
7
|
+
return {
|
|
8
|
+
node: {
|
|
9
|
+
create: vi.fn().mockResolvedValue({
|
|
10
|
+
id: "00000000-0000-0000-0000-000000000001",
|
|
11
|
+
type: "note",
|
|
12
|
+
title: "test",
|
|
13
|
+
content: "test",
|
|
14
|
+
metadata: {},
|
|
15
|
+
status: "draft",
|
|
16
|
+
createdBy: "test",
|
|
17
|
+
createdAt: new Date(),
|
|
18
|
+
updatedAt: new Date(),
|
|
19
|
+
version: 1,
|
|
20
|
+
hasPii: false,
|
|
21
|
+
domainId: null,
|
|
22
|
+
organizationId: "org-1",
|
|
23
|
+
}),
|
|
24
|
+
findUnique: vi.fn().mockResolvedValue(null),
|
|
25
|
+
update: vi.fn().mockResolvedValue({}),
|
|
26
|
+
},
|
|
27
|
+
$executeRaw: vi.fn((...args) => {
|
|
28
|
+
rawCalls.push(args);
|
|
29
|
+
return Promise.resolve(1);
|
|
30
|
+
}),
|
|
31
|
+
$queryRaw: vi.fn().mockResolvedValue([]),
|
|
32
|
+
_rawCalls: rawCalls,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function createMockEmbeddings() {
|
|
36
|
+
return {
|
|
37
|
+
lastInput: null,
|
|
38
|
+
embed: vi.fn(async function (text) {
|
|
39
|
+
this.lastInput = text;
|
|
40
|
+
return [0.1, 0.2, 0.3];
|
|
41
|
+
}),
|
|
42
|
+
embedBatch: vi.fn(async (texts) => texts.map(() => [0.1, 0.2, 0.3])),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
describe("NodeService", () => {
|
|
46
|
+
const detector = new PiiDetectorService();
|
|
47
|
+
const piiEncryption = new PiiEncryptionService(detector);
|
|
48
|
+
const tenantCtx = { organizationId: "org-1" };
|
|
49
|
+
describe("create — PII stripping for embeddings", () => {
|
|
50
|
+
let db;
|
|
51
|
+
let embeddings;
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
db = createMockDb();
|
|
54
|
+
embeddings = createMockEmbeddings();
|
|
55
|
+
});
|
|
56
|
+
it("strips email before embed() in encrypt mode", async () => {
|
|
57
|
+
const service = new NodeService(db, embeddings, tenantCtx, piiEncryption, {
|
|
58
|
+
mode: "encrypt",
|
|
59
|
+
orgKey: Buffer.alloc(32),
|
|
60
|
+
});
|
|
61
|
+
await service.create({
|
|
62
|
+
type: "note",
|
|
63
|
+
title: "Contact",
|
|
64
|
+
content: "Email john@example.com for info",
|
|
65
|
+
createdBy: "test",
|
|
66
|
+
});
|
|
67
|
+
// embed() should receive stripped text
|
|
68
|
+
expect(embeddings.embed).toHaveBeenCalledWith(expect.stringContaining("[EMAIL]"));
|
|
69
|
+
expect(embeddings.embed).toHaveBeenCalledWith(expect.not.stringContaining("john@example.com"));
|
|
70
|
+
});
|
|
71
|
+
it("does not strip when mode is disabled", async () => {
|
|
72
|
+
const service = new NodeService(db, embeddings, tenantCtx, piiEncryption, {
|
|
73
|
+
mode: "disabled",
|
|
74
|
+
});
|
|
75
|
+
await service.create({
|
|
76
|
+
type: "note",
|
|
77
|
+
title: "Contact",
|
|
78
|
+
content: "Email john@example.com for info",
|
|
79
|
+
createdBy: "test",
|
|
80
|
+
});
|
|
81
|
+
expect(embeddings.embed).toHaveBeenCalledWith(expect.stringContaining("john@example.com"));
|
|
82
|
+
});
|
|
83
|
+
it("strips PII before embed() in detect-warn mode", async () => {
|
|
84
|
+
const service = new NodeService(db, embeddings, tenantCtx, piiEncryption, {
|
|
85
|
+
mode: "detect-warn",
|
|
86
|
+
});
|
|
87
|
+
await service.create({
|
|
88
|
+
type: "note",
|
|
89
|
+
title: "Contact",
|
|
90
|
+
content: "Email john@example.com for info",
|
|
91
|
+
createdBy: "test",
|
|
92
|
+
});
|
|
93
|
+
// In detect-warn, embedding text should still be stripped
|
|
94
|
+
expect(embeddings.embed).toHaveBeenCalledWith(expect.stringContaining("[EMAIL]"));
|
|
95
|
+
});
|
|
96
|
+
it("calls chunkService.chunkAndEmbed when provided", async () => {
|
|
97
|
+
const chunkService = {
|
|
98
|
+
chunkAndEmbed: vi.fn().mockResolvedValue(undefined),
|
|
99
|
+
deleteByNodeId: vi.fn().mockResolvedValue(undefined),
|
|
100
|
+
getByNodeId: vi.fn().mockResolvedValue([]),
|
|
101
|
+
};
|
|
102
|
+
const service = new NodeService(db, embeddings, tenantCtx, undefined, undefined, chunkService);
|
|
103
|
+
await service.create({
|
|
104
|
+
type: "note",
|
|
105
|
+
title: "Test",
|
|
106
|
+
content: "Some content",
|
|
107
|
+
createdBy: "test",
|
|
108
|
+
});
|
|
109
|
+
expect(chunkService.chunkAndEmbed).toHaveBeenCalledWith("00000000-0000-0000-0000-000000000001", "Test", "Some content");
|
|
110
|
+
});
|
|
111
|
+
it("works without chunkService (backward compat)", async () => {
|
|
112
|
+
const service = new NodeService(db, embeddings, tenantCtx);
|
|
113
|
+
const result = await service.create({
|
|
114
|
+
type: "note",
|
|
115
|
+
title: "Test",
|
|
116
|
+
content: "Some content",
|
|
117
|
+
createdBy: "test",
|
|
118
|
+
});
|
|
119
|
+
expect(result).toBeDefined();
|
|
120
|
+
expect(result.id).toBe("00000000-0000-0000-0000-000000000001");
|
|
121
|
+
});
|
|
122
|
+
it("stamps embedding_pii_mode in raw SQL", async () => {
|
|
123
|
+
const service = new NodeService(db, embeddings, tenantCtx, piiEncryption, {
|
|
124
|
+
mode: "encrypt",
|
|
125
|
+
orgKey: Buffer.alloc(32),
|
|
126
|
+
});
|
|
127
|
+
await service.create({
|
|
128
|
+
type: "note",
|
|
129
|
+
title: "test",
|
|
130
|
+
content: "test content",
|
|
131
|
+
createdBy: "test",
|
|
132
|
+
});
|
|
133
|
+
// The $executeRaw should have been called with the pii mode
|
|
134
|
+
expect(db.$executeRaw).toHaveBeenCalled();
|
|
135
|
+
// Check that the raw SQL template includes embedding_pii_mode
|
|
136
|
+
const call = db.$executeRaw.mock.calls[0];
|
|
137
|
+
// Tagged template: first arg is TemplateStringsArray
|
|
138
|
+
const templateStrings = call[0];
|
|
139
|
+
const sqlParts = templateStrings.raw.join("");
|
|
140
|
+
expect(sqlParts).toContain("embedding_pii_mode");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
describe("update — re-chunks when content changes", () => {
|
|
144
|
+
let db;
|
|
145
|
+
let embeddings;
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
db = createMockDb();
|
|
148
|
+
embeddings = createMockEmbeddings();
|
|
149
|
+
// Mock findUnique to return an existing node (needed by update)
|
|
150
|
+
db.node.findUnique = vi.fn().mockResolvedValue({
|
|
151
|
+
id: "00000000-0000-0000-0000-000000000001",
|
|
152
|
+
type: "note",
|
|
153
|
+
title: "Old Title",
|
|
154
|
+
content: "Old Content",
|
|
155
|
+
metadata: {},
|
|
156
|
+
status: "draft",
|
|
157
|
+
createdBy: "test",
|
|
158
|
+
createdAt: new Date(),
|
|
159
|
+
updatedAt: new Date(),
|
|
160
|
+
version: 1,
|
|
161
|
+
hasPii: false,
|
|
162
|
+
domainId: null,
|
|
163
|
+
organizationId: "org-1",
|
|
164
|
+
});
|
|
165
|
+
db.node.update = vi.fn().mockResolvedValue({
|
|
166
|
+
id: "00000000-0000-0000-0000-000000000001",
|
|
167
|
+
type: "note",
|
|
168
|
+
title: "New Title",
|
|
169
|
+
content: "New Content",
|
|
170
|
+
metadata: {},
|
|
171
|
+
status: "draft",
|
|
172
|
+
createdBy: "test",
|
|
173
|
+
createdAt: new Date(),
|
|
174
|
+
updatedAt: new Date(),
|
|
175
|
+
version: 2,
|
|
176
|
+
hasPii: false,
|
|
177
|
+
domainId: null,
|
|
178
|
+
organizationId: "org-1",
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
it("re-chunks when content changes", async () => {
|
|
182
|
+
const chunkService = {
|
|
183
|
+
chunkAndEmbed: vi.fn().mockResolvedValue(undefined),
|
|
184
|
+
deleteByNodeId: vi.fn().mockResolvedValue(undefined),
|
|
185
|
+
getByNodeId: vi.fn().mockResolvedValue([]),
|
|
186
|
+
};
|
|
187
|
+
const service = new NodeService(db, embeddings, tenantCtx, undefined, undefined, chunkService);
|
|
188
|
+
await service.update("00000000-0000-0000-0000-000000000001", {
|
|
189
|
+
content: "New Content",
|
|
190
|
+
});
|
|
191
|
+
expect(chunkService.chunkAndEmbed).toHaveBeenCalledWith("00000000-0000-0000-0000-000000000001", "Old Title", "New Content");
|
|
192
|
+
});
|
|
193
|
+
it("does not re-chunk when only metadata changes", async () => {
|
|
194
|
+
const chunkService = {
|
|
195
|
+
chunkAndEmbed: vi.fn().mockResolvedValue(undefined),
|
|
196
|
+
deleteByNodeId: vi.fn().mockResolvedValue(undefined),
|
|
197
|
+
getByNodeId: vi.fn().mockResolvedValue([]),
|
|
198
|
+
};
|
|
199
|
+
const service = new NodeService(db, embeddings, tenantCtx, undefined, undefined, chunkService);
|
|
200
|
+
await service.update("00000000-0000-0000-0000-000000000001", {
|
|
201
|
+
metadata: { foo: "bar" },
|
|
202
|
+
});
|
|
203
|
+
expect(chunkService.chunkAndEmbed).not.toHaveBeenCalled();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
//# sourceMappingURL=node-service.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/node-service.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,6 +50,57 @@ describe("PiiDetectorService", () => {
|
|
|
50
50
|
expect(text.slice(match.start, match.end)).toBe("john@example.com");
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
|
+
describe("Norwegian patterns", () => {
|
|
54
|
+
// Synthetic valid fødselsnummer: 01010150074
|
|
55
|
+
// DD=01 MM=01 YY=01 IND=500 K1=2 K2=2 — passes modulo-11
|
|
56
|
+
it("detects valid fødselsnummer", () => {
|
|
57
|
+
const result = detector.detect("FNR: 01010150074");
|
|
58
|
+
expect(result.hasPii).toBe(true);
|
|
59
|
+
expect(result.matches.some((m) => m.type === "fodselsnummer")).toBe(true);
|
|
60
|
+
expect(result.matches.find((m) => m.type === "fodselsnummer").value).toBe("01010150074");
|
|
61
|
+
});
|
|
62
|
+
// D-nummer: first digit +40, so DD=41 → day 1
|
|
63
|
+
it("detects valid D-nummer", () => {
|
|
64
|
+
const result = detector.detect("D-nr: 41019004927");
|
|
65
|
+
expect(result.hasPii).toBe(true);
|
|
66
|
+
expect(result.matches.some((m) => m.type === "fodselsnummer")).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
it("rejects invalid 11-digit numbers (bad checksum)", () => {
|
|
69
|
+
const result = detector.detect("Invalid: 12345678901");
|
|
70
|
+
// Should not match as fodselsnummer
|
|
71
|
+
const fnrMatches = result.matches.filter((m) => m.type === "fodselsnummer");
|
|
72
|
+
expect(fnrMatches).toHaveLength(0);
|
|
73
|
+
});
|
|
74
|
+
it("detects valid organisasjonsnummer", () => {
|
|
75
|
+
// 912345678: starts with 9, checksum: 3*9+2*1+7*2+6*3+5*4+4*5+3*6+2*7 = 27+2+14+18+20+20+18+14 = 133
|
|
76
|
+
// 133 % 11 = 1, check = 11-1 = 10 → invalid
|
|
77
|
+
// Use a synthetic valid one: 987654325
|
|
78
|
+
// weights [3,2,7,6,5,4,3,2]: 3*9+2*8+7*7+6*6+5*5+4*4+3*3+2*2 = 27+16+49+36+25+16+9+4 = 182
|
|
79
|
+
// 182 % 11 = 6, check = 11-6 = 5 → digit 9 should be 5 ✓
|
|
80
|
+
const result = detector.detect("Org: 987654325");
|
|
81
|
+
expect(result.hasPii).toBe(true);
|
|
82
|
+
expect(result.matches.some((m) => m.type === "organisasjonsnummer")).toBe(true);
|
|
83
|
+
expect(result.matches.find((m) => m.type === "organisasjonsnummer").value).toBe("987654325");
|
|
84
|
+
});
|
|
85
|
+
it("rejects invalid 9-digit numbers (bad checksum)", () => {
|
|
86
|
+
const result = detector.detect("Org: 987654321");
|
|
87
|
+
const orgMatches = result.matches.filter((m) => m.type === "organisasjonsnummer");
|
|
88
|
+
expect(orgMatches).toHaveLength(0);
|
|
89
|
+
});
|
|
90
|
+
it("rejects 9-digit numbers not starting with 8 or 9", () => {
|
|
91
|
+
const result = detector.detect("Number: 123456789");
|
|
92
|
+
const orgMatches = result.matches.filter((m) => m.type === "organisasjonsnummer");
|
|
93
|
+
expect(orgMatches).toHaveLength(0);
|
|
94
|
+
});
|
|
95
|
+
it("coexists with existing email and SSN patterns", () => {
|
|
96
|
+
const result = detector.detect("Email: john@example.com, SSN: 123-45-6789, FNR: 01010150074");
|
|
97
|
+
expect(result.hasPii).toBe(true);
|
|
98
|
+
const types = result.matches.map((m) => m.type);
|
|
99
|
+
expect(types).toContain("email");
|
|
100
|
+
expect(types).toContain("ssn_us");
|
|
101
|
+
expect(types).toContain("fodselsnummer");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
53
104
|
describe("detectInObject", () => {
|
|
54
105
|
it("detects PII in string values", () => {
|
|
55
106
|
const results = detector.detectInObject({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pii-detector-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/pii-detector-service.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pii-detector-service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/pii-detector-service.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -133,6 +133,43 @@ describe("PiiEncryptionService", () => {
|
|
|
133
133
|
expect(decrypted.content).toBe(original.content);
|
|
134
134
|
});
|
|
135
135
|
});
|
|
136
|
+
describe("stripPii", () => {
|
|
137
|
+
it("strips email → [EMAIL]", () => {
|
|
138
|
+
const result = service.stripPii("Contact john@example.com please");
|
|
139
|
+
expect(result.text).toBe("Contact [EMAIL] please");
|
|
140
|
+
expect(result.piiDetected).toBe(true);
|
|
141
|
+
expect(result.types).toContain("email");
|
|
142
|
+
});
|
|
143
|
+
it("strips fødselsnummer → [FODSELSNUMMER]", () => {
|
|
144
|
+
const result = service.stripPii("FNR: 01010150074");
|
|
145
|
+
expect(result.text).toBe("FNR: [FODSELSNUMMER]");
|
|
146
|
+
expect(result.piiDetected).toBe(true);
|
|
147
|
+
expect(result.types).toContain("fodselsnummer");
|
|
148
|
+
});
|
|
149
|
+
it("respects allowedTypes — listed types pass through", () => {
|
|
150
|
+
const result = service.stripPii("Email john@example.com, SSN 123-45-6789", ["email"]);
|
|
151
|
+
expect(result.text).toContain("john@example.com");
|
|
152
|
+
expect(result.text).not.toContain("123-45-6789");
|
|
153
|
+
expect(result.text).toContain("[SSN_US]");
|
|
154
|
+
});
|
|
155
|
+
it("strips multiple PII in same text", () => {
|
|
156
|
+
const result = service.stripPii("Email john@example.com SSN 123-45-6789");
|
|
157
|
+
expect(result.text).not.toContain("john@example.com");
|
|
158
|
+
expect(result.text).not.toContain("123-45-6789");
|
|
159
|
+
expect(result.text).toContain("[EMAIL]");
|
|
160
|
+
expect(result.text).toContain("[SSN_US]");
|
|
161
|
+
});
|
|
162
|
+
it("returns unchanged text when no PII", () => {
|
|
163
|
+
const result = service.stripPii("Just a normal sentence");
|
|
164
|
+
expect(result.text).toBe("Just a normal sentence");
|
|
165
|
+
expect(result.piiDetected).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
it("returns unchanged for empty text", () => {
|
|
168
|
+
const result = service.stripPii("");
|
|
169
|
+
expect(result.text).toBe("");
|
|
170
|
+
expect(result.piiDetected).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
136
173
|
describe("token format", () => {
|
|
137
174
|
it("produces parseable PII tokens", () => {
|
|
138
175
|
const result = service.processText("Email john@example.com", {
|