@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.
- package/.github/workflows/build.yml +6 -0
- package/.github/workflows/publish.yml +59 -11
- package/.github/workflows/test.yml +1 -0
- package/CHANGELOG.md +102 -0
- package/RELEASE.md +15 -3
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +14 -7
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/rename.js +1 -1
- package/dist/cli/rename.js.map +1 -1
- package/dist/parsers/factory.d.ts +2 -1
- package/dist/parsers/factory.d.ts.map +1 -1
- package/dist/parsers/factory.js +9 -6
- package/dist/parsers/factory.js.map +1 -1
- package/dist/parsers/pdf-parser.d.ts +1 -0
- package/dist/parsers/pdf-parser.d.ts.map +1 -1
- package/dist/parsers/pdf-parser.js +20 -1
- package/dist/parsers/pdf-parser.js.map +1 -1
- package/dist/services/claude-service.d.ts.map +1 -1
- package/dist/services/claude-service.js +59 -65
- package/dist/services/claude-service.js.map +1 -1
- package/dist/services/lmstudio-service.d.ts.map +1 -1
- package/dist/services/lmstudio-service.js +16 -19
- package/dist/services/lmstudio-service.js.map +1 -1
- package/dist/services/ollama-service.d.ts +1 -0
- package/dist/services/ollama-service.d.ts.map +1 -1
- package/dist/services/ollama-service.js +61 -32
- package/dist/services/ollama-service.js.map +1 -1
- package/dist/services/openai-service.d.ts.map +1 -1
- package/dist/services/openai-service.js +59 -66
- package/dist/services/openai-service.js.map +1 -1
- package/dist/utils/ai-prompts.d.ts +20 -0
- package/dist/utils/ai-prompts.d.ts.map +1 -0
- package/dist/utils/ai-prompts.js +71 -0
- package/dist/utils/ai-prompts.js.map +1 -0
- package/dist/utils/pdf-to-image.d.ts +11 -0
- package/dist/utils/pdf-to-image.d.ts.map +1 -0
- package/dist/utils/pdf-to-image.js +91 -0
- package/dist/utils/pdf-to-image.js.map +1 -0
- package/eng.traineddata +0 -0
- package/package.json +5 -2
- package/src/cli/commands.ts +14 -7
- package/src/cli/rename.ts +1 -1
- package/src/parsers/factory.ts +11 -7
- package/src/parsers/pdf-parser.ts +22 -1
- package/src/services/claude-service.ts +63 -58
- package/src/services/lmstudio-service.ts +20 -20
- package/src/services/ollama-service.ts +75 -31
- package/src/services/openai-service.ts +63 -59
- package/src/utils/ai-prompts.ts +76 -0
- package/src/utils/pdf-to-image.ts +124 -0
- package/tests/integration/ai-prompting.test.ts +386 -0
- package/tests/integration/end-to-end.test.ts +9 -9
- package/tests/integration/person-name-extraction.test.ts +440 -0
- package/tests/unit/cli/commands.test.ts +9 -3
- package/tests/unit/services/lmstudio-service.test.ts +4 -4
- package/tests/unit/services/ollama-service.test.ts +4 -4
- package/tests/unit/utils/ai-prompts.test.ts +213 -0
- 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('
|
|
105
|
-
expect(userMessage.content).toContain('Author:
|
|
106
|
-
expect(userMessage.content).toContain('Subject:
|
|
107
|
-
expect(userMessage.content).toContain('
|
|
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('
|
|
96
|
-
expect(userMessage.content).toContain('Author:
|
|
97
|
-
expect(userMessage.content).toContain('Subject:
|
|
98
|
-
expect(userMessage.content).toContain('
|
|
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 () => {
|