@aj-archipelago/cortex 1.3.51 → 1.3.52

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 (41) hide show
  1. package/helper-apps/cortex-file-handler/{.env.test.azure → .env.test.azure.sample} +2 -1
  2. package/helper-apps/cortex-file-handler/{.env.test.gcs → .env.test.gcs.sample} +2 -1
  3. package/helper-apps/cortex-file-handler/{.env.test → .env.test.sample} +2 -1
  4. package/helper-apps/cortex-file-handler/Dockerfile +1 -1
  5. package/helper-apps/cortex-file-handler/INTERFACE.md +178 -0
  6. package/helper-apps/cortex-file-handler/package.json +4 -3
  7. package/helper-apps/cortex-file-handler/scripts/test-azure.sh +3 -0
  8. package/helper-apps/cortex-file-handler/{blobHandler.js → src/blobHandler.js} +167 -99
  9. package/helper-apps/cortex-file-handler/{fileChunker.js → src/fileChunker.js} +11 -24
  10. package/helper-apps/cortex-file-handler/{index.js → src/index.js} +236 -256
  11. package/helper-apps/cortex-file-handler/{services → src/services}/ConversionService.js +39 -18
  12. package/helper-apps/cortex-file-handler/{services → src/services}/FileConversionService.js +7 -3
  13. package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +177 -0
  14. package/helper-apps/cortex-file-handler/src/services/storage/GCSStorageProvider.js +258 -0
  15. package/helper-apps/cortex-file-handler/src/services/storage/LocalStorageProvider.js +182 -0
  16. package/helper-apps/cortex-file-handler/src/services/storage/StorageFactory.js +86 -0
  17. package/helper-apps/cortex-file-handler/src/services/storage/StorageProvider.js +53 -0
  18. package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +259 -0
  19. package/helper-apps/cortex-file-handler/{start.js → src/start.js} +1 -1
  20. package/helper-apps/cortex-file-handler/src/utils/filenameUtils.js +28 -0
  21. package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +1 -1
  22. package/helper-apps/cortex-file-handler/tests/blobHandler.test.js +4 -4
  23. package/helper-apps/cortex-file-handler/tests/conversionResilience.test.js +152 -0
  24. package/helper-apps/cortex-file-handler/tests/fileChunker.test.js +2 -28
  25. package/helper-apps/cortex-file-handler/tests/fileUpload.test.js +134 -23
  26. package/helper-apps/cortex-file-handler/tests/getOperations.test.js +307 -0
  27. package/helper-apps/cortex-file-handler/tests/postOperations.test.js +291 -0
  28. package/helper-apps/cortex-file-handler/tests/start.test.js +50 -14
  29. package/helper-apps/cortex-file-handler/tests/storage/AzureStorageProvider.test.js +120 -0
  30. package/helper-apps/cortex-file-handler/tests/storage/GCSStorageProvider.test.js +193 -0
  31. package/helper-apps/cortex-file-handler/tests/storage/LocalStorageProvider.test.js +148 -0
  32. package/helper-apps/cortex-file-handler/tests/storage/StorageFactory.test.js +100 -0
  33. package/helper-apps/cortex-file-handler/tests/storage/StorageService.test.js +113 -0
  34. package/helper-apps/cortex-file-handler/tests/testUtils.helper.js +73 -19
  35. package/lib/entityConstants.js +1 -1
  36. package/package.json +1 -1
  37. /package/helper-apps/cortex-file-handler/{constants.js → src/constants.js} +0 -0
  38. /package/helper-apps/cortex-file-handler/{docHelper.js → src/docHelper.js} +0 -0
  39. /package/helper-apps/cortex-file-handler/{helper.js → src/helper.js} +0 -0
  40. /package/helper-apps/cortex-file-handler/{localFileHandler.js → src/localFileHandler.js} +0 -0
  41. /package/helper-apps/cortex-file-handler/{redis.js → src/redis.js} +0 -0
@@ -11,7 +11,7 @@ import axios from 'axios';
11
11
  import FormData from 'form-data';
12
12
  import { v4 as uuidv4 } from 'uuid';
13
13
 
14
- import { port, publicFolder, ipAddress } from '../start.js';
14
+ import { port, publicFolder, ipAddress } from '../src/start.js';
15
15
  import { cleanupHashAndFile, getFolderNameFromUrl } from './testUtils.helper.js';
16
16
 
17
17
  // Add these helper functions at the top after imports
@@ -259,10 +259,11 @@ test.serial('should reject invalid URLs', async (t) => {
259
259
  },
260
260
  );
261
261
 
262
- t.is(response.status, 500, 'Should return 500 for invalid URL');
263
- t.true(
264
- response.data.includes('Invalid URL'),
265
- 'Should indicate invalid URL in error message',
262
+ t.is(response.status, 400, 'Should return 400 for invalid URL');
263
+ t.is(
264
+ response.data,
265
+ 'Invalid URL format',
266
+ 'Should indicate invalid URL format in error message',
266
267
  );
267
268
  });
268
269
 
@@ -279,10 +280,11 @@ test.serial('should reject unsupported protocols', async (t) => {
279
280
  },
280
281
  );
281
282
 
282
- t.is(response.status, 500, 'Should return 500 for unsupported protocol');
283
- t.true(
284
- response.data.includes('Error processing media file'),
285
- 'Should indicate error processing media file',
283
+ t.is(response.status, 400, 'Should return 400 for unsupported protocol');
284
+ t.is(
285
+ response.data,
286
+ 'Invalid URL protocol - only HTTP, HTTPS, and GCS URLs are supported',
287
+ 'Should indicate invalid protocol in error message',
286
288
  );
287
289
  });
288
290
 
@@ -668,7 +670,7 @@ test.serial(
668
670
  t.is(uploadResponse.status, 200, 'Upload should succeed');
669
671
  t.truthy(uploadResponse.data.url, 'Response should contain URL');
670
672
 
671
- await cleanupUploadedFile(t, uploadResponse.data.url);
673
+ await cleanupHashAndFile(testHash, uploadResponse.data.url, baseUrl);
672
674
 
673
675
  // Verify hash is gone by trying to get the file URL
674
676
  const hashCheckResponse = await axios.get(`${baseUrl}`, {
@@ -835,13 +837,19 @@ async function checkGCSFile(gcsUrl) {
835
837
  const [, , bucket, ...objectParts] = gcsUrl.split('/');
836
838
  const object = objectParts.join('/');
837
839
 
840
+ console.log(`[checkGCSFile] Checking file in GCS: ${gcsUrl}`);
841
+ console.log(`[checkGCSFile] Bucket: ${bucket}, Object: ${object}`);
842
+ console.log(`[checkGCSFile] Using emulator at ${process.env.STORAGE_EMULATOR_HOST}`);
843
+
838
844
  // Query fake-gcs-server
839
845
  const response = await axios.get(
840
- `http://localhost:4443/storage/v1/b/${bucket}/o/${encodeURIComponent(object)}`,
846
+ `${process.env.STORAGE_EMULATOR_HOST}/storage/v1/b/${bucket}/o/${encodeURIComponent(object)}`,
841
847
  {
842
848
  validateStatus: (status) => true,
843
849
  },
844
850
  );
851
+ console.log(`[checkGCSFile] Response status: ${response.status}`);
852
+ console.log(`[checkGCSFile] File ${response.status === 200 ? 'exists' : 'does not exist'}`);
845
853
  return response.status === 200;
846
854
  }
847
855
 
@@ -1031,6 +1039,7 @@ test.serial(
1031
1039
  params: {
1032
1040
  uri: testFileUrl,
1033
1041
  requestId,
1042
+ useGCS: true,
1034
1043
  },
1035
1044
  validateStatus: (status) => true,
1036
1045
  timeout: 5000,
@@ -1327,13 +1336,14 @@ test.serial(
1327
1336
  async (t) => {
1328
1337
  const testContent = 'test content for legacy sequence';
1329
1338
  const testHash = 'test-legacy-sequence-hash';
1339
+ const legacyBaseUrl = `http://localhost:${port}/api/MediaFileChunker`;
1330
1340
  const form = new FormData();
1331
1341
  form.append('file', Buffer.from(testContent), 'sequence-test.txt');
1332
1342
  form.append('hash', testHash);
1333
1343
 
1334
1344
  // Upload file with hash through legacy endpoint
1335
1345
  const uploadResponse = await axios.post(
1336
- `http://localhost:${port}/api/MediaFileChunker`,
1346
+ legacyBaseUrl,
1337
1347
  form,
1338
1348
  {
1339
1349
  headers: form.getHeaders(),
@@ -1348,17 +1358,43 @@ test.serial(
1348
1358
  );
1349
1359
  t.truthy(uploadResponse.data.url, 'Response should contain URL');
1350
1360
 
1351
- await cleanupUploadedFile(t, uploadResponse.data.url);
1361
+ // Wait for Redis to be updated after upload
1362
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1363
+
1364
+ // Verify hash exists after upload using legacy endpoint
1365
+ const initialHashCheck = await axios.get(
1366
+ legacyBaseUrl,
1367
+ {
1368
+ params: {
1369
+ hash: testHash,
1370
+ checkHash: true,
1371
+ },
1372
+ validateStatus: (status) => true,
1373
+ timeout: 5000,
1374
+ },
1375
+ );
1376
+ t.is(initialHashCheck.status, 200, 'Hash should exist after upload');
1377
+ t.truthy(initialHashCheck.data.url, 'Hash check should return file URL');
1378
+
1379
+ // Wait for Redis to be updated after initial check
1380
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1381
+
1382
+ // Clean up the file and hash using the legacy endpoint
1383
+ await cleanupHashAndFile(testHash, uploadResponse.data.url, legacyBaseUrl);
1384
+
1385
+ // Wait for Redis to be updated after cleanup
1386
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1352
1387
 
1353
1388
  // Verify hash is gone by trying to get the file URL through legacy endpoint
1354
1389
  const hashCheckResponse = await axios.get(
1355
- `http://localhost:${port}/api/MediaFileChunker`,
1390
+ legacyBaseUrl,
1356
1391
  {
1357
1392
  params: {
1358
1393
  hash: testHash,
1359
1394
  checkHash: true,
1360
1395
  },
1361
1396
  validateStatus: (status) => true,
1397
+ timeout: 5000,
1362
1398
  },
1363
1399
  );
1364
1400
  t.is(hashCheckResponse.status, 404, 'Hash should not exist after deletion');
@@ -0,0 +1,120 @@
1
+ import test from 'ava';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { AzureStorageProvider } from '../../src/services/storage/AzureStorageProvider.js';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ test.before(() => {
11
+ // Ensure we have the required environment variables
12
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
13
+ console.warn('Skipping Azure tests - AZURE_STORAGE_CONNECTION_STRING not set');
14
+ }
15
+ });
16
+
17
+ test('should create provider with valid credentials', (t) => {
18
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
19
+ t.pass('Skipping test - Azure not configured');
20
+ return;
21
+ }
22
+
23
+ const provider = new AzureStorageProvider(
24
+ process.env.AZURE_STORAGE_CONNECTION_STRING,
25
+ 'test-container'
26
+ );
27
+ t.truthy(provider);
28
+ });
29
+
30
+ test('should throw error with missing credentials', (t) => {
31
+ t.throws(() => {
32
+ new AzureStorageProvider(null, 'test-container');
33
+ }, { message: 'Missing Azure Storage connection string or container name' });
34
+ });
35
+
36
+ test('should upload and delete file', async (t) => {
37
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
38
+ t.pass('Skipping test - Azure not configured');
39
+ return;
40
+ }
41
+
42
+ const provider = new AzureStorageProvider(
43
+ process.env.AZURE_STORAGE_CONNECTION_STRING,
44
+ 'test-container'
45
+ );
46
+
47
+ // Create test file
48
+ const testContent = 'Hello World!';
49
+ const testFile = path.join(__dirname, 'test.txt');
50
+ fs.writeFileSync(testFile, testContent);
51
+
52
+ try {
53
+ // Upload file
54
+ const requestId = 'test-upload';
55
+ const result = await provider.uploadFile({}, testFile, requestId);
56
+
57
+ t.truthy(result.url);
58
+ t.truthy(result.blobName);
59
+ t.true(result.url.includes('test-container'));
60
+ t.true(result.blobName.startsWith(requestId));
61
+
62
+ // Verify file exists
63
+ const exists = await provider.fileExists(result.url);
64
+ t.true(exists);
65
+
66
+ // Delete file
67
+ const deleted = await provider.deleteFiles(requestId);
68
+ t.true(deleted.length > 0);
69
+ t.true(deleted[0].startsWith(requestId));
70
+
71
+ // Verify file is gone
72
+ const existsAfterDelete = await provider.fileExists(result.url);
73
+ t.false(existsAfterDelete);
74
+ } finally {
75
+ // Cleanup test file
76
+ if (fs.existsSync(testFile)) {
77
+ fs.unlinkSync(testFile);
78
+ }
79
+ }
80
+ });
81
+
82
+ test('should handle file download', async (t) => {
83
+ if (!process.env.AZURE_STORAGE_CONNECTION_STRING) {
84
+ t.pass('Skipping test - Azure not configured');
85
+ return;
86
+ }
87
+
88
+ const provider = new AzureStorageProvider(
89
+ process.env.AZURE_STORAGE_CONNECTION_STRING,
90
+ 'test-container'
91
+ );
92
+
93
+ // Create test file
94
+ const testContent = 'Hello World!';
95
+ const testFile = path.join(__dirname, 'test.txt');
96
+ fs.writeFileSync(testFile, testContent);
97
+
98
+ try {
99
+ // Upload file
100
+ const requestId = 'test-download';
101
+ const result = await provider.uploadFile({}, testFile, requestId);
102
+
103
+ // Download to new location
104
+ const downloadPath = path.join(__dirname, 'downloaded.txt');
105
+ await provider.downloadFile(result.url, downloadPath);
106
+
107
+ // Verify content
108
+ const downloadedContent = fs.readFileSync(downloadPath, 'utf8');
109
+ t.is(downloadedContent, testContent);
110
+
111
+ // Cleanup
112
+ await provider.deleteFiles(requestId);
113
+ fs.unlinkSync(downloadPath);
114
+ } finally {
115
+ // Cleanup test file
116
+ if (fs.existsSync(testFile)) {
117
+ fs.unlinkSync(testFile);
118
+ }
119
+ }
120
+ });
@@ -0,0 +1,193 @@
1
+ import test from 'ava';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { GCSStorageProvider } from '../../src/services/storage/GCSStorageProvider.js';
6
+ import { sanitizeFilename } from '../../src/utils/filenameUtils.js';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ test.before(() => {
12
+ // Ensure we have the required environment variables
13
+ if (!process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64 && !process.env.GCP_SERVICE_ACCOUNT_KEY) {
14
+ console.warn('Skipping GCS tests - GCP credentials not set');
15
+ }
16
+ });
17
+
18
+ test('should create provider with valid credentials', (t) => {
19
+ if (!process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64 && !process.env.GCP_SERVICE_ACCOUNT_KEY) {
20
+ t.pass('Skipping test - GCS not configured');
21
+ return;
22
+ }
23
+
24
+ const credentials = {
25
+ project_id: 'test-project',
26
+ client_email: 'test@test.com',
27
+ private_key: 'test-key'
28
+ };
29
+
30
+ const provider = new GCSStorageProvider(credentials, 'test-bucket');
31
+ t.truthy(provider);
32
+ });
33
+
34
+ test('should throw error with missing credentials', (t) => {
35
+ t.throws(() => {
36
+ new GCSStorageProvider(null, 'test-bucket');
37
+ }, { message: 'Missing GCS credentials or bucket name' });
38
+ });
39
+
40
+ test('should upload and delete file', async (t) => {
41
+ if (!process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64 && !process.env.GCP_SERVICE_ACCOUNT_KEY) {
42
+ t.pass('Skipping test - GCS not configured');
43
+ return;
44
+ }
45
+
46
+ const credentials = JSON.parse(
47
+ process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64
48
+ ? Buffer.from(process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64, 'base64').toString()
49
+ : process.env.GCP_SERVICE_ACCOUNT_KEY
50
+ );
51
+
52
+ const provider = new GCSStorageProvider(
53
+ credentials,
54
+ process.env.GCS_BUCKETNAME || 'cortextempfiles'
55
+ );
56
+
57
+ // Create test file
58
+ const testContent = 'Hello World!';
59
+ const testFile = path.join(__dirname, 'test.txt');
60
+ fs.writeFileSync(testFile, testContent);
61
+
62
+ try {
63
+ // Upload file
64
+ const requestId = 'test-upload';
65
+ const result = await provider.uploadFile({}, testFile, requestId);
66
+
67
+ t.truthy(result.url);
68
+ t.truthy(result.blobName);
69
+ t.true(result.url.startsWith('gs://'));
70
+ t.true(result.blobName.startsWith(requestId));
71
+
72
+ // Verify file exists
73
+ const exists = await provider.fileExists(result.url);
74
+ t.true(exists);
75
+
76
+ // Delete file
77
+ const deleted = await provider.deleteFiles(requestId);
78
+ t.true(deleted.length > 0);
79
+ t.true(deleted[0].startsWith(requestId));
80
+
81
+ // Verify file is gone
82
+ const existsAfterDelete = await provider.fileExists(result.url);
83
+ t.false(existsAfterDelete);
84
+ } finally {
85
+ // Cleanup test file
86
+ if (fs.existsSync(testFile)) {
87
+ fs.unlinkSync(testFile);
88
+ }
89
+ }
90
+ });
91
+
92
+ test('should handle file download', async (t) => {
93
+ if (!process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64 && !process.env.GCP_SERVICE_ACCOUNT_KEY) {
94
+ t.pass('Skipping test - GCS not configured');
95
+ return;
96
+ }
97
+
98
+ const credentials = JSON.parse(
99
+ process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64
100
+ ? Buffer.from(process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64, 'base64').toString()
101
+ : process.env.GCP_SERVICE_ACCOUNT_KEY
102
+ );
103
+
104
+ const provider = new GCSStorageProvider(
105
+ credentials,
106
+ process.env.GCS_BUCKETNAME || 'cortextempfiles'
107
+ );
108
+
109
+ // Create test file
110
+ const testContent = 'Hello World!';
111
+ const testFile = path.join(__dirname, 'test.txt');
112
+ fs.writeFileSync(testFile, testContent);
113
+
114
+ try {
115
+ // Upload file
116
+ const requestId = 'test-download';
117
+ const result = await provider.uploadFile({}, testFile, requestId);
118
+
119
+ // Download to new location
120
+ const downloadPath = path.join(__dirname, 'downloaded.txt');
121
+ await provider.downloadFile(result.url, downloadPath);
122
+
123
+ // Verify content
124
+ const downloadedContent = fs.readFileSync(downloadPath, 'utf8');
125
+ t.is(downloadedContent, testContent);
126
+
127
+ // Cleanup
128
+ await provider.deleteFiles(requestId);
129
+ fs.unlinkSync(downloadPath);
130
+ } finally {
131
+ // Cleanup test file
132
+ if (fs.existsSync(testFile)) {
133
+ fs.unlinkSync(testFile);
134
+ }
135
+ }
136
+ });
137
+
138
+ test('should handle file existence check with spaces and special characters', async (t) => {
139
+ if (!process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64 && !process.env.GCP_SERVICE_ACCOUNT_KEY) {
140
+ t.pass('Skipping test - GCS not configured');
141
+ return;
142
+ }
143
+
144
+ const credentials = JSON.parse(
145
+ process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64
146
+ ? Buffer.from(process.env.GCP_SERVICE_ACCOUNT_KEY_BASE64, 'base64').toString()
147
+ : process.env.GCP_SERVICE_ACCOUNT_KEY
148
+ );
149
+
150
+ const provider = new GCSStorageProvider(
151
+ credentials,
152
+ process.env.GCS_BUCKETNAME || 'cortextempfiles'
153
+ );
154
+
155
+ // Create test file with spaces and special characters in name
156
+ const testContent = 'Hello World!';
157
+ const testFileName = 'test file with spaces & special chars!.txt';
158
+ const testFile = path.join(__dirname, testFileName);
159
+ fs.writeFileSync(testFile, testContent);
160
+
161
+ try {
162
+ // Upload file
163
+ const requestId = 'test-special-chars';
164
+ const result = await provider.uploadFile({}, testFile, requestId);
165
+
166
+ t.truthy(result.url);
167
+ // Compare against sanitized filename instead of original
168
+ const sanitizedFileName = sanitizeFilename(testFileName);
169
+ t.true(result.url.includes(sanitizedFileName));
170
+ t.true(result.url.startsWith('gs://'));
171
+
172
+ // Verify file exists with original URL
173
+ const exists = await provider.fileExists(result.url);
174
+ t.true(exists, 'File should exist with original URL');
175
+
176
+ // Verify file exists with encoded URL
177
+ const encodedUrl = result.url.replace(/ /g, '%20');
178
+ const existsEncoded = await provider.fileExists(encodedUrl);
179
+ t.true(existsEncoded, 'File should exist with encoded URL');
180
+
181
+ // Cleanup
182
+ await provider.deleteFiles(requestId);
183
+
184
+ // Verify file is gone
185
+ const existsAfterDelete = await provider.fileExists(result.url);
186
+ t.false(existsAfterDelete, 'File should not exist after deletion');
187
+ } finally {
188
+ // Cleanup test file
189
+ if (fs.existsSync(testFile)) {
190
+ fs.unlinkSync(testFile);
191
+ }
192
+ }
193
+ });
@@ -0,0 +1,148 @@
1
+ import test from 'ava';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { LocalStorageProvider } from '../../src/services/storage/LocalStorageProvider.js';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ test.before(() => {
11
+ // Create test directory
12
+ const testDir = path.join(__dirname, 'test-files');
13
+ if (!fs.existsSync(testDir)) {
14
+ fs.mkdirSync(testDir, { recursive: true });
15
+ }
16
+ });
17
+
18
+ test.after(() => {
19
+ // Cleanup test directory
20
+ const testDir = path.join(__dirname, 'test-files');
21
+ if (fs.existsSync(testDir)) {
22
+ fs.rmSync(testDir, { recursive: true });
23
+ }
24
+ });
25
+
26
+ test('should create provider with valid path', (t) => {
27
+ const provider = new LocalStorageProvider(path.join(__dirname, 'test-files'));
28
+ t.truthy(provider);
29
+ });
30
+
31
+ test('should throw error with missing path', (t) => {
32
+ t.throws(() => {
33
+ new LocalStorageProvider(null);
34
+ }, { message: 'Missing public folder path' });
35
+ });
36
+
37
+ test('should create public folder if it does not exist', (t) => {
38
+ const testDir = path.join(__dirname, 'test-files', 'new-folder');
39
+ if (fs.existsSync(testDir)) {
40
+ fs.rmSync(testDir, { recursive: true });
41
+ }
42
+
43
+ const provider = new LocalStorageProvider(testDir);
44
+ t.true(fs.existsSync(testDir));
45
+ });
46
+
47
+ test('should upload and delete file', async (t) => {
48
+ const provider = new LocalStorageProvider(path.join(__dirname, 'test-files'));
49
+
50
+ // Create test file
51
+ const testContent = 'Hello World!';
52
+ const testFile = path.join(__dirname, 'test.txt');
53
+ fs.writeFileSync(testFile, testContent);
54
+
55
+ try {
56
+ // Upload file
57
+ const requestId = 'test-upload';
58
+ const result = await provider.uploadFile({}, testFile, requestId);
59
+
60
+ t.truthy(result.url);
61
+ t.truthy(result.blobName);
62
+ t.true(result.url.includes('/files/'));
63
+ t.true(result.blobName.startsWith(requestId));
64
+
65
+ // Verify file exists
66
+ const exists = await provider.fileExists(result.url);
67
+ t.true(exists);
68
+
69
+ // Delete file
70
+ const deleted = await provider.deleteFiles(requestId);
71
+ t.true(deleted.length > 0);
72
+ t.true(deleted[0].startsWith(requestId));
73
+
74
+ // Verify file is gone
75
+ const existsAfterDelete = await provider.fileExists(result.url);
76
+ t.false(existsAfterDelete);
77
+ } finally {
78
+ // Cleanup test file
79
+ if (fs.existsSync(testFile)) {
80
+ fs.unlinkSync(testFile);
81
+ }
82
+ }
83
+ });
84
+
85
+ test('should handle file download', async (t) => {
86
+ const provider = new LocalStorageProvider(path.join(__dirname, 'test-files'));
87
+
88
+ // Create test file
89
+ const testContent = 'Hello World!';
90
+ const testFile = path.join(__dirname, 'test.txt');
91
+ fs.writeFileSync(testFile, testContent);
92
+
93
+ try {
94
+ // Upload file
95
+ const requestId = 'test-download';
96
+ const result = await provider.uploadFile({}, testFile, requestId);
97
+
98
+ // Download to new location
99
+ const downloadPath = path.join(__dirname, 'downloaded.txt');
100
+ await provider.downloadFile(result.url, downloadPath);
101
+
102
+ // Verify content
103
+ const downloadedContent = fs.readFileSync(downloadPath, 'utf8');
104
+ t.is(downloadedContent, testContent);
105
+
106
+ // Cleanup
107
+ await provider.deleteFiles(requestId);
108
+ fs.unlinkSync(downloadPath);
109
+ } finally {
110
+ // Cleanup test file
111
+ if (fs.existsSync(testFile)) {
112
+ fs.unlinkSync(testFile);
113
+ }
114
+ }
115
+ });
116
+
117
+ test('should handle cleanup of multiple files', async (t) => {
118
+ const provider = new LocalStorageProvider(path.join(__dirname, 'test-files'));
119
+
120
+ // Create test files
121
+ const testContent = 'Hello World!';
122
+ const testFile1 = path.join(__dirname, 'test1.txt');
123
+ const testFile2 = path.join(__dirname, 'test2.txt');
124
+ fs.writeFileSync(testFile1, testContent);
125
+ fs.writeFileSync(testFile2, testContent);
126
+
127
+ try {
128
+ // Upload files
129
+ const requestId1 = 'test-cleanup-1';
130
+ const requestId2 = 'test-cleanup-2';
131
+ const result1 = await provider.uploadFile({}, testFile1, requestId1);
132
+ const result2 = await provider.uploadFile({}, testFile2, requestId2);
133
+
134
+ // Cleanup files
135
+ const cleaned = await provider.cleanup([result1.url, result2.url]);
136
+ t.is(cleaned.length, 2);
137
+
138
+ // Verify files are gone
139
+ const exists1 = await provider.fileExists(result1.url);
140
+ const exists2 = await provider.fileExists(result2.url);
141
+ t.false(exists1);
142
+ t.false(exists2);
143
+ } finally {
144
+ // Cleanup test files
145
+ if (fs.existsSync(testFile1)) fs.unlinkSync(testFile1);
146
+ if (fs.existsSync(testFile2)) fs.unlinkSync(testFile2);
147
+ }
148
+ });