@@ -148,6 +148,22 @@ describe('ConfigLoader', () => {
148
148
149
149
expect ( spy . calledOnce ) . to . be . true ; // Should only emit once
150
150
} ) ;
151
+
152
+ it ( 'should not emit event if configurationSources is disabled' , async ( ) => {
153
+ const config = {
154
+ configurationSources : {
155
+ enabled : false ,
156
+ } ,
157
+ } ;
158
+
159
+ configLoader = new ConfigLoader ( config ) ;
160
+ const spy = sinon . spy ( ) ;
161
+ configLoader . on ( 'configurationChanged' , spy ) ;
162
+
163
+ await configLoader . reloadConfiguration ( ) ;
164
+
165
+ expect ( spy . called ) . to . be . false ;
166
+ } ) ;
151
167
} ) ;
152
168
153
169
describe ( 'initialize' , ( ) => {
@@ -182,6 +198,98 @@ describe('ConfigLoader', () => {
182
198
} ) ;
183
199
} ) ;
184
200
201
+ describe ( 'start' , ( ) => {
202
+ it ( 'should perform initial load on start if configurationSources is enabled' , async ( ) => {
203
+ const mockConfig = {
204
+ configurationSources : {
205
+ enabled : true ,
206
+ sources : [
207
+ {
208
+ type : 'file' ,
209
+ enabled : true ,
210
+ path : tempConfigFile ,
211
+ } ,
212
+ ] ,
213
+ reloadIntervalSeconds : 30 ,
214
+ } ,
215
+ } ;
216
+
217
+ const configLoader = new ConfigLoader ( mockConfig ) ;
218
+ const spy = sinon . spy ( configLoader , 'reloadConfiguration' ) ;
219
+ await configLoader . start ( ) ;
220
+
221
+ expect ( spy . calledOnce ) . to . be . true ;
222
+ } ) ;
223
+
224
+ it ( 'should clear an existing reload interval if it exists' , async ( ) => {
225
+ const mockConfig = {
226
+ configurationSources : {
227
+ enabled : true ,
228
+ sources : [
229
+ {
230
+ type : 'file' ,
231
+ enabled : true ,
232
+ path : tempConfigFile ,
233
+ } ,
234
+ ] ,
235
+ } ,
236
+ } ;
237
+
238
+ const configLoader = new ConfigLoader ( mockConfig ) ;
239
+ configLoader . reloadTimer = setInterval ( ( ) => { } , 1000 ) ;
240
+ await configLoader . start ( ) ;
241
+
242
+ expect ( configLoader . reloadTimer ) . to . be . null ;
243
+ } ) ;
244
+
245
+ it ( 'should run reloadConfiguration multiple times on short reload interval' , async ( ) => {
246
+ const mockConfig = {
247
+ configurationSources : {
248
+ enabled : true ,
249
+ sources : [
250
+ {
251
+ type : 'file' ,
252
+ enabled : true ,
253
+ path : tempConfigFile ,
254
+ } ,
255
+ ] ,
256
+ reloadIntervalSeconds : 0.01 ,
257
+ } ,
258
+ } ;
259
+
260
+ const configLoader = new ConfigLoader ( mockConfig ) ;
261
+ const spy = sinon . spy ( configLoader , 'reloadConfiguration' ) ;
262
+ await configLoader . start ( ) ;
263
+
264
+ // Make sure the reload interval is triggered
265
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
266
+
267
+ expect ( spy . callCount ) . to . greaterThan ( 1 ) ;
268
+ } ) ;
269
+
270
+ it ( 'should clear the interval when stop is called' , async ( ) => {
271
+ const mockConfig = {
272
+ configurationSources : {
273
+ enabled : true ,
274
+ sources : [
275
+ {
276
+ type : 'file' ,
277
+ enabled : true ,
278
+ path : tempConfigFile ,
279
+ } ,
280
+ ] ,
281
+ } ,
282
+ } ;
283
+
284
+ const configLoader = new ConfigLoader ( mockConfig ) ;
285
+ configLoader . reloadTimer = setInterval ( ( ) => { } , 1000 ) ;
286
+ expect ( configLoader . reloadTimer ) . to . not . be . null ;
287
+
288
+ await configLoader . stop ( ) ;
289
+ expect ( configLoader . reloadTimer ) . to . be . null ;
290
+ } ) ;
291
+ } ) ;
292
+
185
293
describe ( 'loadRemoteConfig' , ( ) => {
186
294
let configLoader ;
187
295
beforeEach ( async ( ) => {
@@ -205,15 +313,15 @@ describe('ConfigLoader', () => {
205
313
enabled : true ,
206
314
} ;
207
315
208
- const config = await configLoader . loadFromGit ( source ) ;
316
+ const config = await configLoader . loadFromSource ( source ) ;
209
317
210
318
// Verify the loaded config has expected structure
211
319
expect ( config ) . to . be . an ( 'object' ) ;
212
320
expect ( config ) . to . have . property ( 'proxyUrl' ) ;
213
321
expect ( config ) . to . have . property ( 'cookieSecret' ) ;
214
322
} ) ;
215
323
216
- it ( 'should throw error for invalid configuration file path' , async function ( ) {
324
+ it ( 'should throw error for invalid configuration file path (git) ' , async function ( ) {
217
325
const source = {
218
326
type : 'git' ,
219
327
repository : 'https://github.com/finos/git-proxy.git' ,
@@ -223,13 +331,28 @@ describe('ConfigLoader', () => {
223
331
} ;
224
332
225
333
try {
226
- await configLoader . loadFromGit ( source ) ;
334
+ await configLoader . loadFromSource ( source ) ;
227
335
throw new Error ( 'Expected error was not thrown' ) ;
228
336
} catch ( error ) {
229
337
expect ( error . message ) . to . equal ( 'Invalid configuration file path in repository' ) ;
230
338
}
231
339
} ) ;
232
340
341
+ it ( 'should throw error for invalid configuration file path (file)' , async function ( ) {
342
+ const source = {
343
+ type : 'file' ,
344
+ path : '\0' , // Invalid path
345
+ enabled : true ,
346
+ } ;
347
+
348
+ try {
349
+ await configLoader . loadFromSource ( source ) ;
350
+ throw new Error ( 'Expected error was not thrown' ) ;
351
+ } catch ( error ) {
352
+ expect ( error . message ) . to . equal ( 'Invalid configuration file path' ) ;
353
+ }
354
+ } ) ;
355
+
233
356
it ( 'should load configuration from http' , async function ( ) {
234
357
// eslint-disable-next-line no-invalid-this
235
358
this . timeout ( 10000 ) ;
@@ -240,13 +363,132 @@ describe('ConfigLoader', () => {
240
363
enabled : true ,
241
364
} ;
242
365
243
- const config = await configLoader . loadFromHttp ( source ) ;
366
+ const config = await configLoader . loadFromSource ( source ) ;
244
367
245
368
// Verify the loaded config has expected structure
246
369
expect ( config ) . to . be . an ( 'object' ) ;
247
370
expect ( config ) . to . have . property ( 'proxyUrl' ) ;
248
371
expect ( config ) . to . have . property ( 'cookieSecret' ) ;
249
372
} ) ;
373
+
374
+ it ( 'should throw error if repository is invalid' , async function ( ) {
375
+ const source = {
376
+ type : 'git' ,
377
+ repository : 'invalid-repository' ,
378
+ path : 'proxy.config.json' ,
379
+ branch : 'main' ,
380
+ enabled : true ,
381
+ } ;
382
+
383
+ try {
384
+ await configLoader . loadFromSource ( source ) ;
385
+ throw new Error ( 'Expected error was not thrown' ) ;
386
+ } catch ( error ) {
387
+ expect ( error . message ) . to . equal ( 'Invalid repository URL format' ) ;
388
+ }
389
+ } ) ;
390
+
391
+ it ( 'should throw error if branch name is invalid' , async function ( ) {
392
+ const source = {
393
+ type : 'git' ,
394
+ repository : 'https://github.com/finos/git-proxy.git' ,
395
+ path : 'proxy.config.json' ,
396
+ branch : '..' , // invalid branch pattern
397
+ enabled : true ,
398
+ } ;
399
+
400
+ try {
401
+ await configLoader . loadFromSource ( source ) ;
402
+ throw new Error ( 'Expected error was not thrown' ) ;
403
+ } catch ( error ) {
404
+ expect ( error . message ) . to . equal ( 'Invalid branch name format' ) ;
405
+ }
406
+ } ) ;
407
+
408
+ it ( 'should throw error if configuration source is invalid' , async function ( ) {
409
+ const source = {
410
+ type : 'invalid' ,
411
+ repository : 'https://github.com/finos/git-proxy.git' ,
412
+ path : 'proxy.config.json' ,
413
+ branch : 'main' ,
414
+ enabled : true ,
415
+ } ;
416
+
417
+ try {
418
+ await configLoader . loadFromSource ( source ) ;
419
+ throw new Error ( 'Expected error was not thrown' ) ;
420
+ } catch ( error ) {
421
+ expect ( error . message ) . to . contain ( 'Unsupported configuration source type' ) ;
422
+ }
423
+ } ) ;
424
+
425
+ it ( 'should throw error if repository is a valid URL but not a git repository' , async function ( ) {
426
+ const source = {
427
+ type : 'git' ,
428
+ repository : 'https://github.com/test-org/test-repo.git' ,
429
+ path : 'proxy.config.json' ,
430
+ branch : 'main' ,
431
+ enabled : true ,
432
+ } ;
433
+
434
+ try {
435
+ await configLoader . loadFromSource ( source ) ;
436
+ throw new Error ( 'Expected error was not thrown' ) ;
437
+ } catch ( error ) {
438
+ expect ( error . message ) . to . contain ( 'Failed to clone repository' ) ;
439
+ }
440
+ } ) ;
441
+
442
+ it ( 'should throw error if repository is a valid git repo but the branch does not exist' , async function ( ) {
443
+ const source = {
444
+ type : 'git' ,
445
+ repository : 'https://github.com/finos/git-proxy.git' ,
446
+ path : 'proxy.config.json' ,
447
+ branch : 'branch-does-not-exist' ,
448
+ enabled : true ,
449
+ } ;
450
+
451
+ try {
452
+ await configLoader . loadFromSource ( source ) ;
453
+ throw new Error ( 'Expected error was not thrown' ) ;
454
+ } catch ( error ) {
455
+ expect ( error . message ) . to . contain ( 'Failed to checkout branch' ) ;
456
+ }
457
+ } ) ;
458
+
459
+ it ( 'should throw error if config path was not found' , async function ( ) {
460
+ const source = {
461
+ type : 'git' ,
462
+ repository : 'https://github.com/finos/git-proxy.git' ,
463
+ path : 'path-not-found.json' ,
464
+ branch : 'main' ,
465
+ enabled : true ,
466
+ } ;
467
+
468
+ try {
469
+ await configLoader . loadFromSource ( source ) ;
470
+ throw new Error ( 'Expected error was not thrown' ) ;
471
+ } catch ( error ) {
472
+ expect ( error . message ) . to . contain ( 'Configuration file not found at' ) ;
473
+ }
474
+ } ) ;
475
+
476
+ it ( 'should throw error if config file is not valid JSON' , async function ( ) {
477
+ const source = {
478
+ type : 'git' ,
479
+ repository : 'https://github.com/finos/git-proxy.git' ,
480
+ path : 'test/fixtures/baz.js' ,
481
+ branch : 'main' ,
482
+ enabled : true ,
483
+ } ;
484
+
485
+ try {
486
+ await configLoader . loadFromSource ( source ) ;
487
+ throw new Error ( 'Expected error was not thrown' ) ;
488
+ } catch ( error ) {
489
+ expect ( error . message ) . to . contain ( 'Failed to read or parse configuration file' ) ;
490
+ }
491
+ } ) ;
250
492
} ) ;
251
493
252
494
describe ( 'deepMerge' , ( ) => {
0 commit comments