@onlineapps/conn-base-storage 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 (39) hide show
  1. package/API.md +618 -0
  2. package/README.md +341 -0
  3. package/SHARED_URL_ADDRESSING.md +258 -0
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/clover.xml +213 -0
  7. package/coverage/coverage-final.json +3 -0
  8. package/coverage/favicon.png +0 -0
  9. package/coverage/index.html +131 -0
  10. package/coverage/index.js.html +1579 -0
  11. package/coverage/internal-url-adapter.js.html +604 -0
  12. package/coverage/lcov-report/base.css +224 -0
  13. package/coverage/lcov-report/block-navigation.js +87 -0
  14. package/coverage/lcov-report/favicon.png +0 -0
  15. package/coverage/lcov-report/index.html +131 -0
  16. package/coverage/lcov-report/index.js.html +1579 -0
  17. package/coverage/lcov-report/internal-url-adapter.js.html +604 -0
  18. package/coverage/lcov-report/prettify.css +1 -0
  19. package/coverage/lcov-report/prettify.js +2 -0
  20. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  21. package/coverage/lcov-report/sorter.js +210 -0
  22. package/coverage/lcov.info +434 -0
  23. package/coverage/prettify.css +1 -0
  24. package/coverage/prettify.js +2 -0
  25. package/coverage/sort-arrow-sprite.png +0 -0
  26. package/coverage/sorter.js +210 -0
  27. package/jest.config.js +13 -0
  28. package/jest.integration.config.js +9 -0
  29. package/package.json +33 -0
  30. package/src/index.js +853 -0
  31. package/src/internal-url-adapter.js +174 -0
  32. package/src/sharedUrlAdapter.js +258 -0
  33. package/test/component/storage.component.test.js +363 -0
  34. package/test/integration/setup.js +3 -0
  35. package/test/integration/storage.integration.test.js +224 -0
  36. package/test/unit/internal-url-adapter.test.js +211 -0
  37. package/test/unit/legacy.storage.test.js.bak +614 -0
  38. package/test/unit/storage.extended.unit.test.js +435 -0
  39. package/test/unit/storage.unit.test.js +373 -0
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Unit Tests pro StorageConnector
3
+ * 100% mockované - MUSÍ VŽDY PROJÍT (100% pass rate)
4
+ * Žádné externí závislosti - vše je mockované
5
+ *
6
+ * Testovací strategie:
7
+ * - Všechny MinIO operace jsou mockovány
8
+ * - Testy běží bez potřeby MinIO serveru
9
+ * - Pokrývají všechny metody a edge cases
10
+ */
11
+
12
+ const StorageConnector = require('../../src/index');
13
+ const crypto = require('crypto');
14
+
15
+ // Mock MinIO client kompletně
16
+ jest.mock('minio', () => {
17
+ return {
18
+ Client: jest.fn().mockImplementation(() => ({
19
+ bucketExists: jest.fn(),
20
+ makeBucket: jest.fn(),
21
+ setBucketPolicy: jest.fn(),
22
+ putObject: jest.fn(),
23
+ getObject: jest.fn(),
24
+ statObject: jest.fn(),
25
+ removeObject: jest.fn(),
26
+ removeBucket: jest.fn(),
27
+ listObjectsV2: jest.fn(),
28
+ presignedGetObject: jest.fn(),
29
+ presignedGetUrl: jest.fn(),
30
+ useSSL: false,
31
+ port: 9000,
32
+ host: 'localhost'
33
+ }))
34
+ };
35
+ });
36
+
37
+ describe('StorageConnector Unit Tests', () => {
38
+ describe('Initialization', () => {
39
+ test('should create instance with default config', () => {
40
+ const storage = new StorageConnector();
41
+ expect(storage).toBeDefined();
42
+ expect(storage.config).toBeDefined();
43
+ expect(storage.config.endPoint).toBe('localhost');
44
+ expect(storage.config.port).toBe(9000);
45
+ });
46
+
47
+ test('should create instance with custom config', () => {
48
+ const config = {
49
+ endPoint: 'minio.example.com',
50
+ port: 9001,
51
+ useSSL: true,
52
+ accessKey: 'custom-key',
53
+ secretKey: 'custom-secret',
54
+ bucketName: 'custom-bucket'
55
+ };
56
+
57
+ const storage = new StorageConnector(config);
58
+ expect(storage.config.endPoint).toBe('minio.example.com');
59
+ expect(storage.config.port).toBe(9001);
60
+ expect(storage.config.useSSL).toBe(true);
61
+ expect(storage.config.bucketName).toBe('custom-bucket');
62
+ });
63
+ });
64
+
65
+ describe('Fingerprinting', () => {
66
+ let storage;
67
+
68
+ beforeEach(() => {
69
+ storage = new StorageConnector();
70
+ });
71
+
72
+ test('should generate fingerprint for string content', () => {
73
+ const content = 'Hello, World!';
74
+ const fingerprint = storage.generateFingerprint(content);
75
+
76
+ expect(fingerprint).toBeDefined();
77
+ expect(fingerprint).toHaveLength(64); // SHA256 produces 64 hex characters
78
+ expect(fingerprint).toMatch(/^[a-f0-9]{64}$/);
79
+ });
80
+
81
+ test('should generate consistent fingerprint for same content', () => {
82
+ const content = 'Test content';
83
+ const fingerprint1 = storage.generateFingerprint(content);
84
+ const fingerprint2 = storage.generateFingerprint(content);
85
+
86
+ expect(fingerprint1).toBe(fingerprint2);
87
+ });
88
+
89
+ test('should generate different fingerprints for different content', () => {
90
+ const fingerprint1 = storage.generateFingerprint('Content 1');
91
+ const fingerprint2 = storage.generateFingerprint('Content 2');
92
+
93
+ expect(fingerprint1).not.toBe(fingerprint2);
94
+ });
95
+
96
+ test('should handle Buffer content', () => {
97
+ const buffer = Buffer.from('Buffer content');
98
+ const fingerprint = storage.generateFingerprint(buffer);
99
+
100
+ expect(fingerprint).toBeDefined();
101
+ expect(fingerprint).toHaveLength(64);
102
+ });
103
+
104
+ test('should handle object content', () => {
105
+ const obj = { key: 'value', nested: { data: 123 } };
106
+ const fingerprint = storage.generateFingerprint(obj);
107
+
108
+ expect(fingerprint).toBeDefined();
109
+ expect(fingerprint).toHaveLength(64);
110
+ });
111
+
112
+ test('should handle array content', () => {
113
+ const arr = [1, 2, 3, 'test', { key: 'value' }];
114
+ const fingerprint = storage.generateFingerprint(arr);
115
+
116
+ expect(fingerprint).toBeDefined();
117
+ expect(fingerprint).toHaveLength(64);
118
+ });
119
+
120
+ test('should handle null content', () => {
121
+ const fingerprint = storage.generateFingerprint(null);
122
+ expect(fingerprint).toBeDefined();
123
+ expect(fingerprint).toHaveLength(64);
124
+ });
125
+
126
+ test('should handle undefined content', () => {
127
+ const fingerprint = storage.generateFingerprint(undefined);
128
+ expect(fingerprint).toBeDefined();
129
+ expect(fingerprint).toHaveLength(64);
130
+ });
131
+
132
+ test('should handle circular references in objects', () => {
133
+ const obj = { name: 'test' };
134
+ obj.circular = obj;
135
+
136
+ expect(() => storage.generateFingerprint(obj)).not.toThrow();
137
+ const fingerprint = storage.generateFingerprint(obj);
138
+ expect(fingerprint).toBeDefined();
139
+ });
140
+ });
141
+
142
+ describe('Cache Management', () => {
143
+ let storage;
144
+
145
+ beforeEach(() => {
146
+ storage = new StorageConnector({ cacheMaxSize: 3 });
147
+ });
148
+
149
+ test('should add items to cache', () => {
150
+ storage.addToCache('key1', 'value1');
151
+ expect(storage.getFromCache('key1')).toBe('value1');
152
+ });
153
+
154
+ test('should maintain insertion order (Map behavior)', () => {
155
+ storage.addToCache('key1', 'value1');
156
+ storage.addToCache('key2', 'value2');
157
+ storage.addToCache('key3', 'value3');
158
+ storage.addToCache('key4', 'value4');
159
+
160
+ // Map maintains insertion order, removes oldest
161
+ expect(storage.getFromCache('key1')).toBeUndefined();
162
+ expect(storage.getFromCache('key2')).toBe('value2');
163
+ expect(storage.getFromCache('key3')).toBe('value3');
164
+ expect(storage.getFromCache('key4')).toBe('value4');
165
+ });
166
+
167
+ test('should clear cache', () => {
168
+ storage.addToCache('key1', 'value1');
169
+ storage.addToCache('key2', 'value2');
170
+
171
+ storage.clearCache();
172
+
173
+ expect(storage.getFromCache('key1')).toBeUndefined();
174
+ expect(storage.getFromCache('key2')).toBeUndefined();
175
+ });
176
+
177
+ test('should respect cache max size', () => {
178
+ for (let i = 0; i < 10; i++) {
179
+ storage.addToCache(`key${i}`, `value${i}`);
180
+ }
181
+
182
+ // Only last 3 items should be in cache
183
+ expect(storage.cache.size).toBe(3);
184
+ });
185
+ });
186
+
187
+ describe('Error Handling', () => {
188
+ let storage;
189
+
190
+ beforeEach(() => {
191
+ storage = new StorageConnector();
192
+ });
193
+
194
+ test('should handle invalid content types for fingerprinting', () => {
195
+ const symbol = Symbol('test');
196
+ expect(() => storage.generateFingerprint(symbol)).not.toThrow();
197
+ });
198
+
199
+ test('should validate bucket name', () => {
200
+ const validNames = ['valid-bucket', 'bucket123', 'my.bucket'];
201
+ const invalidNames = ['', 'a', 'UPPERCASE', 'bucket_name', 'bucket-'];
202
+
203
+ validNames.forEach(name => {
204
+ const s = new StorageConnector({ bucketName: name });
205
+ expect(s.config.bucketName).toBe(name);
206
+ });
207
+
208
+ // Invalid names should use default
209
+ invalidNames.forEach(name => {
210
+ const s = new StorageConnector({ bucketName: name });
211
+ expect(s.config.bucketName).toBe('oa-drive-storage');
212
+ });
213
+ });
214
+ });
215
+
216
+ describe('Utility Methods', () => {
217
+ let storage;
218
+
219
+ beforeEach(() => {
220
+ storage = new StorageConnector();
221
+ });
222
+
223
+ test('should generate object name with fingerprint', () => {
224
+ const originalName = 'test-file.json';
225
+ const fingerprint = 'abc123def456';
226
+
227
+ const objectName = storage.getObjectNameWithFingerprint(originalName, fingerprint);
228
+
229
+ expect(objectName).toContain(fingerprint);
230
+ expect(objectName).toContain('test-file');
231
+ expect(objectName).toContain('.json');
232
+ });
233
+
234
+ test('should extract fingerprint from object name', () => {
235
+ // SHA256 produces 64 character hex string
236
+ const testFingerprint = 'a'.repeat(64);
237
+ const objectName = `test-file-${testFingerprint}.json`;
238
+ const fingerprint = storage.extractFingerprintFromName(objectName);
239
+
240
+ expect(fingerprint).toBe(testFingerprint);
241
+ });
242
+
243
+ test('should return null for object name without fingerprint', () => {
244
+ const objectName = 'regular-file.json';
245
+ const fingerprint = storage.extractFingerprintFromName(objectName);
246
+
247
+ expect(fingerprint).toBeNull();
248
+ });
249
+ });
250
+
251
+ describe('Mocked MinIO Operations', () => {
252
+ let storage;
253
+ let mockClient;
254
+
255
+ beforeEach(() => {
256
+ jest.clearAllMocks();
257
+ storage = new StorageConnector();
258
+ mockClient = storage.minioClient;
259
+ });
260
+
261
+ test('should initialize with mocked MinIO client', async () => {
262
+ mockClient.bucketExists.mockResolvedValue(false);
263
+ mockClient.makeBucket.mockResolvedValue();
264
+ mockClient.setBucketPolicy.mockResolvedValue();
265
+
266
+ await storage.initialize();
267
+
268
+ expect(storage.initialized).toBe(true);
269
+ expect(mockClient.bucketExists).toHaveBeenCalled();
270
+ expect(mockClient.makeBucket).toHaveBeenCalled();
271
+ });
272
+
273
+ test('should upload with fingerprint using mocked client', async () => {
274
+ mockClient.statObject.mockRejectedValue({ code: 'NotFound' });
275
+ mockClient.putObject.mockResolvedValue();
276
+
277
+ const result = await storage.uploadWithFingerprint('test-bucket', 'test content');
278
+
279
+ expect(result.fingerprint).toBeDefined();
280
+ expect(result.existed).toBe(false);
281
+ expect(mockClient.putObject).toHaveBeenCalled();
282
+ });
283
+
284
+ test('should download with verification using mocked client', async () => {
285
+ const content = 'test content';
286
+ const fingerprint = storage.generateFingerprint(content);
287
+
288
+ // Mock stream for getObject
289
+ const mockStream = {
290
+ [Symbol.asyncIterator]: async function* () {
291
+ yield Buffer.from(content);
292
+ }
293
+ };
294
+ mockClient.getObject.mockResolvedValue(mockStream);
295
+
296
+ const result = await storage.downloadWithVerification(
297
+ 'test-bucket',
298
+ 'test-path',
299
+ fingerprint
300
+ );
301
+
302
+ expect(result).toBe(content);
303
+ expect(mockClient.getObject).toHaveBeenCalledWith('test-bucket', 'test-path');
304
+ });
305
+
306
+ test('should list objects with mocked client', async () => {
307
+ const mockObjects = [
308
+ { name: 'file1.txt', size: 100, lastModified: new Date() },
309
+ { name: 'file2.txt', size: 200, lastModified: new Date() }
310
+ ];
311
+
312
+ mockClient.listObjectsV2.mockReturnValue({
313
+ on: jest.fn((event, callback) => {
314
+ if (event === 'data') {
315
+ mockObjects.forEach(callback);
316
+ } else if (event === 'end') {
317
+ callback();
318
+ }
319
+ return mockClient.listObjectsV2();
320
+ })
321
+ });
322
+
323
+ const result = await storage.listObjectsWithFingerprints('test-bucket');
324
+
325
+ expect(result).toHaveLength(2);
326
+ expect(result[0].name).toBe('file1.txt');
327
+ });
328
+
329
+ test('should generate presigned URL with mocked client', async () => {
330
+ const expectedUrl = 'http://localhost:9000/bucket/object?signature=xyz';
331
+ mockClient.presignedGetUrl.mockResolvedValue(expectedUrl);
332
+
333
+ const url = await storage.getPresignedUrl('test-bucket', 'test-object');
334
+
335
+ expect(url).toBe(expectedUrl);
336
+ expect(mockClient.presignedGetUrl).toHaveBeenCalledWith(
337
+ 'GET',
338
+ 'test-bucket',
339
+ 'test-object',
340
+ 86400
341
+ );
342
+ });
343
+
344
+ test('should handle errors from mocked client', async () => {
345
+ mockClient.bucketExists.mockRejectedValue(new Error('Connection failed'));
346
+
347
+ await expect(storage.initialize()).rejects.toThrow('Connection failed');
348
+ });
349
+
350
+ test('should detect existing objects (idempotency)', async () => {
351
+ mockClient.statObject.mockResolvedValue({ size: 100, metaData: {} });
352
+
353
+ const result = await storage.uploadWithFingerprint('test-bucket', 'content');
354
+
355
+ expect(result.existed).toBe(true);
356
+ expect(mockClient.putObject).not.toHaveBeenCalled();
357
+ });
358
+
359
+ test('should verify fingerprint mismatch', async () => {
360
+ const wrongContent = 'wrong content';
361
+ const mockStream = {
362
+ [Symbol.asyncIterator]: async function* () {
363
+ yield Buffer.from(wrongContent);
364
+ }
365
+ };
366
+ mockClient.getObject.mockResolvedValue(mockStream);
367
+
368
+ await expect(
369
+ storage.downloadWithVerification('bucket', 'path', 'expectedfingerprint123')
370
+ ).rejects.toThrow('Fingerprint mismatch');
371
+ });
372
+ });
373
+ });