@adobe/spacecat-shared-tokowaka-client 1.1.1 → 1.2.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.
@@ -41,9 +41,9 @@ describe('BaseOpportunityMapper', () => {
41
41
  .to.throw('requiresPrerender() must be implemented by subclass');
42
42
  });
43
43
 
44
- it('suggestionToPatch should throw error', () => {
45
- expect(() => mapper.suggestionToPatch({}, 'opp-123'))
46
- .to.throw('suggestionToPatch() must be implemented by subclass');
44
+ it('suggestionsToPatches should throw error', () => {
45
+ expect(() => mapper.suggestionsToPatches('/path', [], 'opp-123', null))
46
+ .to.throw('suggestionsToPatches() must be implemented by subclass');
47
47
  });
48
48
 
49
49
  it('canDeploy should throw error if not implemented', () => {
@@ -60,14 +60,15 @@ describe('BaseOpportunityMapper', () => {
60
60
 
61
61
  requiresPrerender() { return true; }
62
62
 
63
- suggestionToPatch() { return {}; }
63
+ suggestionsToPatches() { return []; }
64
64
 
65
65
  canDeploy() { return { eligible: true }; }
66
66
  }
67
67
 
68
- const testMapper = new TestMapper();
68
+ const testMapper = new TestMapper(log);
69
69
  const suggestion = {
70
70
  getId: () => 'test-123',
71
+ getData: () => ({}),
71
72
  getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
72
73
  };
73
74
 
@@ -85,14 +86,15 @@ describe('BaseOpportunityMapper', () => {
85
86
 
86
87
  requiresPrerender() { return true; }
87
88
 
88
- suggestionToPatch() { return {}; }
89
+ suggestionsToPatches() { return []; }
89
90
 
90
91
  canDeploy() { return { eligible: true }; }
91
92
  }
92
93
 
93
- const testMapper = new TestMapper();
94
+ const testMapper = new TestMapper(log);
94
95
  const suggestion = {
95
96
  getId: () => 'test-no-date',
97
+ getData: () => ({}),
96
98
  getUpdatedAt: () => null, // Returns null
97
99
  };
98
100
 
@@ -106,5 +108,246 @@ describe('BaseOpportunityMapper', () => {
106
108
  expect(patch.lastUpdated).to.be.at.most(afterTime);
107
109
  expect(patch.prerenderRequired).to.be.true;
108
110
  });
111
+
112
+ it('should prioritize scrapedAt from getData()', () => {
113
+ class TestMapper extends BaseOpportunityMapper {
114
+ getOpportunityType() { return 'test'; }
115
+
116
+ requiresPrerender() { return true; }
117
+
118
+ suggestionsToPatches() { return []; }
119
+
120
+ canDeploy() { return { eligible: true }; }
121
+ }
122
+
123
+ const testMapper = new TestMapper(log);
124
+ const scrapedTime = '2025-01-20T15:30:00.000Z';
125
+ const suggestion = {
126
+ getId: () => 'test-scraped',
127
+ getData: () => ({ scrapedAt: scrapedTime }),
128
+ getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
129
+ };
130
+
131
+ const patch = testMapper.createBasePatch(suggestion, 'opp-scraped');
132
+
133
+ expect(patch.lastUpdated).to.equal(new Date(scrapedTime).getTime());
134
+ });
135
+
136
+ it('should use transformRules.scrapedAt when scrapedAt is not available', () => {
137
+ class TestMapper extends BaseOpportunityMapper {
138
+ getOpportunityType() { return 'test'; }
139
+
140
+ requiresPrerender() { return true; }
141
+
142
+ suggestionsToPatches() { return []; }
143
+
144
+ canDeploy() { return { eligible: true }; }
145
+ }
146
+
147
+ const testMapper = new TestMapper(log);
148
+ const transformScrapedTime = '2025-01-18T12:00:00.000Z';
149
+ const suggestion = {
150
+ getId: () => 'test-transform',
151
+ getData: () => ({ transformRules: { scrapedAt: transformScrapedTime } }),
152
+ getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
153
+ };
154
+
155
+ const patch = testMapper.createBasePatch(suggestion, 'opp-transform');
156
+
157
+ expect(patch.lastUpdated).to.equal(new Date(transformScrapedTime).getTime());
158
+ });
159
+
160
+ it('should handle invalid date strings by using Date.now()', () => {
161
+ class TestMapper extends BaseOpportunityMapper {
162
+ getOpportunityType() { return 'test'; }
163
+
164
+ requiresPrerender() { return true; }
165
+
166
+ suggestionsToPatches() { return []; }
167
+
168
+ canDeploy() { return { eligible: true }; }
169
+ }
170
+
171
+ const testMapper = new TestMapper(log);
172
+ const suggestion = {
173
+ getId: () => 'test-invalid',
174
+ getData: () => ({}),
175
+ getUpdatedAt: () => 'invalid-date-string',
176
+ };
177
+
178
+ const beforeTime = Date.now();
179
+ const patch = testMapper.createBasePatch(suggestion, 'opp-invalid');
180
+ const afterTime = Date.now();
181
+
182
+ // Should fallback to Date.now() for invalid dates
183
+ expect(patch.lastUpdated).to.be.at.least(beforeTime);
184
+ expect(patch.lastUpdated).to.be.at.most(afterTime);
185
+ });
186
+
187
+ it('should handle missing getData() gracefully', () => {
188
+ class TestMapper extends BaseOpportunityMapper {
189
+ getOpportunityType() { return 'test'; }
190
+
191
+ requiresPrerender() { return true; }
192
+
193
+ suggestionsToPatches() { return []; }
194
+
195
+ canDeploy() { return { eligible: true }; }
196
+ }
197
+
198
+ const testMapper = new TestMapper(log);
199
+ const suggestion = {
200
+ getId: () => 'test-no-data',
201
+ getData: () => null,
202
+ getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
203
+ };
204
+
205
+ const patch = testMapper.createBasePatch(suggestion, 'opp-no-data');
206
+
207
+ expect(patch.lastUpdated).to.equal(new Date('2025-01-15T10:00:00.000Z').getTime());
208
+ });
209
+ });
210
+
211
+ describe('rollbackPatches', () => {
212
+ let testMapper;
213
+
214
+ beforeEach(() => {
215
+ class TestMapper extends BaseOpportunityMapper {
216
+ getOpportunityType() { return 'test'; }
217
+
218
+ requiresPrerender() { return true; }
219
+
220
+ suggestionsToPatches() { return []; }
221
+
222
+ canDeploy() { return { eligible: true }; }
223
+ }
224
+
225
+ testMapper = new TestMapper(log);
226
+ });
227
+
228
+ it('should remove patches by suggestion IDs using default implementation', () => {
229
+ const config = {
230
+ url: 'https://example.com/page1',
231
+ version: '1.0',
232
+ forceFail: false,
233
+ prerender: true,
234
+ patches: [
235
+ {
236
+ opportunityId: 'opp-test',
237
+ suggestionId: 'sugg-1',
238
+ op: 'replace',
239
+ value: 'value-1',
240
+ },
241
+ {
242
+ opportunityId: 'opp-test',
243
+ suggestionId: 'sugg-2',
244
+ op: 'replace',
245
+ value: 'value-2',
246
+ },
247
+ ],
248
+ };
249
+
250
+ const result = testMapper.rollbackPatches(config, ['sugg-1'], 'opp-test');
251
+
252
+ expect(result.patches).to.have.lengthOf(1);
253
+ expect(result.patches[0].suggestionId).to.equal('sugg-2');
254
+ expect(result.removedCount).to.equal(1);
255
+ });
256
+
257
+ it('should handle null/undefined config gracefully', () => {
258
+ const result1 = testMapper.rollbackPatches(null, ['sugg-1'], 'opp-test');
259
+ expect(result1).to.be.null;
260
+
261
+ const result2 = testMapper.rollbackPatches(undefined, ['sugg-1'], 'opp-test');
262
+ expect(result2).to.be.undefined;
263
+ });
264
+
265
+ it('should remove patches for multiple suggestion IDs', () => {
266
+ const config = {
267
+ url: 'https://example.com/page1',
268
+ version: '1.0',
269
+ forceFail: false,
270
+ prerender: true,
271
+ patches: [
272
+ {
273
+ opportunityId: 'opp-test',
274
+ suggestionId: 'sugg-1',
275
+ op: 'replace',
276
+ value: 'value-1',
277
+ },
278
+ {
279
+ opportunityId: 'opp-test',
280
+ suggestionId: 'sugg-2',
281
+ op: 'replace',
282
+ value: 'value-2',
283
+ },
284
+ {
285
+ opportunityId: 'opp-test',
286
+ suggestionId: 'sugg-3',
287
+ op: 'replace',
288
+ value: 'value-3',
289
+ },
290
+ ],
291
+ };
292
+
293
+ const result = testMapper.rollbackPatches(config, ['sugg-1', 'sugg-3'], 'opp-test');
294
+
295
+ expect(result.patches).to.have.lengthOf(1);
296
+ expect(result.patches[0].suggestionId).to.equal('sugg-2');
297
+ expect(result.removedCount).to.equal(2);
298
+ });
299
+
300
+ it('should remove URL path when all patches are removed', () => {
301
+ const config = {
302
+ url: 'https://example.com/page1',
303
+ version: '1.0',
304
+ forceFail: false,
305
+ prerender: true,
306
+ patches: [
307
+ {
308
+ opportunityId: 'opp-test',
309
+ suggestionId: 'sugg-1',
310
+ op: 'replace',
311
+ value: 'value-1',
312
+ },
313
+ ],
314
+ };
315
+
316
+ const result = testMapper.rollbackPatches(config, ['sugg-1'], 'opp-test');
317
+
318
+ // All patches removed, patches array should be empty
319
+ expect(result.patches).to.have.lengthOf(0);
320
+ expect(result.removedCount).to.equal(1);
321
+ });
322
+
323
+ it('should preserve patches from other opportunities', () => {
324
+ const config = {
325
+ url: 'https://example.com/page1',
326
+ version: '1.0',
327
+ forceFail: false,
328
+ prerender: true,
329
+ patches: [
330
+ {
331
+ opportunityId: 'opp-test',
332
+ suggestionId: 'sugg-1',
333
+ op: 'replace',
334
+ value: 'test-value',
335
+ },
336
+ {
337
+ opportunityId: 'opp-other',
338
+ suggestionId: 'sugg-2',
339
+ op: 'replace',
340
+ value: 'other-value',
341
+ },
342
+ ],
343
+ };
344
+
345
+ // Default implementation removes by suggestionId regardless of opportunity
346
+ const result = testMapper.rollbackPatches(config, ['sugg-1'], 'opp-test');
347
+
348
+ expect(result.patches).to.have.lengthOf(1);
349
+ expect(result.patches[0].suggestionId).to.equal('sugg-2');
350
+ expect(result.removedCount).to.equal(1);
351
+ });
109
352
  });
110
353
  });
@@ -13,7 +13,6 @@
13
13
  /* eslint-env mocha */
14
14
 
15
15
  import { expect } from 'chai';
16
- import sinon from 'sinon';
17
16
  import ContentMapper from '../../src/mappers/content-summarization-mapper.js';
18
17
 
19
18
  describe('ContentMapper', () => {
@@ -142,7 +141,7 @@ describe('ContentMapper', () => {
142
141
  });
143
142
  });
144
143
 
145
- describe('suggestionToPatch', () => {
144
+ describe('suggestionsToPatches', () => {
146
145
  it('should create patch with HAST value from markdown', () => {
147
146
  const suggestion = {
148
147
  getId: () => 'sugg-content-123',
@@ -156,7 +155,9 @@ describe('ContentMapper', () => {
156
155
  }),
157
156
  };
158
157
 
159
- const patch = mapper.suggestionToPatch(suggestion, 'opp-content-123');
158
+ const patches = mapper.suggestionsToPatches('/path', [suggestion], 'opp-content-123');
159
+ expect(patches.length).to.equal(1);
160
+ const patch = patches[0];
160
161
 
161
162
  expect(patch).to.exist;
162
163
  expect(patch.op).to.equal('insertAfter');
@@ -187,7 +188,9 @@ describe('ContentMapper', () => {
187
188
  }),
188
189
  };
189
190
 
190
- const patch = mapper.suggestionToPatch(suggestion, 'opp-bold');
191
+ const patches = mapper.suggestionsToPatches('/path', [suggestion], 'opp-bold');
192
+ expect(patches.length).to.equal(1);
193
+ const patch = patches[0];
191
194
 
192
195
  expect(patch).to.exist;
193
196
  expect(patch.value.type).to.equal('root');
@@ -216,7 +219,9 @@ describe('ContentMapper', () => {
216
219
  }),
217
220
  };
218
221
 
219
- const patch = mapper.suggestionToPatch(suggestion, 'opp-heading');
222
+ const patches = mapper.suggestionsToPatches('/path', [suggestion], 'opp-heading');
223
+ expect(patches.length).to.equal(1);
224
+ const patch = patches[0];
220
225
 
221
226
  expect(patch).to.exist;
222
227
  expect(patch.value.children).to.be.an('array');
@@ -241,7 +246,9 @@ describe('ContentMapper', () => {
241
246
  }),
242
247
  };
243
248
 
244
- const patch = mapper.suggestionToPatch(suggestion, 'opp-list');
249
+ const patches = mapper.suggestionsToPatches('/path', [suggestion], 'opp-list');
250
+ expect(patches.length).to.equal(1);
251
+ const patch = patches[0];
245
252
 
246
253
  expect(patch).to.exist;
247
254
 
@@ -250,7 +257,7 @@ describe('ContentMapper', () => {
250
257
  expect(hasList).to.be.true;
251
258
  });
252
259
 
253
- it('should return null when summarizationText is missing', () => {
260
+ it('should return empty array when summarizationText is missing', () => {
254
261
  const suggestion = {
255
262
  getId: () => 'sugg-invalid',
256
263
  getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
@@ -262,12 +269,11 @@ describe('ContentMapper', () => {
262
269
  }),
263
270
  };
264
271
 
265
- const patch = mapper.suggestionToPatch(suggestion, 'opp-invalid');
266
-
267
- expect(patch).to.be.null;
272
+ const patches = mapper.suggestionsToPatches('/path', [suggestion], 'opp-invalid');
273
+ expect(patches.length).to.equal(0);
268
274
  });
269
275
 
270
- it('should return null when transformRules are incomplete', () => {
276
+ it('should return empty array when transformRules are incomplete', () => {
271
277
  const suggestion = {
272
278
  getId: () => 'sugg-invalid-2',
273
279
  getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
@@ -279,9 +285,8 @@ describe('ContentMapper', () => {
279
285
  }),
280
286
  };
281
287
 
282
- const patch = mapper.suggestionToPatch(suggestion, 'opp-invalid-2');
283
-
284
- expect(patch).to.be.null;
288
+ const patches = mapper.suggestionsToPatches('/path', [suggestion], 'opp-invalid-2');
289
+ expect(patches.length).to.equal(0);
285
290
  });
286
291
 
287
292
  it('should handle complex markdown with multiple elements', () => {
@@ -305,7 +310,9 @@ describe('ContentMapper', () => {
305
310
  }),
306
311
  };
307
312
 
308
- const patch = mapper.suggestionToPatch(suggestion, 'opp-complex');
313
+ const patches = mapper.suggestionsToPatches('/path', [suggestion], 'opp-complex');
314
+ expect(patches.length).to.equal(1);
315
+ const patch = patches[0];
309
316
 
310
317
  expect(patch).to.exist;
311
318
  expect(patch.value.children).to.be.an('array');
@@ -329,13 +336,11 @@ describe('ContentMapper', () => {
329
336
 
330
337
  const errorMapper = new ContentMapper(errorLog);
331
338
 
332
- // Stub the markdownToHast method to throw an error
333
- const stub = sinon.stub(errorMapper, 'markdownToHast').throws(new Error('Markdown parsing failed'));
334
-
339
+ // Pass an object instead of string to trigger natural error in markdown parser
335
340
  const suggestion = {
336
341
  getId: () => 'sugg-error',
337
342
  getData: () => ({
338
- summarizationText: 'Some content',
343
+ summarizationText: { invalid: 'object' }, // This will cause markdown parser to fail
339
344
  transformRules: {
340
345
  action: 'insertAfter',
341
346
  selector: '#selector',
@@ -343,13 +348,10 @@ describe('ContentMapper', () => {
343
348
  }),
344
349
  };
345
350
 
346
- const patch = errorMapper.suggestionToPatch(suggestion, 'opp-error');
351
+ const patches = errorMapper.suggestionsToPatches('/path', [suggestion], 'opp-error');
347
352
 
348
- expect(patch).to.be.null;
353
+ expect(patches.length).to.equal(0);
349
354
  expect(errorMessage).to.include('Failed to convert markdown to HAST');
350
- expect(errorMessage).to.include('Markdown parsing failed');
351
-
352
- stub.restore();
353
355
  });
354
356
  });
355
357
  });