@onlineapps/conn-base-storage 1.0.1 → 1.0.3
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/coverage/clover.xml +288 -204
- package/coverage/coverage-final.json +5 -2
- package/coverage/lcov-report/config.js.html +244 -0
- package/coverage/lcov-report/defaults.js.html +214 -0
- package/coverage/lcov-report/index.html +72 -27
- package/coverage/lcov-report/index.js.html +1228 -199
- package/coverage/lcov-report/internal-url-adapter.js.html +28 -73
- package/coverage/lcov-report/sharedUrlAdapter.js.html +856 -0
- package/coverage/lcov.info +522 -393
- package/package.json +3 -1
- package/src/config.js +53 -0
- package/src/defaults.js +43 -0
- package/src/index.js +72 -83
- package/src/internal-url-adapter.js +13 -28
- package/tests/unit/internal-url-adapter.test.js +4 -0
- package/tests/unit/storage.extended.unit.test.js +14 -6
- package/tests/unit/storage.unit.test.js +16 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/conn-base-storage",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "MinIO storage connector with fingerprinting for immutable content storage",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"connector"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@onlineapps/runtime-config": "1.0.2",
|
|
24
|
+
"@onlineapps/storage-core": "1.0.8",
|
|
23
25
|
"minio": "^7.1.3",
|
|
24
26
|
"crypto": "^1.0.1",
|
|
25
27
|
"winston": "^3.8.2"
|
package/src/config.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Runtime configuration schema for @onlineapps/conn-base-storage.
|
|
5
|
+
*
|
|
6
|
+
* Uses @onlineapps/runtime-config for unified priority:
|
|
7
|
+
* 1. Explicit config (passed to constructor)
|
|
8
|
+
* 2. Environment variable
|
|
9
|
+
* 3. Owner defaults (from ./defaults.js)
|
|
10
|
+
*
|
|
11
|
+
* NOTE:
|
|
12
|
+
* This module resolves MinIO runtime configuration via @onlineapps/runtime-config
|
|
13
|
+
* to avoid inline process.env access in implementation code.
|
|
14
|
+
*
|
|
15
|
+
* @module @onlineapps/conn-base-storage/config
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { createRuntimeConfig } = require('@onlineapps/runtime-config');
|
|
19
|
+
const DEFAULTS = require('./defaults');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Runtime config schema for StorageConnector
|
|
23
|
+
*/
|
|
24
|
+
const runtimeCfg = createRuntimeConfig({
|
|
25
|
+
defaults: DEFAULTS,
|
|
26
|
+
schema: {
|
|
27
|
+
// MinIO topology (FAIL-FAST)
|
|
28
|
+
// Keep key names aligned with constructor config for predictability.
|
|
29
|
+
endPoint: { env: 'MINIO_ENDPOINT', required: true },
|
|
30
|
+
port: { env: 'MINIO_PORT', required: true, type: 'number' },
|
|
31
|
+
useSSL: { env: 'MINIO_USE_SSL', default: false, type: 'boolean' },
|
|
32
|
+
accessKey: { env: 'MINIO_ACCESS_KEY', required: true },
|
|
33
|
+
secretKey: { env: 'MINIO_SECRET_KEY', required: true },
|
|
34
|
+
|
|
35
|
+
// Wrapper behavior (module-owned defaults)
|
|
36
|
+
logLevel: { env: 'STORAGE_LOG_LEVEL', defaultKey: 'logLevel' },
|
|
37
|
+
defaultBucket: { env: 'MINIO_DEFAULT_BUCKET', defaultKey: 'defaultBucket' },
|
|
38
|
+
bucketName: { defaultKey: 'bucketName' },
|
|
39
|
+
cacheMaxSize: { defaultKey: 'cacheMaxSize', type: 'number' },
|
|
40
|
+
maxCacheSize: { defaultKey: 'maxCacheSize', type: 'number' },
|
|
41
|
+
|
|
42
|
+
// InternalUrlAdapter behavior (module-owned defaults + env hints)
|
|
43
|
+
nodeEnv: { env: 'NODE_ENV', default: 'development' },
|
|
44
|
+
dockerNetwork: { env: 'DOCKER_NETWORK', default: false, type: 'boolean' },
|
|
45
|
+
dockerEnv: { env: 'DOCKER_ENV' },
|
|
46
|
+
dockerContainer: { env: 'DOCKER_CONTAINER' },
|
|
47
|
+
hostname: { env: 'HOSTNAME' },
|
|
48
|
+
internalUrlServiceMap: { defaultKey: 'internalUrlServiceMap' },
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
module.exports = runtimeCfg;
|
|
53
|
+
|
package/src/defaults.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Module-owned defaults for @onlineapps/conn-base-storage.
|
|
5
|
+
*
|
|
6
|
+
* Ownership rule:
|
|
7
|
+
* - Defaults for the StorageConnector wrapper live here (bucket naming, cache sizing, log level).
|
|
8
|
+
* - MinIO connection defaults belong to @onlineapps/storage-core.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
logLevel: 'info',
|
|
13
|
+
defaultBucket: 'api-storage',
|
|
14
|
+
bucketName: 'oa-drive-storage',
|
|
15
|
+
cacheMaxSize: 100,
|
|
16
|
+
maxCacheSize: 100,
|
|
17
|
+
|
|
18
|
+
// InternalUrlAdapter defaults (module-owned)
|
|
19
|
+
internalUrlServiceMap: {
|
|
20
|
+
storage: {
|
|
21
|
+
docker: 'api_services_storage:9000',
|
|
22
|
+
local: 'localhost:9000'
|
|
23
|
+
},
|
|
24
|
+
registry: {
|
|
25
|
+
docker: 'api_registry:8080',
|
|
26
|
+
local: 'localhost:8080'
|
|
27
|
+
},
|
|
28
|
+
monitoring: {
|
|
29
|
+
docker: 'api_monitoring:3000',
|
|
30
|
+
local: 'localhost:3000'
|
|
31
|
+
},
|
|
32
|
+
mq: {
|
|
33
|
+
docker: 'api_services_rabbit:5672',
|
|
34
|
+
local: 'localhost:5672'
|
|
35
|
+
},
|
|
36
|
+
cache: {
|
|
37
|
+
docker: 'api_node_cache:6379',
|
|
38
|
+
local: 'localhost:6379'
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
|
package/src/index.js
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* @since 1.0.0
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
const crypto = require('crypto');
|
|
12
|
+
const { StorageCore } = require('@onlineapps/storage-core');
|
|
14
13
|
const winston = require('winston');
|
|
15
14
|
const SharedUrlAdapter = require('./sharedUrlAdapter');
|
|
15
|
+
const runtimeCfg = require('./config');
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Storage connector for MinIO with automatic fingerprinting
|
|
@@ -69,9 +69,12 @@ class StorageConnector {
|
|
|
69
69
|
* });
|
|
70
70
|
*/
|
|
71
71
|
constructor(config = {}) {
|
|
72
|
+
// Resolve runtime configuration for wrapper behavior
|
|
73
|
+
const resolved = runtimeCfg.resolve(config);
|
|
74
|
+
|
|
72
75
|
// Logger setup
|
|
73
76
|
this.logger = winston.createLogger({
|
|
74
|
-
level:
|
|
77
|
+
level: resolved.logLevel,
|
|
75
78
|
format: winston.format.combine(
|
|
76
79
|
winston.format.timestamp(),
|
|
77
80
|
winston.format.json()
|
|
@@ -81,33 +84,37 @@ class StorageConnector {
|
|
|
81
84
|
]
|
|
82
85
|
});
|
|
83
86
|
|
|
84
|
-
// MinIO client
|
|
85
|
-
|
|
86
|
-
endPoint:
|
|
87
|
-
port:
|
|
88
|
-
useSSL:
|
|
89
|
-
accessKey:
|
|
90
|
-
secretKey:
|
|
91
|
-
};
|
|
87
|
+
// Create StorageCore instance (handles MinIO client internally)
|
|
88
|
+
this.core = new StorageCore({
|
|
89
|
+
endPoint: resolved.endPoint,
|
|
90
|
+
port: resolved.port,
|
|
91
|
+
useSSL: resolved.useSSL,
|
|
92
|
+
accessKey: resolved.accessKey,
|
|
93
|
+
secretKey: resolved.secretKey
|
|
94
|
+
});
|
|
92
95
|
|
|
93
|
-
|
|
94
|
-
this.
|
|
96
|
+
// Expose MinIO client for backward compatibility
|
|
97
|
+
this.client = this.core.client;
|
|
98
|
+
this.minioClient = this.core.client;
|
|
99
|
+
|
|
100
|
+
this.defaultBucket = resolved.defaultBucket;
|
|
95
101
|
|
|
96
102
|
// Store config for access in tests
|
|
97
103
|
this.config = {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
endPoint: this.core.config.endPoint,
|
|
105
|
+
port: this.core.config.port,
|
|
106
|
+
useSSL: this.core.config.useSSL,
|
|
107
|
+
accessKey: this.core.config.accessKey,
|
|
108
|
+
secretKey: this.core.config.secretKey,
|
|
109
|
+
bucketName: this.validateBucketName(config.bucketName || resolved.bucketName),
|
|
110
|
+
cacheMaxSize: resolved.cacheMaxSize
|
|
101
111
|
};
|
|
102
112
|
|
|
103
|
-
// MinIO client
|
|
104
|
-
this.minioClient = this.client;
|
|
105
|
-
|
|
106
113
|
// Cache configuration
|
|
107
114
|
this.cacheEnabled = config.cacheEnabled !== false;
|
|
108
115
|
this.cache = new Map();
|
|
109
116
|
this.contentCache = new Map();
|
|
110
|
-
this.maxCacheSize =
|
|
117
|
+
this.maxCacheSize = resolved.maxCacheSize;
|
|
111
118
|
|
|
112
119
|
// Track initialization
|
|
113
120
|
this.initialized = false;
|
|
@@ -116,8 +123,8 @@ class StorageConnector {
|
|
|
116
123
|
this.sharedUrl = new SharedUrlAdapter(this);
|
|
117
124
|
|
|
118
125
|
this.logger.info('StorageConnector initialized', {
|
|
119
|
-
endpoint:
|
|
120
|
-
port:
|
|
126
|
+
endpoint: this.core.config.endPoint,
|
|
127
|
+
port: this.core.config.port,
|
|
121
128
|
defaultBucket: this.defaultBucket
|
|
122
129
|
});
|
|
123
130
|
}
|
|
@@ -141,15 +148,16 @@ class StorageConnector {
|
|
|
141
148
|
*/
|
|
142
149
|
async initialize() {
|
|
143
150
|
try {
|
|
144
|
-
//
|
|
145
|
-
|
|
151
|
+
// Initialize StorageCore
|
|
152
|
+
await this.core.initialize();
|
|
146
153
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
// Ensure default bucket exists (using StorageCore)
|
|
155
|
+
const created = await this.core.ensureBucket(this.defaultBucket);
|
|
156
|
+
|
|
157
|
+
if (created) {
|
|
150
158
|
this.logger.info(`Bucket ${this.defaultBucket} created`);
|
|
151
159
|
|
|
152
|
-
// Set bucket policy for read access
|
|
160
|
+
// Set bucket policy for read access (business-specific feature)
|
|
153
161
|
const policy = {
|
|
154
162
|
Version: '2012-10-17',
|
|
155
163
|
Statement: [
|
|
@@ -193,25 +201,8 @@ class StorageConnector {
|
|
|
193
201
|
* // Returns consistent hash for the object
|
|
194
202
|
*/
|
|
195
203
|
generateFingerprint(content) {
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
content = String(content);
|
|
199
|
-
} else if (typeof content === 'symbol') {
|
|
200
|
-
content = content.toString();
|
|
201
|
-
} else if (typeof content !== 'string' && !Buffer.isBuffer(content)) {
|
|
202
|
-
// Handle circular references
|
|
203
|
-
try {
|
|
204
|
-
content = JSON.stringify(content);
|
|
205
|
-
} catch (err) {
|
|
206
|
-
// If circular reference, use a deterministic string representation
|
|
207
|
-
content = '[Circular Reference]';
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return crypto
|
|
212
|
-
.createHash('sha256')
|
|
213
|
-
.update(content)
|
|
214
|
-
.digest('hex');
|
|
204
|
+
// Delegate to StorageCore
|
|
205
|
+
return this.core.calculateFingerprint(content);
|
|
215
206
|
}
|
|
216
207
|
|
|
217
208
|
/**
|
|
@@ -277,9 +268,9 @@ class StorageConnector {
|
|
|
277
268
|
? `${pathPrefix}/${fingerprint}.json`
|
|
278
269
|
: `${fingerprint}.json`;
|
|
279
270
|
|
|
280
|
-
// Check if object already exists (immutable)
|
|
281
|
-
|
|
282
|
-
|
|
271
|
+
// Check if object already exists (immutable) - using StorageCore
|
|
272
|
+
const exists = await this.core.objectExists(bucket, path);
|
|
273
|
+
if (exists) {
|
|
283
274
|
this.logger.debug(`Object already exists: ${bucket}/${path}`);
|
|
284
275
|
|
|
285
276
|
return {
|
|
@@ -289,16 +280,13 @@ class StorageConnector {
|
|
|
289
280
|
url: this.getPublicUrl(bucket, path),
|
|
290
281
|
existed: true
|
|
291
282
|
};
|
|
292
|
-
} catch (err) {
|
|
293
|
-
// Object doesn't exist, proceed with upload
|
|
294
283
|
}
|
|
295
284
|
|
|
296
|
-
// Upload to MinIO
|
|
297
|
-
await this.
|
|
285
|
+
// Upload to MinIO - using StorageCore
|
|
286
|
+
await this.core.putObject(
|
|
298
287
|
bucket,
|
|
299
288
|
path,
|
|
300
289
|
buffer,
|
|
301
|
-
buffer.length,
|
|
302
290
|
{
|
|
303
291
|
'Content-Type': 'application/json',
|
|
304
292
|
'x-amz-meta-fingerprint': fingerprint,
|
|
@@ -366,8 +354,8 @@ class StorageConnector {
|
|
|
366
354
|
return this.contentCache.get(cacheKey);
|
|
367
355
|
}
|
|
368
356
|
|
|
369
|
-
// Download from MinIO
|
|
370
|
-
const stream = await this.
|
|
357
|
+
// Download from MinIO - using StorageCore
|
|
358
|
+
const stream = await this.core.getObject(bucket, path);
|
|
371
359
|
|
|
372
360
|
// Collect stream data
|
|
373
361
|
const chunks = [];
|
|
@@ -376,16 +364,14 @@ class StorageConnector {
|
|
|
376
364
|
}
|
|
377
365
|
const buffer = Buffer.concat(chunks);
|
|
378
366
|
|
|
379
|
-
// Verify fingerprint if provided
|
|
367
|
+
// Verify fingerprint if provided - using StorageCore
|
|
380
368
|
if (expectedFingerprint) {
|
|
381
|
-
const actualFingerprint = this.
|
|
382
|
-
|
|
369
|
+
const actualFingerprint = this.core.calculateFingerprint(buffer);
|
|
383
370
|
if (actualFingerprint !== expectedFingerprint) {
|
|
384
371
|
throw new Error(
|
|
385
372
|
`Fingerprint mismatch! Expected: ${expectedFingerprint}, Got: ${actualFingerprint}`
|
|
386
373
|
);
|
|
387
374
|
}
|
|
388
|
-
|
|
389
375
|
this.logger.debug(`Fingerprint verified for ${bucket}/${path}`);
|
|
390
376
|
}
|
|
391
377
|
|
|
@@ -436,11 +422,8 @@ class StorageConnector {
|
|
|
436
422
|
try {
|
|
437
423
|
bucket = bucket || this.defaultBucket;
|
|
438
424
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
path,
|
|
442
|
-
expiry
|
|
443
|
-
);
|
|
425
|
+
// Use StorageCore
|
|
426
|
+
const url = await this.core.getPresignedUrl(bucket, path, expiry);
|
|
444
427
|
|
|
445
428
|
this.logger.debug(`Generated presigned URL for ${bucket}/${path}`);
|
|
446
429
|
return url;
|
|
@@ -476,20 +459,25 @@ class StorageConnector {
|
|
|
476
459
|
try {
|
|
477
460
|
bucket = bucket || this.defaultBucket;
|
|
478
461
|
|
|
462
|
+
// Use StorageCore to list objects
|
|
463
|
+
const objectNames = await this.core.listByPrefix(bucket, prefix, true);
|
|
464
|
+
|
|
465
|
+
// Get metadata for each object
|
|
479
466
|
const objects = [];
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
467
|
+
for (const name of objectNames) {
|
|
468
|
+
try {
|
|
469
|
+
const stat = await this.core.statObject(bucket, name);
|
|
470
|
+
objects.push({
|
|
471
|
+
name,
|
|
472
|
+
size: stat.size,
|
|
473
|
+
lastModified: stat.lastModified,
|
|
474
|
+
fingerprint: stat.metaData?.['x-amz-meta-fingerprint'],
|
|
475
|
+
uploadedAt: stat.metaData?.['x-amz-meta-uploaded-at']
|
|
476
|
+
});
|
|
477
|
+
} catch (err) {
|
|
478
|
+
// Skip objects that can't be stat'd
|
|
479
|
+
this.logger.warn(`Failed to stat object ${name}`, err);
|
|
480
|
+
}
|
|
493
481
|
}
|
|
494
482
|
|
|
495
483
|
return objects;
|
|
@@ -520,7 +508,8 @@ class StorageConnector {
|
|
|
520
508
|
try {
|
|
521
509
|
bucket = bucket || this.defaultBucket;
|
|
522
510
|
|
|
523
|
-
|
|
511
|
+
// Use StorageCore
|
|
512
|
+
await this.core.deleteObject(bucket, path);
|
|
524
513
|
this.logger.warn(`Deleted object: ${bucket}/${path}`);
|
|
525
514
|
|
|
526
515
|
// Clear from cache
|
|
@@ -594,10 +583,10 @@ class StorageConnector {
|
|
|
594
583
|
*/
|
|
595
584
|
async ensureBucket(bucketName) {
|
|
596
585
|
try {
|
|
597
|
-
|
|
586
|
+
// Use StorageCore
|
|
587
|
+
const created = await this.core.ensureBucket(bucketName);
|
|
598
588
|
|
|
599
|
-
if (
|
|
600
|
-
await this.client.makeBucket(bucketName, 'us-east-1');
|
|
589
|
+
if (created) {
|
|
601
590
|
this.logger.info(`Bucket ${bucketName} created`);
|
|
602
591
|
}
|
|
603
592
|
|
|
@@ -4,34 +4,16 @@
|
|
|
4
4
|
* Skrývá implementační detaily před službami
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const runtimeCfg = require('./config');
|
|
9
|
+
|
|
7
10
|
class InternalUrlAdapter {
|
|
8
11
|
constructor(config = {}) {
|
|
9
|
-
this.environment = config.environment
|
|
10
|
-
this.dockerNetwork =
|
|
12
|
+
this.environment = runtimeCfg.get('nodeEnv', config.environment);
|
|
13
|
+
this.dockerNetwork = runtimeCfg.get('dockerNetwork', config.dockerNetwork);
|
|
11
14
|
|
|
12
15
|
// Mapování služeb na jejich interní adresy
|
|
13
|
-
this.serviceMap = {
|
|
14
|
-
storage: {
|
|
15
|
-
docker: 'api_services_storage:9000',
|
|
16
|
-
local: 'localhost:9000'
|
|
17
|
-
},
|
|
18
|
-
registry: {
|
|
19
|
-
docker: 'api_registry:8080',
|
|
20
|
-
local: 'localhost:8080'
|
|
21
|
-
},
|
|
22
|
-
monitoring: {
|
|
23
|
-
docker: 'api_monitoring:3000',
|
|
24
|
-
local: 'localhost:3000'
|
|
25
|
-
},
|
|
26
|
-
mq: {
|
|
27
|
-
docker: 'api_services_rabbit:5672',
|
|
28
|
-
local: 'localhost:5672'
|
|
29
|
-
},
|
|
30
|
-
cache: {
|
|
31
|
-
docker: 'api_node_cache:6379',
|
|
32
|
-
local: 'localhost:6379'
|
|
33
|
-
}
|
|
34
|
-
};
|
|
16
|
+
this.serviceMap = { ...runtimeCfg.get('internalUrlServiceMap') };
|
|
35
17
|
|
|
36
18
|
// Rozšiřitelné přes config
|
|
37
19
|
if (config.services) {
|
|
@@ -91,11 +73,14 @@ class InternalUrlAdapter {
|
|
|
91
73
|
*/
|
|
92
74
|
isRunningInDocker() {
|
|
93
75
|
// Několik způsobů detekce Docker kontejneru
|
|
76
|
+
const dockerEnv = runtimeCfg.get('dockerEnv');
|
|
77
|
+
const dockerContainer = runtimeCfg.get('dockerContainer');
|
|
78
|
+
const hostname = runtimeCfg.get('hostname');
|
|
94
79
|
return !!(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
(
|
|
98
|
-
(
|
|
80
|
+
dockerEnv ||
|
|
81
|
+
dockerContainer ||
|
|
82
|
+
(fs.existsSync('/.dockerenv')) ||
|
|
83
|
+
(hostname && hostname.length === 12) // Docker container ID
|
|
99
84
|
);
|
|
100
85
|
}
|
|
101
86
|
|
|
@@ -9,6 +9,8 @@ describe('InternalUrlAdapter @unit', () => {
|
|
|
9
9
|
|
|
10
10
|
beforeEach(() => {
|
|
11
11
|
adapter = new InternalUrlAdapter();
|
|
12
|
+
// Tests run inside a Docker container; force local-mode behavior unless explicitly enabled.
|
|
13
|
+
adapter.isRunningInDocker = jest.fn(() => false);
|
|
12
14
|
});
|
|
13
15
|
|
|
14
16
|
describe('URL Resolution', () => {
|
|
@@ -202,6 +204,8 @@ describe('InternalUrlAdapter @unit', () => {
|
|
|
202
204
|
}
|
|
203
205
|
});
|
|
204
206
|
|
|
207
|
+
// Tests run inside Docker; force local-mode behavior for this adapter instance.
|
|
208
|
+
customAdapter.isRunningInDocker = jest.fn(() => false);
|
|
205
209
|
customAdapter.dockerNetwork = false;
|
|
206
210
|
const resolved = customAdapter.resolve('internal://my-service/api');
|
|
207
211
|
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
const StorageConnector = require('../../src/index');
|
|
7
7
|
const Minio = require('minio');
|
|
8
8
|
|
|
9
|
+
// Minimal required MinIO config for tests (all external I/O is mocked)
|
|
10
|
+
const MINIO_CONFIG = {
|
|
11
|
+
endPoint: 'localhost',
|
|
12
|
+
port: 9000,
|
|
13
|
+
accessKey: 'test',
|
|
14
|
+
secretKey: 'test',
|
|
15
|
+
};
|
|
16
|
+
|
|
9
17
|
// Mock MinIO client
|
|
10
18
|
jest.mock('minio');
|
|
11
19
|
|
|
@@ -321,15 +329,15 @@ describe('StorageConnector Extended Unit Tests @unit', () => {
|
|
|
321
329
|
|
|
322
330
|
describe('Internal URL Generation', () => {
|
|
323
331
|
test('should generate abstract internal URL', () => {
|
|
324
|
-
storage = new StorageConnector();
|
|
332
|
+
storage = new StorageConnector(MINIO_CONFIG);
|
|
325
333
|
|
|
326
334
|
const url = storage.getInternalUrl('bucket', 'path/to/object');
|
|
327
335
|
expect(url).toBe('internal://storage/bucket/path/to/object');
|
|
328
336
|
});
|
|
329
337
|
|
|
330
338
|
test('should generate consistent internal URLs', () => {
|
|
331
|
-
const storage1 = new StorageConnector();
|
|
332
|
-
const storage2 = new StorageConnector();
|
|
339
|
+
const storage1 = new StorageConnector(MINIO_CONFIG);
|
|
340
|
+
const storage2 = new StorageConnector(MINIO_CONFIG);
|
|
333
341
|
|
|
334
342
|
const url1 = storage1.getInternalUrl('bucket', 'object');
|
|
335
343
|
const url2 = storage2.getInternalUrl('bucket', 'object');
|
|
@@ -341,7 +349,7 @@ describe('StorageConnector Extended Unit Tests @unit', () => {
|
|
|
341
349
|
|
|
342
350
|
describe('Bucket Operations', () => {
|
|
343
351
|
beforeEach(() => {
|
|
344
|
-
storage = new StorageConnector();
|
|
352
|
+
storage = new StorageConnector(MINIO_CONFIG);
|
|
345
353
|
});
|
|
346
354
|
|
|
347
355
|
test('should ensure bucket exists', async () => {
|
|
@@ -373,7 +381,7 @@ describe('StorageConnector Extended Unit Tests @unit', () => {
|
|
|
373
381
|
|
|
374
382
|
describe('Cache Operations', () => {
|
|
375
383
|
test('should clear all caches', () => {
|
|
376
|
-
storage = new StorageConnector();
|
|
384
|
+
storage = new StorageConnector(MINIO_CONFIG);
|
|
377
385
|
|
|
378
386
|
// Add items to caches
|
|
379
387
|
storage.cache.set('key1', 'value1');
|
|
@@ -389,7 +397,7 @@ describe('StorageConnector Extended Unit Tests @unit', () => {
|
|
|
389
397
|
});
|
|
390
398
|
|
|
391
399
|
test('should handle cache size limits', () => {
|
|
392
|
-
storage = new StorageConnector({ cacheMaxSize: 2 });
|
|
400
|
+
storage = new StorageConnector({ ...MINIO_CONFIG, cacheMaxSize: 2 });
|
|
393
401
|
|
|
394
402
|
storage.addToCache('key1', 'value1');
|
|
395
403
|
storage.addToCache('key2', 'value2');
|
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
const StorageConnector = require('../../src/index');
|
|
13
13
|
const crypto = require('crypto');
|
|
14
14
|
|
|
15
|
+
// Minimal required MinIO config for tests (all external I/O is mocked)
|
|
16
|
+
const MINIO_CONFIG = {
|
|
17
|
+
endPoint: 'localhost',
|
|
18
|
+
port: 9000,
|
|
19
|
+
accessKey: 'test',
|
|
20
|
+
secretKey: 'test',
|
|
21
|
+
};
|
|
22
|
+
|
|
15
23
|
// Mock MinIO client kompletně
|
|
16
24
|
jest.mock('minio', () => {
|
|
17
25
|
return {
|
|
@@ -38,7 +46,7 @@ jest.mock('minio', () => {
|
|
|
38
46
|
describe('StorageConnector Unit Tests @unit', () => {
|
|
39
47
|
describe('Initialization', () => {
|
|
40
48
|
test('should create instance with default config', () => {
|
|
41
|
-
const storage = new StorageConnector();
|
|
49
|
+
const storage = new StorageConnector(MINIO_CONFIG);
|
|
42
50
|
expect(storage).toBeDefined();
|
|
43
51
|
expect(storage.config).toBeDefined();
|
|
44
52
|
expect(storage.config.endPoint).toBe('localhost');
|
|
@@ -67,7 +75,7 @@ describe('StorageConnector Unit Tests @unit', () => {
|
|
|
67
75
|
let storage;
|
|
68
76
|
|
|
69
77
|
beforeEach(() => {
|
|
70
|
-
storage = new StorageConnector();
|
|
78
|
+
storage = new StorageConnector(MINIO_CONFIG);
|
|
71
79
|
});
|
|
72
80
|
|
|
73
81
|
test('should generate fingerprint for string content', () => {
|
|
@@ -144,7 +152,7 @@ describe('StorageConnector Unit Tests @unit', () => {
|
|
|
144
152
|
let storage;
|
|
145
153
|
|
|
146
154
|
beforeEach(() => {
|
|
147
|
-
storage = new StorageConnector({ cacheMaxSize: 3 });
|
|
155
|
+
storage = new StorageConnector({ ...MINIO_CONFIG, cacheMaxSize: 3 });
|
|
148
156
|
});
|
|
149
157
|
|
|
150
158
|
test('should add items to cache', () => {
|
|
@@ -189,7 +197,7 @@ describe('StorageConnector Unit Tests @unit', () => {
|
|
|
189
197
|
let storage;
|
|
190
198
|
|
|
191
199
|
beforeEach(() => {
|
|
192
|
-
storage = new StorageConnector();
|
|
200
|
+
storage = new StorageConnector(MINIO_CONFIG);
|
|
193
201
|
});
|
|
194
202
|
|
|
195
203
|
test('should handle invalid content types for fingerprinting', () => {
|
|
@@ -202,13 +210,13 @@ describe('StorageConnector Unit Tests @unit', () => {
|
|
|
202
210
|
const invalidNames = ['', 'a', 'UPPERCASE', 'bucket_name', 'bucket-'];
|
|
203
211
|
|
|
204
212
|
validNames.forEach(name => {
|
|
205
|
-
const s = new StorageConnector({ bucketName: name });
|
|
213
|
+
const s = new StorageConnector({ ...MINIO_CONFIG, bucketName: name });
|
|
206
214
|
expect(s.config.bucketName).toBe(name);
|
|
207
215
|
});
|
|
208
216
|
|
|
209
217
|
// Invalid names should use default
|
|
210
218
|
invalidNames.forEach(name => {
|
|
211
|
-
const s = new StorageConnector({ bucketName: name });
|
|
219
|
+
const s = new StorageConnector({ ...MINIO_CONFIG, bucketName: name });
|
|
212
220
|
expect(s.config.bucketName).toBe('oa-drive-storage');
|
|
213
221
|
});
|
|
214
222
|
});
|
|
@@ -218,7 +226,7 @@ describe('StorageConnector Unit Tests @unit', () => {
|
|
|
218
226
|
let storage;
|
|
219
227
|
|
|
220
228
|
beforeEach(() => {
|
|
221
|
-
storage = new StorageConnector();
|
|
229
|
+
storage = new StorageConnector(MINIO_CONFIG);
|
|
222
230
|
});
|
|
223
231
|
|
|
224
232
|
test('should generate object name with fingerprint', () => {
|
|
@@ -255,7 +263,7 @@ describe('StorageConnector Unit Tests @unit', () => {
|
|
|
255
263
|
|
|
256
264
|
beforeEach(() => {
|
|
257
265
|
jest.clearAllMocks();
|
|
258
|
-
storage = new StorageConnector();
|
|
266
|
+
storage = new StorageConnector(MINIO_CONFIG);
|
|
259
267
|
mockClient = storage.minioClient;
|
|
260
268
|
});
|
|
261
269
|
|