@adobe/spacecat-shared-tokowaka-client 1.2.4 → 1.3.1
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.d.ts +20 -1
- package/src/index.js +59 -5
- package/src/mappers/base-mapper.js +12 -0
- package/src/mappers/generic-mapper.js +117 -0
- package/src/mappers/headings-mapper.js +16 -1
- package/src/mappers/mapper-registry.js +6 -0
- package/src/mappers/prerender-mapper.js +78 -0
- package/src/mappers/toc-mapper.js +116 -0
- package/test/index.test.js +243 -0
- package/test/mappers/generic-mapper.test.js +671 -0
- package/test/mappers/headings-mapper.test.js +154 -3
- package/test/mappers/prerender-mapper.test.js +216 -0
- package/test/mappers/toc-mapper.test.js +616 -0
package/test/index.test.js
CHANGED
|
@@ -1040,6 +1040,59 @@ describe('TokowakaClient', () => {
|
|
|
1040
1040
|
// Both suggestions are in result but sugg-1 skipped deployment due to no patches
|
|
1041
1041
|
expect(result.succeededSuggestions).to.have.length(2);
|
|
1042
1042
|
expect(result.s3Paths).to.have.length(1); // Only one URL actually deployed
|
|
1043
|
+
expect(log.warn).to.have.been.calledWith('No config generated for URL: https://example.com/page1');
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
it('should skip URL when config has no patches after generation', async () => {
|
|
1047
|
+
// Stub generateConfig to return a config with no patches (defensive check)
|
|
1048
|
+
const originalGenerateConfig = client.generateConfig.bind(client);
|
|
1049
|
+
sinon.stub(client, 'generateConfig').callsFake((url, ...args) => {
|
|
1050
|
+
const config = originalGenerateConfig(url, ...args);
|
|
1051
|
+
if (config && url === 'https://example.com/page1') {
|
|
1052
|
+
// Return config but with empty patches array (simulating edge case)
|
|
1053
|
+
return { ...config, patches: [] };
|
|
1054
|
+
}
|
|
1055
|
+
return config;
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
mockSuggestions = [
|
|
1059
|
+
{
|
|
1060
|
+
getId: () => 'sugg-1',
|
|
1061
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
1062
|
+
getData: () => ({
|
|
1063
|
+
url: 'https://example.com/page1',
|
|
1064
|
+
recommendedAction: 'New Heading',
|
|
1065
|
+
checkType: 'heading-empty',
|
|
1066
|
+
transformRules: {
|
|
1067
|
+
action: 'replace',
|
|
1068
|
+
selector: 'h1',
|
|
1069
|
+
},
|
|
1070
|
+
}),
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
getId: () => 'sugg-2',
|
|
1074
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
1075
|
+
getData: () => ({
|
|
1076
|
+
url: 'https://example.com/page2',
|
|
1077
|
+
recommendedAction: 'New Subtitle',
|
|
1078
|
+
checkType: 'heading-empty',
|
|
1079
|
+
transformRules: {
|
|
1080
|
+
action: 'replace',
|
|
1081
|
+
selector: 'h2',
|
|
1082
|
+
},
|
|
1083
|
+
}),
|
|
1084
|
+
},
|
|
1085
|
+
];
|
|
1086
|
+
|
|
1087
|
+
const result = await client.deploySuggestions(
|
|
1088
|
+
mockSite,
|
|
1089
|
+
mockOpportunity,
|
|
1090
|
+
mockSuggestions,
|
|
1091
|
+
);
|
|
1092
|
+
|
|
1093
|
+
expect(result.succeededSuggestions).to.have.length(2);
|
|
1094
|
+
expect(result.s3Paths).to.have.length(1); // Only page2 deployed
|
|
1095
|
+
expect(log.warn).to.have.been.calledWith('No eligible suggestions to deploy for URL: https://example.com/page1');
|
|
1043
1096
|
});
|
|
1044
1097
|
|
|
1045
1098
|
it('should return early when no eligible suggestions', async () => {
|
|
@@ -1066,6 +1119,50 @@ describe('TokowakaClient', () => {
|
|
|
1066
1119
|
expect(s3Client.send).to.not.have.been.called;
|
|
1067
1120
|
});
|
|
1068
1121
|
|
|
1122
|
+
it('should deploy prerender-only suggestions with no patches', async () => {
|
|
1123
|
+
// Create prerender opportunity
|
|
1124
|
+
const prerenderOpportunity = {
|
|
1125
|
+
getId: () => 'opp-prerender-123',
|
|
1126
|
+
getType: () => 'prerender',
|
|
1127
|
+
};
|
|
1128
|
+
|
|
1129
|
+
// Create prerender suggestions with no transform rules (prerender-only)
|
|
1130
|
+
const prerenderSuggestions = [
|
|
1131
|
+
{
|
|
1132
|
+
getId: () => 'prerender-sugg-1',
|
|
1133
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
1134
|
+
getData: () => ({
|
|
1135
|
+
url: 'https://example.com/page1',
|
|
1136
|
+
// No transform rules - prerender only
|
|
1137
|
+
}),
|
|
1138
|
+
},
|
|
1139
|
+
];
|
|
1140
|
+
|
|
1141
|
+
const result = await client.deploySuggestions(
|
|
1142
|
+
mockSite,
|
|
1143
|
+
prerenderOpportunity,
|
|
1144
|
+
prerenderSuggestions,
|
|
1145
|
+
);
|
|
1146
|
+
|
|
1147
|
+
expect(result.succeededSuggestions).to.have.length(1);
|
|
1148
|
+
expect(result.failedSuggestions).to.have.length(0);
|
|
1149
|
+
expect(result.s3Paths).to.have.length(1);
|
|
1150
|
+
expect(result.cdnInvalidations).to.have.length(1);
|
|
1151
|
+
|
|
1152
|
+
// Verify uploaded config has no patches but prerender is enabled
|
|
1153
|
+
const uploadedConfig = JSON.parse(s3Client.send.firstCall.args[0].input.Body);
|
|
1154
|
+
expect(uploadedConfig.patches).to.have.length(0);
|
|
1155
|
+
expect(uploadedConfig.prerender).to.equal(true);
|
|
1156
|
+
expect(uploadedConfig.url).to.equal('https://example.com/page1');
|
|
1157
|
+
|
|
1158
|
+
// Verify CDN was invalidated
|
|
1159
|
+
expect(client.invalidateCdnCache).to.have.been.calledOnce;
|
|
1160
|
+
expect(client.invalidateCdnCache).to.have.been.calledWith(
|
|
1161
|
+
'https://example.com/page1',
|
|
1162
|
+
'cloudfront',
|
|
1163
|
+
);
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1069
1166
|
it('should throw error for unsupported opportunity type', async () => {
|
|
1070
1167
|
mockOpportunity.getType = () => 'unsupported-type';
|
|
1071
1168
|
|
|
@@ -1222,6 +1319,70 @@ describe('TokowakaClient', () => {
|
|
|
1222
1319
|
expect(uploadedConfig.patches[0].suggestionId).to.equal('sugg-3');
|
|
1223
1320
|
});
|
|
1224
1321
|
|
|
1322
|
+
it('should rollback prerender suggestions by disabling prerender flag', async () => {
|
|
1323
|
+
// Create prerender opportunity
|
|
1324
|
+
const prerenderOpportunity = {
|
|
1325
|
+
getId: () => 'opp-prerender-123',
|
|
1326
|
+
getType: () => 'prerender',
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
// Create prerender suggestions
|
|
1330
|
+
const prerenderSuggestions = [
|
|
1331
|
+
{
|
|
1332
|
+
getId: () => 'prerender-sugg-1',
|
|
1333
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
1334
|
+
getData: () => ({
|
|
1335
|
+
url: 'https://example.com/page1',
|
|
1336
|
+
}),
|
|
1337
|
+
},
|
|
1338
|
+
];
|
|
1339
|
+
|
|
1340
|
+
const existingConfig = {
|
|
1341
|
+
url: 'https://example.com/page1',
|
|
1342
|
+
version: '1.0',
|
|
1343
|
+
forceFail: false,
|
|
1344
|
+
prerender: true,
|
|
1345
|
+
patches: [
|
|
1346
|
+
{
|
|
1347
|
+
op: 'replace',
|
|
1348
|
+
selector: 'h1',
|
|
1349
|
+
value: 'Heading 1',
|
|
1350
|
+
opportunityId: 'opp-other-123',
|
|
1351
|
+
suggestionId: 'other-sugg-1',
|
|
1352
|
+
prerenderRequired: false,
|
|
1353
|
+
lastUpdated: 1234567890,
|
|
1354
|
+
},
|
|
1355
|
+
],
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1358
|
+
sinon.stub(client, 'fetchConfig').resolves(existingConfig);
|
|
1359
|
+
|
|
1360
|
+
const result = await client.rollbackSuggestions(
|
|
1361
|
+
mockSite,
|
|
1362
|
+
prerenderOpportunity,
|
|
1363
|
+
prerenderSuggestions,
|
|
1364
|
+
);
|
|
1365
|
+
|
|
1366
|
+
expect(result.s3Paths).to.have.length(1);
|
|
1367
|
+
expect(result.s3Paths[0]).to.equal('opportunities/example.com/L3BhZ2Ux');
|
|
1368
|
+
expect(result.succeededSuggestions).to.have.length(1);
|
|
1369
|
+
expect(result.failedSuggestions).to.have.length(0);
|
|
1370
|
+
expect(result.removedPatchesCount).to.equal(1);
|
|
1371
|
+
|
|
1372
|
+
// Verify uploaded config has prerender disabled but patches intact
|
|
1373
|
+
const uploadedConfig = JSON.parse(s3Client.send.firstCall.args[0].input.Body);
|
|
1374
|
+
expect(uploadedConfig.prerender).to.equal(false);
|
|
1375
|
+
expect(uploadedConfig.patches).to.have.length(1);
|
|
1376
|
+
expect(uploadedConfig.patches[0].suggestionId).to.equal('other-sugg-1');
|
|
1377
|
+
|
|
1378
|
+
// Verify CDN was invalidated
|
|
1379
|
+
expect(client.invalidateCdnCache).to.have.been.calledOnce;
|
|
1380
|
+
expect(client.invalidateCdnCache).to.have.been.calledWith(
|
|
1381
|
+
'https://example.com/page1',
|
|
1382
|
+
'cloudfront',
|
|
1383
|
+
);
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1225
1386
|
it('should handle no existing config gracefully', async () => {
|
|
1226
1387
|
sinon.stub(client, 'fetchConfig').resolves(null);
|
|
1227
1388
|
|
|
@@ -1262,6 +1423,30 @@ describe('TokowakaClient', () => {
|
|
|
1262
1423
|
expect(s3Client.send).to.not.have.been.called;
|
|
1263
1424
|
});
|
|
1264
1425
|
|
|
1426
|
+
it('should handle missing patches property in config', async () => {
|
|
1427
|
+
const existingConfig = {
|
|
1428
|
+
url: 'https://example.com/page1',
|
|
1429
|
+
version: '1.0',
|
|
1430
|
+
forceFail: false,
|
|
1431
|
+
prerender: true,
|
|
1432
|
+
// patches property is missing
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1435
|
+
sinon.stub(client, 'fetchConfig').resolves(existingConfig);
|
|
1436
|
+
|
|
1437
|
+
const result = await client.rollbackSuggestions(
|
|
1438
|
+
mockSite,
|
|
1439
|
+
mockOpportunity,
|
|
1440
|
+
mockSuggestions,
|
|
1441
|
+
);
|
|
1442
|
+
|
|
1443
|
+
// Code marks eligible suggestions as succeeded even if patches property missing
|
|
1444
|
+
expect(result.succeededSuggestions).to.have.length(2);
|
|
1445
|
+
expect(result.failedSuggestions).to.have.length(0);
|
|
1446
|
+
expect(result.s3Paths).to.have.length(0);
|
|
1447
|
+
expect(s3Client.send).to.not.have.been.called;
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1265
1450
|
it('should handle suggestions not found in config', async () => {
|
|
1266
1451
|
const existingConfig = {
|
|
1267
1452
|
url: 'https://example.com/page1',
|
|
@@ -1589,6 +1774,64 @@ describe('TokowakaClient', () => {
|
|
|
1589
1774
|
expect(s3Client.send).to.have.been.calledOnce;
|
|
1590
1775
|
});
|
|
1591
1776
|
|
|
1777
|
+
it('should preview prerender-only suggestions with no patches', async () => {
|
|
1778
|
+
// Update fetchConfig to return existing config with deployed patches
|
|
1779
|
+
client.fetchConfig.resolves({
|
|
1780
|
+
url: 'https://example.com/page1',
|
|
1781
|
+
version: '1.0',
|
|
1782
|
+
forceFail: false,
|
|
1783
|
+
prerender: false,
|
|
1784
|
+
patches: [
|
|
1785
|
+
{
|
|
1786
|
+
op: 'replace',
|
|
1787
|
+
selector: 'h1',
|
|
1788
|
+
value: 'Existing Heading',
|
|
1789
|
+
opportunityId: 'opp-other-123',
|
|
1790
|
+
suggestionId: 'sugg-other',
|
|
1791
|
+
prerenderRequired: false,
|
|
1792
|
+
lastUpdated: 1234567890,
|
|
1793
|
+
},
|
|
1794
|
+
],
|
|
1795
|
+
});
|
|
1796
|
+
|
|
1797
|
+
// Create prerender opportunity
|
|
1798
|
+
const prerenderOpportunity = {
|
|
1799
|
+
getId: () => 'opp-prerender-123',
|
|
1800
|
+
getType: () => 'prerender',
|
|
1801
|
+
};
|
|
1802
|
+
|
|
1803
|
+
// Create prerender suggestions with no transform rules (prerender-only)
|
|
1804
|
+
const prerenderSuggestions = [
|
|
1805
|
+
{
|
|
1806
|
+
getId: () => 'prerender-sugg-1',
|
|
1807
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
1808
|
+
getData: () => ({
|
|
1809
|
+
url: 'https://example.com/page1',
|
|
1810
|
+
// No transform rules - prerender only
|
|
1811
|
+
}),
|
|
1812
|
+
},
|
|
1813
|
+
];
|
|
1814
|
+
|
|
1815
|
+
const result = await client.previewSuggestions(
|
|
1816
|
+
mockSite,
|
|
1817
|
+
prerenderOpportunity,
|
|
1818
|
+
prerenderSuggestions,
|
|
1819
|
+
{ warmupDelayMs: 0 },
|
|
1820
|
+
);
|
|
1821
|
+
|
|
1822
|
+
expect(result).to.have.property('s3Path');
|
|
1823
|
+
expect(result.config).to.not.be.null;
|
|
1824
|
+
expect(result.config.patches).to.have.length(1); // Merged with existing deployed patch
|
|
1825
|
+
expect(result.config.prerender).to.equal(true); // Prerender enabled
|
|
1826
|
+
expect(result.succeededSuggestions).to.have.length(1);
|
|
1827
|
+
expect(result.failedSuggestions).to.have.length(0);
|
|
1828
|
+
expect(result).to.have.property('html');
|
|
1829
|
+
|
|
1830
|
+
// Verify fetch was called for HTML fetching
|
|
1831
|
+
expect(fetchStub.callCount).to.equal(4);
|
|
1832
|
+
expect(s3Client.send).to.have.been.calledOnce;
|
|
1833
|
+
});
|
|
1834
|
+
|
|
1592
1835
|
it('should throw error if TOKOWAKA_EDGE_URL is not configured', async () => {
|
|
1593
1836
|
delete client.env.TOKOWAKA_EDGE_URL;
|
|
1594
1837
|
|