@adobe/spacecat-shared-tokowaka-client 1.5.1 → 1.5.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/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/index.js +61 -4
- package/test/index.test.js +621 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-tokowaka-client-v1.5.3](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.5.2...@adobe/spacecat-shared-tokowaka-client-v1.5.3) (2026-01-21)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* update tokowaka config ([#1275](https://github.com/adobe/spacecat-shared/issues/1275)) ([06cedcd](https://github.com/adobe/spacecat-shared/commit/06cedcd3d6f5956d895f7dedb7579d2eefffe58e))
|
|
7
|
+
|
|
8
|
+
# [@adobe/spacecat-shared-tokowaka-client-v1.5.2](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.5.1...@adobe/spacecat-shared-tokowaka-client-v1.5.2) (2026-01-21)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* create and update tokowaka config api ([#1273](https://github.com/adobe/spacecat-shared/issues/1273)) ([0fb6f4a](https://github.com/adobe/spacecat-shared/commit/0fb6f4aaa1efac18803f3890c86d4fc1bc69009f))
|
|
14
|
+
|
|
1
15
|
# [@adobe/spacecat-shared-tokowaka-client-v1.5.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.5.0...@adobe/spacecat-shared-tokowaka-client-v1.5.1) (2026-01-20)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -282,12 +282,12 @@ class TokowakaClient {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
/**
|
|
285
|
-
* Creates and uploads domain-level metaconfig to S3
|
|
285
|
+
* Creates and uploads domain-level metaconfig to S3 if it does not exists
|
|
286
286
|
* Generates a new API key and creates the metaconfig structure
|
|
287
287
|
* @param {string} url - Full URL (used to extract domain)
|
|
288
288
|
* @param {string} siteId - Site ID
|
|
289
289
|
* @param {Object} options - Optional configuration
|
|
290
|
-
* @param {boolean} options.
|
|
290
|
+
* @param {boolean} options.enhancements - Whether to enable enhancements (default: true)
|
|
291
291
|
* @returns {Promise<Object>} - Object with s3Path and metaconfig
|
|
292
292
|
*/
|
|
293
293
|
async createMetaconfig(url, siteId, options = {}) {
|
|
@@ -295,6 +295,12 @@ class TokowakaClient {
|
|
|
295
295
|
throw this.#createError('URL is required', HTTP_BAD_REQUEST);
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
const existingMetaconfig = await this.fetchMetaconfig(url);
|
|
299
|
+
|
|
300
|
+
if (existingMetaconfig) {
|
|
301
|
+
throw this.#createError('Metaconfig already exists for this URL', HTTP_BAD_REQUEST);
|
|
302
|
+
}
|
|
303
|
+
|
|
298
304
|
if (!hasText(siteId)) {
|
|
299
305
|
throw this.#createError('Site ID is required', HTTP_BAD_REQUEST);
|
|
300
306
|
}
|
|
@@ -305,8 +311,59 @@ class TokowakaClient {
|
|
|
305
311
|
const metaconfig = {
|
|
306
312
|
siteId,
|
|
307
313
|
apiKeys: [apiKey],
|
|
308
|
-
tokowakaEnabled:
|
|
309
|
-
enhancements: options.enhancements ??
|
|
314
|
+
tokowakaEnabled: true,
|
|
315
|
+
enhancements: options.enhancements ?? true,
|
|
316
|
+
patches: {},
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const s3Path = await this.uploadMetaconfig(url, metaconfig);
|
|
320
|
+
|
|
321
|
+
this.log.info(`Created new Tokowaka metaconfig for ${normalizedHostName} at ${s3Path}`);
|
|
322
|
+
|
|
323
|
+
return metaconfig;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Updates domain-level metaconfig to S3 if it does not exists
|
|
328
|
+
* Reuses the same API key and updates the metaconfig structure
|
|
329
|
+
* @param {string} url - Full URL (used to extract domain)
|
|
330
|
+
* @param {string} siteId - Site ID
|
|
331
|
+
* @param {Object} options - Optional configuration
|
|
332
|
+
* @returns {Promise<Object>} - Object with s3Path and metaconfig
|
|
333
|
+
*/
|
|
334
|
+
async updateMetaconfig(url, siteId, options = {}) {
|
|
335
|
+
if (!hasText(url)) {
|
|
336
|
+
throw this.#createError('URL is required', HTTP_BAD_REQUEST);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const existingMetaconfig = await this.fetchMetaconfig(url);
|
|
340
|
+
if (!existingMetaconfig) {
|
|
341
|
+
throw this.#createError('Metaconfig does not exist for this URL', HTTP_BAD_REQUEST);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (!hasText(siteId)) {
|
|
345
|
+
throw this.#createError('Site ID is required', HTTP_BAD_REQUEST);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const normalizedHostName = getHostName(url, this.log);
|
|
349
|
+
|
|
350
|
+
// dont override api keys
|
|
351
|
+
// if patches exist, they cannot reset to empty object
|
|
352
|
+
const hasForceFail = options.forceFail !== undefined
|
|
353
|
+
|| existingMetaconfig.forceFail !== undefined;
|
|
354
|
+
const forceFail = options.forceFail
|
|
355
|
+
?? existingMetaconfig.forceFail
|
|
356
|
+
?? false;
|
|
357
|
+
|
|
358
|
+
const metaconfig = {
|
|
359
|
+
siteId,
|
|
360
|
+
apiKeys: existingMetaconfig.apiKeys,
|
|
361
|
+
tokowakaEnabled: options.tokowakaEnabled ?? existingMetaconfig.tokowakaEnabled ?? true,
|
|
362
|
+
enhancements: options.enhancements ?? existingMetaconfig.enhancements ?? true,
|
|
363
|
+
patches: isNonEmptyObject(options.patches)
|
|
364
|
+
? options.patches
|
|
365
|
+
: (existingMetaconfig.patches ?? {}),
|
|
366
|
+
...(hasForceFail && { forceFail }),
|
|
310
367
|
};
|
|
311
368
|
|
|
312
369
|
const s3Path = await this.uploadMetaconfig(url, metaconfig);
|
package/test/index.test.js
CHANGED
|
@@ -457,9 +457,12 @@ describe('TokowakaClient', () => {
|
|
|
457
457
|
});
|
|
458
458
|
|
|
459
459
|
describe('createMetaconfig', () => {
|
|
460
|
-
it('should create metaconfig with generated API key
|
|
460
|
+
it('should create the default metaconfig with generated API key', async () => {
|
|
461
461
|
const siteId = 'site-123';
|
|
462
462
|
const url = 'https://www.example.com/page1';
|
|
463
|
+
const noSuchKeyError = new Error('NoSuchKey');
|
|
464
|
+
noSuchKeyError.name = 'NoSuchKey';
|
|
465
|
+
s3Client.send.onFirstCall().rejects(noSuchKeyError);
|
|
463
466
|
|
|
464
467
|
const result = await client.createMetaconfig(url, siteId);
|
|
465
468
|
|
|
@@ -468,31 +471,47 @@ describe('TokowakaClient', () => {
|
|
|
468
471
|
expect(result.apiKeys).to.be.an('array').with.lengthOf(1);
|
|
469
472
|
expect(result.apiKeys[0]).to.be.a('string');
|
|
470
473
|
expect(result).to.have.property('tokowakaEnabled', true);
|
|
471
|
-
expect(result).to.have.property('enhancements',
|
|
474
|
+
expect(result).to.have.property('enhancements', true);
|
|
475
|
+
expect(result.patches).to.be.empty;
|
|
472
476
|
|
|
473
477
|
// Verify uploadMetaconfig was called with correct metaconfig
|
|
474
|
-
expect(s3Client.send).to.have.been.
|
|
478
|
+
expect(s3Client.send).to.have.been.calledTwice;
|
|
475
479
|
const command = s3Client.send.firstCall.args[0];
|
|
476
480
|
expect(command.input.Bucket).to.equal('test-bucket');
|
|
477
481
|
expect(command.input.Key).to.equal('opportunities/example.com/config');
|
|
478
482
|
});
|
|
479
483
|
|
|
480
|
-
it('should
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
484
|
+
it('should throw error if metaconfig exists', async () => {
|
|
485
|
+
const existingMetaconfig = {
|
|
486
|
+
siteId: 'site-123',
|
|
487
|
+
apiKeys: ['existing-api-key-123'],
|
|
488
|
+
tokowakaEnabled: true,
|
|
489
|
+
enhancements: true,
|
|
490
|
+
patches: {},
|
|
491
|
+
};
|
|
492
|
+
// Mock fetchMetaconfig to return existing config
|
|
493
|
+
s3Client.send.onFirstCall().resolves({
|
|
494
|
+
Body: {
|
|
495
|
+
transformToString: sinon.stub().resolves(JSON.stringify(existingMetaconfig)),
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
try {
|
|
499
|
+
await client.createMetaconfig('https://example.com', 'site-123');
|
|
500
|
+
expect.fail('Should have thrown error');
|
|
501
|
+
} catch (error) {
|
|
502
|
+
expect(error.message).to.include('Metaconfig already exists for this URL');
|
|
503
|
+
expect(error.status).to.equal(400);
|
|
504
|
+
}
|
|
489
505
|
});
|
|
490
506
|
|
|
491
|
-
it('should create metaconfig with
|
|
492
|
-
const siteId = 'site-
|
|
507
|
+
it('should create metaconfig with enhancements set to false', async () => {
|
|
508
|
+
const siteId = 'site-789';
|
|
493
509
|
const url = 'https://example.com';
|
|
510
|
+
const noSuchKeyError = new Error('NoSuchKey');
|
|
511
|
+
noSuchKeyError.name = 'NoSuchKey';
|
|
512
|
+
s3Client.send.onFirstCall().rejects(noSuchKeyError);
|
|
494
513
|
|
|
495
|
-
const result = await client.createMetaconfig(url, siteId, {
|
|
514
|
+
const result = await client.createMetaconfig(url, siteId, { enhancements: false });
|
|
496
515
|
|
|
497
516
|
expect(result).to.have.property('tokowakaEnabled', true);
|
|
498
517
|
expect(result).to.have.property('enhancements', false);
|
|
@@ -509,6 +528,9 @@ describe('TokowakaClient', () => {
|
|
|
509
528
|
});
|
|
510
529
|
|
|
511
530
|
it('should throw error if siteId is missing', async () => {
|
|
531
|
+
const noSuchKeyError = new Error('NoSuchKey');
|
|
532
|
+
noSuchKeyError.name = 'NoSuchKey';
|
|
533
|
+
s3Client.send.onFirstCall().rejects(noSuchKeyError);
|
|
512
534
|
try {
|
|
513
535
|
await client.createMetaconfig('https://example.com', '');
|
|
514
536
|
expect.fail('Should have thrown error');
|
|
@@ -519,9 +541,11 @@ describe('TokowakaClient', () => {
|
|
|
519
541
|
});
|
|
520
542
|
|
|
521
543
|
it('should handle S3 upload failure', async () => {
|
|
544
|
+
const noSuchKeyError = new Error('NoSuchKey');
|
|
545
|
+
noSuchKeyError.name = 'NoSuchKey';
|
|
546
|
+
s3Client.send.onFirstCall().rejects(noSuchKeyError);
|
|
522
547
|
const s3Error = new Error('S3 network error');
|
|
523
|
-
s3Client.send.rejects(s3Error);
|
|
524
|
-
|
|
548
|
+
s3Client.send.onSecondCall().rejects(s3Error);
|
|
525
549
|
try {
|
|
526
550
|
await client.createMetaconfig('https://example.com', 'site-123');
|
|
527
551
|
expect.fail('Should have thrown error');
|
|
@@ -534,6 +558,9 @@ describe('TokowakaClient', () => {
|
|
|
534
558
|
it('should strip www. from domain in metaconfig path', async () => {
|
|
535
559
|
const siteId = 'site-123';
|
|
536
560
|
const url = 'https://www.example.com/some/path';
|
|
561
|
+
const noSuchKeyError = new Error('NoSuchKey');
|
|
562
|
+
noSuchKeyError.name = 'NoSuchKey';
|
|
563
|
+
s3Client.send.onFirstCall().rejects(noSuchKeyError);
|
|
537
564
|
|
|
538
565
|
await client.createMetaconfig(url, siteId);
|
|
539
566
|
|
|
@@ -542,6 +569,583 @@ describe('TokowakaClient', () => {
|
|
|
542
569
|
});
|
|
543
570
|
});
|
|
544
571
|
|
|
572
|
+
describe('updateMetaconfig', () => {
|
|
573
|
+
const existingMetaconfig = {
|
|
574
|
+
siteId: 'site-456',
|
|
575
|
+
apiKeys: ['existing-api-key-123'],
|
|
576
|
+
tokowakaEnabled: false,
|
|
577
|
+
enhancements: false,
|
|
578
|
+
patches: { 'existing-patch': 'value' },
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
beforeEach(() => {
|
|
582
|
+
// Mock fetchMetaconfig to return existing config
|
|
583
|
+
s3Client.send.onFirstCall().resolves({
|
|
584
|
+
Body: {
|
|
585
|
+
transformToString: sinon.stub().resolves(JSON.stringify(existingMetaconfig)),
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
// Mock uploadMetaconfig S3 upload
|
|
589
|
+
s3Client.send.onSecondCall().resolves();
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('should update metaconfig with default options', async () => {
|
|
593
|
+
const siteId = 'site-789';
|
|
594
|
+
const url = 'https://www.example.com/page1';
|
|
595
|
+
|
|
596
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
597
|
+
|
|
598
|
+
expect(result).to.have.property('siteId', siteId);
|
|
599
|
+
expect(result).to.have.property('apiKeys');
|
|
600
|
+
expect(result.apiKeys).to.deep.equal(['existing-api-key-123']);
|
|
601
|
+
// Should preserve existing metaconfig values when options not provided
|
|
602
|
+
expect(result).to.have.property('tokowakaEnabled', false);
|
|
603
|
+
expect(result).to.have.property('enhancements', false);
|
|
604
|
+
expect(result.patches).to.deep.equal({ 'existing-patch': 'value' });
|
|
605
|
+
expect(result).to.not.have.property('forceFail');
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('should update metaconfig with tokowakaEnabled set to false', async () => {
|
|
609
|
+
const siteId = 'site-789';
|
|
610
|
+
const url = 'https://example.com';
|
|
611
|
+
|
|
612
|
+
const result = await client.updateMetaconfig(url, siteId, { tokowakaEnabled: false });
|
|
613
|
+
|
|
614
|
+
expect(result).to.have.property('tokowakaEnabled', false);
|
|
615
|
+
expect(result).to.have.property('enhancements', false);
|
|
616
|
+
expect(result.patches).to.deep.equal({ 'existing-patch': 'value' });
|
|
617
|
+
expect(result).to.not.have.property('forceFail');
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('should update metaconfig with tokowakaEnabled set to true explicitly', async () => {
|
|
621
|
+
const siteId = 'site-789';
|
|
622
|
+
const url = 'https://example.com';
|
|
623
|
+
|
|
624
|
+
const result = await client.updateMetaconfig(url, siteId, { tokowakaEnabled: true });
|
|
625
|
+
|
|
626
|
+
expect(result).to.have.property('tokowakaEnabled', true);
|
|
627
|
+
expect(result).to.have.property('enhancements', false);
|
|
628
|
+
expect(result.patches).to.deep.equal({ 'existing-patch': 'value' });
|
|
629
|
+
expect(result).to.not.have.property('forceFail');
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it('should update metaconfig with enhancements set to false', async () => {
|
|
633
|
+
const siteId = 'site-789';
|
|
634
|
+
const url = 'https://example.com';
|
|
635
|
+
|
|
636
|
+
const result = await client.updateMetaconfig(url, siteId, { enhancements: false });
|
|
637
|
+
|
|
638
|
+
expect(result).to.have.property('tokowakaEnabled', false);
|
|
639
|
+
expect(result).to.have.property('enhancements', false);
|
|
640
|
+
expect(result.patches).to.deep.equal({ 'existing-patch': 'value' });
|
|
641
|
+
expect(result).to.not.have.property('forceFail');
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it('should update metaconfig with enhancements set to true explicitly', async () => {
|
|
645
|
+
const siteId = 'site-789';
|
|
646
|
+
const url = 'https://example.com';
|
|
647
|
+
|
|
648
|
+
const result = await client.updateMetaconfig(url, siteId, { enhancements: true });
|
|
649
|
+
|
|
650
|
+
expect(result).to.have.property('tokowakaEnabled', false);
|
|
651
|
+
expect(result).to.have.property('enhancements', true);
|
|
652
|
+
expect(result.patches).to.deep.equal({ 'existing-patch': 'value' });
|
|
653
|
+
expect(result).to.not.have.property('forceFail');
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it('should override patches when non-empty patches object is provided', async () => {
|
|
657
|
+
const siteId = 'site-789';
|
|
658
|
+
const url = 'https://example.com';
|
|
659
|
+
const newPatches = { 'new-patch': 'new-value', 'another-patch': 'another-value' };
|
|
660
|
+
|
|
661
|
+
const result = await client.updateMetaconfig(url, siteId, { patches: newPatches });
|
|
662
|
+
|
|
663
|
+
expect(result.patches).to.deep.equal(newPatches);
|
|
664
|
+
expect(result.patches).to.not.deep.equal({ 'existing-patch': 'value' });
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it('should preserve existing patches when empty patches object is provided', async () => {
|
|
668
|
+
const siteId = 'site-789';
|
|
669
|
+
const url = 'https://example.com';
|
|
670
|
+
|
|
671
|
+
const result = await client.updateMetaconfig(url, siteId, { patches: {} });
|
|
672
|
+
|
|
673
|
+
expect(result.patches).to.deep.equal({ 'existing-patch': 'value' });
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it('should preserve existing patches when patches is undefined', async () => {
|
|
677
|
+
const siteId = 'site-789';
|
|
678
|
+
const url = 'https://example.com';
|
|
679
|
+
|
|
680
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
681
|
+
|
|
682
|
+
expect(result.patches).to.deep.equal({ 'existing-patch': 'value' });
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
it('should use empty patches object when existing config has no patches and no patches provided', async () => {
|
|
686
|
+
const configWithoutPatches = {
|
|
687
|
+
siteId: 'site-456',
|
|
688
|
+
apiKeys: ['existing-api-key-123'],
|
|
689
|
+
tokowakaEnabled: false,
|
|
690
|
+
enhancements: false,
|
|
691
|
+
};
|
|
692
|
+
s3Client.send.onFirstCall().resolves({
|
|
693
|
+
Body: {
|
|
694
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithoutPatches)),
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
const siteId = 'site-789';
|
|
699
|
+
const url = 'https://example.com';
|
|
700
|
+
|
|
701
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
702
|
+
|
|
703
|
+
expect(result.patches).to.deep.equal({});
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it('should include forceFail when set to true', async () => {
|
|
707
|
+
const siteId = 'site-789';
|
|
708
|
+
const url = 'https://example.com';
|
|
709
|
+
|
|
710
|
+
const result = await client.updateMetaconfig(url, siteId, { forceFail: true });
|
|
711
|
+
|
|
712
|
+
expect(result).to.have.property('forceFail', true);
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it('should include forceFail when set to false', async () => {
|
|
716
|
+
const siteId = 'site-789';
|
|
717
|
+
const url = 'https://example.com';
|
|
718
|
+
|
|
719
|
+
const result = await client.updateMetaconfig(url, siteId, { forceFail: false });
|
|
720
|
+
|
|
721
|
+
expect(result).to.have.property('forceFail', false);
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
it('should not include forceFail when undefined', async () => {
|
|
725
|
+
const siteId = 'site-789';
|
|
726
|
+
const url = 'https://example.com';
|
|
727
|
+
|
|
728
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
729
|
+
|
|
730
|
+
expect(result).to.not.have.property('forceFail');
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it('should use forceFail as false when options.forceFail is null and existingMetaconfig has no forceFail', async () => {
|
|
734
|
+
const siteId = 'site-789';
|
|
735
|
+
const url = 'https://example.com';
|
|
736
|
+
|
|
737
|
+
const result = await client.updateMetaconfig(url, siteId, { forceFail: null });
|
|
738
|
+
|
|
739
|
+
expect(result).to.have.property('forceFail', false);
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it('should preserve existingMetaconfig forceFail when options.forceFail is null', async () => {
|
|
743
|
+
const configWithForceFail = {
|
|
744
|
+
siteId: 'site-456',
|
|
745
|
+
apiKeys: ['existing-api-key-123'],
|
|
746
|
+
tokowakaEnabled: true,
|
|
747
|
+
enhancements: true,
|
|
748
|
+
patches: {},
|
|
749
|
+
forceFail: true,
|
|
750
|
+
};
|
|
751
|
+
s3Client.send.onFirstCall().resolves({
|
|
752
|
+
Body: {
|
|
753
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithForceFail)),
|
|
754
|
+
},
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
const siteId = 'site-789';
|
|
758
|
+
const url = 'https://example.com';
|
|
759
|
+
|
|
760
|
+
const result = await client.updateMetaconfig(url, siteId, { forceFail: null });
|
|
761
|
+
|
|
762
|
+
expect(result).to.have.property('forceFail', true);
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it('should update metaconfig with multiple options', async () => {
|
|
766
|
+
const siteId = 'site-789';
|
|
767
|
+
const url = 'https://example.com';
|
|
768
|
+
const newPatches = { 'custom-patch': 'custom-value' };
|
|
769
|
+
|
|
770
|
+
const result = await client.updateMetaconfig(url, siteId, {
|
|
771
|
+
tokowakaEnabled: false,
|
|
772
|
+
enhancements: false,
|
|
773
|
+
patches: newPatches,
|
|
774
|
+
forceFail: true,
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
expect(result).to.have.property('tokowakaEnabled', false);
|
|
778
|
+
expect(result).to.have.property('enhancements', false);
|
|
779
|
+
expect(result.patches).to.deep.equal(newPatches);
|
|
780
|
+
expect(result).to.have.property('forceFail', true);
|
|
781
|
+
expect(result.apiKeys).to.deep.equal(['existing-api-key-123']);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
it('should preserve apiKeys from existing metaconfig', async () => {
|
|
785
|
+
const existingWithMultipleKeys = {
|
|
786
|
+
siteId: 'site-456',
|
|
787
|
+
apiKeys: ['key-1', 'key-2', 'key-3'],
|
|
788
|
+
tokowakaEnabled: true,
|
|
789
|
+
enhancements: true,
|
|
790
|
+
patches: {},
|
|
791
|
+
};
|
|
792
|
+
s3Client.send.onFirstCall().resolves({
|
|
793
|
+
Body: {
|
|
794
|
+
transformToString: sinon.stub().resolves(JSON.stringify(existingWithMultipleKeys)),
|
|
795
|
+
},
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
const siteId = 'site-789';
|
|
799
|
+
const url = 'https://example.com';
|
|
800
|
+
|
|
801
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
802
|
+
|
|
803
|
+
expect(result.apiKeys).to.deep.equal(['key-1', 'key-2', 'key-3']);
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
it('should throw error if URL is missing', async () => {
|
|
807
|
+
try {
|
|
808
|
+
await client.updateMetaconfig('', 'site-123');
|
|
809
|
+
expect.fail('Should have thrown error');
|
|
810
|
+
} catch (error) {
|
|
811
|
+
expect(error.message).to.include('URL is required');
|
|
812
|
+
expect(error.status).to.equal(400);
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
it('should throw error if siteId is missing', async () => {
|
|
817
|
+
try {
|
|
818
|
+
await client.updateMetaconfig('https://example.com', '');
|
|
819
|
+
expect.fail('Should have thrown error');
|
|
820
|
+
} catch (error) {
|
|
821
|
+
expect(error.message).to.include('Site ID is required');
|
|
822
|
+
expect(error.status).to.equal(400);
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
it('should throw error if metaconfig does not exist', async () => {
|
|
827
|
+
const noSuchKeyError = new Error('NoSuchKey');
|
|
828
|
+
noSuchKeyError.name = 'NoSuchKey';
|
|
829
|
+
s3Client.send.onFirstCall().rejects(noSuchKeyError);
|
|
830
|
+
|
|
831
|
+
try {
|
|
832
|
+
await client.updateMetaconfig('https://example.com', 'site-123');
|
|
833
|
+
expect.fail('Should have thrown error');
|
|
834
|
+
} catch (error) {
|
|
835
|
+
expect(error.message).to.include('Metaconfig does not exist for this URL');
|
|
836
|
+
expect(error.status).to.equal(400);
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
it('should handle S3 upload failure', async () => {
|
|
841
|
+
const s3Error = new Error('S3 network error');
|
|
842
|
+
s3Client.send.onSecondCall().rejects(s3Error);
|
|
843
|
+
|
|
844
|
+
try {
|
|
845
|
+
await client.updateMetaconfig('https://example.com', 'site-123');
|
|
846
|
+
expect.fail('Should have thrown error');
|
|
847
|
+
} catch (error) {
|
|
848
|
+
expect(error.message).to.include('S3 upload failed');
|
|
849
|
+
expect(error.status).to.equal(500);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
it('should strip www. from domain in metaconfig path', async () => {
|
|
854
|
+
const siteId = 'site-789';
|
|
855
|
+
const url = 'https://www.example.com/some/path';
|
|
856
|
+
|
|
857
|
+
await client.updateMetaconfig(url, siteId);
|
|
858
|
+
|
|
859
|
+
const uploadCommand = s3Client.send.secondCall.args[0];
|
|
860
|
+
expect(uploadCommand.input.Key).to.equal('opportunities/example.com/config');
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
it('should handle metaconfig with null patches', async () => {
|
|
864
|
+
const configWithNullPatches = {
|
|
865
|
+
siteId: 'site-456',
|
|
866
|
+
apiKeys: ['existing-api-key-123'],
|
|
867
|
+
tokowakaEnabled: true,
|
|
868
|
+
enhancements: true,
|
|
869
|
+
patches: null,
|
|
870
|
+
};
|
|
871
|
+
s3Client.send.onFirstCall().resolves({
|
|
872
|
+
Body: {
|
|
873
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithNullPatches)),
|
|
874
|
+
},
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
const siteId = 'site-789';
|
|
878
|
+
const url = 'https://example.com';
|
|
879
|
+
|
|
880
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
881
|
+
|
|
882
|
+
expect(result.patches).to.deep.equal({});
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
it('should handle single patch in options.patches', async () => {
|
|
886
|
+
const siteId = 'site-789';
|
|
887
|
+
const url = 'https://example.com';
|
|
888
|
+
const singlePatch = { 'only-patch': 'only-value' };
|
|
889
|
+
|
|
890
|
+
const result = await client.updateMetaconfig(url, siteId, { patches: singlePatch });
|
|
891
|
+
|
|
892
|
+
expect(result.patches).to.deep.equal(singlePatch);
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
it('should preserve existing patches when options.patches is null', async () => {
|
|
896
|
+
const siteId = 'site-789';
|
|
897
|
+
const url = 'https://example.com';
|
|
898
|
+
|
|
899
|
+
const result = await client.updateMetaconfig(url, siteId, { patches: null });
|
|
900
|
+
|
|
901
|
+
expect(result.patches).to.deep.equal({ 'existing-patch': 'value' });
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it('should preserve tokowakaEnabled=true from existingMetaconfig when options not provided', async () => {
|
|
905
|
+
const configWithTokowakaEnabled = {
|
|
906
|
+
siteId: 'site-456',
|
|
907
|
+
apiKeys: ['existing-api-key-123'],
|
|
908
|
+
tokowakaEnabled: true,
|
|
909
|
+
enhancements: false,
|
|
910
|
+
patches: {},
|
|
911
|
+
};
|
|
912
|
+
s3Client.send.onFirstCall().resolves({
|
|
913
|
+
Body: {
|
|
914
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithTokowakaEnabled)),
|
|
915
|
+
},
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
const siteId = 'site-789';
|
|
919
|
+
const url = 'https://example.com';
|
|
920
|
+
|
|
921
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
922
|
+
|
|
923
|
+
expect(result).to.have.property('tokowakaEnabled', true);
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
it('should preserve enhancements=true from existingMetaconfig when options not provided', async () => {
|
|
927
|
+
const configWithEnhancements = {
|
|
928
|
+
siteId: 'site-456',
|
|
929
|
+
apiKeys: ['existing-api-key-123'],
|
|
930
|
+
tokowakaEnabled: false,
|
|
931
|
+
enhancements: true,
|
|
932
|
+
patches: {},
|
|
933
|
+
};
|
|
934
|
+
s3Client.send.onFirstCall().resolves({
|
|
935
|
+
Body: {
|
|
936
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithEnhancements)),
|
|
937
|
+
},
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
const siteId = 'site-789';
|
|
941
|
+
const url = 'https://example.com';
|
|
942
|
+
|
|
943
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
944
|
+
|
|
945
|
+
expect(result).to.have.property('enhancements', true);
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
it('should default tokowakaEnabled to true when not in existingMetaconfig or options', async () => {
|
|
949
|
+
const configWithoutTokowakaEnabled = {
|
|
950
|
+
siteId: 'site-456',
|
|
951
|
+
apiKeys: ['existing-api-key-123'],
|
|
952
|
+
enhancements: false,
|
|
953
|
+
patches: {},
|
|
954
|
+
};
|
|
955
|
+
s3Client.send.onFirstCall().resolves({
|
|
956
|
+
Body: {
|
|
957
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithoutTokowakaEnabled)),
|
|
958
|
+
},
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
const siteId = 'site-789';
|
|
962
|
+
const url = 'https://example.com';
|
|
963
|
+
|
|
964
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
965
|
+
|
|
966
|
+
expect(result).to.have.property('tokowakaEnabled', true);
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
it('should default enhancements to true when not in existingMetaconfig or options', async () => {
|
|
970
|
+
const configWithoutEnhancements = {
|
|
971
|
+
siteId: 'site-456',
|
|
972
|
+
apiKeys: ['existing-api-key-123'],
|
|
973
|
+
tokowakaEnabled: false,
|
|
974
|
+
patches: {},
|
|
975
|
+
};
|
|
976
|
+
s3Client.send.onFirstCall().resolves({
|
|
977
|
+
Body: {
|
|
978
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithoutEnhancements)),
|
|
979
|
+
},
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
const siteId = 'site-789';
|
|
983
|
+
const url = 'https://example.com';
|
|
984
|
+
|
|
985
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
986
|
+
|
|
987
|
+
expect(result).to.have.property('enhancements', true);
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
it('should preserve forceFail=true from existingMetaconfig when options not provided', async () => {
|
|
991
|
+
const configWithForceFail = {
|
|
992
|
+
siteId: 'site-456',
|
|
993
|
+
apiKeys: ['existing-api-key-123'],
|
|
994
|
+
tokowakaEnabled: true,
|
|
995
|
+
enhancements: true,
|
|
996
|
+
patches: {},
|
|
997
|
+
forceFail: true,
|
|
998
|
+
};
|
|
999
|
+
s3Client.send.onFirstCall().resolves({
|
|
1000
|
+
Body: {
|
|
1001
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithForceFail)),
|
|
1002
|
+
},
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
const siteId = 'site-789';
|
|
1006
|
+
const url = 'https://example.com';
|
|
1007
|
+
|
|
1008
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
1009
|
+
|
|
1010
|
+
expect(result).to.have.property('forceFail', true);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it('should override existingMetaconfig forceFail when explicitly set to false in options', async () => {
|
|
1014
|
+
const configWithForceFail = {
|
|
1015
|
+
siteId: 'site-456',
|
|
1016
|
+
apiKeys: ['existing-api-key-123'],
|
|
1017
|
+
tokowakaEnabled: true,
|
|
1018
|
+
enhancements: true,
|
|
1019
|
+
patches: {},
|
|
1020
|
+
forceFail: true,
|
|
1021
|
+
};
|
|
1022
|
+
s3Client.send.onFirstCall().resolves({
|
|
1023
|
+
Body: {
|
|
1024
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithForceFail)),
|
|
1025
|
+
},
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
const siteId = 'site-789';
|
|
1029
|
+
const url = 'https://example.com';
|
|
1030
|
+
|
|
1031
|
+
const result = await client.updateMetaconfig(url, siteId, { forceFail: false });
|
|
1032
|
+
|
|
1033
|
+
expect(result).to.have.property('forceFail', false);
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
it('should override existingMetaconfig forceFail when explicitly set to true in options', async () => {
|
|
1037
|
+
const configWithoutForceFail = {
|
|
1038
|
+
siteId: 'site-456',
|
|
1039
|
+
apiKeys: ['existing-api-key-123'],
|
|
1040
|
+
tokowakaEnabled: true,
|
|
1041
|
+
enhancements: true,
|
|
1042
|
+
patches: {},
|
|
1043
|
+
forceFail: false,
|
|
1044
|
+
};
|
|
1045
|
+
s3Client.send.onFirstCall().resolves({
|
|
1046
|
+
Body: {
|
|
1047
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithoutForceFail)),
|
|
1048
|
+
},
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
const siteId = 'site-789';
|
|
1052
|
+
const url = 'https://example.com';
|
|
1053
|
+
|
|
1054
|
+
const result = await client.updateMetaconfig(url, siteId, { forceFail: true });
|
|
1055
|
+
|
|
1056
|
+
expect(result).to.have.property('forceFail', true);
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
it('should preserve forceFail=false from existingMetaconfig when options not provided', async () => {
|
|
1060
|
+
const configWithForceFail = {
|
|
1061
|
+
siteId: 'site-456',
|
|
1062
|
+
apiKeys: ['existing-api-key-123'],
|
|
1063
|
+
tokowakaEnabled: true,
|
|
1064
|
+
enhancements: true,
|
|
1065
|
+
patches: {},
|
|
1066
|
+
forceFail: false,
|
|
1067
|
+
};
|
|
1068
|
+
s3Client.send.onFirstCall().resolves({
|
|
1069
|
+
Body: {
|
|
1070
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithForceFail)),
|
|
1071
|
+
},
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
const siteId = 'site-789';
|
|
1075
|
+
const url = 'https://example.com';
|
|
1076
|
+
|
|
1077
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
1078
|
+
|
|
1079
|
+
expect(result).to.have.property('forceFail', false);
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
it('should override existingMetaconfig tokowakaEnabled=false when explicitly set to true', async () => {
|
|
1083
|
+
const siteId = 'site-789';
|
|
1084
|
+
const url = 'https://example.com';
|
|
1085
|
+
// existingMetaconfig has tokowakaEnabled: false
|
|
1086
|
+
|
|
1087
|
+
const result = await client.updateMetaconfig(url, siteId, { tokowakaEnabled: true });
|
|
1088
|
+
|
|
1089
|
+
expect(result).to.have.property('tokowakaEnabled', true);
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
it('should override existingMetaconfig enhancements=false when explicitly set to true', async () => {
|
|
1093
|
+
const siteId = 'site-789';
|
|
1094
|
+
const url = 'https://example.com';
|
|
1095
|
+
// existingMetaconfig has enhancements: false
|
|
1096
|
+
|
|
1097
|
+
const result = await client.updateMetaconfig(url, siteId, { enhancements: true });
|
|
1098
|
+
|
|
1099
|
+
expect(result).to.have.property('enhancements', true);
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1102
|
+
it('should handle case where options.forceFail and existingMetaconfig.forceFail are both true', async () => {
|
|
1103
|
+
const configWithForceFail = {
|
|
1104
|
+
siteId: 'site-456',
|
|
1105
|
+
apiKeys: ['existing-api-key-123'],
|
|
1106
|
+
tokowakaEnabled: true,
|
|
1107
|
+
enhancements: true,
|
|
1108
|
+
patches: {},
|
|
1109
|
+
forceFail: true,
|
|
1110
|
+
};
|
|
1111
|
+
s3Client.send.onFirstCall().resolves({
|
|
1112
|
+
Body: {
|
|
1113
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithForceFail)),
|
|
1114
|
+
},
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
const siteId = 'site-789';
|
|
1118
|
+
const url = 'https://example.com';
|
|
1119
|
+
|
|
1120
|
+
const result = await client.updateMetaconfig(url, siteId, { forceFail: true });
|
|
1121
|
+
|
|
1122
|
+
expect(result).to.have.property('forceFail', true);
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
it('should handle case where options.forceFail and existingMetaconfig.forceFail are both false', async () => {
|
|
1126
|
+
const configWithForceFail = {
|
|
1127
|
+
siteId: 'site-456',
|
|
1128
|
+
apiKeys: ['existing-api-key-123'],
|
|
1129
|
+
tokowakaEnabled: true,
|
|
1130
|
+
enhancements: true,
|
|
1131
|
+
patches: {},
|
|
1132
|
+
forceFail: false,
|
|
1133
|
+
};
|
|
1134
|
+
s3Client.send.onFirstCall().resolves({
|
|
1135
|
+
Body: {
|
|
1136
|
+
transformToString: sinon.stub().resolves(JSON.stringify(configWithForceFail)),
|
|
1137
|
+
},
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
const siteId = 'site-789';
|
|
1141
|
+
const url = 'https://example.com';
|
|
1142
|
+
|
|
1143
|
+
const result = await client.updateMetaconfig(url, siteId);
|
|
1144
|
+
|
|
1145
|
+
expect(result).to.have.property('forceFail', false);
|
|
1146
|
+
});
|
|
1147
|
+
});
|
|
1148
|
+
|
|
545
1149
|
describe('uploadConfig', () => {
|
|
546
1150
|
it('should upload config to S3', async () => {
|
|
547
1151
|
const config = {
|