@amirdaraee/namewise 0.5.3 → 0.5.5
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/CHANGELOG.md +9 -0
- package/README.md +60 -60
- package/dist/index.js +0 -0
- package/dist/services/claude-service.d.ts.map +1 -1
- package/dist/services/claude-service.js +3 -0
- package/dist/services/claude-service.js.map +1 -1
- package/dist/services/lmstudio-service.d.ts +1 -0
- package/dist/services/lmstudio-service.d.ts.map +1 -1
- package/dist/services/lmstudio-service.js +16 -1
- 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 +16 -1
- 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 +3 -0
- package/dist/services/openai-service.js.map +1 -1
- package/package.json +8 -8
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -82
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -61
- package/.github/workflows/auto-release.yml +0 -81
- package/.github/workflows/build.yml +0 -55
- package/.github/workflows/publish.yml +0 -134
- package/.github/workflows/test.yml +0 -45
- package/eng.traineddata +0 -0
- package/src/cli/commands.ts +0 -64
- package/src/cli/rename.ts +0 -171
- package/src/index.ts +0 -54
- package/src/parsers/excel-parser.ts +0 -66
- package/src/parsers/factory.ts +0 -38
- package/src/parsers/pdf-parser.ts +0 -99
- package/src/parsers/text-parser.ts +0 -43
- package/src/parsers/word-parser.ts +0 -50
- package/src/services/ai-factory.ts +0 -39
- package/src/services/claude-service.ts +0 -119
- package/src/services/file-renamer.ts +0 -141
- package/src/services/lmstudio-service.ts +0 -161
- package/src/services/ollama-service.ts +0 -191
- package/src/services/openai-service.ts +0 -117
- package/src/types/index.ts +0 -76
- package/src/types/pdf-extraction.d.ts +0 -7
- package/src/utils/ai-prompts.ts +0 -76
- package/src/utils/file-templates.ts +0 -275
- package/src/utils/naming-conventions.ts +0 -67
- package/src/utils/pdf-to-image.ts +0 -137
- package/tests/data/console-test-1.txt +0 -1
- package/tests/data/console-test-2.txt +0 -1
- package/tests/data/console-test-long-filename-for-display-testing.txt +0 -1
- package/tests/data/empty-file.txt +0 -0
- package/tests/data/failure.txt +0 -1
- package/tests/data/file1.txt +0 -1
- package/tests/data/file2.txt +0 -1
- package/tests/data/much-longer-filename-to-test-clearing.txt +0 -1
- package/tests/data/sample-markdown.md +0 -9
- package/tests/data/sample-pdf.pdf +0 -0
- package/tests/data/sample-text.txt +0 -25
- package/tests/data/short.txt +0 -1
- package/tests/data/single-file.txt +0 -1
- package/tests/data/success.txt +0 -1
- package/tests/data/this-is-a-very-long-filename-that-should-be-truncated-for-better-display-purposes.txt +0 -1
- package/tests/data/very-long-filename-that-should-be-cleared-properly.txt +0 -1
- package/tests/data/x.txt +0 -1
- package/tests/integration/ai-prompting.test.ts +0 -386
- package/tests/integration/end-to-end.test.ts +0 -209
- package/tests/integration/person-name-extraction.test.ts +0 -440
- package/tests/integration/workflow.test.ts +0 -336
- package/tests/mocks/mock-ai-service.ts +0 -58
- package/tests/unit/cli/commands.test.ts +0 -169
- package/tests/unit/parsers/factory.test.ts +0 -100
- package/tests/unit/parsers/pdf-parser.test.ts +0 -63
- package/tests/unit/parsers/text-parser.test.ts +0 -85
- package/tests/unit/services/ai-factory.test.ts +0 -85
- package/tests/unit/services/claude-service.test.ts +0 -188
- package/tests/unit/services/file-renamer.test.ts +0 -514
- package/tests/unit/services/lmstudio-service.test.ts +0 -326
- package/tests/unit/services/ollama-service.test.ts +0 -264
- package/tests/unit/services/openai-service.test.ts +0 -196
- package/tests/unit/utils/ai-prompts.test.ts +0 -213
- package/tests/unit/utils/file-templates.test.ts +0 -199
- package/tests/unit/utils/naming-conventions.test.ts +0 -88
- package/tests/unit/utils/pdf-to-image.test.ts +0 -127
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -30
|
@@ -1,386 +0,0 @@
|
|
|
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
|
-
// Mock AI Provider that captures prompts for testing
|
|
7
|
-
class MockAIProvider implements AIProvider {
|
|
8
|
-
name = 'MockAI';
|
|
9
|
-
public capturedPrompts: string[] = [];
|
|
10
|
-
public capturedContext: any[] = [];
|
|
11
|
-
|
|
12
|
-
async generateFileName(
|
|
13
|
-
content: string,
|
|
14
|
-
originalName: string,
|
|
15
|
-
namingConvention = 'kebab-case',
|
|
16
|
-
category = 'general',
|
|
17
|
-
fileInfo?: FileInfo
|
|
18
|
-
): Promise<string> {
|
|
19
|
-
// Capture the prompt for analysis (we can't see the actual prompt here, but we can test the result)
|
|
20
|
-
this.capturedContext.push({
|
|
21
|
-
content,
|
|
22
|
-
originalName,
|
|
23
|
-
namingConvention,
|
|
24
|
-
category,
|
|
25
|
-
fileInfo
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Mock intelligent responses based on content (AI generates core name, template applies later)
|
|
29
|
-
if (content.includes('Setareh') && content.includes('visa')) {
|
|
30
|
-
return 'setareh-visitor-visa-application-for-family-members-in-canada';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (content.includes('John Doe') && content.includes('contract')) {
|
|
34
|
-
return 'john-doe-employment-contract-software-engineer';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (content.includes('Maria') && content.includes('wedding')) {
|
|
38
|
-
return 'maria-wedding-ceremony-invitation-june-2024';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Default response
|
|
42
|
-
return 'generic-document-filename';
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
describe('AI Prompting Integration Tests', () => {
|
|
47
|
-
let mockAI: MockAIProvider;
|
|
48
|
-
let fileRenamer: FileRenamer;
|
|
49
|
-
let config: Config;
|
|
50
|
-
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
mockAI = new MockAIProvider();
|
|
53
|
-
const parserFactory = new DocumentParserFactory();
|
|
54
|
-
|
|
55
|
-
config = {
|
|
56
|
-
provider: 'claude',
|
|
57
|
-
apiKey: 'test-key',
|
|
58
|
-
maxFileSize: 10 * 1024 * 1024,
|
|
59
|
-
namingConvention: 'kebab-case',
|
|
60
|
-
templateOptions: {
|
|
61
|
-
category: 'document',
|
|
62
|
-
personalName: 'TestUser',
|
|
63
|
-
dateFormat: 'YYYY-MM-DD'
|
|
64
|
-
},
|
|
65
|
-
dryRun: true
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
fileRenamer = new FileRenamer(parserFactory, mockAI, config);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('Person Name Detection and Placement', () => {
|
|
72
|
-
it('should place person names at the beginning when detected in content', async () => {
|
|
73
|
-
const fileInfo: FileInfo = {
|
|
74
|
-
name: 'visa-application.pdf',
|
|
75
|
-
path: '/no/visa-application.pdf', // Note: irrelevant folder name "no"
|
|
76
|
-
extension: '.pdf',
|
|
77
|
-
size: 1024 * 50,
|
|
78
|
-
createdAt: new Date('2024-01-15'),
|
|
79
|
-
modifiedAt: new Date('2024-02-01'),
|
|
80
|
-
parentFolder: 'no', // Should be ignored
|
|
81
|
-
folderPath: ['home', 'documents', 'no']
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// Mock the parser to return content with person name
|
|
85
|
-
const mockParser = {
|
|
86
|
-
parse: vi.fn().mockResolvedValue({
|
|
87
|
-
content: 'Visitor visa application for Setareh Ahmadi and family members to visit Canada. This application includes documentation for tourism purposes.',
|
|
88
|
-
metadata: {
|
|
89
|
-
title: 'Visa Application Form',
|
|
90
|
-
pages: 3
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// Mock the parser factory
|
|
96
|
-
vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
|
|
97
|
-
|
|
98
|
-
const results = await fileRenamer.renameFiles([fileInfo]);
|
|
99
|
-
|
|
100
|
-
expect(results[0].success).toBe(true);
|
|
101
|
-
// Template applies: {content}-{personalName}-{date}
|
|
102
|
-
expect(results[0].suggestedName).toMatch(/setareh-visitor-visa-application-for-family-members-in-canada-testuser-\d{4}-\d{2}-\d{2}\.pdf/);
|
|
103
|
-
|
|
104
|
-
// Verify the AI was called with the right context
|
|
105
|
-
expect(mockAI.capturedContext).toHaveLength(1);
|
|
106
|
-
expect(mockAI.capturedContext[0].content).toContain('Setareh Ahmadi');
|
|
107
|
-
expect(mockAI.capturedContext[0].fileInfo.parentFolder).toBe('no');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should handle contract documents with person names', async () => {
|
|
111
|
-
const fileInfo: FileInfo = {
|
|
112
|
-
name: 'employment-contract.pdf',
|
|
113
|
-
path: '/contracts/employment-contract.pdf',
|
|
114
|
-
extension: '.pdf',
|
|
115
|
-
size: 1024 * 75,
|
|
116
|
-
createdAt: new Date('2024-01-15'),
|
|
117
|
-
modifiedAt: new Date('2024-02-01'),
|
|
118
|
-
parentFolder: 'contracts',
|
|
119
|
-
folderPath: ['home', 'legal', 'contracts']
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const mockParser = {
|
|
123
|
-
parse: vi.fn().mockResolvedValue({
|
|
124
|
-
content: 'Employment Contract between TechCorp Inc. and John Doe for the position of Senior Software Engineer. This contract outlines terms of employment, salary, and benefits.',
|
|
125
|
-
metadata: {
|
|
126
|
-
title: 'Employment Agreement',
|
|
127
|
-
author: 'Legal Department',
|
|
128
|
-
pages: 8
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
|
|
134
|
-
|
|
135
|
-
const results = await fileRenamer.renameFiles([fileInfo]);
|
|
136
|
-
|
|
137
|
-
expect(results[0].success).toBe(true);
|
|
138
|
-
// Template applies: {content}-{personalName}-{date}
|
|
139
|
-
expect(results[0].suggestedName).toMatch(/john-doe-employment-contract-software-engineer-testuser-\d{4}-\d{2}-\d{2}\.pdf/);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should handle wedding documents with person names', async () => {
|
|
143
|
-
const fileInfo: FileInfo = {
|
|
144
|
-
name: 'invitation.pdf',
|
|
145
|
-
path: '/events/invitation.pdf',
|
|
146
|
-
extension: '.pdf',
|
|
147
|
-
size: 1024 * 25,
|
|
148
|
-
createdAt: new Date('2024-01-15'),
|
|
149
|
-
modifiedAt: new Date('2024-02-01'),
|
|
150
|
-
parentFolder: 'events',
|
|
151
|
-
folderPath: ['home', 'personal', 'events']
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const mockParser = {
|
|
155
|
-
parse: vi.fn().mockResolvedValue({
|
|
156
|
-
content: 'You are cordially invited to the wedding ceremony of Maria Rodriguez and David Thompson on June 15th, 2024 at St. Mary\'s Church.',
|
|
157
|
-
metadata: {
|
|
158
|
-
title: 'Wedding Invitation',
|
|
159
|
-
pages: 1
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
|
|
165
|
-
|
|
166
|
-
const results = await fileRenamer.renameFiles([fileInfo]);
|
|
167
|
-
|
|
168
|
-
expect(results[0].success).toBe(true);
|
|
169
|
-
// Template applies: {content}-{personalName}-{date}
|
|
170
|
-
expect(results[0].suggestedName).toMatch(/maria-wedding-ceremony-invitation-june-2024-testuser-\d{4}-\d{2}-\d{2}\.pdf/);
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
describe('Folder Name Filtering', () => {
|
|
175
|
-
it('should ignore irrelevant folder names like "no"', async () => {
|
|
176
|
-
const fileInfo: FileInfo = {
|
|
177
|
-
name: 'document.pdf',
|
|
178
|
-
path: '/no/document.pdf',
|
|
179
|
-
extension: '.pdf',
|
|
180
|
-
size: 1024 * 30,
|
|
181
|
-
createdAt: new Date('2024-01-15'),
|
|
182
|
-
modifiedAt: new Date('2024-02-01'),
|
|
183
|
-
parentFolder: 'no', // This should be ignored by AI
|
|
184
|
-
folderPath: ['home', 'downloads', 'no']
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const mockParser = {
|
|
188
|
-
parse: vi.fn().mockResolvedValue({
|
|
189
|
-
content: 'This is a general business report about quarterly sales performance and market analysis.',
|
|
190
|
-
metadata: {
|
|
191
|
-
title: 'Q4 Sales Report',
|
|
192
|
-
pages: 12
|
|
193
|
-
}
|
|
194
|
-
})
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
|
|
198
|
-
|
|
199
|
-
const results = await fileRenamer.renameFiles([fileInfo]);
|
|
200
|
-
|
|
201
|
-
expect(results[0].success).toBe(true);
|
|
202
|
-
// Should not include "no" in the filename, template applies: {content}-{personalName}-{date}
|
|
203
|
-
expect(results[0].suggestedName).not.toContain('no');
|
|
204
|
-
expect(results[0].suggestedName).toMatch(/generic-document-filename-testuser-\d{4}-\d{2}-\d{2}\.pdf/);
|
|
205
|
-
|
|
206
|
-
// Verify the folder context was passed but should be ignored by prompt instructions
|
|
207
|
-
expect(mockAI.capturedContext[0].fileInfo.parentFolder).toBe('no');
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('should handle meaningful folder names appropriately', async () => {
|
|
211
|
-
const fileInfo: FileInfo = {
|
|
212
|
-
name: 'report.pdf',
|
|
213
|
-
path: '/financial-reports/report.pdf',
|
|
214
|
-
extension: '.pdf',
|
|
215
|
-
size: 1024 * 45,
|
|
216
|
-
createdAt: new Date('2024-01-15'),
|
|
217
|
-
modifiedAt: new Date('2024-02-01'),
|
|
218
|
-
parentFolder: 'financial-reports',
|
|
219
|
-
folderPath: ['home', 'business', 'financial-reports']
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const mockParser = {
|
|
223
|
-
parse: vi.fn().mockResolvedValue({
|
|
224
|
-
content: 'Annual financial statement showing revenue, expenses, and profit margins for fiscal year 2023.',
|
|
225
|
-
metadata: {
|
|
226
|
-
title: 'Annual Financial Statement',
|
|
227
|
-
pages: 20
|
|
228
|
-
}
|
|
229
|
-
})
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
|
|
233
|
-
|
|
234
|
-
const results = await fileRenamer.renameFiles([fileInfo]);
|
|
235
|
-
|
|
236
|
-
expect(results[0].success).toBe(true);
|
|
237
|
-
// The AI gets the folder context but should focus on content, not folder name
|
|
238
|
-
expect(mockAI.capturedContext[0].fileInfo.parentFolder).toBe('financial-reports');
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
describe('Template Integration with AI Prompting', () => {
|
|
243
|
-
it('should work with document template and person detection', async () => {
|
|
244
|
-
config.templateOptions.category = 'document';
|
|
245
|
-
config.templateOptions.personalName = 'TestUser';
|
|
246
|
-
|
|
247
|
-
const fileInfo: FileInfo = {
|
|
248
|
-
name: 'contract.pdf',
|
|
249
|
-
path: '/legal/contract.pdf',
|
|
250
|
-
extension: '.pdf',
|
|
251
|
-
size: 1024 * 60,
|
|
252
|
-
createdAt: new Date('2024-01-15'),
|
|
253
|
-
modifiedAt: new Date('2024-02-01'),
|
|
254
|
-
parentFolder: 'legal',
|
|
255
|
-
folderPath: ['home', 'legal']
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
const mockParser = {
|
|
259
|
-
parse: vi.fn().mockResolvedValue({
|
|
260
|
-
content: 'Service agreement between Company ABC and Setareh Ahmadi for consulting services.',
|
|
261
|
-
metadata: {
|
|
262
|
-
title: 'Service Agreement',
|
|
263
|
-
pages: 5
|
|
264
|
-
}
|
|
265
|
-
})
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
|
|
269
|
-
|
|
270
|
-
const results = await fileRenamer.renameFiles([fileInfo]);
|
|
271
|
-
|
|
272
|
-
expect(results[0].success).toBe(true);
|
|
273
|
-
// AI should detect "Setareh" and include it at the beginning, but our mock doesn't match this content
|
|
274
|
-
expect(results[0].suggestedName).toMatch(/generic-document-filename-testuser-\d{4}-\d{2}-\d{2}\.pdf/);
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it('should pass correct category information to AI', async () => {
|
|
278
|
-
config.templateOptions.category = 'movie';
|
|
279
|
-
|
|
280
|
-
const fileInfo: FileInfo = {
|
|
281
|
-
name: 'film.mp4',
|
|
282
|
-
path: '/movies/film.mp4',
|
|
283
|
-
extension: '.mp4',
|
|
284
|
-
size: 1024 * 1024 * 500, // 500MB
|
|
285
|
-
createdAt: new Date('2024-01-15'),
|
|
286
|
-
modifiedAt: new Date('2024-02-01'),
|
|
287
|
-
parentFolder: 'movies',
|
|
288
|
-
folderPath: ['home', 'media', 'movies']
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
const mockParser = {
|
|
292
|
-
parse: vi.fn().mockResolvedValue({
|
|
293
|
-
content: 'Movie file metadata or subtitle content here.',
|
|
294
|
-
metadata: {}
|
|
295
|
-
})
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
|
|
299
|
-
|
|
300
|
-
// Reduce file size to avoid maxFileSize limit
|
|
301
|
-
fileInfo.size = 1024 * 1024; // 1MB instead of 500MB
|
|
302
|
-
|
|
303
|
-
const results = await fileRenamer.renameFiles([fileInfo]);
|
|
304
|
-
|
|
305
|
-
expect(results[0].success).toBe(true);
|
|
306
|
-
expect(mockAI.capturedContext[0].category).toBe('movie');
|
|
307
|
-
// Movie template doesn't include personalName, just {content}-{year}
|
|
308
|
-
expect(results[0].suggestedName).toBe('generic-document-filename.mp4');
|
|
309
|
-
});
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
describe('Naming Convention Integration', () => {
|
|
313
|
-
it('should pass naming convention to AI prompting system', async () => {
|
|
314
|
-
config.namingConvention = 'snake_case';
|
|
315
|
-
|
|
316
|
-
const fileInfo: FileInfo = {
|
|
317
|
-
name: 'test.txt',
|
|
318
|
-
path: '/test.txt',
|
|
319
|
-
extension: '.txt',
|
|
320
|
-
size: 1024,
|
|
321
|
-
createdAt: new Date('2024-01-15'),
|
|
322
|
-
modifiedAt: new Date('2024-02-01'),
|
|
323
|
-
parentFolder: 'root',
|
|
324
|
-
folderPath: ['root']
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
const mockParser = {
|
|
328
|
-
parse: vi.fn().mockResolvedValue({
|
|
329
|
-
content: 'Test document content.',
|
|
330
|
-
metadata: {}
|
|
331
|
-
})
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
|
|
335
|
-
|
|
336
|
-
const results = await fileRenamer.renameFiles([fileInfo]);
|
|
337
|
-
|
|
338
|
-
expect(results[0].success).toBe(true);
|
|
339
|
-
expect(mockAI.capturedContext[0].namingConvention).toBe('snake_case');
|
|
340
|
-
});
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
describe('Content and Metadata Passing', () => {
|
|
344
|
-
it('should pass all relevant metadata to AI service', async () => {
|
|
345
|
-
const fileInfo: FileInfo = {
|
|
346
|
-
name: 'comprehensive.pdf',
|
|
347
|
-
path: '/docs/comprehensive.pdf',
|
|
348
|
-
extension: '.pdf',
|
|
349
|
-
size: 1024 * 100,
|
|
350
|
-
createdAt: new Date('2024-01-15'),
|
|
351
|
-
modifiedAt: new Date('2024-02-01'),
|
|
352
|
-
parentFolder: 'docs',
|
|
353
|
-
folderPath: ['home', 'documents', 'docs'],
|
|
354
|
-
documentMetadata: {
|
|
355
|
-
title: 'Comprehensive Report',
|
|
356
|
-
author: 'Dr. Smith',
|
|
357
|
-
creator: 'LaTeX',
|
|
358
|
-
subject: 'Research Findings',
|
|
359
|
-
keywords: ['research', 'analysis', 'data'],
|
|
360
|
-
creationDate: new Date('2024-01-10'),
|
|
361
|
-
modificationDate: new Date('2024-01-25'),
|
|
362
|
-
pages: 45,
|
|
363
|
-
wordCount: 12000
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
const mockParser = {
|
|
368
|
-
parse: vi.fn().mockResolvedValue({
|
|
369
|
-
content: 'Comprehensive research report on data analysis methodologies and findings.',
|
|
370
|
-
metadata: fileInfo.documentMetadata
|
|
371
|
-
})
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
|
|
375
|
-
|
|
376
|
-
const results = await fileRenamer.renameFiles([fileInfo]);
|
|
377
|
-
|
|
378
|
-
expect(results[0].success).toBe(true);
|
|
379
|
-
|
|
380
|
-
const capturedContext = mockAI.capturedContext[0];
|
|
381
|
-
expect(capturedContext.fileInfo.documentMetadata.title).toBe('Comprehensive Report');
|
|
382
|
-
expect(capturedContext.fileInfo.documentMetadata.author).toBe('Dr. Smith');
|
|
383
|
-
expect(capturedContext.fileInfo.documentMetadata.keywords).toEqual(['research', 'analysis', 'data']);
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
});
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { promises as fs } from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { exec } from 'child_process';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
|
-
|
|
7
|
-
const execAsync = promisify(exec);
|
|
8
|
-
|
|
9
|
-
// Mock fs operations for integration tests
|
|
10
|
-
vi.mock('fs', async () => {
|
|
11
|
-
const actual = await vi.importActual('fs');
|
|
12
|
-
return {
|
|
13
|
-
...actual,
|
|
14
|
-
promises: {
|
|
15
|
-
...actual.promises,
|
|
16
|
-
rename: vi.fn(),
|
|
17
|
-
access: vi.fn()
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe('End-to-End Integration Tests', () => {
|
|
23
|
-
const testDataDir = path.join(process.cwd(), 'tests/data');
|
|
24
|
-
const cliPath = path.join(process.cwd(), 'dist/index.js');
|
|
25
|
-
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
vi.clearAllMocks();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
vi.restoreAllMocks();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('CLI Integration', () => {
|
|
35
|
-
it('should show help message', async () => {
|
|
36
|
-
const { stdout } = await execAsync(`node ${cliPath} --help`);
|
|
37
|
-
|
|
38
|
-
expect(stdout).toContain('AI-powered CLI tool that intelligently renames files based on their content');
|
|
39
|
-
expect(stdout).toContain('rename [options] [directory]');
|
|
40
|
-
expect(stdout).toContain('Commands:');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should show rename command help', async () => {
|
|
44
|
-
const { stdout } = await execAsync(`node ${cliPath} rename --help`);
|
|
45
|
-
|
|
46
|
-
expect(stdout).toContain('Rename files in a directory based on their content using AI analysis');
|
|
47
|
-
expect(stdout).toContain('Arguments:');
|
|
48
|
-
expect(stdout).toContain('directory');
|
|
49
|
-
expect(stdout).toContain('Options:');
|
|
50
|
-
expect(stdout).toContain('-p, --provider');
|
|
51
|
-
expect(stdout).toContain('-k, --api-key');
|
|
52
|
-
expect(stdout).toContain('-c, --case');
|
|
53
|
-
expect(stdout).toContain('-t, --template');
|
|
54
|
-
expect(stdout).toContain('-n, --name');
|
|
55
|
-
expect(stdout).toContain('-d, --date');
|
|
56
|
-
expect(stdout).toContain('--dry-run');
|
|
57
|
-
expect(stdout).toContain('--max-size');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should show naming convention options in help', async () => {
|
|
61
|
-
const { stdout } = await execAsync(`node ${cliPath} rename --help`);
|
|
62
|
-
|
|
63
|
-
expect(stdout).toContain('kebab-case');
|
|
64
|
-
expect(stdout).toContain('snake_case');
|
|
65
|
-
expect(stdout).toContain('camelCase');
|
|
66
|
-
expect(stdout).toContain('PascalCase');
|
|
67
|
-
expect(stdout).toContain('lowercase');
|
|
68
|
-
expect(stdout).toContain('UPPERCASE');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should show template category options in help', async () => {
|
|
72
|
-
const { stdout } = await execAsync(`node ${cliPath} rename --help`);
|
|
73
|
-
|
|
74
|
-
expect(stdout).toContain('document');
|
|
75
|
-
expect(stdout).toContain('movie');
|
|
76
|
-
expect(stdout).toContain('music');
|
|
77
|
-
expect(stdout).toContain('series');
|
|
78
|
-
expect(stdout).toContain('photo');
|
|
79
|
-
expect(stdout).toContain('book');
|
|
80
|
-
expect(stdout).toContain('general');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should show date format options in help', async () => {
|
|
84
|
-
const { stdout } = await execAsync(`node ${cliPath} rename --help`);
|
|
85
|
-
|
|
86
|
-
expect(stdout).toContain('YYYY-MM-DD');
|
|
87
|
-
expect(stdout).toContain('YYYY');
|
|
88
|
-
expect(stdout).toContain('YYYYMMDD');
|
|
89
|
-
expect(stdout).toContain('none');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should show version', async () => {
|
|
93
|
-
const { stdout } = await execAsync(`node ${cliPath} --version`);
|
|
94
|
-
|
|
95
|
-
expect(stdout.trim()).toMatch(/^\d+\.\d+\.\d+$/);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should accept optional directory argument', async () => {
|
|
99
|
-
const { stdout } = await execAsync(`node ${cliPath} rename --help`);
|
|
100
|
-
|
|
101
|
-
// Verify directory is shown as optional (in brackets) in help
|
|
102
|
-
expect(stdout).toContain('[directory]');
|
|
103
|
-
expect(stdout).toContain('current directory');
|
|
104
|
-
expect(stdout).toContain('(default: ".")');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should handle non-existent directory', async () => {
|
|
108
|
-
try {
|
|
109
|
-
await execAsync(`node ${cliPath} rename /non/existent/directory --dry-run`, {
|
|
110
|
-
input: 'test-key\n'
|
|
111
|
-
});
|
|
112
|
-
expect.fail('Should have thrown an error');
|
|
113
|
-
} catch (error: any) {
|
|
114
|
-
expect(error.stderr || error.stdout).toContain('Error:');
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
describe('Full Workflow Integration', () => {
|
|
120
|
-
it('should process files with mock AI service (dry run)', async () => {
|
|
121
|
-
// This test would need actual AI service mocking at the CLI level
|
|
122
|
-
// For now, we'll test the structure
|
|
123
|
-
|
|
124
|
-
const testDir = path.join(testDataDir);
|
|
125
|
-
|
|
126
|
-
// Mock the AI service response by setting environment variables or config
|
|
127
|
-
process.env.MOCK_AI_RESPONSE = 'test-document-name';
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
// This would fail without real API key, but tests the flow
|
|
131
|
-
const command = `echo "test-key" | node ${cliPath} rename ${testDir} --dry-run --provider claude`;
|
|
132
|
-
|
|
133
|
-
// For a real test, we'd need to mock the AI service at a higher level
|
|
134
|
-
// This is a placeholder for the integration test structure
|
|
135
|
-
expect(true).toBe(true); // Placeholder assertion
|
|
136
|
-
} catch (error) {
|
|
137
|
-
// Expected in test environment without real API key
|
|
138
|
-
expect(true).toBe(true);
|
|
139
|
-
} finally {
|
|
140
|
-
delete process.env.MOCK_AI_RESPONSE;
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
describe('File Processing Integration', () => {
|
|
146
|
-
it('should detect supported files correctly', async () => {
|
|
147
|
-
// Create a temporary test directory structure
|
|
148
|
-
const tempDir = path.join(process.cwd(), 'temp-test');
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
await fs.mkdir(tempDir, { recursive: true });
|
|
152
|
-
await fs.writeFile(path.join(tempDir, 'test.txt'), 'Test content');
|
|
153
|
-
await fs.writeFile(path.join(tempDir, 'test.md'), '# Test markdown');
|
|
154
|
-
await fs.writeFile(path.join(tempDir, 'unsupported.xyz'), 'Unsupported file');
|
|
155
|
-
|
|
156
|
-
// The actual CLI would process only supported files
|
|
157
|
-
// This test validates the file detection logic
|
|
158
|
-
|
|
159
|
-
const files = await fs.readdir(tempDir);
|
|
160
|
-
const supportedExtensions = ['.txt', '.md', '.pdf', '.docx', '.xlsx'];
|
|
161
|
-
const supportedFiles = files.filter(file =>
|
|
162
|
-
supportedExtensions.some(ext => file.endsWith(ext))
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
expect(supportedFiles).toHaveLength(2);
|
|
166
|
-
expect(supportedFiles).toContain('test.txt');
|
|
167
|
-
expect(supportedFiles).toContain('test.md');
|
|
168
|
-
expect(supportedFiles).not.toContain('unsupported.xyz');
|
|
169
|
-
|
|
170
|
-
} finally {
|
|
171
|
-
// Clean up
|
|
172
|
-
try {
|
|
173
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
174
|
-
} catch (error) {
|
|
175
|
-
// Ignore cleanup errors
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
describe('Error Handling Integration', () => {
|
|
182
|
-
it('should handle parser errors gracefully', async () => {
|
|
183
|
-
// Test that the application handles various error conditions
|
|
184
|
-
// without crashing and provides meaningful error messages
|
|
185
|
-
|
|
186
|
-
const invalidFiles = [
|
|
187
|
-
'non-existent.pdf',
|
|
188
|
-
'empty.txt',
|
|
189
|
-
'corrupted.docx'
|
|
190
|
-
];
|
|
191
|
-
|
|
192
|
-
// Each of these should be handled gracefully by the application
|
|
193
|
-
// without causing the entire process to fail
|
|
194
|
-
|
|
195
|
-
expect(invalidFiles.length).toBeGreaterThan(0); // Placeholder assertion
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('should validate configuration parameters', async () => {
|
|
199
|
-
const invalidConfigs = [
|
|
200
|
-
{ maxSize: -1 },
|
|
201
|
-
{ maxSize: 'invalid' },
|
|
202
|
-
{ provider: 'invalid-provider' }
|
|
203
|
-
];
|
|
204
|
-
|
|
205
|
-
// Each invalid config should be caught and handled appropriately
|
|
206
|
-
expect(invalidConfigs.length).toBeGreaterThan(0); // Placeholder assertion
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
});
|