@arela/uploader 0.2.13 → 1.0.0

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 (43) hide show
  1. package/.env.template +66 -0
  2. package/README.md +263 -62
  3. package/docs/API_ENDPOINTS_FOR_DETECTION.md +647 -0
  4. package/docs/QUICK_REFERENCE_API_DETECTION.md +264 -0
  5. package/docs/REFACTORING_SUMMARY_DETECT_PEDIMENTOS.md +200 -0
  6. package/package.json +3 -2
  7. package/scripts/cleanup-ds-store.js +109 -0
  8. package/scripts/cleanup-system-files.js +69 -0
  9. package/scripts/tests/phase-7-features.test.js +415 -0
  10. package/scripts/tests/signal-handling.test.js +275 -0
  11. package/scripts/tests/smart-watch-integration.test.js +554 -0
  12. package/scripts/tests/watch-service-integration.test.js +584 -0
  13. package/src/commands/UploadCommand.js +31 -4
  14. package/src/commands/WatchCommand.js +1342 -0
  15. package/src/config/config.js +270 -2
  16. package/src/document-type-shared.js +2 -0
  17. package/src/document-types/support-document.js +200 -0
  18. package/src/file-detection.js +9 -1
  19. package/src/index.js +163 -4
  20. package/src/services/AdvancedFilterService.js +505 -0
  21. package/src/services/AutoProcessingService.js +749 -0
  22. package/src/services/BenchmarkingService.js +381 -0
  23. package/src/services/DatabaseService.js +1019 -539
  24. package/src/services/ErrorMonitor.js +275 -0
  25. package/src/services/LoggingService.js +419 -1
  26. package/src/services/MonitoringService.js +401 -0
  27. package/src/services/PerformanceOptimizer.js +511 -0
  28. package/src/services/ReportingService.js +511 -0
  29. package/src/services/SignalHandler.js +255 -0
  30. package/src/services/SmartWatchDatabaseService.js +527 -0
  31. package/src/services/WatchService.js +783 -0
  32. package/src/services/upload/ApiUploadService.js +447 -3
  33. package/src/services/upload/MultiApiUploadService.js +233 -0
  34. package/src/services/upload/SupabaseUploadService.js +12 -5
  35. package/src/services/upload/UploadServiceFactory.js +24 -0
  36. package/src/utils/CleanupManager.js +262 -0
  37. package/src/utils/FileOperations.js +44 -0
  38. package/src/utils/WatchEventHandler.js +522 -0
  39. package/supabase/migrations/001_create_initial_schema.sql +366 -0
  40. package/supabase/migrations/002_align_with_arela_api_schema.sql +145 -0
  41. package/.envbackup +0 -37
  42. package/SUPABASE_UPLOAD_FIX.md +0 -157
  43. package/commands.md +0 -14
@@ -0,0 +1,584 @@
1
+ /**
2
+ * watch-service-integration.test.js
3
+ * Paso 2: WatchService Integration Tests
4
+ *
5
+ * Comprehensive test suite for:
6
+ * - WatchService initialization with SmartWatchDatabaseService
7
+ * - File detection and pedimento pattern matching
8
+ * - Queue statistics and progress tracking
9
+ * - Event handling and cascading operations
10
+ */
11
+
12
+ import path from 'path';
13
+ import { fileURLToPath } from 'url';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+
18
+ // ═══════════════════════════════════════════════════════════════════════════
19
+ // TEST UTILITIES
20
+ // ═══════════════════════════════════════════════════════════════════════════
21
+
22
+ class TestLogger {
23
+ constructor() {
24
+ this.logs = [];
25
+ }
26
+ info(msg) {
27
+ this.logs.push({ level: 'info', msg });
28
+ }
29
+ warn(msg) {
30
+ this.logs.push({ level: 'warn', msg });
31
+ }
32
+ error(msg) {
33
+ this.logs.push({ level: 'error', msg });
34
+ }
35
+ debug(msg) {
36
+ this.logs.push({ level: 'debug', msg });
37
+ }
38
+ getLogs() {
39
+ return this.logs;
40
+ }
41
+ clear() {
42
+ this.logs = [];
43
+ }
44
+ }
45
+
46
+ class MockSupabaseClient {
47
+ constructor() {
48
+ this.data = { uploader: [] };
49
+ }
50
+
51
+ from(table) {
52
+ const self = this;
53
+ return {
54
+ select: (columns = '*') => {
55
+ const selectResult = {
56
+ eq: (col, val) => {
57
+ const filtered = self.data[table].filter((row) => row[col] === val);
58
+ return { ...selectResult, data: filtered, error: null };
59
+ },
60
+ in: (col, vals) => {
61
+ const filtered = self.data[table].filter((row) => vals.includes(row[col]));
62
+ return { ...selectResult, data: filtered, error: null };
63
+ },
64
+ gt: (col, val) => {
65
+ const filtered = self.data[table].filter((row) => row[col] > val);
66
+ return { ...selectResult, data: filtered, error: null };
67
+ },
68
+ data: self.data[table],
69
+ error: null,
70
+ };
71
+ return selectResult;
72
+ },
73
+ insert: (records) => {
74
+ self.data[table].push(...records);
75
+ return { data: records, error: null };
76
+ },
77
+ update: (updates) => ({
78
+ eq: (col, val) => {
79
+ const index = self.data[table].findIndex((row) => row[col] === val);
80
+ if (index !== -1) {
81
+ self.data[table][index] = { ...self.data[table][index], ...updates };
82
+ }
83
+ return { data: { updated: true }, error: null };
84
+ },
85
+ }),
86
+ };
87
+ }
88
+ }
89
+
90
+ class MockDatabaseService {
91
+ constructor(logger = new TestLogger()) {
92
+ this.logger = logger;
93
+ this.supabaseClient = new MockSupabaseClient();
94
+ }
95
+
96
+ getSupabaseClient() {
97
+ return this.supabaseClient;
98
+ }
99
+
100
+ async queryWithRetry(queryFn, context = 'query') {
101
+ return await queryFn();
102
+ }
103
+ }
104
+
105
+ class SmartWatchDatabaseService {
106
+ constructor(databaseService) {
107
+ this.db = databaseService;
108
+ this.logger = databaseService.logger;
109
+ }
110
+
111
+ async insertFileToUploader(filePath, options = {}) {
112
+ const { processingStatus = 'PENDING', dependsOnPath, size = 0, fileExtension = '' } = options;
113
+
114
+ return await this.db.queryWithRetry(async () => {
115
+ const { data, error } = await this.db
116
+ .getSupabaseClient()
117
+ .from('uploader')
118
+ .insert([
119
+ {
120
+ filePath,
121
+ processingStatus,
122
+ dependsOnPath,
123
+ fileSize: size,
124
+ fileExtension,
125
+ createdAt: new Date().toISOString(),
126
+ },
127
+ ]);
128
+
129
+ if (error) throw error;
130
+ return data;
131
+ });
132
+ }
133
+
134
+ async getPendingFilesInDirectory(dirPath) {
135
+ return await this.db.queryWithRetry(async () => {
136
+ const { data, error } = await this.db
137
+ .getSupabaseClient()
138
+ .from('uploader')
139
+ .select()
140
+ .eq('dependsOnPath', dirPath)
141
+ .eq('processingStatus', 'PENDING');
142
+
143
+ if (error) throw error;
144
+ return data;
145
+ });
146
+ }
147
+
148
+ async markPedimentoDetected(dirPath, pedimentoPath) {
149
+ await this.insertFileToUploader(pedimentoPath, {
150
+ processingStatus: 'READY_TO_UPLOAD',
151
+ dependsOnPath: dirPath,
152
+ });
153
+
154
+ const pendingFiles = await this.getPendingFilesInDirectory(dirPath);
155
+
156
+ for (const file of pendingFiles) {
157
+ await this.updateFileStatus(file.filePath, {
158
+ processingStatus: 'READY_TO_UPLOAD',
159
+ });
160
+ }
161
+
162
+ return pendingFiles.length;
163
+ }
164
+
165
+ async updateFileStatus(filePath, updates) {
166
+ return await this.db.queryWithRetry(async () => {
167
+ const { data, error } = await this.db
168
+ .getSupabaseClient()
169
+ .from('uploader')
170
+ .update({ ...updates, updatedAt: new Date().toISOString() })
171
+ .eq('filePath', filePath);
172
+
173
+ if (error) throw error;
174
+ return data;
175
+ });
176
+ }
177
+
178
+ async getProcessingStats() {
179
+ return await this.db.queryWithRetry(async () => {
180
+ const { data, error } = await this.db.getSupabaseClient().from('uploader').select();
181
+
182
+ if (error) throw error;
183
+
184
+ const stats = {
185
+ PENDING: 0,
186
+ READY_TO_UPLOAD: 0,
187
+ PROCESSING: 0,
188
+ UPLOADED: 0,
189
+ FAILED: 0,
190
+ };
191
+
192
+ data.forEach((file) => {
193
+ if (stats.hasOwnProperty(file.processingStatus)) {
194
+ stats[file.processingStatus]++;
195
+ }
196
+ });
197
+
198
+ return stats;
199
+ });
200
+ }
201
+
202
+ async getOverallProgress() {
203
+ const stats = await this.getProcessingStats();
204
+ const total = Object.values(stats).reduce((a, b) => a + b, 0);
205
+ const uploaded = stats.UPLOADED || 0;
206
+ const percentage = total > 0 ? Math.round((uploaded / total) * 100) : 0;
207
+
208
+ return {
209
+ percentComplete: percentage,
210
+ filesProcessed: uploaded,
211
+ totalFiles: total,
212
+ remainingFiles: total - uploaded,
213
+ estimatedTimeRemaining: total > uploaded ? `${(total - uploaded) * 2} seconds` : '0 seconds',
214
+ };
215
+ }
216
+ }
217
+
218
+ // ═══════════════════════════════════════════════════════════════════════════
219
+ // WATCHSERVICE MOCK
220
+ // ═══════════════════════════════════════════════════════════════════════════
221
+
222
+ class WatchService {
223
+ constructor(config = {}, databaseService, logger) {
224
+ this.config = config;
225
+ this.databaseService = databaseService;
226
+ this.logger = logger || new TestLogger();
227
+
228
+ // SmartWatchDatabaseService integration
229
+ this.smartWatchDb = new SmartWatchDatabaseService(databaseService);
230
+ this.pedimentoPattern = /simplif|simple/i;
231
+
232
+ // Queue statistics
233
+ this.queueStats = {
234
+ pending: 0,
235
+ readyToUpload: 0,
236
+ processing: 0,
237
+ uploaded: 0,
238
+ failed: 0,
239
+ total: 0,
240
+ };
241
+
242
+ this.logger.info('WatchService initialized with SmartWatchDatabaseService');
243
+ }
244
+
245
+ #isPedimentoSimplificado(fileName) {
246
+ return this.pedimentoPattern.test(fileName);
247
+ }
248
+
249
+ async handleFileAdded(filePath) {
250
+ const fileName = path.basename(filePath);
251
+ const parentDir = path.dirname(filePath);
252
+ const isPedimento = this.#isPedimentoSimplificado(fileName);
253
+
254
+ this.logger.info(`File detected: ${filePath} (isPedimento: ${isPedimento})`);
255
+
256
+ try {
257
+ if (isPedimento) {
258
+ const updatedCount = await this.smartWatchDb.markPedimentoDetected(parentDir, filePath);
259
+ this.logger.info(`Pedimento processing: updated ${updatedCount} dependent files`);
260
+ } else {
261
+ await this.smartWatchDb.insertFileToUploader(filePath, {
262
+ processingStatus: 'PENDING',
263
+ dependsOnPath: parentDir,
264
+ size: 0,
265
+ fileExtension: path.extname(fileName).slice(1).toLowerCase(),
266
+ });
267
+ this.logger.info(`File registered: ${filePath} with PENDING status`);
268
+ }
269
+ } catch (error) {
270
+ this.logger.error(`Error handling file: ${error.message}`);
271
+ throw error;
272
+ }
273
+ }
274
+
275
+ async getQueueStats() {
276
+ const stats = await this.smartWatchDb.getProcessingStats();
277
+ const progress = await this.smartWatchDb.getOverallProgress();
278
+
279
+ return {
280
+ queue: {
281
+ pending: stats.PENDING || 0,
282
+ readyToUpload: stats.READY_TO_UPLOAD || 0,
283
+ processing: stats.PROCESSING || 0,
284
+ uploaded: stats.UPLOADED || 0,
285
+ failed: stats.FAILED || 0,
286
+ total: Object.values(stats).reduce((a, b) => a + b, 0),
287
+ },
288
+ progress: {
289
+ percentComplete: progress.percentComplete,
290
+ filesProcessed: progress.filesProcessed,
291
+ totalFiles: progress.totalFiles,
292
+ remainingFiles: progress.remainingFiles,
293
+ estimatedTimeRemaining: progress.estimatedTimeRemaining,
294
+ },
295
+ timestamp: new Date().toISOString(),
296
+ };
297
+ }
298
+ }
299
+
300
+ // ═══════════════════════════════════════════════════════════════════════════
301
+ // TEST SUITE
302
+ // ═══════════════════════════════════════════════════════════════════════════
303
+
304
+ console.log('\n╔════════════════════════════════════════════════════════════════════════════╗');
305
+ console.log('║ PASO 2: WatchService Integration Tests ║');
306
+ console.log('╚════════════════════════════════════════════════════════════════════════════╝\n');
307
+
308
+ let testsPassed = 0;
309
+ let testsFailed = 0;
310
+
311
+ async function runTest(name, testFn) {
312
+ process.stdout.write(`\n📋 ${name}... `);
313
+ try {
314
+ await testFn();
315
+ console.log('✅ PASS');
316
+ testsPassed++;
317
+ } catch (error) {
318
+ console.log(`❌ FAIL`);
319
+ console.log(` Error: ${error.message}`);
320
+ testsFailed++;
321
+ }
322
+ }
323
+
324
+ function assert(condition, message) {
325
+ if (!condition) {
326
+ throw new Error(message);
327
+ }
328
+ }
329
+
330
+ // ─────────────────────────────────────────────────────────────────────────────
331
+ // TEST GROUP 1: WatchService Instantiation
332
+ // ─────────────────────────────────────────────────────────────────────────────
333
+
334
+ console.log('\n🔍 Test Group 1: WatchService Instantiation & Configuration\n');
335
+
336
+ await runTest('WatchService should initialize with SmartWatchDatabaseService', async () => {
337
+ const logger = new TestLogger();
338
+ const dbService = new MockDatabaseService(logger);
339
+ const watchService = new WatchService({}, dbService, logger);
340
+
341
+ assert(watchService.smartWatchDb !== null, 'SmartWatchDatabaseService should be initialized');
342
+ assert(watchService.smartWatchDb instanceof SmartWatchDatabaseService, 'Should be SmartWatchDatabaseService instance');
343
+ });
344
+
345
+ await runTest('WatchService should have pedimento pattern configured', async () => {
346
+ const logger = new TestLogger();
347
+ const dbService = new MockDatabaseService(logger);
348
+ const watchService = new WatchService({}, dbService, logger);
349
+
350
+ assert(watchService.pedimentoPattern !== null, 'Should have pedimento pattern');
351
+ assert(watchService.pedimentoPattern instanceof RegExp, 'Should be a regex pattern');
352
+ });
353
+
354
+ // ─────────────────────────────────────────────────────────────────────────────
355
+ // TEST GROUP 2: Pedimento Detection
356
+ // ─────────────────────────────────────────────────────────────────────────────
357
+
358
+ console.log('\n🔍 Test Group 2: Pedimento Simplificado Detection\n');
359
+
360
+ await runTest('Should detect pedimento with "simplificado" in filename', async () => {
361
+ const logger = new TestLogger();
362
+ const dbService = new MockDatabaseService(logger);
363
+ const watchService = new WatchService({}, dbService, logger);
364
+
365
+ // Access private method through prototype for testing
366
+ const isPedimento = watchService.pedimentoPattern.test('pedimento-simplificado.pdf');
367
+ assert(isPedimento === true, 'Should detect pedimento-simplificado');
368
+ });
369
+
370
+ await runTest('Should detect pedimento with "simple" in filename', async () => {
371
+ const logger = new TestLogger();
372
+ const dbService = new MockDatabaseService(logger);
373
+ const watchService = new WatchService({}, dbService, logger);
374
+
375
+ const isPedimento = watchService.pedimentoPattern.test('documento-simple.pdf');
376
+ assert(isPedimento === true, 'Should detect documento-simple');
377
+ });
378
+
379
+ await runTest('Should not detect regular files as pedimento', async () => {
380
+ const logger = new TestLogger();
381
+ const dbService = new MockDatabaseService(logger);
382
+ const watchService = new WatchService({}, dbService, logger);
383
+
384
+ const isPedimento = watchService.pedimentoPattern.test('factura.xml');
385
+ assert(isPedimento === false, 'Should not detect regular file as pedimento');
386
+ });
387
+
388
+ await runTest('Should detect pedimento case-insensitively', async () => {
389
+ const logger = new TestLogger();
390
+ const dbService = new MockDatabaseService(logger);
391
+ const watchService = new WatchService({}, dbService, logger);
392
+
393
+ const test1 = watchService.pedimentoPattern.test('PEDIMENTO-SIMPLIFICADO.PDF');
394
+ const test2 = watchService.pedimentoPattern.test('Pedimento-Simple.xml');
395
+
396
+ assert(test1 === true, 'Should detect uppercase');
397
+ assert(test2 === true, 'Should detect mixed case');
398
+ });
399
+
400
+ // ─────────────────────────────────────────────────────────────────────────────
401
+ // TEST GROUP 3: File Handling
402
+ // ─────────────────────────────────────────────────────────────────────────────
403
+
404
+ console.log('\n🔍 Test Group 3: File Detection & Queue Registration\n');
405
+
406
+ await runTest('handleFileAdded should register non-pedimento file as PENDING', async () => {
407
+ const logger = new TestLogger();
408
+ const dbService = new MockDatabaseService(logger);
409
+ const watchService = new WatchService({}, dbService, logger);
410
+
411
+ await watchService.handleFileAdded('/test/factura.xml');
412
+
413
+ const insertedFiles = dbService.getSupabaseClient().data.uploader;
414
+ assert(insertedFiles.length > 0, 'Should insert file');
415
+ assert(insertedFiles[0].processingStatus === 'PENDING', 'Should have PENDING status');
416
+ assert(insertedFiles[0].filePath === '/test/factura.xml', 'Should match file path');
417
+ });
418
+
419
+ await runTest('handleFileAdded should handle pedimento file', async () => {
420
+ const logger = new TestLogger();
421
+ const dbService = new MockDatabaseService(logger);
422
+ const watchService = new WatchService({}, dbService, logger);
423
+
424
+ // Add a pending file first
425
+ await watchService.handleFileAdded('/test/factura.xml');
426
+
427
+ // Then add pedimento
428
+ await watchService.handleFileAdded('/test/pedimento-simplificado.pdf');
429
+
430
+ const files = dbService.getSupabaseClient().data.uploader;
431
+ assert(files.length >= 2, 'Should have at least 2 files');
432
+
433
+ const pedimento = files.find((f) => f.filePath.includes('pedimento'));
434
+ assert(pedimento !== undefined, 'Should have pedimento file');
435
+ assert(pedimento.processingStatus === 'READY_TO_UPLOAD', 'Pedimento should be READY_TO_UPLOAD');
436
+ });
437
+
438
+ await runTest('handleFileAdded should capture file extension', async () => {
439
+ const logger = new TestLogger();
440
+ const dbService = new MockDatabaseService(logger);
441
+ const watchService = new WatchService({}, dbService, logger);
442
+
443
+ await watchService.handleFileAdded('/test/document.pdf');
444
+
445
+ const files = dbService.getSupabaseClient().data.uploader;
446
+ const file = files.find((f) => f.filePath === '/test/document.pdf');
447
+
448
+ assert(file !== undefined, 'Should find inserted file');
449
+ assert(file.fileExtension === 'pdf', 'Should capture correct extension');
450
+ });
451
+
452
+ await runTest('handleFileAdded should set dependsOnPath to parent directory', async () => {
453
+ const logger = new TestLogger();
454
+ const dbService = new MockDatabaseService(logger);
455
+ const watchService = new WatchService({}, dbService, logger);
456
+
457
+ await watchService.handleFileAdded('/documents/subfolder/file.xml');
458
+
459
+ const files = dbService.getSupabaseClient().data.uploader;
460
+ const file = files[files.length - 1];
461
+
462
+ assert(file.dependsOnPath === '/documents/subfolder', 'Should set parent directory as dependsOnPath');
463
+ });
464
+
465
+ // ─────────────────────────────────────────────────────────────────────────────
466
+ // TEST GROUP 4: Queue Statistics
467
+ // ─────────────────────────────────────────────────────────────────────────────
468
+
469
+ console.log('\n🔍 Test Group 4: Queue Statistics & Progress Tracking\n');
470
+
471
+ await runTest('getQueueStats should return complete queue information', async () => {
472
+ const logger = new TestLogger();
473
+ const dbService = new MockDatabaseService(logger);
474
+ const watchService = new WatchService({}, dbService, logger);
475
+
476
+ // Add some files
477
+ await watchService.handleFileAdded('/test/file1.xml');
478
+ await watchService.handleFileAdded('/test/file2.xml');
479
+
480
+ const stats = await watchService.getQueueStats();
481
+
482
+ assert(stats.queue !== undefined, 'Should have queue object');
483
+ assert(stats.progress !== undefined, 'Should have progress object');
484
+ assert(stats.timestamp !== undefined, 'Should have timestamp');
485
+ assert(typeof stats.queue.pending === 'number', 'Should have pending count');
486
+ assert(typeof stats.queue.total === 'number', 'Should have total count');
487
+ assert(stats.queue.total >= 2, 'Should count both files');
488
+ });
489
+
490
+ await runTest('getQueueStats should show correct file counts', async () => {
491
+ const logger = new TestLogger();
492
+ const dbService = new MockDatabaseService(logger);
493
+ const watchService = new WatchService({}, dbService, logger);
494
+
495
+ await watchService.handleFileAdded('/test/file1.xml');
496
+ await watchService.handleFileAdded('/test/file2.xml');
497
+ await watchService.handleFileAdded('/test/pedimento-simple.pdf');
498
+
499
+ const stats = await watchService.getQueueStats();
500
+
501
+ // After pedimento is added, file1 and file2 get marked READY_TO_UPLOAD
502
+ // So we should have: 1 pedimento (READY) + 2 files (READY) = 3 total
503
+ assert(stats.queue.total >= 3, 'Should have at least 3 total files');
504
+ assert(stats.queue.readyToUpload >= 1, 'Should have at least 1 ready file (pedimento)');
505
+ });
506
+
507
+ await runTest('getQueueStats should calculate progress percentage', async () => {
508
+ const logger = new TestLogger();
509
+ const dbService = new MockDatabaseService(logger);
510
+ const watchService = new WatchService({}, dbService, logger);
511
+
512
+ await watchService.handleFileAdded('/test/file1.xml');
513
+
514
+ const stats = await watchService.getQueueStats();
515
+
516
+ assert(stats.progress.percentComplete >= 0, 'Percentage should be >= 0');
517
+ assert(stats.progress.percentComplete <= 100, 'Percentage should be <= 100');
518
+ assert(stats.progress.filesProcessed >= 0, 'filesProcessed should be >= 0');
519
+ assert(stats.progress.totalFiles >= 1, 'totalFiles should be >= 1');
520
+ });
521
+
522
+ // ─────────────────────────────────────────────────────────────────────────────
523
+ // TEST GROUP 5: Error Handling
524
+ // ─────────────────────────────────────────────────────────────────────────────
525
+
526
+ console.log('\n🔍 Test Group 5: Error Handling & Logging\n');
527
+
528
+ await runTest('handleFileAdded should log file detection', async () => {
529
+ const logger = new TestLogger();
530
+ const dbService = new MockDatabaseService(logger);
531
+ const watchService = new WatchService({}, dbService, logger);
532
+
533
+ await watchService.handleFileAdded('/test/file.xml');
534
+
535
+ const logs = logger.getLogs();
536
+ const detectionLog = logs.find((l) => l.msg.includes('File detected'));
537
+
538
+ assert(detectionLog !== undefined, 'Should log file detection');
539
+ });
540
+
541
+ await runTest('handleFileAdded should log pedimento detection separately', async () => {
542
+ const logger = new TestLogger();
543
+ const dbService = new MockDatabaseService(logger);
544
+ const watchService = new WatchService({}, dbService, logger);
545
+
546
+ await watchService.handleFileAdded('/test/pedimento-simple.pdf');
547
+
548
+ const logs = logger.getLogs();
549
+ const pedimentoLog = logs.find((l) => l.msg.includes('isPedimento: true'));
550
+
551
+ assert(pedimentoLog !== undefined, 'Should log pedimento detection');
552
+ });
553
+
554
+ await runTest('handleFileAdded should handle errors gracefully', async () => {
555
+ const logger = new TestLogger();
556
+ const dbService = new MockDatabaseService(logger);
557
+ const watchService = new WatchService({}, dbService, logger);
558
+
559
+ // Should not throw
560
+ await watchService.handleFileAdded('/test/valid-file.xml');
561
+ console.log(' (No exceptions on valid operations)');
562
+ });
563
+
564
+ // ═══════════════════════════════════════════════════════════════════════════
565
+ // TEST RESULTS SUMMARY
566
+ // ═══════════════════════════════════════════════════════════════════════════
567
+
568
+ console.log('\n╔════════════════════════════════════════════════════════════════════════════╗');
569
+ console.log('║ TEST RESULTS SUMMARY ║');
570
+ console.log('╠════════════════════════════════════════════════════════════════════════════╣');
571
+ console.log(`║ ✅ Tests Passed: ${testsPassed.toString().padEnd(60)} ║`);
572
+ console.log(`║ ❌ Tests Failed: ${testsFailed.toString().padEnd(60)} ║`);
573
+ console.log(`║ 📊 Total Tests: ${(testsPassed + testsFailed).toString().padEnd(60)} ║`);
574
+ console.log('╠════════════════════════════════════════════════════════════════════════════╣');
575
+
576
+ if (testsFailed === 0) {
577
+ console.log('║ 🎉 ALL TESTS PASSED! WatchService Integration Fully Validated ║');
578
+ } else {
579
+ console.log('║ ⚠️ Some tests failed. Please review the errors above. ║');
580
+ }
581
+
582
+ console.log('╚════════════════════════════════════════════════════════════════════════════╝\n');
583
+
584
+ process.exit(testsFailed > 0 ? 1 : 0);
@@ -5,6 +5,7 @@ import path from 'path';
5
5
 
6
6
  import databaseService from '../services/DatabaseService.js';
7
7
  import logger from '../services/LoggingService.js';
8
+ import watchService from '../services/WatchService.js';
8
9
  import uploadServiceFactory from '../services/upload/UploadServiceFactory.js';
9
10
 
10
11
  import appConfig from '../config/config.js';
@@ -32,6 +33,27 @@ export class UploadCommand {
32
33
  */
33
34
  async execute(options) {
34
35
  try {
36
+ // Prevent direct uploads while in watch mode
37
+ if (watchService.isWatchActive()) {
38
+ logger.error('❌ Cannot upload directly while in watch mode');
39
+ logger.info('💡 Files in watch mode are processed automatically');
40
+ logger.info(
41
+ '💡 Stop watch mode first (Ctrl+C) before using upload command',
42
+ );
43
+ return {
44
+ success: false,
45
+ reason: 'Watch mode is active - cannot upload directly',
46
+ source: null,
47
+ stats: {
48
+ successCount: 0,
49
+ detectedCount: 0,
50
+ organizedCount: 0,
51
+ failureCount: 0,
52
+ skippedCount: 0,
53
+ },
54
+ };
55
+ }
56
+
35
57
  // Validate configuration
36
58
  this.#validateOptions(options);
37
59
 
@@ -321,7 +343,10 @@ export class UploadCommand {
321
343
  try {
322
344
  const result = await databaseService.insertStatsOnlyToUploaderTable(
323
345
  fileObjects,
324
- options,
346
+ {
347
+ ...options,
348
+ quietMode: options.quietMode || false, // Pass through quiet mode flag
349
+ },
325
350
  );
326
351
  batchResults.successCount = result.totalInserted;
327
352
  batchResults.skippedCount = result.totalSkipped;
@@ -439,13 +464,15 @@ export class UploadCommand {
439
464
  });
440
465
  } else {
441
466
  // Supabase direct upload
442
- const uploadResult = await uploadService.upload([fileObject], { uploadPath });
443
-
467
+ const uploadResult = await uploadService.upload([fileObject], {
468
+ uploadPath,
469
+ });
470
+
444
471
  // Check if upload was successful
445
472
  if (!uploadResult.success) {
446
473
  throw new Error(`Supabase upload failed: ${uploadResult.error}`);
447
474
  }
448
-
475
+
449
476
  result = { successCount: 1 };
450
477
  }
451
478