@adobe/spacecat-shared-tokowaka-client 1.1.0 → 1.2.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.
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2024 Adobe. All rights reserved.
2
+ * Copyright 2025 Adobe. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at http://www.apache.org/licenses/LICENSE-2.0
@@ -20,25 +20,25 @@ describe('Patch Utils', () => {
20
20
  it('should merge individual patches with same key', () => {
21
21
  const existingPatches = [
22
22
  {
23
+ opportunityId: 'opp-1',
24
+ suggestionId: 'sugg-1',
23
25
  op: 'replace',
24
- opportunityId: 'opp-headings',
25
- suggestionId: 'sugg-123',
26
26
  value: 'old-value',
27
27
  },
28
28
  ];
29
29
 
30
30
  const newPatches = [
31
31
  {
32
+ opportunityId: 'opp-1',
33
+ suggestionId: 'sugg-1',
32
34
  op: 'replace',
33
- opportunityId: 'opp-headings',
34
- suggestionId: 'sugg-123',
35
35
  value: 'new-value',
36
36
  },
37
37
  ];
38
38
 
39
39
  const result = mergePatches(existingPatches, newPatches);
40
+
40
41
  expect(result.patches).to.have.lengthOf(1);
41
- expect(result.patches[0].suggestionId).to.equal('sugg-123');
42
42
  expect(result.patches[0].value).to.equal('new-value');
43
43
  expect(result.updateCount).to.equal(1);
44
44
  expect(result.addCount).to.equal(0);
@@ -47,41 +47,43 @@ describe('Patch Utils', () => {
47
47
  it('should keep individual patches with different keys', () => {
48
48
  const existingPatches = [
49
49
  {
50
- op: 'replace',
51
- opportunityId: 'opp-headings',
50
+ opportunityId: 'opp-1',
52
51
  suggestionId: 'sugg-1',
52
+ op: 'replace',
53
53
  value: 'value-1',
54
54
  },
55
55
  ];
56
56
 
57
57
  const newPatches = [
58
58
  {
59
- op: 'replace',
60
- opportunityId: 'opp-headings',
59
+ opportunityId: 'opp-1',
61
60
  suggestionId: 'sugg-2',
61
+ op: 'replace',
62
62
  value: 'value-2',
63
63
  },
64
64
  ];
65
65
 
66
66
  const result = mergePatches(existingPatches, newPatches);
67
+
67
68
  expect(result.patches).to.have.lengthOf(2);
68
69
  expect(result.updateCount).to.equal(0);
69
70
  expect(result.addCount).to.equal(1);
70
71
  });
71
72
 
72
73
  it('should handle empty existing patches', () => {
74
+ const existingPatches = [];
73
75
  const newPatches = [
74
76
  {
75
- op: 'appendChild',
76
- opportunityId: 'opp-123',
77
- suggestionId: 'sugg-new',
78
- value: 'new-value',
77
+ opportunityId: 'opp-1',
78
+ suggestionId: 'sugg-1',
79
+ op: 'replace',
80
+ value: 'value-1',
79
81
  },
80
82
  ];
81
83
 
82
- const result = mergePatches([], newPatches);
84
+ const result = mergePatches(existingPatches, newPatches);
85
+
83
86
  expect(result.patches).to.have.lengthOf(1);
84
- expect(result.patches[0]).to.deep.equal(newPatches[0]);
85
87
  expect(result.updateCount).to.equal(0);
86
88
  expect(result.addCount).to.equal(1);
87
89
  });
@@ -89,351 +91,318 @@ describe('Patch Utils', () => {
89
91
  it('should handle empty new patches', () => {
90
92
  const existingPatches = [
91
93
  {
92
- op: 'appendChild',
93
- opportunityId: 'opp-123',
94
- suggestionId: 'sugg-old',
95
- value: 'old-value',
94
+ opportunityId: 'opp-1',
95
+ suggestionId: 'sugg-1',
96
+ op: 'replace',
97
+ value: 'value-1',
96
98
  },
97
99
  ];
100
+ const newPatches = [];
101
+
102
+ const result = mergePatches(existingPatches, newPatches);
98
103
 
99
- const result = mergePatches(existingPatches, []);
100
104
  expect(result.patches).to.have.lengthOf(1);
101
- expect(result.patches[0]).to.deep.equal(existingPatches[0]);
102
105
  expect(result.updateCount).to.equal(0);
103
106
  expect(result.addCount).to.equal(0);
104
107
  });
105
108
 
106
109
  it('should handle patch without suggestionId (heading patch)', () => {
107
- const existingPatches = [];
110
+ const existingPatches = [
111
+ {
112
+ opportunityId: 'opp-faq',
113
+ op: 'appendChild',
114
+ value: 'Old Heading',
115
+ },
116
+ ];
117
+
108
118
  const newPatches = [
109
119
  {
120
+ opportunityId: 'opp-faq',
110
121
  op: 'appendChild',
111
- opportunityId: 'opp-123',
112
- value: { type: 'element', tagName: 'h2' },
113
- // No suggestionId - this is a heading patch
122
+ value: 'New Heading',
114
123
  },
115
124
  ];
116
125
 
117
126
  const result = mergePatches(existingPatches, newPatches);
118
127
 
119
128
  expect(result.patches).to.have.lengthOf(1);
120
- expect(result.addCount).to.equal(1);
129
+ expect(result.patches[0].value).to.equal('New Heading');
130
+ expect(result.updateCount).to.equal(1);
131
+ expect(result.addCount).to.equal(0);
121
132
  });
122
133
 
123
134
  it('should merge heading patches with same opportunityId', () => {
124
135
  const existingPatches = [
125
136
  {
137
+ opportunityId: 'opp-faq',
138
+ op: 'appendChild',
139
+ value: 'Heading',
140
+ },
141
+ {
142
+ opportunityId: 'opp-faq',
143
+ suggestionId: 'sugg-1',
126
144
  op: 'appendChild',
127
- opportunityId: 'opp-123',
128
- value: { type: 'element', tagName: 'h2', children: [{ type: 'text', value: 'Old' }] },
129
- // No suggestionId
145
+ value: 'FAQ 1',
130
146
  },
131
147
  ];
148
+
132
149
  const newPatches = [
133
150
  {
151
+ opportunityId: 'opp-faq',
152
+ op: 'appendChild',
153
+ value: 'Updated Heading',
154
+ },
155
+ {
156
+ opportunityId: 'opp-faq',
157
+ suggestionId: 'sugg-2',
134
158
  op: 'appendChild',
135
- opportunityId: 'opp-123',
136
- value: { type: 'element', tagName: 'h2', children: [{ type: 'text', value: 'New' }] },
137
- // No suggestionId
159
+ value: 'FAQ 2',
138
160
  },
139
161
  ];
140
162
 
141
163
  const result = mergePatches(existingPatches, newPatches);
142
164
 
143
- expect(result.patches).to.have.lengthOf(1);
165
+ expect(result.patches).to.have.lengthOf(3);
166
+ expect(result.patches[0].value).to.equal('Updated Heading');
144
167
  expect(result.updateCount).to.equal(1);
145
- expect(result.patches[0].value.children[0].value).to.equal('New');
168
+ expect(result.addCount).to.equal(1);
146
169
  });
147
170
  });
148
171
 
149
172
  describe('removePatchesBySuggestionIds', () => {
150
173
  it('should remove patches with matching suggestion IDs', () => {
151
174
  const config = {
152
- siteId: 'site-123',
153
- baseURL: 'https://example.com',
175
+ url: 'https://example.com/page1',
154
176
  version: '1.0',
155
- tokowakaOptimizations: {
156
- '/page1': {
157
- prerender: true,
158
- patches: [
159
- {
160
- opportunityId: 'opp-1',
161
- suggestionId: 'sugg-1',
162
- op: 'replace',
163
- value: 'value-1',
164
- },
165
- {
166
- opportunityId: 'opp-1',
167
- suggestionId: 'sugg-2',
168
- op: 'replace',
169
- value: 'value-2',
170
- },
171
- ],
177
+ forceFail: false,
178
+ prerender: true,
179
+ patches: [
180
+ {
181
+ opportunityId: 'opp-1',
182
+ suggestionId: 'sugg-1',
183
+ op: 'replace',
184
+ value: 'value-1',
172
185
  },
173
- '/page2': {
174
- prerender: true,
175
- patches: [
176
- {
177
- opportunityId: 'opp-1',
178
- suggestionId: 'sugg-3',
179
- op: 'replace',
180
- value: 'value-3',
181
- },
182
- ],
186
+ {
187
+ opportunityId: 'opp-1',
188
+ suggestionId: 'sugg-2',
189
+ op: 'replace',
190
+ value: 'value-2',
183
191
  },
184
- },
192
+ ],
185
193
  };
186
194
 
187
- const result = removePatchesBySuggestionIds(config, ['sugg-1', 'sugg-3']);
195
+ const result = removePatchesBySuggestionIds(config, ['sugg-1']);
188
196
 
189
- expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(1);
190
- expect(result.tokowakaOptimizations['/page1'].patches[0].suggestionId).to.equal('sugg-2');
191
- expect(result.tokowakaOptimizations['/page2']).to.be.undefined; // URL removed because no patches left
192
- expect(result.removedCount).to.equal(2);
197
+ expect(result.patches).to.have.lengthOf(1);
198
+ expect(result.patches[0].suggestionId).to.equal('sugg-2');
199
+ expect(result.removedCount).to.equal(1);
193
200
  });
194
201
 
195
202
  it('should remove URL paths with no remaining patches', () => {
196
203
  const config = {
197
- siteId: 'site-123',
198
- baseURL: 'https://example.com',
204
+ url: 'https://example.com/page1',
199
205
  version: '1.0',
200
- tokowakaOptimizations: {
201
- '/page1': {
202
- prerender: true,
203
- patches: [
204
- {
205
- opportunityId: 'opp-1',
206
- suggestionId: 'sugg-1',
207
- op: 'replace',
208
- value: 'value-1',
209
- },
210
- ],
206
+ forceFail: false,
207
+ prerender: true,
208
+ patches: [
209
+ {
210
+ opportunityId: 'opp-1',
211
+ suggestionId: 'sugg-1',
212
+ op: 'replace',
213
+ value: 'value-1',
211
214
  },
212
- },
215
+ ],
213
216
  };
214
217
 
215
218
  const result = removePatchesBySuggestionIds(config, ['sugg-1']);
216
219
 
217
- expect(result.tokowakaOptimizations).to.deep.equal({});
220
+ expect(result.patches).to.have.lengthOf(0);
218
221
  expect(result.removedCount).to.equal(1);
219
222
  });
220
223
 
221
224
  it('should handle empty suggestion IDs array', () => {
222
225
  const config = {
223
- siteId: 'site-123',
224
- baseURL: 'https://example.com',
226
+ url: 'https://example.com/page1',
225
227
  version: '1.0',
226
- tokowakaOptimizations: {
227
- '/page1': {
228
- prerender: true,
229
- patches: [
230
- {
231
- opportunityId: 'opp-1',
232
- suggestionId: 'sugg-1',
233
- op: 'replace',
234
- value: 'value-1',
235
- },
236
- ],
228
+ forceFail: false,
229
+ prerender: true,
230
+ patches: [
231
+ {
232
+ opportunityId: 'opp-1',
233
+ suggestionId: 'sugg-1',
234
+ op: 'replace',
235
+ value: 'value-1',
237
236
  },
238
- },
237
+ ],
239
238
  };
240
239
 
241
240
  const result = removePatchesBySuggestionIds(config, []);
242
241
 
243
- expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(1);
242
+ expect(result.patches).to.have.lengthOf(1);
244
243
  expect(result.removedCount).to.equal(0);
245
244
  });
246
245
 
247
246
  it('should handle non-matching suggestion IDs', () => {
248
247
  const config = {
249
- siteId: 'site-123',
250
- baseURL: 'https://example.com',
248
+ url: 'https://example.com/page1',
251
249
  version: '1.0',
252
- tokowakaOptimizations: {
253
- '/page1': {
254
- prerender: true,
255
- patches: [
256
- {
257
- opportunityId: 'opp-1',
258
- suggestionId: 'sugg-1',
259
- op: 'replace',
260
- value: 'value-1',
261
- },
262
- ],
250
+ forceFail: false,
251
+ prerender: true,
252
+ patches: [
253
+ {
254
+ opportunityId: 'opp-1',
255
+ suggestionId: 'sugg-1',
256
+ op: 'replace',
257
+ value: 'value-1',
263
258
  },
264
- },
259
+ ],
265
260
  };
266
261
 
267
262
  const result = removePatchesBySuggestionIds(config, ['sugg-999']);
268
263
 
269
- expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(1);
264
+ expect(result.patches).to.have.lengthOf(1);
270
265
  expect(result.removedCount).to.equal(0);
271
266
  });
272
267
 
273
268
  it('should handle null/undefined config gracefully', () => {
274
269
  const result1 = removePatchesBySuggestionIds(null, ['sugg-1']);
275
- expect(result1).to.be.null;
276
-
277
270
  const result2 = removePatchesBySuggestionIds(undefined, ['sugg-1']);
271
+
272
+ expect(result1).to.be.null;
278
273
  expect(result2).to.be.undefined;
279
274
  });
280
275
 
281
276
  it('should preserve patches without suggestionId (heading patches)', () => {
282
277
  const config = {
283
- siteId: 'site-123',
284
- baseURL: 'https://example.com',
278
+ url: 'https://example.com/page1',
285
279
  version: '1.0',
286
- tokowakaOptimizations: {
287
- '/page1': {
288
- prerender: true,
289
- patches: [
290
- {
291
- opportunityId: 'opp-1',
292
- // No suggestionId - heading patch
293
- op: 'replace',
294
- value: 'heading-value',
295
- },
296
- {
297
- opportunityId: 'opp-1',
298
- suggestionId: 'sugg-1',
299
- op: 'replace',
300
- value: 'value-1',
301
- },
302
- ],
280
+ forceFail: false,
281
+ prerender: true,
282
+ patches: [
283
+ {
284
+ opportunityId: 'opp-faq',
285
+ op: 'appendChild',
286
+ value: 'FAQs',
303
287
  },
304
- },
288
+ {
289
+ opportunityId: 'opp-faq',
290
+ suggestionId: 'sugg-1',
291
+ op: 'appendChild',
292
+ value: 'FAQ item 1',
293
+ },
294
+ ],
305
295
  };
306
296
 
307
297
  const result = removePatchesBySuggestionIds(config, ['sugg-1']);
308
298
 
309
- expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(1);
310
- expect(result.tokowakaOptimizations['/page1'].patches[0]).to.not.have.property('suggestionId');
299
+ expect(result.patches).to.have.lengthOf(1);
300
+ expect(result.patches[0].value).to.equal('FAQs');
311
301
  expect(result.removedCount).to.equal(1);
312
302
  });
313
303
 
314
304
  it('should remove patches by additional patch keys', () => {
315
305
  const config = {
316
- siteId: 'site-123',
317
- baseURL: 'https://example.com',
306
+ url: 'https://example.com/page1',
318
307
  version: '1.0',
319
- tokowakaOptimizations: {
320
- '/page1': {
321
- prerender: true,
322
- patches: [
323
- {
324
- opportunityId: 'opp-faq',
325
- // No suggestionId - FAQ heading patch
326
- op: 'appendChild',
327
- value: 'FAQs',
328
- },
329
- {
330
- opportunityId: 'opp-faq',
331
- suggestionId: 'sugg-1',
332
- op: 'appendChild',
333
- value: 'FAQ item 1',
334
- },
335
- {
336
- opportunityId: 'opp-faq',
337
- suggestionId: 'sugg-2',
338
- op: 'appendChild',
339
- value: 'FAQ item 2',
340
- },
341
- ],
308
+ forceFail: false,
309
+ prerender: true,
310
+ patches: [
311
+ {
312
+ opportunityId: 'opp-faq',
313
+ op: 'appendChild',
314
+ value: 'FAQs',
342
315
  },
343
- },
316
+ {
317
+ opportunityId: 'opp-faq',
318
+ suggestionId: 'sugg-1',
319
+ op: 'appendChild',
320
+ value: 'FAQ item 1',
321
+ },
322
+ {
323
+ opportunityId: 'opp-faq',
324
+ suggestionId: 'sugg-2',
325
+ op: 'appendChild',
326
+ value: 'FAQ item 2',
327
+ },
328
+ ],
344
329
  };
345
330
 
346
- // Remove all FAQ suggestions and the heading patch (identified by opportunityId only)
347
- const result = removePatchesBySuggestionIds(
348
- config,
349
- ['sugg-1', 'sugg-2'],
350
- ['/page1:opp-faq'], // Additional patch key for FAQ heading
351
- );
331
+ // Remove all FAQs by passing heading patch key and suggestion IDs
332
+ const result = removePatchesBySuggestionIds(config, ['sugg-1', 'sugg-2'], ['opp-faq']);
352
333
 
353
- expect(result.tokowakaOptimizations).to.deep.equal({});
334
+ expect(result.patches).to.have.lengthOf(0);
354
335
  expect(result.removedCount).to.equal(3);
355
336
  });
356
337
 
357
338
  it('should remove patches by additional patch keys while keeping other suggestions', () => {
358
339
  const config = {
359
- siteId: 'site-123',
360
- baseURL: 'https://example.com',
340
+ url: 'https://example.com/page1',
361
341
  version: '1.0',
362
- tokowakaOptimizations: {
363
- '/page1': {
364
- prerender: true,
365
- patches: [
366
- {
367
- opportunityId: 'opp-faq',
368
- // No suggestionId - FAQ heading patch
369
- op: 'appendChild',
370
- value: 'FAQs',
371
- },
372
- {
373
- opportunityId: 'opp-faq',
374
- suggestionId: 'sugg-1',
375
- op: 'appendChild',
376
- value: 'FAQ item 1',
377
- },
378
- {
379
- opportunityId: 'opp-faq',
380
- suggestionId: 'sugg-2',
381
- op: 'appendChild',
382
- value: 'FAQ item 2',
383
- },
384
- ],
342
+ forceFail: false,
343
+ prerender: true,
344
+ patches: [
345
+ {
346
+ opportunityId: 'opp-faq',
347
+ op: 'appendChild',
348
+ value: 'FAQs',
385
349
  },
386
- },
350
+ {
351
+ opportunityId: 'opp-faq',
352
+ suggestionId: 'sugg-1',
353
+ op: 'appendChild',
354
+ value: 'FAQ item 1',
355
+ },
356
+ {
357
+ opportunityId: 'opp-faq',
358
+ suggestionId: 'sugg-2',
359
+ op: 'appendChild',
360
+ value: 'FAQ item 2',
361
+ },
362
+ ],
387
363
  };
388
364
 
389
- // Remove only one FAQ suggestion, keep the heading
390
- const result = removePatchesBySuggestionIds(config, ['sugg-1'], []);
365
+ // Remove only sugg-1, heading patch should remain
366
+ const result = removePatchesBySuggestionIds(config, ['sugg-1']);
391
367
 
392
- expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(2);
368
+ expect(result.patches).to.have.lengthOf(2);
369
+ expect(result.patches[0].value).to.equal('FAQs');
370
+ expect(result.patches[1].suggestionId).to.equal('sugg-2');
393
371
  expect(result.removedCount).to.equal(1);
394
372
  });
395
373
 
396
374
  it('should handle both suggestionIds and additional patch keys together', () => {
397
375
  const config = {
398
- siteId: 'site-123',
399
- baseURL: 'https://example.com',
376
+ url: 'https://example.com/page1',
400
377
  version: '1.0',
401
- tokowakaOptimizations: {
402
- '/page1': {
403
- prerender: true,
404
- patches: [
405
- {
406
- opportunityId: 'opp-headings',
407
- suggestionId: 'sugg-h1',
408
- op: 'replace',
409
- value: 'Heading 1',
410
- },
411
- {
412
- opportunityId: 'opp-faq',
413
- // FAQ heading patch
414
- op: 'appendChild',
415
- value: 'FAQs',
416
- },
417
- {
418
- opportunityId: 'opp-faq',
419
- suggestionId: 'sugg-f1',
420
- op: 'appendChild',
421
- value: 'FAQ 1',
422
- },
423
- ],
378
+ forceFail: false,
379
+ prerender: true,
380
+ patches: [
381
+ {
382
+ opportunityId: 'opp-faq',
383
+ op: 'appendChild',
384
+ value: 'FAQs',
424
385
  },
425
- },
386
+ {
387
+ opportunityId: 'opp-faq',
388
+ suggestionId: 'sugg-1',
389
+ op: 'appendChild',
390
+ value: 'FAQ item 1',
391
+ },
392
+ {
393
+ opportunityId: 'opp-other',
394
+ suggestionId: 'sugg-2',
395
+ op: 'replace',
396
+ value: 'Other suggestion',
397
+ },
398
+ ],
426
399
  };
427
400
 
428
- // Remove FAQ suggestion and heading, keep heading patch
429
- const result = removePatchesBySuggestionIds(
430
- config,
431
- ['sugg-f1'],
432
- ['/page1:opp-faq'], // Remove FAQ heading
433
- );
401
+ // Remove sugg-1 and the heading patch
402
+ const result = removePatchesBySuggestionIds(config, ['sugg-1'], ['opp-faq']);
434
403
 
435
- expect(result.tokowakaOptimizations['/page1'].patches).to.have.lengthOf(1);
436
- expect(result.tokowakaOptimizations['/page1'].patches[0].suggestionId).to.equal('sugg-h1');
404
+ expect(result.patches).to.have.lengthOf(1);
405
+ expect(result.patches[0].suggestionId).to.equal('sugg-2');
437
406
  expect(result.removedCount).to.equal(2);
438
407
  });
439
408
  });