@arela/uploader 1.0.3 → 1.0.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.
Files changed (44) hide show
  1. package/.env.local +316 -0
  2. package/coverage/IdentifyCommand.js.html +1462 -0
  3. package/coverage/PropagateCommand.js.html +1507 -0
  4. package/coverage/PushCommand.js.html +1504 -0
  5. package/coverage/ScanCommand.js.html +1654 -0
  6. package/coverage/UploadCommand.js.html +1846 -0
  7. package/coverage/WatchCommand.js.html +4111 -0
  8. package/coverage/base.css +224 -0
  9. package/coverage/block-navigation.js +87 -0
  10. package/coverage/favicon.png +0 -0
  11. package/coverage/index.html +191 -0
  12. package/coverage/lcov-report/IdentifyCommand.js.html +1462 -0
  13. package/coverage/lcov-report/PropagateCommand.js.html +1507 -0
  14. package/coverage/lcov-report/PushCommand.js.html +1504 -0
  15. package/coverage/lcov-report/ScanCommand.js.html +1654 -0
  16. package/coverage/lcov-report/UploadCommand.js.html +1846 -0
  17. package/coverage/lcov-report/WatchCommand.js.html +4111 -0
  18. package/coverage/lcov-report/base.css +224 -0
  19. package/coverage/lcov-report/block-navigation.js +87 -0
  20. package/coverage/lcov-report/favicon.png +0 -0
  21. package/coverage/lcov-report/index.html +191 -0
  22. package/coverage/lcov-report/prettify.css +1 -0
  23. package/coverage/lcov-report/prettify.js +2 -0
  24. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  25. package/coverage/lcov-report/sorter.js +210 -0
  26. package/coverage/lcov.info +1937 -0
  27. package/coverage/prettify.css +1 -0
  28. package/coverage/prettify.js +2 -0
  29. package/coverage/sort-arrow-sprite.png +0 -0
  30. package/coverage/sorter.js +210 -0
  31. package/docs/CROSS_PLATFORM_PATH_HANDLING.md +597 -0
  32. package/package.json +28 -2
  33. package/src/commands/IdentifyCommand.js +1 -28
  34. package/src/commands/PropagateCommand.js +1 -1
  35. package/src/commands/PushCommand.js +1 -1
  36. package/src/commands/ScanCommand.js +27 -20
  37. package/src/config/config.js +27 -48
  38. package/src/services/ScanApiService.js +4 -5
  39. package/src/utils/PathNormalizer.js +272 -0
  40. package/tests/commands/IdentifyCommand.test.js +570 -0
  41. package/tests/commands/PropagateCommand.test.js +568 -0
  42. package/tests/commands/PushCommand.test.js +754 -0
  43. package/tests/commands/ScanCommand.test.js +382 -0
  44. package/tests/unit/PathAndTableNameGeneration.test.js +1211 -0
@@ -0,0 +1,568 @@
1
+ /**
2
+ * Unit tests for PropagateCommand
3
+ * Tests the arela propagate command functionality
4
+ */
5
+ import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals';
6
+
7
+ // Mock dependencies before importing PropagateCommand
8
+ const mockLogger = {
9
+ info: jest.fn(),
10
+ success: jest.fn(),
11
+ error: jest.fn(),
12
+ warn: jest.fn(),
13
+ debug: jest.fn(),
14
+ };
15
+
16
+ const mockProgressBar = {
17
+ start: jest.fn(),
18
+ update: jest.fn(),
19
+ stop: jest.fn(),
20
+ };
21
+
22
+ const mockCliProgress = {
23
+ SingleBar: jest.fn(() => mockProgressBar),
24
+ Presets: {
25
+ shades_classic: {},
26
+ },
27
+ };
28
+
29
+ const mockScanApiService = {
30
+ getInstanceTables: jest.fn(),
31
+ getPropagationStats: jest.fn(),
32
+ markFilesNeedingPropagation: jest.fn(),
33
+ fetchPedimentoSources: jest.fn(),
34
+ fetchFilesNeedingPropagationByDirectory: jest.fn(),
35
+ batchUpdatePropagation: jest.fn(),
36
+ };
37
+
38
+ const mockAppConfig = {
39
+ validateScanConfig: jest.fn(),
40
+ getScanConfig: jest.fn(),
41
+ setApiTarget: jest.fn(),
42
+ };
43
+
44
+ // Store original console methods
45
+ const originalConsoleLog = console.log;
46
+ const originalConsoleError = console.error;
47
+ const originalProcessExit = process.exit;
48
+
49
+ jest.unstable_mockModule('cli-progress', () => ({
50
+ default: mockCliProgress,
51
+ SingleBar: mockCliProgress.SingleBar,
52
+ Presets: mockCliProgress.Presets,
53
+ }));
54
+
55
+ jest.unstable_mockModule('../../src/services/LoggingService.js', () => ({
56
+ default: mockLogger,
57
+ }));
58
+
59
+ jest.unstable_mockModule('../../src/services/ScanApiService.js', () => ({
60
+ default: jest.fn(() => mockScanApiService),
61
+ }));
62
+
63
+ jest.unstable_mockModule('../../src/config/config.js', () => ({
64
+ default: mockAppConfig,
65
+ }));
66
+
67
+ // Import the class after mocking
68
+ const { PropagateCommand } = await import('../../src/commands/PropagateCommand.js');
69
+
70
+ describe('PropagateCommand', () => {
71
+ let propagateCommand;
72
+ let mockConsoleLog;
73
+ let mockConsoleError;
74
+ let mockProcessExit;
75
+
76
+ beforeEach(() => {
77
+ jest.clearAllMocks();
78
+
79
+ // Mock console methods
80
+ mockConsoleLog = jest.fn();
81
+ mockConsoleError = jest.fn();
82
+ mockProcessExit = jest.fn();
83
+ console.log = mockConsoleLog;
84
+ console.error = mockConsoleError;
85
+ process.exit = mockProcessExit;
86
+
87
+ // Default mock implementations
88
+ mockAppConfig.getScanConfig.mockReturnValue({
89
+ companySlug: 'test-company',
90
+ serverId: 'test-server',
91
+ basePathFull: '/test/path',
92
+ });
93
+ mockAppConfig.validateScanConfig.mockReturnValue(undefined);
94
+
95
+ mockScanApiService.getInstanceTables.mockResolvedValue([
96
+ { tableName: 'scan_test_table' },
97
+ ]);
98
+
99
+ mockScanApiService.getPropagationStats.mockResolvedValue({
100
+ totalFiles: 100,
101
+ withArelaPath: 50,
102
+ pedimentoSources: 10,
103
+ needsPropagation: 40,
104
+ pending: 40,
105
+ errors: 0,
106
+ maxAttemptsReached: 0,
107
+ });
108
+
109
+ mockScanApiService.markFilesNeedingPropagation.mockResolvedValue({
110
+ markedCount: 40,
111
+ });
112
+
113
+ mockScanApiService.fetchPedimentoSources.mockResolvedValue([]);
114
+ mockScanApiService.fetchFilesNeedingPropagationByDirectory.mockResolvedValue([]);
115
+ mockScanApiService.batchUpdatePropagation.mockResolvedValue({ updated: 0 });
116
+
117
+ propagateCommand = new PropagateCommand();
118
+ });
119
+
120
+ afterEach(() => {
121
+ // Restore console methods
122
+ console.log = originalConsoleLog;
123
+ console.error = originalConsoleError;
124
+ process.exit = originalProcessExit;
125
+ jest.restoreAllMocks();
126
+ });
127
+
128
+ describe('constructor', () => {
129
+ it('should create a new PropagateCommand instance with default options', () => {
130
+ expect(propagateCommand).toBeInstanceOf(PropagateCommand);
131
+ expect(propagateCommand.options.batchSize).toBe(50);
132
+ expect(propagateCommand.options.showStats).toBe(false);
133
+ expect(propagateCommand.options.api).toBe('default');
134
+ });
135
+
136
+ it('should accept custom options', () => {
137
+ const customCommand = new PropagateCommand({
138
+ batchSize: 100,
139
+ showStats: true,
140
+ api: 'agencia',
141
+ });
142
+
143
+ expect(customCommand.options.batchSize).toBe(100);
144
+ expect(customCommand.options.showStats).toBe(true);
145
+ expect(customCommand.options.api).toBe('agencia');
146
+ });
147
+
148
+ it('should initialize stats object', () => {
149
+ expect(propagateCommand.stats).toBeDefined();
150
+ expect(propagateCommand.stats.pedimentosProcessed).toBe(0);
151
+ expect(propagateCommand.stats.filesUpdated).toBe(0);
152
+ expect(propagateCommand.stats.filesFailed).toBe(0);
153
+ expect(propagateCommand.stats.directoriesProcessed).toBe(0);
154
+ });
155
+ });
156
+
157
+ describe('execute', () => {
158
+ it('should validate configuration', async () => {
159
+ await propagateCommand.execute();
160
+
161
+ expect(mockAppConfig.validateScanConfig).toHaveBeenCalled();
162
+ });
163
+
164
+ it('should set API target', async () => {
165
+ const command = new PropagateCommand({ api: 'cliente' });
166
+ await command.execute();
167
+
168
+ expect(mockAppConfig.setApiTarget).toHaveBeenCalledWith('cliente');
169
+ });
170
+
171
+ it('should fetch instance tables', async () => {
172
+ await propagateCommand.execute();
173
+
174
+ expect(mockScanApiService.getInstanceTables).toHaveBeenCalledWith(
175
+ 'test-company',
176
+ 'test-server',
177
+ '/test/path'
178
+ );
179
+ });
180
+
181
+ it('should exit when no tables found', async () => {
182
+ mockScanApiService.getInstanceTables.mockResolvedValue([]);
183
+
184
+ await propagateCommand.execute();
185
+
186
+ expect(mockProcessExit).toHaveBeenCalledWith(1);
187
+ expect(mockConsoleError).toHaveBeenCalledWith(
188
+ expect.stringContaining('No tables found')
189
+ );
190
+ });
191
+
192
+ it('should log found tables', async () => {
193
+ mockScanApiService.getInstanceTables.mockResolvedValue([
194
+ { tableName: 'scan_table_1' },
195
+ { tableName: 'scan_table_2' },
196
+ ]);
197
+
198
+ await propagateCommand.execute();
199
+
200
+ expect(mockConsoleLog).toHaveBeenCalledWith(
201
+ expect.stringContaining('Found 2 tables')
202
+ );
203
+ });
204
+
205
+ it('should show initial status', async () => {
206
+ await propagateCommand.execute();
207
+
208
+ expect(mockConsoleLog).toHaveBeenCalledWith('📈 Initial Status:');
209
+ });
210
+ });
211
+
212
+ describe('table processing', () => {
213
+ it('should skip table when no pedimento sources', async () => {
214
+ mockScanApiService.getPropagationStats.mockResolvedValue({
215
+ totalFiles: 100,
216
+ withArelaPath: 0,
217
+ pedimentoSources: 0,
218
+ needsPropagation: 0,
219
+ pending: 0,
220
+ errors: 0,
221
+ });
222
+
223
+ await propagateCommand.execute();
224
+
225
+ expect(mockConsoleLog).toHaveBeenCalledWith(
226
+ expect.stringContaining('No pedimento sources found')
227
+ );
228
+ });
229
+
230
+ it('should skip table when all files have arela_path', async () => {
231
+ mockScanApiService.getPropagationStats
232
+ .mockResolvedValueOnce({
233
+ totalFiles: 100,
234
+ withArelaPath: 100,
235
+ pedimentoSources: 10,
236
+ needsPropagation: 0,
237
+ pending: 0,
238
+ errors: 0,
239
+ })
240
+ .mockResolvedValueOnce({
241
+ totalFiles: 100,
242
+ withArelaPath: 100,
243
+ pedimentoSources: 10,
244
+ needsPropagation: 0,
245
+ pending: 0,
246
+ errors: 0,
247
+ });
248
+
249
+ await propagateCommand.execute();
250
+
251
+ expect(mockConsoleLog).toHaveBeenCalledWith(
252
+ expect.stringContaining('All files already have arela_path')
253
+ );
254
+ });
255
+
256
+ it('should mark files needing propagation', async () => {
257
+ mockScanApiService.getPropagationStats
258
+ .mockResolvedValueOnce({
259
+ totalFiles: 100,
260
+ withArelaPath: 10,
261
+ pedimentoSources: 10,
262
+ needsPropagation: 0,
263
+ pending: 90,
264
+ errors: 0,
265
+ })
266
+ .mockResolvedValueOnce({
267
+ totalFiles: 100,
268
+ withArelaPath: 10,
269
+ pedimentoSources: 10,
270
+ needsPropagation: 90,
271
+ pending: 90,
272
+ errors: 0,
273
+ });
274
+
275
+ await propagateCommand.execute();
276
+
277
+ expect(mockScanApiService.markFilesNeedingPropagation).toHaveBeenCalledWith(
278
+ 'scan_test_table'
279
+ );
280
+ });
281
+ });
282
+
283
+ describe('propagation processing', () => {
284
+ beforeEach(() => {
285
+ // Setup for active propagation
286
+ mockScanApiService.getPropagationStats
287
+ .mockResolvedValueOnce({
288
+ totalFiles: 100,
289
+ withArelaPath: 10,
290
+ pedimentoSources: 5,
291
+ needsPropagation: 90,
292
+ pending: 90,
293
+ errors: 0,
294
+ })
295
+ .mockResolvedValueOnce({
296
+ totalFiles: 100,
297
+ withArelaPath: 10,
298
+ pedimentoSources: 5,
299
+ needsPropagation: 90,
300
+ pending: 90,
301
+ errors: 0,
302
+ })
303
+ .mockResolvedValueOnce({
304
+ totalFiles: 100,
305
+ withArelaPath: 100,
306
+ pedimentoSources: 5,
307
+ needsPropagation: 0,
308
+ pending: 0,
309
+ errors: 0,
310
+ });
311
+ });
312
+
313
+ it('should fetch pedimento sources', async () => {
314
+ mockScanApiService.fetchPedimentoSources.mockResolvedValue([
315
+ {
316
+ id: 1,
317
+ directory_path: '/test/dir1',
318
+ arela_path: 'RFC/2023/3429/07/12345/',
319
+ rfc: 'RFC123',
320
+ detected_pedimento_year: 2023,
321
+ },
322
+ ]);
323
+
324
+ mockScanApiService.fetchFilesNeedingPropagationByDirectory.mockResolvedValue([]);
325
+
326
+ await propagateCommand.execute();
327
+
328
+ expect(mockScanApiService.fetchPedimentoSources).toHaveBeenCalled();
329
+ });
330
+
331
+ it('should process files in each directory', async () => {
332
+ mockScanApiService.fetchPedimentoSources.mockResolvedValue([
333
+ {
334
+ id: 1,
335
+ directory_path: '/test/dir1',
336
+ arela_path: 'RFC/2023/3429/07/12345/',
337
+ rfc: 'RFC123',
338
+ detected_pedimento_year: 2023,
339
+ },
340
+ ]);
341
+
342
+ mockScanApiService.fetchFilesNeedingPropagationByDirectory.mockResolvedValue([
343
+ { id: 2, file_name: 'file.xml' },
344
+ { id: 3, file_name: 'file.txt' },
345
+ ]);
346
+
347
+ mockScanApiService.batchUpdatePropagation.mockResolvedValue({ updated: 2 });
348
+
349
+ await propagateCommand.execute();
350
+
351
+ expect(mockScanApiService.fetchFilesNeedingPropagationByDirectory).toHaveBeenCalledWith(
352
+ 'scan_test_table',
353
+ '/test/dir1'
354
+ );
355
+ });
356
+
357
+ it('should update progress bar during processing', async () => {
358
+ mockScanApiService.fetchPedimentoSources.mockResolvedValue([
359
+ {
360
+ id: 1,
361
+ directory_path: '/test/dir1',
362
+ arela_path: 'RFC/2023/3429/07/12345/',
363
+ rfc: 'RFC123',
364
+ detected_pedimento_year: 2023,
365
+ },
366
+ ]);
367
+
368
+ mockScanApiService.fetchFilesNeedingPropagationByDirectory.mockResolvedValue([]);
369
+
370
+ await propagateCommand.execute();
371
+
372
+ expect(mockProgressBar.start).toHaveBeenCalled();
373
+ expect(mockProgressBar.update).toHaveBeenCalled();
374
+ expect(mockProgressBar.stop).toHaveBeenCalled();
375
+ });
376
+
377
+ it('should process multiple batches of pedimentos', async () => {
378
+ mockScanApiService.fetchPedimentoSources
379
+ .mockResolvedValueOnce([
380
+ {
381
+ id: 1,
382
+ directory_path: '/test/dir1',
383
+ arela_path: 'RFC/2023/3429/07/12345/',
384
+ rfc: 'RFC123',
385
+ detected_pedimento_year: 2023,
386
+ },
387
+ ])
388
+ .mockResolvedValueOnce([]);
389
+
390
+ mockScanApiService.fetchFilesNeedingPropagationByDirectory.mockResolvedValue([]);
391
+
392
+ await propagateCommand.execute();
393
+
394
+ // The implementation may call once or twice depending on batch size logic
395
+ expect(mockScanApiService.fetchPedimentoSources).toHaveBeenCalled();
396
+ });
397
+ });
398
+
399
+ describe('error handling', () => {
400
+ it('should handle configuration validation errors', async () => {
401
+ mockAppConfig.validateScanConfig.mockImplementation(() => {
402
+ throw new Error('Missing required config');
403
+ });
404
+
405
+ await propagateCommand.execute();
406
+
407
+ expect(mockProcessExit).toHaveBeenCalledWith(1);
408
+ });
409
+
410
+ it('should handle API errors gracefully', async () => {
411
+ mockScanApiService.getInstanceTables.mockRejectedValue(
412
+ new Error('API connection failed')
413
+ );
414
+
415
+ await propagateCommand.execute();
416
+
417
+ expect(mockProcessExit).toHaveBeenCalledWith(1);
418
+ expect(mockConsoleError).toHaveBeenCalledWith(
419
+ expect.stringContaining('API connection failed')
420
+ );
421
+ });
422
+
423
+ it('should handle fetch pedimento sources errors', async () => {
424
+ mockScanApiService.getPropagationStats
425
+ .mockResolvedValueOnce({
426
+ totalFiles: 100,
427
+ withArelaPath: 10,
428
+ pedimentoSources: 5,
429
+ needsPropagation: 90,
430
+ pending: 90,
431
+ errors: 0,
432
+ })
433
+ .mockResolvedValueOnce({
434
+ totalFiles: 100,
435
+ withArelaPath: 10,
436
+ pedimentoSources: 5,
437
+ needsPropagation: 90,
438
+ pending: 90,
439
+ errors: 0,
440
+ });
441
+
442
+ mockScanApiService.fetchPedimentoSources.mockRejectedValue(
443
+ new Error('Database error')
444
+ );
445
+
446
+ // The command should handle the error (exit or log)
447
+ await propagateCommand.execute();
448
+
449
+ // Should have attempted to fetch pedimento sources
450
+ expect(mockScanApiService.fetchPedimentoSources).toHaveBeenCalled();
451
+ });
452
+
453
+ it('should handle invalid API response for files', async () => {
454
+ mockScanApiService.getPropagationStats
455
+ .mockResolvedValueOnce({
456
+ totalFiles: 100,
457
+ withArelaPath: 10,
458
+ pedimentoSources: 5,
459
+ needsPropagation: 90,
460
+ pending: 90,
461
+ errors: 0,
462
+ })
463
+ .mockResolvedValueOnce({
464
+ totalFiles: 100,
465
+ withArelaPath: 10,
466
+ pedimentoSources: 5,
467
+ needsPropagation: 90,
468
+ pending: 90,
469
+ errors: 0,
470
+ });
471
+
472
+ mockScanApiService.fetchPedimentoSources.mockResolvedValue([
473
+ {
474
+ id: 1,
475
+ directory_path: '/test/dir1',
476
+ arela_path: 'RFC/2023/3429/07/12345/',
477
+ },
478
+ ]);
479
+
480
+ // Return invalid response (not an array)
481
+ mockScanApiService.fetchFilesNeedingPropagationByDirectory.mockResolvedValue(null);
482
+
483
+ await propagateCommand.execute();
484
+
485
+ expect(mockLogger.error).toHaveBeenCalledWith(
486
+ expect.stringMatching(/Invalid response/i),
487
+ null
488
+ );
489
+ });
490
+ });
491
+
492
+ describe('statistics display', () => {
493
+ it('should show final statistics', async () => {
494
+ mockScanApiService.getPropagationStats.mockResolvedValue({
495
+ totalFiles: 100,
496
+ withArelaPath: 100,
497
+ pedimentoSources: 10,
498
+ needsPropagation: 0,
499
+ pending: 0,
500
+ errors: 0,
501
+ maxAttemptsReached: 0,
502
+ });
503
+
504
+ await propagateCommand.execute();
505
+
506
+ // Final stats are shown after processing, check for any status output
507
+ expect(mockConsoleLog).toHaveBeenCalledWith(
508
+ expect.stringContaining('Initial Status')
509
+ );
510
+ });
511
+
512
+ it('should warn about max attempts reached', async () => {
513
+ // This test verifies the command handles maxAttemptsReached stats
514
+ mockScanApiService.getPropagationStats.mockResolvedValue({
515
+ totalFiles: 100,
516
+ withArelaPath: 90,
517
+ pedimentoSources: 10,
518
+ needsPropagation: 0,
519
+ pending: 0,
520
+ errors: 0,
521
+ maxAttemptsReached: 5,
522
+ });
523
+
524
+ // Should complete without error
525
+ await expect(propagateCommand.execute()).resolves.not.toThrow();
526
+ });
527
+
528
+ it('should show combined results for multiple tables', async () => {
529
+ mockScanApiService.getInstanceTables.mockResolvedValue([
530
+ { tableName: 'scan_table_1' },
531
+ { tableName: 'scan_table_2' },
532
+ ]);
533
+
534
+ mockScanApiService.getPropagationStats.mockResolvedValue({
535
+ totalFiles: 50,
536
+ withArelaPath: 50,
537
+ pedimentoSources: 0,
538
+ needsPropagation: 0,
539
+ pending: 0,
540
+ errors: 0,
541
+ });
542
+
543
+ await propagateCommand.execute();
544
+
545
+ expect(mockConsoleLog).toHaveBeenCalledWith(
546
+ expect.stringContaining('Tables Processed: 2')
547
+ );
548
+ });
549
+ });
550
+
551
+ describe('memory usage', () => {
552
+ it('should report memory usage when showStats is enabled', async () => {
553
+ const command = new PropagateCommand({ showStats: true });
554
+
555
+ mockScanApiService.getPropagationStats.mockResolvedValue({
556
+ totalFiles: 100,
557
+ withArelaPath: 100,
558
+ pedimentoSources: 0,
559
+ needsPropagation: 0,
560
+ pending: 0,
561
+ errors: 0,
562
+ });
563
+
564
+ // The command should complete without error when showStats is enabled
565
+ await expect(command.execute()).resolves.not.toThrow();
566
+ });
567
+ });
568
+ });