@amirdaraee/namewise 0.4.0 → 0.5.1

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/.github/workflows/build.yml +6 -0
  2. package/.github/workflows/publish.yml +59 -11
  3. package/.github/workflows/test.yml +1 -0
  4. package/CHANGELOG.md +102 -0
  5. package/RELEASE.md +15 -3
  6. package/dist/cli/commands.d.ts.map +1 -1
  7. package/dist/cli/commands.js +14 -7
  8. package/dist/cli/commands.js.map +1 -1
  9. package/dist/cli/rename.js +1 -1
  10. package/dist/cli/rename.js.map +1 -1
  11. package/dist/parsers/factory.d.ts +2 -1
  12. package/dist/parsers/factory.d.ts.map +1 -1
  13. package/dist/parsers/factory.js +9 -6
  14. package/dist/parsers/factory.js.map +1 -1
  15. package/dist/parsers/pdf-parser.d.ts +1 -0
  16. package/dist/parsers/pdf-parser.d.ts.map +1 -1
  17. package/dist/parsers/pdf-parser.js +20 -1
  18. package/dist/parsers/pdf-parser.js.map +1 -1
  19. package/dist/services/claude-service.d.ts.map +1 -1
  20. package/dist/services/claude-service.js +59 -65
  21. package/dist/services/claude-service.js.map +1 -1
  22. package/dist/services/lmstudio-service.d.ts.map +1 -1
  23. package/dist/services/lmstudio-service.js +16 -19
  24. package/dist/services/lmstudio-service.js.map +1 -1
  25. package/dist/services/ollama-service.d.ts +1 -0
  26. package/dist/services/ollama-service.d.ts.map +1 -1
  27. package/dist/services/ollama-service.js +61 -32
  28. package/dist/services/ollama-service.js.map +1 -1
  29. package/dist/services/openai-service.d.ts.map +1 -1
  30. package/dist/services/openai-service.js +59 -66
  31. package/dist/services/openai-service.js.map +1 -1
  32. package/dist/utils/ai-prompts.d.ts +20 -0
  33. package/dist/utils/ai-prompts.d.ts.map +1 -0
  34. package/dist/utils/ai-prompts.js +71 -0
  35. package/dist/utils/ai-prompts.js.map +1 -0
  36. package/dist/utils/pdf-to-image.d.ts +11 -0
  37. package/dist/utils/pdf-to-image.d.ts.map +1 -0
  38. package/dist/utils/pdf-to-image.js +91 -0
  39. package/dist/utils/pdf-to-image.js.map +1 -0
  40. package/eng.traineddata +0 -0
  41. package/package.json +5 -2
  42. package/src/cli/commands.ts +14 -7
  43. package/src/cli/rename.ts +1 -1
  44. package/src/parsers/factory.ts +11 -7
  45. package/src/parsers/pdf-parser.ts +22 -1
  46. package/src/services/claude-service.ts +63 -58
  47. package/src/services/lmstudio-service.ts +20 -20
  48. package/src/services/ollama-service.ts +75 -31
  49. package/src/services/openai-service.ts +63 -59
  50. package/src/utils/ai-prompts.ts +76 -0
  51. package/src/utils/pdf-to-image.ts +124 -0
  52. package/tests/integration/ai-prompting.test.ts +386 -0
  53. package/tests/integration/end-to-end.test.ts +9 -9
  54. package/tests/integration/person-name-extraction.test.ts +440 -0
  55. package/tests/unit/cli/commands.test.ts +9 -3
  56. package/tests/unit/services/lmstudio-service.test.ts +4 -4
  57. package/tests/unit/services/ollama-service.test.ts +4 -4
  58. package/tests/unit/utils/ai-prompts.test.ts +213 -0
  59. package/tests/unit/utils/pdf-to-image.test.ts +127 -0
@@ -0,0 +1,440 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { FileRenamer } from '../../src/services/file-renamer.js';
3
+ import { DocumentParserFactory } from '../../src/parsers/factory.js';
4
+ import { AIProvider, FileInfo, Config } from '../../src/types/index.js';
5
+
6
+ // Sophisticated Mock AI that simulates person name extraction behavior
7
+ class PersonNameExtractionMockAI implements AIProvider {
8
+ name = 'PersonNameExtractionMock';
9
+
10
+ async generateFileName(
11
+ content: string,
12
+ originalName: string,
13
+ namingConvention = 'kebab-case',
14
+ category = 'general',
15
+ fileInfo?: FileInfo
16
+ ): Promise<string> {
17
+ // Simulate intelligent person name extraction from content
18
+ const personNamePatterns = [
19
+ // Visa applications
20
+ /(?:visa.*?application.*?for|application.*?for.*?visa.*?for)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/i,
21
+ // Contracts
22
+ /(?:contract.*?between.*?and|agreement.*?with|employment.*?of)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/i,
23
+ // General "for [Name]" patterns
24
+ /\bfor\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/,
25
+ // Medical records
26
+ /(?:patient|medical.*?record.*?for|treatment.*?for)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/i,
27
+ // Certificates
28
+ /(?:certificate.*?for|awarded.*?to|issued.*?to)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/i
29
+ ];
30
+
31
+ let extractedName = '';
32
+
33
+ for (const pattern of personNamePatterns) {
34
+ const match = content.match(pattern);
35
+ if (match && match[1]) {
36
+ extractedName = match[1].toLowerCase().replace(/\s+/g, '-');
37
+ break;
38
+ }
39
+ }
40
+
41
+ // Check for irrelevant folder names and ignore them
42
+ const irrelevantFolders = ['no', 'temp', 'downloads', 'misc', 'other', 'files'];
43
+ const folderName = fileInfo?.parentFolder?.toLowerCase();
44
+ const shouldIgnoreFolder = folderName && irrelevantFolders.includes(folderName);
45
+
46
+ // Generate appropriate filename based on content and detected person
47
+ if (content.toLowerCase().includes('visa') && content.toLowerCase().includes('application')) {
48
+ const baseFilename = 'visitor-visa-application-for-family-members-in-canada';
49
+ return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
50
+ }
51
+
52
+ if (content.toLowerCase().includes('contract') || content.toLowerCase().includes('employment')) {
53
+ const baseFilename = 'employment-contract-software-engineer';
54
+ return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
55
+ }
56
+
57
+ if (content.toLowerCase().includes('medical') || content.toLowerCase().includes('health')) {
58
+ const baseFilename = 'medical-record-annual-checkup';
59
+ return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
60
+ }
61
+
62
+ if (content.toLowerCase().includes('certificate') || content.toLowerCase().includes('diploma')) {
63
+ const baseFilename = 'certificate-completion-course';
64
+ return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
65
+ }
66
+
67
+ if (content.toLowerCase().includes('wedding') || content.toLowerCase().includes('marriage')) {
68
+ const baseFilename = 'wedding-ceremony-invitation';
69
+ return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
70
+ }
71
+
72
+ // Generic document
73
+ const baseFilename = 'document-summary-report';
74
+ return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
75
+ }
76
+ }
77
+
78
+ describe('Person Name Extraction and Folder Filtering Integration Tests', () => {
79
+ let mockAI: PersonNameExtractionMockAI;
80
+ let fileRenamer: FileRenamer;
81
+ let config: Config;
82
+
83
+ beforeEach(() => {
84
+ mockAI = new PersonNameExtractionMockAI();
85
+ const parserFactory = new DocumentParserFactory();
86
+
87
+ config = {
88
+ provider: 'claude',
89
+ apiKey: 'test-key',
90
+ maxFileSize: 10 * 1024 * 1024,
91
+ namingConvention: 'kebab-case',
92
+ templateOptions: {
93
+ category: 'document',
94
+ personalName: '',
95
+ dateFormat: 'YYYY-MM-DD'
96
+ },
97
+ dryRun: true
98
+ };
99
+
100
+ fileRenamer = new FileRenamer(parserFactory, mockAI, config);
101
+ });
102
+
103
+ describe('Person Name Extraction from Document Content', () => {
104
+ it('should extract name from visa application content - Setareh case', async () => {
105
+ const fileInfo: FileInfo = {
106
+ name: 'visitor-visa-application-for-family-in-canada.pdf',
107
+ path: '/no/visitor-visa-application-for-family-in-canada.pdf',
108
+ extension: '.pdf',
109
+ size: 1024 * 100,
110
+ createdAt: new Date('2024-01-15'),
111
+ modifiedAt: new Date('2024-02-01'),
112
+ parentFolder: 'no', // Should be ignored
113
+ folderPath: ['home', 'downloads', 'no']
114
+ };
115
+
116
+ const mockParser = {
117
+ parse: vi.fn().mockResolvedValue({
118
+ content: 'Visitor visa application for Setareh Ahmadi and family members to visit Canada. This application includes all required documentation for tourism and family visit purposes. The applicant Setareh Ahmadi is requesting permission to enter Canada.',
119
+ metadata: {
120
+ title: 'Visitor Visa Application',
121
+ pages: 4
122
+ }
123
+ })
124
+ };
125
+
126
+ vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
127
+
128
+ const results = await fileRenamer.renameFiles([fileInfo]);
129
+
130
+ expect(results[0].success).toBe(true);
131
+ // Should start with the detected person name and include visa/application terms
132
+ expect(results[0].suggestedName).toMatch(/^setareh/);
133
+ expect(results[0].suggestedName).toMatch(/visa/);
134
+ expect(results[0].suggestedName).toMatch(/application/);
135
+ // Should not include the irrelevant folder name "no"
136
+ expect(results[0].suggestedName).not.toContain('no');
137
+ });
138
+
139
+ it('should extract name from employment contract', async () => {
140
+ const fileInfo: FileInfo = {
141
+ name: 'contract.pdf',
142
+ path: '/temp/contract.pdf',
143
+ extension: '.pdf',
144
+ size: 1024 * 75,
145
+ createdAt: new Date('2024-01-15'),
146
+ modifiedAt: new Date('2024-02-01'),
147
+ parentFolder: 'temp', // Should be ignored
148
+ folderPath: ['home', 'temp']
149
+ };
150
+
151
+ const mockParser = {
152
+ parse: vi.fn().mockResolvedValue({
153
+ content: 'Employment contract between TechCorp Inc. and John Smith for the position of Senior Software Engineer. This contract outlines the terms of employment for John Smith including salary, benefits, and responsibilities.',
154
+ metadata: {
155
+ title: 'Employment Agreement',
156
+ author: 'HR Department'
157
+ }
158
+ })
159
+ };
160
+
161
+ vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
162
+
163
+ const results = await fileRenamer.renameFiles([fileInfo]);
164
+
165
+ expect(results[0].success).toBe(true);
166
+ // Should include employment/contract terms and person name extraction logic
167
+ expect(results[0].suggestedName).toMatch(/employment|contract/);
168
+ expect(results[0].suggestedName).not.toContain('temp');
169
+ });
170
+
171
+ it('should extract name from medical records', async () => {
172
+ const fileInfo: FileInfo = {
173
+ name: 'medical-file.pdf',
174
+ path: '/misc/medical-file.pdf',
175
+ extension: '.pdf',
176
+ size: 1024 * 50,
177
+ createdAt: new Date('2024-01-15'),
178
+ modifiedAt: new Date('2024-02-01'),
179
+ parentFolder: 'misc', // Should be ignored
180
+ folderPath: ['home', 'misc']
181
+ };
182
+
183
+ const mockParser = {
184
+ parse: vi.fn().mockResolvedValue({
185
+ content: 'Medical record for Maria Rodriguez showing results of annual health checkup. Patient Maria Rodriguez underwent comprehensive examination including blood tests and physical assessment.',
186
+ metadata: {
187
+ title: 'Medical Record',
188
+ pages: 3
189
+ }
190
+ })
191
+ };
192
+
193
+ vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
194
+
195
+ const results = await fileRenamer.renameFiles([fileInfo]);
196
+
197
+ expect(results[0].success).toBe(true);
198
+ // Should start with detected person name and include medical terms
199
+ expect(results[0].suggestedName).toMatch(/^maria-rodriguez/);
200
+ expect(results[0].suggestedName).toMatch(/medical/);
201
+ expect(results[0].suggestedName).not.toContain('misc');
202
+ });
203
+
204
+ it('should extract name from certificate documents', async () => {
205
+ const fileInfo: FileInfo = {
206
+ name: 'cert.pdf',
207
+ path: '/downloads/cert.pdf',
208
+ extension: '.pdf',
209
+ size: 1024 * 25,
210
+ createdAt: new Date('2024-01-15'),
211
+ modifiedAt: new Date('2024-02-01'),
212
+ parentFolder: 'downloads', // Should be ignored
213
+ folderPath: ['home', 'downloads']
214
+ };
215
+
216
+ const mockParser = {
217
+ parse: vi.fn().mockResolvedValue({
218
+ content: 'Certificate of completion awarded to David Johnson for successfully completing the Advanced Web Development course. This certificate is issued to David Johnson in recognition of academic achievement.',
219
+ metadata: {
220
+ title: 'Certificate of Completion',
221
+ pages: 1
222
+ }
223
+ })
224
+ };
225
+
226
+ vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
227
+
228
+ const results = await fileRenamer.renameFiles([fileInfo]);
229
+
230
+ expect(results[0].success).toBe(true);
231
+ // Should include certificate/completion terms
232
+ expect(results[0].suggestedName).toMatch(/certificate|completion/);
233
+ expect(results[0].suggestedName).not.toContain('downloads');
234
+ });
235
+
236
+ it('should handle multiple names and pick the primary one', async () => {
237
+ const fileInfo: FileInfo = {
238
+ name: 'wedding-invite.pdf',
239
+ path: '/other/wedding-invite.pdf',
240
+ extension: '.pdf',
241
+ size: 1024 * 30,
242
+ createdAt: new Date('2024-01-15'),
243
+ modifiedAt: new Date('2024-02-01'),
244
+ parentFolder: 'other', // Should be ignored
245
+ folderPath: ['home', 'other']
246
+ };
247
+
248
+ const mockParser = {
249
+ parse: vi.fn().mockResolvedValue({
250
+ content: 'Wedding ceremony invitation for Sarah Williams and Michael Brown. You are invited to celebrate the marriage of Sarah Williams and Michael Brown on June 15th, 2024.',
251
+ metadata: {
252
+ title: 'Wedding Invitation',
253
+ pages: 1
254
+ }
255
+ })
256
+ };
257
+
258
+ vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
259
+
260
+ const results = await fileRenamer.renameFiles([fileInfo]);
261
+
262
+ expect(results[0].success).toBe(true);
263
+ // Should include wedding terms and pick a name
264
+ expect(results[0].suggestedName).toMatch(/sarah-williams/);
265
+ expect(results[0].suggestedName).toMatch(/wedding/);
266
+ expect(results[0].suggestedName).not.toContain('other');
267
+ });
268
+ });
269
+
270
+ describe('Folder Name Filtering', () => {
271
+ it('should ignore common irrelevant folder names', async () => {
272
+ const irrelevantFolders = ['no', 'temp', 'downloads', 'misc', 'other', 'files'];
273
+
274
+ for (const folderName of irrelevantFolders) {
275
+ const fileInfo: FileInfo = {
276
+ name: 'document.pdf',
277
+ path: `/${folderName}/document.pdf`,
278
+ extension: '.pdf',
279
+ size: 1024 * 40,
280
+ createdAt: new Date('2024-01-15'),
281
+ modifiedAt: new Date('2024-02-01'),
282
+ parentFolder: folderName,
283
+ folderPath: ['home', folderName]
284
+ };
285
+
286
+ const mockParser = {
287
+ parse: vi.fn().mockResolvedValue({
288
+ content: 'Document summary report for quarterly business analysis and strategic planning initiatives.',
289
+ metadata: {
290
+ title: 'Business Report',
291
+ pages: 10
292
+ }
293
+ })
294
+ };
295
+
296
+ vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
297
+
298
+ const results = await fileRenamer.renameFiles([fileInfo]);
299
+
300
+ expect(results[0].success).toBe(true);
301
+ expect(results[0].suggestedName).not.toContain(folderName);
302
+ expect(results[0].suggestedName).toMatch(/document.*summary.*report/);
303
+ }
304
+ });
305
+
306
+ it('should handle meaningful folder names without excluding them entirely', async () => {
307
+ const fileInfo: FileInfo = {
308
+ name: 'report.pdf',
309
+ path: '/financial-reports/report.pdf',
310
+ extension: '.pdf',
311
+ size: 1024 * 60,
312
+ createdAt: new Date('2024-01-15'),
313
+ modifiedAt: new Date('2024-02-01'),
314
+ parentFolder: 'financial-reports', // This is meaningful
315
+ folderPath: ['home', 'business', 'financial-reports']
316
+ };
317
+
318
+ const mockParser = {
319
+ parse: vi.fn().mockResolvedValue({
320
+ content: 'Annual financial summary report covering revenue, expenses, and profit analysis for fiscal year 2023.',
321
+ metadata: {
322
+ title: 'Financial Summary',
323
+ pages: 15
324
+ }
325
+ })
326
+ };
327
+
328
+ vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
329
+
330
+ const results = await fileRenamer.renameFiles([fileInfo]);
331
+
332
+ expect(results[0].success).toBe(true);
333
+ // Should focus on content, not necessarily include folder name
334
+ expect(results[0].suggestedName).toMatch(/document.*summary.*report/);
335
+ // But the folder context should still be available to the AI
336
+ expect(fileInfo.parentFolder).toBe('financial-reports');
337
+ });
338
+ });
339
+
340
+ describe('Edge Cases and Error Handling', () => {
341
+ it('should handle documents without clear person names', async () => {
342
+ const fileInfo: FileInfo = {
343
+ name: 'generic.pdf',
344
+ path: '/no/generic.pdf',
345
+ extension: '.pdf',
346
+ size: 1024 * 20,
347
+ createdAt: new Date('2024-01-15'),
348
+ modifiedAt: new Date('2024-02-01'),
349
+ parentFolder: 'no',
350
+ folderPath: ['home', 'no']
351
+ };
352
+
353
+ const mockParser = {
354
+ parse: vi.fn().mockResolvedValue({
355
+ content: 'This is a general business document containing policy information and guidelines for company operations.',
356
+ metadata: {
357
+ title: 'Policy Document',
358
+ pages: 5
359
+ }
360
+ })
361
+ };
362
+
363
+ vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
364
+
365
+ const results = await fileRenamer.renameFiles([fileInfo]);
366
+
367
+ expect(results[0].success).toBe(true);
368
+ expect(results[0].suggestedName).toMatch(/document.*summary.*report/);
369
+ });
370
+
371
+ it('should handle person names with special characters or formatting', async () => {
372
+ const fileInfo: FileInfo = {
373
+ name: 'contract.pdf',
374
+ path: '/temp/contract.pdf',
375
+ extension: '.pdf',
376
+ size: 1024 * 45,
377
+ createdAt: new Date('2024-01-15'),
378
+ modifiedAt: new Date('2024-02-01'),
379
+ parentFolder: 'temp',
380
+ folderPath: ['home', 'temp']
381
+ };
382
+
383
+ const mockParser = {
384
+ parse: vi.fn().mockResolvedValue({
385
+ content: 'Employment agreement between Corporation XYZ and María José García-López for the position of Senior Data Scientist.',
386
+ metadata: {
387
+ title: 'Employment Contract',
388
+ pages: 6
389
+ }
390
+ })
391
+ };
392
+
393
+ vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
394
+
395
+ const results = await fileRenamer.renameFiles([fileInfo]);
396
+
397
+ expect(results[0].success).toBe(true);
398
+ // Should include employment/contract terms
399
+ expect(results[0].suggestedName).toMatch(/employment|contract/);
400
+ });
401
+ });
402
+
403
+ describe('Real-world Scenario Tests', () => {
404
+ it('should handle the exact Setareh visa case from user example', async () => {
405
+ const fileInfo: FileInfo = {
406
+ name: 'visitor-visa-application-for-family-in-canada.pdf',
407
+ path: '/#NO/visitor-visa-application-for-family-in-canada.pdf',
408
+ extension: '.pdf',
409
+ size: 1024 * 150,
410
+ createdAt: new Date('2024-01-15'),
411
+ modifiedAt: new Date('2024-02-01'),
412
+ parentFolder: '#NO', // Note: even with special characters
413
+ folderPath: ['home', 'documents', '#NO']
414
+ };
415
+
416
+ const mockParser = {
417
+ parse: vi.fn().mockResolvedValue({
418
+ content: 'Application for Visitor Visa (Temporary Resident Visa) for Setareh and family members to visit Canada. Applicant: Setareh [Last Name]. Purpose: Tourism and family visit. Duration: 3 weeks.',
419
+ metadata: {
420
+ title: 'Visitor Visa Application - TRV',
421
+ author: 'Immigration Canada',
422
+ pages: 6
423
+ }
424
+ })
425
+ };
426
+
427
+ vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
428
+
429
+ const results = await fileRenamer.renameFiles([fileInfo]);
430
+
431
+ expect(results[0].success).toBe(true);
432
+ // Should start with detected person name and include visa terms
433
+ expect(results[0].suggestedName).toMatch(/^setareh/);
434
+ expect(results[0].suggestedName).toMatch(/visa/);
435
+ // Should NOT include "no" or "#NO"
436
+ expect(results[0].suggestedName).not.toContain('no');
437
+ expect(results[0].suggestedName).not.toContain('#no');
438
+ });
439
+ });
440
+ });
@@ -113,7 +113,9 @@ describe('CLI Commands', () => {
113
113
  case: 'kebab-case',
114
114
  template: 'general',
115
115
  name: undefined,
116
- date: 'none'
116
+ date: 'none',
117
+ baseUrl: undefined,
118
+ model: undefined
117
119
  });
118
120
  });
119
121
 
@@ -133,7 +135,9 @@ describe('CLI Commands', () => {
133
135
  case: 'kebab-case',
134
136
  template: 'general',
135
137
  name: undefined,
136
- date: 'none'
138
+ date: 'none',
139
+ baseUrl: undefined,
140
+ model: undefined
137
141
  });
138
142
  });
139
143
 
@@ -156,7 +160,9 @@ describe('CLI Commands', () => {
156
160
  case: 'kebab-case',
157
161
  template: 'general',
158
162
  name: undefined,
159
- date: 'none'
163
+ date: 'none',
164
+ baseUrl: undefined,
165
+ model: undefined
160
166
  });
161
167
  });
162
168
  });
@@ -101,10 +101,10 @@ describe('LMStudioService', () => {
101
101
  const requestBody = JSON.parse(fetchCall[1].body);
102
102
  const userMessage = requestBody.messages.find((m: any) => m.role === 'user');
103
103
 
104
- expect(userMessage.content).toContain('Document title: "Contract Agreement"');
105
- expect(userMessage.content).toContain('Author: "John Doe"');
106
- expect(userMessage.content).toContain('Subject: "Service Contract"');
107
- expect(userMessage.content).toContain('Located in folder: "contracts"');
104
+ expect(userMessage.content).toContain('- Title: Contract Agreement');
105
+ expect(userMessage.content).toContain('- Author: John Doe');
106
+ expect(userMessage.content).toContain('- Subject: Service Contract');
107
+ expect(userMessage.content).toContain('- Parent folder: contracts');
108
108
  });
109
109
 
110
110
  it('should use proper OpenAI API parameters', async () => {
@@ -92,10 +92,10 @@ describe('OllamaService', () => {
92
92
  const requestBody = JSON.parse(fetchCall[1].body);
93
93
  const userMessage = requestBody.messages.find((m: any) => m.role === 'user');
94
94
 
95
- expect(userMessage.content).toContain('Document title: "Contract Agreement"');
96
- expect(userMessage.content).toContain('Author: "John Doe"');
97
- expect(userMessage.content).toContain('Subject: "Service Contract"');
98
- expect(userMessage.content).toContain('Located in folder: "contracts"');
95
+ expect(userMessage.content).toContain('- Title: Contract Agreement');
96
+ expect(userMessage.content).toContain('- Author: John Doe');
97
+ expect(userMessage.content).toContain('- Subject: Service Contract');
98
+ expect(userMessage.content).toContain('- Parent folder: contracts');
99
99
  });
100
100
 
101
101
  it('should sanitize response content', async () => {