@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.
- package/helper-apps/cortex-file-handler/{.env.test.azure → .env.test.azure.sample} +2 -1
- package/helper-apps/cortex-file-handler/{.env.test.gcs → .env.test.gcs.sample} +2 -1
- package/helper-apps/cortex-file-handler/{.env.test → .env.test.sample} +2 -1
- package/helper-apps/cortex-file-handler/Dockerfile +1 -1
- package/helper-apps/cortex-file-handler/INTERFACE.md +178 -0
- package/helper-apps/cortex-file-handler/package.json +4 -3
- package/helper-apps/cortex-file-handler/scripts/test-azure.sh +3 -0
- package/helper-apps/cortex-file-handler/{blobHandler.js → src/blobHandler.js} +167 -99
- package/helper-apps/cortex-file-handler/{fileChunker.js → src/fileChunker.js} +11 -24
- package/helper-apps/cortex-file-handler/{index.js → src/index.js} +236 -256
- package/helper-apps/cortex-file-handler/{services → src/services}/ConversionService.js +39 -18
- package/helper-apps/cortex-file-handler/{services → src/services}/FileConversionService.js +7 -3
- package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +177 -0
- package/helper-apps/cortex-file-handler/src/services/storage/GCSStorageProvider.js +258 -0
- package/helper-apps/cortex-file-handler/src/services/storage/LocalStorageProvider.js +182 -0
- package/helper-apps/cortex-file-handler/src/services/storage/StorageFactory.js +86 -0
- package/helper-apps/cortex-file-handler/src/services/storage/StorageProvider.js +53 -0
- package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +259 -0
- package/helper-apps/cortex-file-handler/{start.js → src/start.js} +1 -1
- package/helper-apps/cortex-file-handler/src/utils/filenameUtils.js +28 -0
- package/helper-apps/cortex-file-handler/tests/FileConversionService.test.js +1 -1
- package/helper-apps/cortex-file-handler/tests/blobHandler.test.js +4 -4
- package/helper-apps/cortex-file-handler/tests/conversionResilience.test.js +152 -0
- package/helper-apps/cortex-file-handler/tests/fileChunker.test.js +2 -28
- package/helper-apps/cortex-file-handler/tests/fileUpload.test.js +134 -23
- package/helper-apps/cortex-file-handler/tests/getOperations.test.js +307 -0
- package/helper-apps/cortex-file-handler/tests/postOperations.test.js +291 -0
- package/helper-apps/cortex-file-handler/tests/start.test.js +50 -14
- package/helper-apps/cortex-file-handler/tests/storage/AzureStorageProvider.test.js +120 -0
- package/helper-apps/cortex-file-handler/tests/storage/GCSStorageProvider.test.js +193 -0
- package/helper-apps/cortex-file-handler/tests/storage/LocalStorageProvider.test.js +148 -0
- package/helper-apps/cortex-file-handler/tests/storage/StorageFactory.test.js +100 -0
- package/helper-apps/cortex-file-handler/tests/storage/StorageService.test.js +113 -0
- package/helper-apps/cortex-file-handler/tests/testUtils.helper.js +73 -19
- package/lib/entityConstants.js +1 -1
- package/package.json +1 -1
- /package/helper-apps/cortex-file-handler/{constants.js → src/constants.js} +0 -0
- /package/helper-apps/cortex-file-handler/{docHelper.js → src/docHelper.js} +0 -0
- /package/helper-apps/cortex-file-handler/{helper.js → src/helper.js} +0 -0
- /package/helper-apps/cortex-file-handler/{localFileHandler.js → src/localFileHandler.js} +0 -0
- /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,
|
|
263
|
-
t.
|
|
264
|
-
response.data
|
|
265
|
-
'
|
|
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,
|
|
283
|
-
t.
|
|
284
|
-
response.data
|
|
285
|
-
'
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
});
|