@onlineapps/conn-base-storage 1.0.6 → 1.0.7
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/package.json +1 -1
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -297
- package/coverage/coverage-final.json +0 -6
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -131
- package/coverage/index.js.html +0 -1579
- package/coverage/internal-url-adapter.js.html +0 -604
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/config.js.html +0 -244
- package/coverage/lcov-report/defaults.js.html +0 -214
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -176
- package/coverage/lcov-report/index.js.html +0 -2608
- package/coverage/lcov-report/internal-url-adapter.js.html +0 -559
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sharedUrlAdapter.js.html +0 -856
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov.info +0 -13
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/tests/component/storage.component.test.js +0 -363
- package/tests/integration/setup.js +0 -3
- package/tests/integration/storage.integration.test.js +0 -224
- package/tests/unit/internal-url-adapter.test.js +0 -210
- package/tests/unit/legacy.storage.test.js.bak +0 -614
- package/tests/unit/storage.extended.unit.test.js +0 -444
- package/tests/unit/storage.unit.test.js +0 -382
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Component Tests pro StorageConnector
|
|
3
|
-
* Pre-integrační testy - testují spolupráci s ostatními connektory
|
|
4
|
-
*
|
|
5
|
-
* Testovací strategie:
|
|
6
|
-
* - Pokud jsou dostupné ostatní connektory (logger, registry), použijí se
|
|
7
|
-
* - Pokud nejsou dostupné, použijí se mocky
|
|
8
|
-
* - MinIO je vždy mockované
|
|
9
|
-
* - Testy MUSÍ VŽDY PROJÍT (100% pass rate)
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const StorageConnector = require('../../src/index');
|
|
13
|
-
const crypto = require('crypto');
|
|
14
|
-
|
|
15
|
-
// Pokus o načtení dalších connectorů
|
|
16
|
-
let LoggerConnector;
|
|
17
|
-
let RegistryClient;
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
LoggerConnector = require('@onlineapps/connector-logger');
|
|
21
|
-
} catch (e) {
|
|
22
|
-
// Logger není dostupný, použijeme mock
|
|
23
|
-
LoggerConnector = class MockLogger {
|
|
24
|
-
constructor() {
|
|
25
|
-
this.info = jest.fn();
|
|
26
|
-
this.error = jest.fn();
|
|
27
|
-
this.warn = jest.fn();
|
|
28
|
-
this.debug = jest.fn();
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
RegistryClient = require('@onlineapps/connector-registry-client');
|
|
35
|
-
} catch (e) {
|
|
36
|
-
// Registry není dostupný, použijeme mock
|
|
37
|
-
RegistryClient = class MockRegistry {
|
|
38
|
-
constructor() {
|
|
39
|
-
this.register = jest.fn().mockResolvedValue(true);
|
|
40
|
-
this.unregister = jest.fn().mockResolvedValue(true);
|
|
41
|
-
this.getService = jest.fn().mockResolvedValue(null);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Mock MinIO vždy
|
|
47
|
-
jest.mock('minio', () => {
|
|
48
|
-
return {
|
|
49
|
-
Client: jest.fn().mockImplementation(() => ({
|
|
50
|
-
bucketExists: jest.fn().mockResolvedValue(true),
|
|
51
|
-
makeBucket: jest.fn().mockResolvedValue(),
|
|
52
|
-
setBucketPolicy: jest.fn().mockResolvedValue(),
|
|
53
|
-
putObject: jest.fn().mockResolvedValue(),
|
|
54
|
-
getObject: jest.fn().mockImplementation(() => {
|
|
55
|
-
// Default mock stream
|
|
56
|
-
return {
|
|
57
|
-
[Symbol.asyncIterator]: async function* () {
|
|
58
|
-
yield Buffer.from('test content');
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
}),
|
|
62
|
-
statObject: jest.fn().mockResolvedValue({
|
|
63
|
-
size: 100,
|
|
64
|
-
metaData: {
|
|
65
|
-
'x-amz-meta-fingerprint': 'mockfingerprint',
|
|
66
|
-
'x-amz-meta-uploaded-at': new Date().toISOString()
|
|
67
|
-
}
|
|
68
|
-
}),
|
|
69
|
-
removeObject: jest.fn().mockResolvedValue(),
|
|
70
|
-
removeBucket: jest.fn().mockResolvedValue(),
|
|
71
|
-
listObjectsV2: jest.fn().mockReturnValue({
|
|
72
|
-
on: jest.fn((event, callback) => {
|
|
73
|
-
if (event === 'data') {
|
|
74
|
-
callback({ name: 'file1.txt', size: 100, lastModified: new Date() });
|
|
75
|
-
} else if (event === 'end') {
|
|
76
|
-
callback();
|
|
77
|
-
}
|
|
78
|
-
return { on: jest.fn() };
|
|
79
|
-
})
|
|
80
|
-
}),
|
|
81
|
-
presignedGetObject: jest.fn().mockResolvedValue('http://mock-url'),
|
|
82
|
-
presignedGetUrl: jest.fn().mockResolvedValue('http://mock-url'),
|
|
83
|
-
useSSL: false,
|
|
84
|
-
port: 9000,
|
|
85
|
-
host: 'localhost'
|
|
86
|
-
}))
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('StorageConnector Component Tests @component', () => {
|
|
91
|
-
let storage;
|
|
92
|
-
let logger;
|
|
93
|
-
let registry;
|
|
94
|
-
|
|
95
|
-
beforeEach(() => {
|
|
96
|
-
jest.clearAllMocks();
|
|
97
|
-
|
|
98
|
-
// Inicializace s loggerem
|
|
99
|
-
logger = new LoggerConnector();
|
|
100
|
-
registry = new RegistryClient();
|
|
101
|
-
|
|
102
|
-
storage = new StorageConnector({
|
|
103
|
-
logger: logger,
|
|
104
|
-
registry: registry
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
describe('Integration with Logger', () => {
|
|
109
|
-
test('should log operations when logger is provided', async () => {
|
|
110
|
-
await storage.initialize();
|
|
111
|
-
|
|
112
|
-
// Zkontrolujeme, že logger byl použit
|
|
113
|
-
if (logger.info && typeof logger.info === 'function') {
|
|
114
|
-
// Logger je dostupný nebo mockovaný
|
|
115
|
-
expect(storage.logger).toBeDefined();
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('should work without logger', async () => {
|
|
120
|
-
const storageNoLogger = new StorageConnector();
|
|
121
|
-
await storageNoLogger.initialize();
|
|
122
|
-
|
|
123
|
-
expect(storageNoLogger.initialized).toBe(true);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
describe('Integration with Registry', () => {
|
|
128
|
-
test('should register service when registry is provided', async () => {
|
|
129
|
-
if (registry.register && typeof registry.register === 'function') {
|
|
130
|
-
await storage.initialize();
|
|
131
|
-
|
|
132
|
-
// Storage by se měl zaregistrovat
|
|
133
|
-
// Toto závisí na implementaci
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('should work without registry', async () => {
|
|
138
|
-
const storageNoRegistry = new StorageConnector();
|
|
139
|
-
await storageNoRegistry.initialize();
|
|
140
|
-
|
|
141
|
-
expect(storageNoRegistry.initialized).toBe(true);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
describe('Storage Operations with Components', () => {
|
|
146
|
-
test('should upload and log operation', async () => {
|
|
147
|
-
await storage.initialize();
|
|
148
|
-
|
|
149
|
-
const result = await storage.uploadWithFingerprint(
|
|
150
|
-
'test-bucket',
|
|
151
|
-
'test content',
|
|
152
|
-
'test-prefix'
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
expect(result.fingerprint).toBeDefined();
|
|
156
|
-
expect(result.bucket).toBe('test-bucket');
|
|
157
|
-
|
|
158
|
-
// Logger je interní winston logger, ne náš mock
|
|
159
|
-
// Zkontrolujeme jen že operace proběhla
|
|
160
|
-
expect(result.existed).toBeDefined();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test('should download and verify with logging', async () => {
|
|
164
|
-
await storage.initialize();
|
|
165
|
-
|
|
166
|
-
const mockClient = storage.minioClient;
|
|
167
|
-
const content = 'verified content';
|
|
168
|
-
const fingerprint = storage.generateFingerprint(content);
|
|
169
|
-
|
|
170
|
-
// Nastavit mock pro správný obsah
|
|
171
|
-
mockClient.getObject.mockReturnValue({
|
|
172
|
-
[Symbol.asyncIterator]: async function* () {
|
|
173
|
-
yield Buffer.from(content);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const result = await storage.downloadWithVerification(
|
|
178
|
-
'test-bucket',
|
|
179
|
-
'test-path',
|
|
180
|
-
fingerprint
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
expect(result).toBe(content);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('should handle errors and log them', async () => {
|
|
187
|
-
const mockClient = storage.minioClient;
|
|
188
|
-
mockClient.bucketExists.mockRejectedValue(new Error('Test error'));
|
|
189
|
-
|
|
190
|
-
await expect(storage.initialize()).rejects.toThrow('Test error');
|
|
191
|
-
|
|
192
|
-
// Logger je interní winston logger
|
|
193
|
-
// Zkontrolujeme jen že error byl vyhozen
|
|
194
|
-
expect(storage.initialized).toBe(false);
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
describe('Cache Coordination', () => {
|
|
199
|
-
test('should coordinate cache between multiple instances', () => {
|
|
200
|
-
const storage1 = new StorageConnector({ cacheMaxSize: 5 });
|
|
201
|
-
const storage2 = new StorageConnector({ cacheMaxSize: 5 });
|
|
202
|
-
|
|
203
|
-
// Každá instance má svou vlastní cache
|
|
204
|
-
storage1.addToCache('key1', 'value1');
|
|
205
|
-
storage2.addToCache('key2', 'value2');
|
|
206
|
-
|
|
207
|
-
expect(storage1.getFromCache('key1')).toBe('value1');
|
|
208
|
-
expect(storage1.getFromCache('key2')).toBeUndefined();
|
|
209
|
-
|
|
210
|
-
expect(storage2.getFromCache('key2')).toBe('value2');
|
|
211
|
-
expect(storage2.getFromCache('key1')).toBeUndefined();
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
describe('Fingerprint Validation Across Components', () => {
|
|
216
|
-
test('should generate consistent fingerprints', () => {
|
|
217
|
-
const storage1 = new StorageConnector();
|
|
218
|
-
const storage2 = new StorageConnector();
|
|
219
|
-
|
|
220
|
-
const content = 'shared content';
|
|
221
|
-
const fp1 = storage1.generateFingerprint(content);
|
|
222
|
-
const fp2 = storage2.generateFingerprint(content);
|
|
223
|
-
|
|
224
|
-
expect(fp1).toBe(fp2);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
test('should validate fingerprints from other sources', async () => {
|
|
228
|
-
await storage.initialize();
|
|
229
|
-
|
|
230
|
-
// Simulace fingerprintu z jiného zdroje
|
|
231
|
-
const externalContent = 'external content';
|
|
232
|
-
const externalFingerprint = crypto.createHash('sha256')
|
|
233
|
-
.update(externalContent)
|
|
234
|
-
.digest('hex');
|
|
235
|
-
|
|
236
|
-
// Náš connector by měl generovat stejný fingerprint
|
|
237
|
-
const ourFingerprint = storage.generateFingerprint(externalContent);
|
|
238
|
-
|
|
239
|
-
expect(ourFingerprint).toBe(externalFingerprint);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe('Batch Operations', () => {
|
|
244
|
-
test('should handle multiple uploads efficiently', async () => {
|
|
245
|
-
await storage.initialize();
|
|
246
|
-
|
|
247
|
-
const uploads = [];
|
|
248
|
-
for (let i = 0; i < 5; i++) {
|
|
249
|
-
uploads.push(
|
|
250
|
-
storage.uploadWithFingerprint(
|
|
251
|
-
'test-bucket',
|
|
252
|
-
`content-${i}`,
|
|
253
|
-
'batch'
|
|
254
|
-
)
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const results = await Promise.all(uploads);
|
|
259
|
-
|
|
260
|
-
expect(results).toHaveLength(5);
|
|
261
|
-
results.forEach((result, index) => {
|
|
262
|
-
expect(result.fingerprint).toBeDefined();
|
|
263
|
-
expect(result.bucket).toBe('test-bucket');
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
test('should handle concurrent downloads', async () => {
|
|
268
|
-
await storage.initialize();
|
|
269
|
-
|
|
270
|
-
const downloads = [];
|
|
271
|
-
for (let i = 0; i < 3; i++) {
|
|
272
|
-
downloads.push(
|
|
273
|
-
storage.downloadWithVerification(
|
|
274
|
-
'test-bucket',
|
|
275
|
-
`path-${i}`,
|
|
276
|
-
null // Bez verifikace fingerprintu
|
|
277
|
-
)
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const results = await Promise.all(downloads);
|
|
282
|
-
|
|
283
|
-
expect(results).toHaveLength(3);
|
|
284
|
-
results.forEach(result => {
|
|
285
|
-
expect(result).toBeDefined();
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
describe('URL Generation with Components', () => {
|
|
291
|
-
test('should generate consistent URLs across instances', () => {
|
|
292
|
-
const storage1 = new StorageConnector({
|
|
293
|
-
endPoint: 'minio.example.com',
|
|
294
|
-
port: 443,
|
|
295
|
-
useSSL: true
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
const storage2 = new StorageConnector({
|
|
299
|
-
endPoint: 'minio.example.com',
|
|
300
|
-
port: 443,
|
|
301
|
-
useSSL: true
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
const url1 = storage1.getInternalUrl('bucket', 'path/file.txt');
|
|
305
|
-
const url2 = storage2.getInternalUrl('bucket', 'path/file.txt');
|
|
306
|
-
|
|
307
|
-
expect(url1).toBe(url2);
|
|
308
|
-
expect(url1).toBe('internal://storage/bucket/path/file.txt');
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test('should generate abstract internal URLs', () => {
|
|
312
|
-
const configs = [
|
|
313
|
-
{ useSSL: false, port: 80 },
|
|
314
|
-
{ useSSL: true, port: 443 },
|
|
315
|
-
{ useSSL: false, port: 9000 },
|
|
316
|
-
{ useSSL: true, port: 9443 }
|
|
317
|
-
];
|
|
318
|
-
|
|
319
|
-
configs.forEach(config => {
|
|
320
|
-
const s = new StorageConnector({
|
|
321
|
-
endPoint: 'localhost',
|
|
322
|
-
...config
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
const url = s.getInternalUrl('bucket', 'file.txt');
|
|
326
|
-
// Vždy vrací stejnou abstraktní URL nezávisle na konfiguraci
|
|
327
|
-
expect(url).toBe('internal://storage/bucket/file.txt');
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
// Export pro použití v jiných testech
|
|
334
|
-
module.exports = {
|
|
335
|
-
MockStorageConnector: class MockStorageConnector {
|
|
336
|
-
constructor() {
|
|
337
|
-
this.initialized = false;
|
|
338
|
-
this.cache = new Map();
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
async initialize() {
|
|
342
|
-
this.initialized = true;
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
generateFingerprint(content) {
|
|
347
|
-
return 'mock-fingerprint-' + String(content).slice(0, 10);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
async uploadWithFingerprint(bucket, content, prefix) {
|
|
351
|
-
return {
|
|
352
|
-
fingerprint: this.generateFingerprint(content),
|
|
353
|
-
bucket,
|
|
354
|
-
path: `${prefix}/mock-path`,
|
|
355
|
-
existed: false
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
async downloadWithVerification(bucket, path, fingerprint) {
|
|
360
|
-
return 'mock-content';
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
};
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration Tests for StorageConnector
|
|
3
|
-
* These tests require MinIO to be running
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const StorageConnector = require('../../src/index');
|
|
7
|
-
const crypto = require('crypto');
|
|
8
|
-
|
|
9
|
-
// Helper to conditionally skip tests
|
|
10
|
-
const testIf = (condition) => condition ? test : test.skip;
|
|
11
|
-
|
|
12
|
-
// Check if MinIO is available
|
|
13
|
-
const checkMinIOConnection = async () => {
|
|
14
|
-
const net = require('net');
|
|
15
|
-
return new Promise((resolve) => {
|
|
16
|
-
const client = new net.Socket();
|
|
17
|
-
const timeout = setTimeout(() => {
|
|
18
|
-
client.destroy();
|
|
19
|
-
resolve(false);
|
|
20
|
-
}, 1000);
|
|
21
|
-
|
|
22
|
-
client.connect(33025, 'localhost', () => {
|
|
23
|
-
clearTimeout(timeout);
|
|
24
|
-
client.destroy();
|
|
25
|
-
resolve(true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
client.on('error', () => {
|
|
29
|
-
clearTimeout(timeout);
|
|
30
|
-
resolve(false);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
describe('StorageConnector Integration Tests @integration', () => {
|
|
36
|
-
let storage;
|
|
37
|
-
let minioAvailable = false;
|
|
38
|
-
|
|
39
|
-
beforeAll(async () => {
|
|
40
|
-
minioAvailable = await checkMinIOConnection();
|
|
41
|
-
if (!minioAvailable) {
|
|
42
|
-
console.log('\n⚠️ MinIO is not available at localhost:33025');
|
|
43
|
-
console.log(' Integration tests will be skipped.');
|
|
44
|
-
console.log(' To run integration tests, start MinIO with:');
|
|
45
|
-
console.log(' docker-compose up api_shared_storage\n');
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
beforeEach(() => {
|
|
50
|
-
if (minioAvailable) {
|
|
51
|
-
storage = new StorageConnector({
|
|
52
|
-
endPoint: 'localhost',
|
|
53
|
-
port: 33025,
|
|
54
|
-
useSSL: false,
|
|
55
|
-
accessKey: 'minioadmin',
|
|
56
|
-
secretKey: 'minioadmin',
|
|
57
|
-
bucketName: 'test-connector-storage'
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
afterEach(async () => {
|
|
63
|
-
if (minioAvailable && storage && storage.initialized) {
|
|
64
|
-
// Clean up test objects but keep the bucket
|
|
65
|
-
try {
|
|
66
|
-
const objectsStream = storage.minioClient.listObjects(storage.config.bucketName, '', true);
|
|
67
|
-
const objects = [];
|
|
68
|
-
objectsStream.on('data', obj => objects.push(obj.name));
|
|
69
|
-
await new Promise((resolve, reject) => {
|
|
70
|
-
objectsStream.on('end', resolve);
|
|
71
|
-
objectsStream.on('error', reject);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
for (const objectName of objects) {
|
|
75
|
-
await storage.minioClient.removeObject(storage.config.bucketName, objectName);
|
|
76
|
-
}
|
|
77
|
-
} catch (err) {
|
|
78
|
-
// Ignore cleanup errors
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('MinIO Operations', () => {
|
|
84
|
-
testIf(minioAvailable)('should initialize and create bucket', async () => {
|
|
85
|
-
await storage.initialize();
|
|
86
|
-
expect(storage.initialized).toBe(true);
|
|
87
|
-
|
|
88
|
-
const bucketExists = await storage.minioClient.bucketExists(storage.config.bucketName);
|
|
89
|
-
expect(bucketExists).toBe(true);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
testIf(minioAvailable)('should upload and download with fingerprint verification', async () => {
|
|
93
|
-
await storage.initialize();
|
|
94
|
-
|
|
95
|
-
const content = 'Test content for integration test';
|
|
96
|
-
const objectName = 'test-file.txt';
|
|
97
|
-
|
|
98
|
-
// Upload with fingerprint
|
|
99
|
-
const uploadResult = await storage.uploadWithFingerprint(objectName, content);
|
|
100
|
-
expect(uploadResult.fingerprint).toBeDefined();
|
|
101
|
-
expect(uploadResult.objectName).toContain(uploadResult.fingerprint);
|
|
102
|
-
|
|
103
|
-
// Download with verification
|
|
104
|
-
const downloadResult = await storage.downloadWithVerification(uploadResult.objectName);
|
|
105
|
-
expect(downloadResult.valid).toBe(true);
|
|
106
|
-
expect(downloadResult.data.toString()).toBe(content);
|
|
107
|
-
expect(downloadResult.fingerprint).toBe(uploadResult.fingerprint);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
testIf(minioAvailable)('should detect fingerprint mismatch', async () => {
|
|
111
|
-
await storage.initialize();
|
|
112
|
-
|
|
113
|
-
const content = 'Original content';
|
|
114
|
-
const objectName = 'test-file.txt';
|
|
115
|
-
|
|
116
|
-
// Upload content
|
|
117
|
-
const uploadResult = await storage.uploadWithFingerprint(objectName, content);
|
|
118
|
-
|
|
119
|
-
// Manually upload different content with same name pattern
|
|
120
|
-
const differentContent = 'Modified content';
|
|
121
|
-
const wrongFingerprint = 'wrong123fingerprint456';
|
|
122
|
-
const tamperedName = storage.getObjectNameWithFingerprint(objectName, wrongFingerprint);
|
|
123
|
-
|
|
124
|
-
await storage.minioClient.putObject(
|
|
125
|
-
storage.config.bucketName,
|
|
126
|
-
tamperedName,
|
|
127
|
-
differentContent
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
// Try to download and verify
|
|
131
|
-
const downloadResult = await storage.downloadWithVerification(tamperedName);
|
|
132
|
-
expect(downloadResult.valid).toBe(false);
|
|
133
|
-
expect(downloadResult.error).toContain('mismatch');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
testIf(minioAvailable)('should handle duplicate uploads (idempotency)', async () => {
|
|
137
|
-
await storage.initialize();
|
|
138
|
-
|
|
139
|
-
const content = 'Duplicate test content';
|
|
140
|
-
const objectName = 'duplicate-file.txt';
|
|
141
|
-
|
|
142
|
-
// Upload twice
|
|
143
|
-
const result1 = await storage.uploadWithFingerprint(objectName, content);
|
|
144
|
-
const result2 = await storage.uploadWithFingerprint(objectName, content);
|
|
145
|
-
|
|
146
|
-
// Should have same fingerprint
|
|
147
|
-
expect(result1.fingerprint).toBe(result2.fingerprint);
|
|
148
|
-
expect(result1.objectName).toBe(result2.objectName);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
testIf(minioAvailable)('should list objects with fingerprints', async () => {
|
|
152
|
-
await storage.initialize();
|
|
153
|
-
|
|
154
|
-
// Upload multiple files
|
|
155
|
-
await storage.uploadWithFingerprint('file1.txt', 'Content 1');
|
|
156
|
-
await storage.uploadWithFingerprint('file2.txt', 'Content 2');
|
|
157
|
-
await storage.uploadWithFingerprint('file3.txt', 'Content 3');
|
|
158
|
-
|
|
159
|
-
// List objects
|
|
160
|
-
const objects = await storage.listObjectsWithFingerprints();
|
|
161
|
-
expect(objects.length).toBeGreaterThanOrEqual(3);
|
|
162
|
-
|
|
163
|
-
objects.forEach(obj => {
|
|
164
|
-
expect(obj.name).toBeDefined();
|
|
165
|
-
expect(obj.fingerprint).toBeDefined();
|
|
166
|
-
expect(obj.size).toBeDefined();
|
|
167
|
-
expect(obj.lastModified).toBeDefined();
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
testIf(minioAvailable)('should generate presigned URL', async () => {
|
|
172
|
-
await storage.initialize();
|
|
173
|
-
|
|
174
|
-
const content = 'Content for presigned URL';
|
|
175
|
-
const objectName = 'presigned-file.txt';
|
|
176
|
-
|
|
177
|
-
// Upload file
|
|
178
|
-
const uploadResult = await storage.uploadWithFingerprint(objectName, content);
|
|
179
|
-
|
|
180
|
-
// Generate presigned URL
|
|
181
|
-
const url = await storage.getPresignedUrl(uploadResult.objectName, 60);
|
|
182
|
-
expect(url).toBeDefined();
|
|
183
|
-
expect(url).toContain('http');
|
|
184
|
-
expect(url).toContain(uploadResult.objectName);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
testIf(minioAvailable)('should handle cache correctly', async () => {
|
|
188
|
-
await storage.initialize();
|
|
189
|
-
|
|
190
|
-
const content = 'Cached content';
|
|
191
|
-
const objectName = 'cached-file.txt';
|
|
192
|
-
|
|
193
|
-
// Upload
|
|
194
|
-
const uploadResult = await storage.uploadWithFingerprint(objectName, content);
|
|
195
|
-
|
|
196
|
-
// First download (not cached)
|
|
197
|
-
const result1 = await storage.downloadWithVerification(uploadResult.objectName);
|
|
198
|
-
expect(result1.fromCache).toBe(false);
|
|
199
|
-
|
|
200
|
-
// Second download (should be cached)
|
|
201
|
-
const result2 = await storage.downloadWithVerification(uploadResult.objectName);
|
|
202
|
-
expect(result2.fromCache).toBe(true);
|
|
203
|
-
expect(result2.data.toString()).toBe(content);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe('Error Handling', () => {
|
|
208
|
-
testIf(minioAvailable)('should handle non-existent object', async () => {
|
|
209
|
-
await storage.initialize();
|
|
210
|
-
|
|
211
|
-
await expect(storage.downloadWithVerification('non-existent-file.txt'))
|
|
212
|
-
.rejects.toThrow();
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
testIf(minioAvailable)('should handle invalid credentials', async () => {
|
|
216
|
-
const invalidStorage = new StorageConnector({
|
|
217
|
-
accessKey: 'invalid',
|
|
218
|
-
secretKey: 'invalid'
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
await expect(invalidStorage.initialize()).rejects.toThrow();
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
});
|