@talkpilot/core-db 1.3.40 → 1.3.41

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.
Files changed (59) hide show
  1. package/README.md +0 -41
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/talkpilot/calls/calls.types.d.ts +0 -1
  7. package/dist/talkpilot/calls/calls.types.d.ts.map +1 -1
  8. package/dist/talkpilot/clientsConfig/clientsConfig.getters.d.ts +0 -39
  9. package/dist/talkpilot/clientsConfig/clientsConfig.getters.d.ts.map +1 -1
  10. package/dist/talkpilot/clientsConfig/clientsConfig.getters.js +1 -92
  11. package/dist/talkpilot/clientsConfig/clientsConfig.getters.js.map +1 -1
  12. package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts +0 -4
  13. package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts.map +1 -1
  14. package/dist/talkpilot/clientsConfig/index.d.ts +0 -1
  15. package/dist/talkpilot/clientsConfig/index.d.ts.map +1 -1
  16. package/dist/talkpilot/clientsConfig/index.js +0 -1
  17. package/dist/talkpilot/clientsConfig/index.js.map +1 -1
  18. package/dist/test-utils/factories/index.d.ts +1 -1
  19. package/dist/test-utils/factories/index.d.ts.map +1 -1
  20. package/dist/test-utils/factories/index.js +1 -1
  21. package/dist/test-utils/factories/index.js.map +1 -1
  22. package/dist/test-utils/factories/websitalk/scans.d.ts.map +1 -1
  23. package/dist/test-utils/factories/websitalk/scans.js +0 -5
  24. package/dist/test-utils/factories/websitalk/scans.js.map +1 -1
  25. package/dist/websitalk/index.d.ts +1 -1
  26. package/dist/websitalk/index.d.ts.map +1 -1
  27. package/dist/websitalk/index.js +1 -1
  28. package/dist/websitalk/index.js.map +1 -1
  29. package/dist/websitalk/scans/scans.getters.d.ts.map +1 -1
  30. package/dist/websitalk/scans/scans.getters.js +2 -8
  31. package/dist/websitalk/scans/scans.getters.js.map +1 -1
  32. package/dist/websitalk/scans/scans.types.d.ts +0 -5
  33. package/dist/websitalk/scans/scans.types.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/index.ts +1 -1
  36. package/src/talkpilot/calls/calls.types.ts +0 -1
  37. package/src/talkpilot/clientsConfig/__tests__/clientsConfig.getters.spec.ts +0 -318
  38. package/src/talkpilot/clientsConfig/clientsConfig.getters.ts +2 -116
  39. package/src/talkpilot/clientsConfig/clientsConfig.types.ts +0 -4
  40. package/src/talkpilot/clientsConfig/index.ts +0 -1
  41. package/src/test-utils/factories/index.ts +1 -1
  42. package/src/test-utils/factories/websitalk/knowledgeDocuments.ts +28 -0
  43. package/src/test-utils/factories/websitalk/scans.ts +0 -5
  44. package/src/websitalk/index.ts +1 -1
  45. package/src/websitalk/knowledgeDocuments/__tests__/knowledgeDocuments.spec.ts +123 -0
  46. package/src/websitalk/knowledgeDocuments/index.ts +3 -0
  47. package/src/websitalk/knowledgeDocuments/knowledgeDocuments.constants.ts +19 -0
  48. package/src/websitalk/knowledgeDocuments/knowledgeDocuments.getters.ts +85 -0
  49. package/src/websitalk/knowledgeDocuments/knowledgeDocuments.types.ts +37 -0
  50. package/src/websitalk/scans/scans.getters.ts +2 -8
  51. package/src/websitalk/scans/scans.types.ts +0 -9
  52. package/src/talkpilot/clientsConfig/clientsConfig.constants.ts +0 -2
  53. package/src/test-utils/factories/websitalk/scanResources.ts +0 -26
  54. package/src/websitalk/scanResources/__tests__/scanResources.spec.ts +0 -136
  55. package/src/websitalk/scanResources/index.ts +0 -4
  56. package/src/websitalk/scanResources/scanResources.constants.ts +0 -47
  57. package/src/websitalk/scanResources/scanResources.getters.ts +0 -94
  58. package/src/websitalk/scanResources/scanResources.types.ts +0 -90
  59. package/src/websitalk/scanResources/scanResources.utils.ts +0 -28
@@ -5,15 +5,7 @@ import {
5
5
  updateActiveScanId,
6
6
  clearActiveScanId,
7
7
  updateProductConfig,
8
- getActiveScanIds,
9
- addActiveScanId,
10
- removeActiveScanId,
11
- getPrimaryScanId,
12
- setPrimaryScanId,
13
- clearPrimaryScanId,
14
8
  } from "../clientsConfig.getters";
15
- import { MAX_ACTIVE_SCANS } from "../clientsConfig.constants";
16
- import { createClientConfig } from "../../../test-utils/factories/talkpilot/clientsConfig";
17
9
 
18
10
  describe("updateProductConfig", () => {
19
11
  beforeEach(async () => {
@@ -111,313 +103,3 @@ describe("activeScanId", () => {
111
103
  expect(await getActiveScanId(clientId)).toBeUndefined();
112
104
  });
113
105
  });
114
-
115
- describe("activeScanIds (CP-1628)", () => {
116
- beforeEach(async () => {
117
- await getClientsConfigCollection().deleteMany({});
118
- });
119
-
120
- afterEach(async () => {
121
- await getClientsConfigCollection().deleteMany({});
122
- });
123
-
124
- it("returns an empty list when nothing is set", async () => {
125
- const clientId = "client-list-1";
126
- await getClientsConfigCollection().insertOne(
127
- createClientConfig({
128
- clientId,
129
- products: {
130
- websiteTalk: { companyName: "X", timezone: "UTC", language: "he" },
131
- },
132
- }),
133
- );
134
-
135
- expect(await getActiveScanIds(clientId)).toEqual([]);
136
- });
137
-
138
- it("falls back to wrapping the legacy activeScanId when activeScanIds is absent", async () => {
139
- const clientId = "client-list-2";
140
- await getClientsConfigCollection().insertOne(
141
- createClientConfig({
142
- clientId,
143
- products: {
144
- websiteTalk: {
145
- companyName: "X",
146
- timezone: "UTC",
147
- language: "he",
148
- activeScanId: "scan-legacy",
149
- },
150
- },
151
- }),
152
- );
153
-
154
- expect(await getActiveScanIds(clientId)).toEqual(["scan-legacy"]);
155
- });
156
-
157
- it("adds a scan id and returns it from getActiveScanIds", async () => {
158
- const clientId = "client-list-3";
159
- await getClientsConfigCollection().insertOne(
160
- createClientConfig({
161
- clientId,
162
- products: {
163
- websiteTalk: { companyName: "X", timezone: "UTC", language: "he" },
164
- },
165
- }),
166
- );
167
-
168
- await addActiveScanId(clientId, "scan-a");
169
- await addActiveScanId(clientId, "scan-b");
170
-
171
- expect(await getActiveScanIds(clientId)).toEqual(["scan-a", "scan-b"]);
172
- });
173
-
174
- it("does not duplicate an already-present scan id, and does not count it against the cap", async () => {
175
- const clientId = "client-list-4";
176
- await getClientsConfigCollection().insertOne(
177
- createClientConfig({
178
- clientId,
179
- products: {
180
- websiteTalk: {
181
- companyName: "X",
182
- timezone: "UTC",
183
- language: "he",
184
- activeScanIds: ["scan-a", "scan-b", "scan-c"],
185
- },
186
- },
187
- }),
188
- );
189
-
190
- await expect(addActiveScanId(clientId, "scan-a")).resolves.toBeUndefined();
191
- expect(await getActiveScanIds(clientId)).toEqual([
192
- "scan-a",
193
- "scan-b",
194
- "scan-c",
195
- ]);
196
- });
197
-
198
- it(`throws when adding a new scan id beyond MAX_ACTIVE_SCANS (${MAX_ACTIVE_SCANS})`, async () => {
199
- const clientId = "client-list-5";
200
- await getClientsConfigCollection().insertOne(
201
- createClientConfig({
202
- clientId,
203
- products: {
204
- websiteTalk: {
205
- companyName: "X",
206
- timezone: "UTC",
207
- language: "he",
208
- activeScanIds: ["scan-a", "scan-b", "scan-c"],
209
- },
210
- },
211
- }),
212
- );
213
-
214
- await expect(addActiveScanId(clientId, "scan-d")).rejects.toThrow(
215
- `Client ${clientId} already has the maximum of ${MAX_ACTIVE_SCANS} active scans`,
216
- );
217
- expect(await getActiveScanIds(clientId)).toEqual([
218
- "scan-a",
219
- "scan-b",
220
- "scan-c",
221
- ]);
222
- });
223
-
224
- it("removes a scan id without affecting the others", async () => {
225
- const clientId = "client-list-6";
226
- await getClientsConfigCollection().insertOne(
227
- createClientConfig({
228
- clientId,
229
- products: {
230
- websiteTalk: {
231
- companyName: "X",
232
- timezone: "UTC",
233
- language: "he",
234
- activeScanIds: ["scan-a", "scan-b"],
235
- },
236
- },
237
- }),
238
- );
239
-
240
- await removeActiveScanId(clientId, "scan-a");
241
- expect(await getActiveScanIds(clientId)).toEqual(["scan-b"]);
242
- });
243
-
244
- it("removing a scan id that isn't present is a harmless no-op", async () => {
245
- const clientId = "client-list-7";
246
- await getClientsConfigCollection().insertOne(
247
- createClientConfig({
248
- clientId,
249
- products: {
250
- websiteTalk: {
251
- companyName: "X",
252
- timezone: "UTC",
253
- language: "he",
254
- activeScanIds: ["scan-a"],
255
- },
256
- },
257
- }),
258
- );
259
-
260
- await expect(
261
- removeActiveScanId(clientId, "scan-x"),
262
- ).resolves.toBeUndefined();
263
- expect(await getActiveScanIds(clientId)).toEqual(["scan-a"]);
264
- });
265
-
266
- it("throws when removing from a client that doesn't exist", async () => {
267
- await expect(
268
- removeActiveScanId("no-such-client", "scan-a"),
269
- ).rejects.toThrow("No client config found for clientId: no-such-client");
270
- });
271
-
272
- it("migrates a legacy-only activeScanId into activeScanIds instead of dropping it when adding a new one", async () => {
273
- const clientId = "client-list-8";
274
- await getClientsConfigCollection().insertOne(
275
- createClientConfig({
276
- clientId,
277
- products: {
278
- websiteTalk: {
279
- companyName: "X",
280
- timezone: "UTC",
281
- language: "he",
282
- activeScanId: "scan-legacy",
283
- },
284
- },
285
- }),
286
- );
287
-
288
- await addActiveScanId(clientId, "scan-new");
289
-
290
- expect(await getActiveScanIds(clientId)).toEqual(["scan-legacy", "scan-new"]);
291
- const doc = await getClientsConfigCollection().findOne({ clientId });
292
- expect(doc?.products?.websiteTalk?.activeScanId).toBeUndefined();
293
- });
294
-
295
- it("throws when adding to a client that doesn't exist", async () => {
296
- await expect(
297
- addActiveScanId("no-such-client", "scan-a"),
298
- ).rejects.toThrow("No client config found for clientId: no-such-client");
299
- });
300
-
301
- it("unsets a legacy-only activeScanId when it is the one being removed", async () => {
302
- const clientId = "client-list-9";
303
- await getClientsConfigCollection().insertOne(
304
- createClientConfig({
305
- clientId,
306
- products: {
307
- websiteTalk: {
308
- companyName: "X",
309
- timezone: "UTC",
310
- language: "he",
311
- activeScanId: "scan-legacy",
312
- },
313
- },
314
- }),
315
- );
316
-
317
- await removeActiveScanId(clientId, "scan-legacy");
318
-
319
- expect(await getActiveScanIds(clientId)).toEqual([]);
320
- const doc = await getClientsConfigCollection().findOne({ clientId });
321
- expect(doc?.products?.websiteTalk?.activeScanId).toBeUndefined();
322
- });
323
- });
324
-
325
- describe("primaryScanId (CP-1628 follow-up)", () => {
326
- beforeEach(async () => {
327
- await getClientsConfigCollection().deleteMany({});
328
- });
329
-
330
- afterEach(async () => {
331
- await getClientsConfigCollection().deleteMany({});
332
- });
333
-
334
- it("returns undefined when nothing is set", async () => {
335
- const clientId = "client-primary-1";
336
- await getClientsConfigCollection().insertOne(
337
- createClientConfig({
338
- clientId,
339
- products: {
340
- websiteTalk: { companyName: "X", timezone: "UTC", language: "he" },
341
- },
342
- }),
343
- );
344
-
345
- expect(await getPrimaryScanId(clientId)).toBeUndefined();
346
- });
347
-
348
- it("sets and returns the primary scan id, independent of activeScanIds", async () => {
349
- const clientId = "client-primary-2";
350
- await getClientsConfigCollection().insertOne(
351
- createClientConfig({
352
- clientId,
353
- products: {
354
- websiteTalk: {
355
- companyName: "X",
356
- timezone: "UTC",
357
- language: "he",
358
- activeScanIds: ["scan-a", "scan-b"],
359
- },
360
- },
361
- }),
362
- );
363
-
364
- await setPrimaryScanId(clientId, "scan-c");
365
-
366
- expect(await getPrimaryScanId(clientId)).toBe("scan-c");
367
- expect(await getActiveScanIds(clientId)).toEqual(["scan-a", "scan-b"]);
368
- });
369
-
370
- it("replaces the previous primary scan id when set again", async () => {
371
- const clientId = "client-primary-3";
372
- await getClientsConfigCollection().insertOne(
373
- createClientConfig({
374
- clientId,
375
- products: {
376
- websiteTalk: {
377
- companyName: "X",
378
- timezone: "UTC",
379
- language: "he",
380
- primaryScanId: "scan-a",
381
- },
382
- },
383
- }),
384
- );
385
-
386
- await setPrimaryScanId(clientId, "scan-b");
387
-
388
- expect(await getPrimaryScanId(clientId)).toBe("scan-b");
389
- });
390
-
391
- it("clears the primary scan id", async () => {
392
- const clientId = "client-primary-4";
393
- await getClientsConfigCollection().insertOne(
394
- createClientConfig({
395
- clientId,
396
- products: {
397
- websiteTalk: {
398
- companyName: "X",
399
- timezone: "UTC",
400
- language: "he",
401
- primaryScanId: "scan-a",
402
- },
403
- },
404
- }),
405
- );
406
-
407
- await clearPrimaryScanId(clientId);
408
-
409
- expect(await getPrimaryScanId(clientId)).toBeUndefined();
410
- });
411
-
412
- it("throws when setting the primary scan for a client that doesn't exist", async () => {
413
- await expect(
414
- setPrimaryScanId("no-such-client", "scan-a"),
415
- ).rejects.toThrow("No client config found for clientId: no-such-client");
416
- });
417
-
418
- it("throws when clearing the primary scan for a client that doesn't exist", async () => {
419
- await expect(
420
- clearPrimaryScanId("no-such-client"),
421
- ).rejects.toThrow("No client config found for clientId: no-such-client");
422
- });
423
- });
@@ -6,9 +6,8 @@ import {
6
6
  KNOWN_PRODUCT_KEYS,
7
7
  KnownProductKey,
8
8
  } from "../index";
9
- import { Collection, UpdateFilter } from "mongodb";
9
+ import { Collection } from "mongodb";
10
10
  import { resetClientQuotaSettings } from "../clients/clients.quota.getters";
11
- import { MAX_ACTIVE_SCANS } from "./clientsConfig.constants";
12
11
 
13
12
  export const getClientsConfigCollection = (): Collection<ClientConfigDoc> => {
14
13
  return getDb().collection<ClientConfigDoc>("clientsConfig");
@@ -18,9 +17,7 @@ export const getClientConfig = (clientId: string) => {
18
17
  return getClientsConfigCollection().findOne({ clientId });
19
18
  };
20
19
 
21
- export const getClientDisplayName = async (
22
- clientId: string,
23
- ): Promise<string> => {
20
+ export const getClientDisplayName = async (clientId: string): Promise<string> => {
24
21
  const doc = await getClientsConfigCollection().findOne(
25
22
  { clientId },
26
23
  { projection: { "quota.clientDisplayName": 1, _id: 0 } },
@@ -74,24 +71,6 @@ export const getActiveScanId = async (
74
71
  return cfg?.products?.websiteTalk?.activeScanId;
75
72
  };
76
73
 
77
- /**
78
- * Returns the client's active scan ids as a list.
79
- * Falls back to wrapping the legacy singular `activeScanId` field when
80
- * `activeScanIds` is absent — lets clients on the old field keep working
81
- * without a data migration.
82
- */
83
- export const getActiveScanIds = async (clientId: string): Promise<string[]> => {
84
- const cfg = await getClientConfig(clientId);
85
- const list = cfg?.products?.websiteTalk?.activeScanIds;
86
- if (list && list.length > 0) return list;
87
- const legacy = cfg?.products?.websiteTalk?.activeScanId;
88
- return legacy ? [legacy] : [];
89
- };
90
-
91
- /**
92
- * @deprecated Use `addActiveScanId` instead — supports multiple simultaneously
93
- * active scans. This function overwrites the entire single-value field.
94
- */
95
74
  export const updateActiveScanId = async (
96
75
  clientId: string,
97
76
  scanId: string,
@@ -99,38 +78,6 @@ export const updateActiveScanId = async (
99
78
  await updateProductConfig(clientId, "websiteTalk", { activeScanId: scanId });
100
79
  };
101
80
 
102
- /**
103
- * Adds a scan id to the client's active list. Writes the full list back
104
- * (rather than a bare array append) so a client still on the legacy
105
- * singular field gets migrated instead of silently losing it.
106
- */
107
- export const addActiveScanId = async (
108
- clientId: string,
109
- scanId: string,
110
- ): Promise<void> => {
111
- const current = await getActiveScanIds(clientId);
112
- if (!current.includes(scanId) && current.length >= MAX_ACTIVE_SCANS) {
113
- throw new Error(
114
- `Client ${clientId} already has the maximum of ${MAX_ACTIVE_SCANS} active scans`,
115
- );
116
- }
117
- const next = current.includes(scanId) ? current : [...current, scanId];
118
- const result = await getClientsConfigCollection().updateOne(
119
- { clientId },
120
- {
121
- $set: { "products.websiteTalk.activeScanIds": next },
122
- $unset: { "products.websiteTalk.activeScanId": "" },
123
- },
124
- );
125
- if (result.matchedCount === 0) {
126
- throw new Error(`No client config found for clientId: ${clientId}`);
127
- }
128
- };
129
-
130
- /**
131
- * @deprecated Use `removeActiveScanId` instead — removes a single scan from
132
- * the active list without clearing the others.
133
- */
134
81
  export const clearActiveScanId = async (clientId: string): Promise<void> => {
135
82
  const result = await getClientsConfigCollection().updateOne(
136
83
  { clientId },
@@ -141,67 +88,6 @@ export const clearActiveScanId = async (clientId: string): Promise<void> => {
141
88
  }
142
89
  };
143
90
 
144
- /**
145
- * Removes a single scan id from the active list. Also clears the legacy
146
- * singular field when it matches, so a not-yet-migrated client's removal
147
- * actually takes effect instead of silently no-op'ing.
148
- */
149
- export const removeActiveScanId = async (
150
- clientId: string,
151
- scanId: string,
152
- ): Promise<void> => {
153
- const cfg = await getClientConfig(clientId);
154
- if (!cfg) {
155
- throw new Error(`No client config found for clientId: ${clientId}`);
156
- }
157
- const updateOps: UpdateFilter<ClientConfigDoc> = {
158
- $pull: { "products.websiteTalk.activeScanIds": scanId },
159
- };
160
- if (cfg.products?.websiteTalk?.activeScanId === scanId) {
161
- updateOps.$unset = { "products.websiteTalk.activeScanId": "" };
162
- }
163
- await getClientsConfigCollection().updateOne({ clientId }, updateOps);
164
- };
165
-
166
- /** Returns the scan whose content seeds the phone call's opening description, if one is set. */
167
- export const getPrimaryScanId = async (
168
- clientId: string,
169
- ): Promise<string | undefined> => {
170
- const cfg = await getClientConfig(clientId);
171
- return cfg?.products?.websiteTalk?.primaryScanId;
172
- };
173
-
174
- /**
175
- * Sets the primary scan, replacing whatever was set before — exactly one
176
- * primary scan at a time. Throws if the client doesn't exist.
177
- */
178
- export const setPrimaryScanId = async (
179
- clientId: string,
180
- scanId: string,
181
- ): Promise<void> => {
182
- const result = await getClientsConfigCollection().updateOne(
183
- { clientId },
184
- { $set: { "products.websiteTalk.primaryScanId": scanId } },
185
- );
186
- if (result.matchedCount === 0) {
187
- throw new Error(`No client config found for clientId: ${clientId}`);
188
- }
189
- };
190
-
191
- /**
192
- * Clears the primary scan. Callers that need the opening description fall
193
- * back to the first active scan. Throws if the client doesn't exist.
194
- */
195
- export const clearPrimaryScanId = async (clientId: string): Promise<void> => {
196
- const result = await getClientsConfigCollection().updateOne(
197
- { clientId },
198
- { $unset: { "products.websiteTalk.primaryScanId": "" } },
199
- );
200
- if (result.matchedCount === 0) {
201
- throw new Error(`No client config found for clientId: ${clientId}`);
202
- }
203
- };
204
-
205
91
  export async function listClientIdsWithConfiguredQuota(): Promise<string[]> {
206
92
  const docs = await getClientsConfigCollection()
207
93
  .find({ "quota.totalQuota": { $exists: true, $ne: null } })
@@ -64,10 +64,6 @@ export type WebsiteTalkProduct = {
64
64
  timezone: string;
65
65
  language: string;
66
66
  activeScanId?: string;
67
- /** Up to MAX_ACTIVE_SCANS scan ids simultaneously serving RAG for phone calls. */
68
- activeScanIds?: string[];
69
- /** The single scan whose content seeds the phone call's opening description. */
70
- primaryScanId?: string;
71
67
  };
72
68
 
73
69
  // ---------------------------------------------------------------------------
@@ -1,3 +1,2 @@
1
1
  export * from "./clientsConfig.types";
2
2
  export * from "./clientsConfig.getters";
3
- export * from "./clientsConfig.constants";
@@ -13,5 +13,5 @@ export * from "./municipal/muniIssues";
13
13
  export * from "./municipal/streets";
14
14
  export * from "./municipal/tickets";
15
15
  export * from "./websitalk/scans";
16
- export * from "./websitalk/scanResources";
17
16
  export * from "./websitalk/websiteUrls";
17
+ export * from "./websitalk/knowledgeDocuments";
@@ -0,0 +1,28 @@
1
+ import { Factory } from "fishery";
2
+ import { faker } from "@faker-js/faker";
3
+ import {
4
+ KNOWLEDGE_DOCUMENT_STATUSES,
5
+ KNOWLEDGE_FILE_TYPES,
6
+ type KnowledgeDocument,
7
+ } from "../../../websitalk/knowledgeDocuments";
8
+
9
+ export const knowledgeDocumentFactory = Factory.define<KnowledgeDocument>(
10
+ () => ({
11
+ clientId: faker.string.uuid(),
12
+ filename: `${faker.system.fileName()}.pdf`,
13
+ contentType: "application/pdf",
14
+ fileType: KNOWLEDGE_FILE_TYPES.PDF,
15
+ storagePath: `knowledge/${faker.string.uuid()}/original.pdf`,
16
+ byteSize: faker.number.int({ min: 1024, max: 5_000_000 }),
17
+ status: KNOWLEDGE_DOCUMENT_STATUSES.UPLOADING,
18
+ progressPercent: 0,
19
+ createdAt: new Date(),
20
+ updatedAt: new Date(),
21
+ }),
22
+ );
23
+
24
+ export function createKnowledgeDocumentFixture(
25
+ overrides?: Partial<KnowledgeDocument>,
26
+ ): KnowledgeDocument {
27
+ return knowledgeDocumentFactory.build(overrides);
28
+ }
@@ -17,11 +17,6 @@ export const scanFactory = Factory.define<Scan>(() => ({
17
17
  },
18
18
  createdAt: new Date(),
19
19
  updatedAt: new Date(),
20
- startedAt: new Date(),
21
- completedAt: null,
22
- pagesCompleted: 0,
23
- pagesFailedPermanent: 0,
24
- pagesDuplicate: 0,
25
20
  }));
26
21
 
27
22
  export function createScanFixture(overrides?: Partial<Scan>): Scan {
@@ -1,8 +1,8 @@
1
1
  import { Db, ObjectId as MongoObjectId } from "mongodb";
2
2
 
3
3
  export * from "./scans";
4
- export * from "./scanResources";
5
4
  export * from "./websiteUrls";
5
+ export * from "./knowledgeDocuments";
6
6
  export { websitalkMongodbClient } from "./mongodb-client";
7
7
 
8
8
  let db: Db;
@@ -0,0 +1,123 @@
1
+ import {
2
+ KNOWLEDGE_DOCUMENT_STATUSES,
3
+ KNOWLEDGE_FILE_TYPES,
4
+ } from "../knowledgeDocuments.constants";
5
+ import {
6
+ createKnowledgeDocument,
7
+ deleteKnowledgeDocument,
8
+ getKnowledgeDocumentById,
9
+ getKnowledgeDocumentsCollection,
10
+ listKnowledgeDocumentsByClientId,
11
+ listReadyKnowledgeDocumentIdsByClientId,
12
+ updateKnowledgeDocument,
13
+ } from "../knowledgeDocuments.getters";
14
+ import { createKnowledgeDocumentFixture } from "../../../test-utils/factories/websitalk/knowledgeDocuments";
15
+
16
+ describe("db.websitalk.knowledgeDocuments", () => {
17
+ const clientId = "test-client";
18
+
19
+ describe("createKnowledgeDocument", () => {
20
+ it("should create a knowledge document", async () => {
21
+ const id = await createKnowledgeDocument({
22
+ clientId,
23
+ filename: "faq.pdf",
24
+ contentType: "application/pdf",
25
+ fileType: KNOWLEDGE_FILE_TYPES.PDF,
26
+ storagePath: "knowledge/test/faq.pdf",
27
+ byteSize: 2048,
28
+ });
29
+
30
+ const saved = await getKnowledgeDocumentById(id.toString());
31
+ expect(saved).toMatchObject({
32
+ clientId,
33
+ filename: "faq.pdf",
34
+ status: KNOWLEDGE_DOCUMENT_STATUSES.UPLOADING,
35
+ progressPercent: 0,
36
+ fileType: KNOWLEDGE_FILE_TYPES.PDF,
37
+ });
38
+ expect(saved?.createdAt).toBeInstanceOf(Date);
39
+ expect(saved?.updatedAt).toBeInstanceOf(Date);
40
+ });
41
+ });
42
+
43
+ describe("listKnowledgeDocumentsByClientId", () => {
44
+ it("should return documents for a client only", async () => {
45
+ await getKnowledgeDocumentsCollection().insertMany([
46
+ createKnowledgeDocumentFixture({ clientId, filename: "a.pdf" }),
47
+ createKnowledgeDocumentFixture({ clientId, filename: "b.pdf" }),
48
+ createKnowledgeDocumentFixture({
49
+ clientId: "other-client",
50
+ filename: "c.pdf",
51
+ }),
52
+ ]);
53
+
54
+ const results = await listKnowledgeDocumentsByClientId(clientId);
55
+ expect(results).toHaveLength(2);
56
+ expect(results.map((doc) => doc.filename).sort()).toEqual([
57
+ "a.pdf",
58
+ "b.pdf",
59
+ ]);
60
+ });
61
+ });
62
+
63
+ describe("listReadyKnowledgeDocumentIdsByClientId", () => {
64
+ it("should return only READY document ids", async () => {
65
+ await getKnowledgeDocumentsCollection().insertMany([
66
+ createKnowledgeDocumentFixture({
67
+ clientId,
68
+ status: KNOWLEDGE_DOCUMENT_STATUSES.READY,
69
+ }),
70
+ createKnowledgeDocumentFixture({
71
+ clientId,
72
+ status: KNOWLEDGE_DOCUMENT_STATUSES.PROCESSING,
73
+ }),
74
+ createKnowledgeDocumentFixture({
75
+ clientId: "other",
76
+ status: KNOWLEDGE_DOCUMENT_STATUSES.READY,
77
+ }),
78
+ ]);
79
+
80
+ const ids = await listReadyKnowledgeDocumentIdsByClientId(clientId);
81
+ expect(ids).toHaveLength(1);
82
+ });
83
+ });
84
+
85
+ describe("updateKnowledgeDocument", () => {
86
+ it("should update status and progress", async () => {
87
+ const id = await createKnowledgeDocument({
88
+ clientId,
89
+ filename: "doc.pdf",
90
+ contentType: "application/pdf",
91
+ fileType: KNOWLEDGE_FILE_TYPES.PDF,
92
+ storagePath: "knowledge/test/doc.pdf",
93
+ byteSize: 100,
94
+ });
95
+
96
+ const updated = await updateKnowledgeDocument(id.toString(), {
97
+ status: KNOWLEDGE_DOCUMENT_STATUSES.READY,
98
+ progressPercent: 100,
99
+ pageCount: 12,
100
+ });
101
+
102
+ expect(updated?.status).toBe(KNOWLEDGE_DOCUMENT_STATUSES.READY);
103
+ expect(updated?.progressPercent).toBe(100);
104
+ expect(updated?.pageCount).toBe(12);
105
+ });
106
+ });
107
+
108
+ describe("deleteKnowledgeDocument", () => {
109
+ it("should delete and return true", async () => {
110
+ const id = await createKnowledgeDocument({
111
+ clientId: "delete-client",
112
+ filename: "gone.pdf",
113
+ contentType: "application/pdf",
114
+ fileType: KNOWLEDGE_FILE_TYPES.PDF,
115
+ storagePath: "knowledge/delete/gone.pdf",
116
+ byteSize: 10,
117
+ });
118
+
119
+ expect(await deleteKnowledgeDocument(id.toString())).toBe(true);
120
+ expect(await getKnowledgeDocumentById(id.toString())).toBeNull();
121
+ });
122
+ });
123
+ });
@@ -0,0 +1,3 @@
1
+ export * from "./knowledgeDocuments.constants";
2
+ export * from "./knowledgeDocuments.types";
3
+ export * from "./knowledgeDocuments.getters";