@adobe/spacecat-shared-tokowaka-client 1.0.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.
- package/.mocha-multi.json +7 -0
- package/.nycrc.json +15 -0
- package/.releaserc.cjs +17 -0
- package/CHANGELOG.md +24 -0
- package/CODE_OF_CONDUCT.md +75 -0
- package/CONTRIBUTING.md +74 -0
- package/LICENSE.txt +203 -0
- package/README.md +101 -0
- package/package.json +53 -0
- package/src/cdn/base-cdn-client.js +50 -0
- package/src/cdn/cdn-client-registry.js +87 -0
- package/src/cdn/cloudfront-cdn-client.js +128 -0
- package/src/constants.js +17 -0
- package/src/index.d.ts +289 -0
- package/src/index.js +447 -0
- package/src/mappers/base-mapper.js +86 -0
- package/src/mappers/content-summarization-mapper.js +106 -0
- package/src/mappers/headings-mapper.js +118 -0
- package/src/mappers/mapper-registry.js +87 -0
- package/test/cdn/base-cdn-client.test.js +52 -0
- package/test/cdn/cdn-client-registry.test.js +179 -0
- package/test/cdn/cloudfront-cdn-client.test.js +330 -0
- package/test/index.test.js +1142 -0
- package/test/mappers/base-mapper.test.js +110 -0
- package/test/mappers/content-mapper.test.js +355 -0
- package/test/mappers/headings-mapper.test.js +428 -0
- package/test/mappers/mapper-registry.test.js +197 -0
- package/test/setup-env.js +18 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/* eslint-env mocha */
|
|
14
|
+
/* eslint-disable max-classes-per-file, class-methods-use-this */
|
|
15
|
+
|
|
16
|
+
import { expect } from 'chai';
|
|
17
|
+
import BaseOpportunityMapper from '../../src/mappers/base-mapper.js';
|
|
18
|
+
|
|
19
|
+
describe('BaseOpportunityMapper', () => {
|
|
20
|
+
let mapper;
|
|
21
|
+
let log;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
log = {
|
|
25
|
+
debug: () => {},
|
|
26
|
+
info: () => {},
|
|
27
|
+
warn: () => {},
|
|
28
|
+
error: () => {},
|
|
29
|
+
};
|
|
30
|
+
mapper = new BaseOpportunityMapper(log);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('abstract methods', () => {
|
|
34
|
+
it('getOpportunityType should throw error', () => {
|
|
35
|
+
expect(() => mapper.getOpportunityType())
|
|
36
|
+
.to.throw('getOpportunityType() must be implemented by subclass');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('requiresPrerender should throw error', () => {
|
|
40
|
+
expect(() => mapper.requiresPrerender())
|
|
41
|
+
.to.throw('requiresPrerender() must be implemented by subclass');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('suggestionToPatch should throw error', () => {
|
|
45
|
+
expect(() => mapper.suggestionToPatch({}, 'opp-123'))
|
|
46
|
+
.to.throw('suggestionToPatch() must be implemented by subclass');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('canDeploy should throw error if not implemented', () => {
|
|
50
|
+
expect(() => mapper.canDeploy({}))
|
|
51
|
+
.to.throw('canDeploy() must be implemented by subclass');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('createBasePatch', () => {
|
|
56
|
+
it('should use getUpdatedAt method when available', () => {
|
|
57
|
+
// Create a concrete subclass for testing
|
|
58
|
+
class TestMapper extends BaseOpportunityMapper {
|
|
59
|
+
getOpportunityType() { return 'test'; }
|
|
60
|
+
|
|
61
|
+
requiresPrerender() { return true; }
|
|
62
|
+
|
|
63
|
+
suggestionToPatch() { return {}; }
|
|
64
|
+
|
|
65
|
+
canDeploy() { return { eligible: true }; }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const testMapper = new TestMapper();
|
|
69
|
+
const suggestion = {
|
|
70
|
+
getId: () => 'test-123',
|
|
71
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const patch = testMapper.createBasePatch(suggestion, 'opp-456');
|
|
75
|
+
|
|
76
|
+
expect(patch.suggestionId).to.equal('test-123');
|
|
77
|
+
expect(patch.opportunityId).to.equal('opp-456');
|
|
78
|
+
expect(patch.lastUpdated).to.equal(new Date('2025-01-15T10:00:00.000Z').getTime());
|
|
79
|
+
expect(patch.prerenderRequired).to.be.true;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should use Date.now() when getUpdatedAt returns null', () => {
|
|
83
|
+
class TestMapper extends BaseOpportunityMapper {
|
|
84
|
+
getOpportunityType() { return 'test'; }
|
|
85
|
+
|
|
86
|
+
requiresPrerender() { return true; }
|
|
87
|
+
|
|
88
|
+
suggestionToPatch() { return {}; }
|
|
89
|
+
|
|
90
|
+
canDeploy() { return { eligible: true }; }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const testMapper = new TestMapper();
|
|
94
|
+
const suggestion = {
|
|
95
|
+
getId: () => 'test-no-date',
|
|
96
|
+
getUpdatedAt: () => null, // Returns null
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const beforeTime = Date.now();
|
|
100
|
+
const patch = testMapper.createBasePatch(suggestion, 'opp-fallback');
|
|
101
|
+
const afterTime = Date.now();
|
|
102
|
+
|
|
103
|
+
expect(patch.suggestionId).to.equal('test-no-date');
|
|
104
|
+
expect(patch.opportunityId).to.equal('opp-fallback');
|
|
105
|
+
expect(patch.lastUpdated).to.be.at.least(beforeTime);
|
|
106
|
+
expect(patch.lastUpdated).to.be.at.most(afterTime);
|
|
107
|
+
expect(patch.prerenderRequired).to.be.true;
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/* eslint-env mocha */
|
|
14
|
+
|
|
15
|
+
import { expect } from 'chai';
|
|
16
|
+
import sinon from 'sinon';
|
|
17
|
+
import ContentMapper from '../../src/mappers/content-summarization-mapper.js';
|
|
18
|
+
|
|
19
|
+
describe('ContentMapper', () => {
|
|
20
|
+
let mapper;
|
|
21
|
+
let log;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
log = {
|
|
25
|
+
debug: () => {},
|
|
26
|
+
info: () => {},
|
|
27
|
+
warn: () => {},
|
|
28
|
+
error: () => {},
|
|
29
|
+
};
|
|
30
|
+
mapper = new ContentMapper(log);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('getOpportunityType', () => {
|
|
34
|
+
it('should return summarization', () => {
|
|
35
|
+
expect(mapper.getOpportunityType()).to.equal('summarization');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('requiresPrerender', () => {
|
|
40
|
+
it('should return true', () => {
|
|
41
|
+
expect(mapper.requiresPrerender()).to.be.true;
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('canDeploy', () => {
|
|
46
|
+
it('should return eligible for valid content suggestion', () => {
|
|
47
|
+
const suggestion = {
|
|
48
|
+
getData: () => ({
|
|
49
|
+
summarizationText: 'Some content',
|
|
50
|
+
transformRules: {
|
|
51
|
+
action: 'insertAfter',
|
|
52
|
+
selector: '#text-85a9876220 > h1:nth-of-type(1)',
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const result = mapper.canDeploy(suggestion);
|
|
58
|
+
|
|
59
|
+
expect(result).to.deep.equal({ eligible: true });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should return ineligible when summarizationText is missing', () => {
|
|
63
|
+
const suggestion = {
|
|
64
|
+
getData: () => ({
|
|
65
|
+
transformRules: {
|
|
66
|
+
action: 'insertAfter',
|
|
67
|
+
selector: '#selector',
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const result = mapper.canDeploy(suggestion);
|
|
73
|
+
|
|
74
|
+
expect(result).to.deep.equal({
|
|
75
|
+
eligible: false,
|
|
76
|
+
reason: 'summarizationText is required',
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should return ineligible when transformRules are missing', () => {
|
|
81
|
+
const suggestion = {
|
|
82
|
+
getData: () => ({
|
|
83
|
+
summarizationText: 'Some content',
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const result = mapper.canDeploy(suggestion);
|
|
88
|
+
|
|
89
|
+
expect(result).to.deep.equal({
|
|
90
|
+
eligible: false,
|
|
91
|
+
reason: 'transformRules is required',
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should return ineligible when transformRules action is missing', () => {
|
|
96
|
+
const suggestion = {
|
|
97
|
+
getData: () => ({
|
|
98
|
+
summarizationText: 'Some content',
|
|
99
|
+
transformRules: {
|
|
100
|
+
selector: '#selector',
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const result = mapper.canDeploy(suggestion);
|
|
106
|
+
|
|
107
|
+
expect(result).to.deep.equal({
|
|
108
|
+
eligible: false,
|
|
109
|
+
reason: 'transformRules.action must be insertAfter, insertBefore, or appendChild',
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should return ineligible when transformRules selector is missing', () => {
|
|
114
|
+
const suggestion = {
|
|
115
|
+
getData: () => ({
|
|
116
|
+
summarizationText: 'Some content',
|
|
117
|
+
transformRules: {
|
|
118
|
+
action: 'insertAfter',
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const result = mapper.canDeploy(suggestion);
|
|
124
|
+
|
|
125
|
+
expect(result).to.deep.equal({
|
|
126
|
+
eligible: false,
|
|
127
|
+
reason: 'transformRules.selector is required',
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should return ineligible when data is null', () => {
|
|
132
|
+
const suggestion = {
|
|
133
|
+
getData: () => null,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const result = mapper.canDeploy(suggestion);
|
|
137
|
+
|
|
138
|
+
expect(result).to.deep.equal({
|
|
139
|
+
eligible: false,
|
|
140
|
+
reason: 'summarizationText is required',
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('suggestionToPatch', () => {
|
|
146
|
+
it('should create patch with HAST value from markdown', () => {
|
|
147
|
+
const suggestion = {
|
|
148
|
+
getId: () => 'sugg-content-123',
|
|
149
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
150
|
+
getData: () => ({
|
|
151
|
+
summarizationText: 'Enter your name exactly as it appears on your **government ID**.',
|
|
152
|
+
transformRules: {
|
|
153
|
+
action: 'insertAfter',
|
|
154
|
+
selector: '#text-85a9876220 > h1:nth-of-type(1)',
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const patch = mapper.suggestionToPatch(suggestion, 'opp-content-123');
|
|
160
|
+
|
|
161
|
+
expect(patch).to.exist;
|
|
162
|
+
expect(patch.op).to.equal('insertAfter');
|
|
163
|
+
expect(patch.selector).to.equal('#text-85a9876220 > h1:nth-of-type(1)');
|
|
164
|
+
expect(patch.valueFormat).to.equal('hast');
|
|
165
|
+
expect(patch.opportunityId).to.equal('opp-content-123');
|
|
166
|
+
expect(patch.suggestionId).to.equal('sugg-content-123');
|
|
167
|
+
expect(patch.prerenderRequired).to.be.true;
|
|
168
|
+
expect(patch.lastUpdated).to.be.a('number');
|
|
169
|
+
|
|
170
|
+
// Verify HAST structure
|
|
171
|
+
expect(patch.value).to.be.an('object');
|
|
172
|
+
expect(patch.value.type).to.equal('root');
|
|
173
|
+
expect(patch.value.children).to.be.an('array');
|
|
174
|
+
expect(patch.value.children.length).to.be.greaterThan(0);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should convert markdown with bold text to HAST', () => {
|
|
178
|
+
const suggestion = {
|
|
179
|
+
getId: () => 'sugg-bold',
|
|
180
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
181
|
+
getData: () => ({
|
|
182
|
+
summarizationText: 'This is **bold** text.',
|
|
183
|
+
transformRules: {
|
|
184
|
+
action: 'insertAfter',
|
|
185
|
+
selector: '.content',
|
|
186
|
+
},
|
|
187
|
+
}),
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const patch = mapper.suggestionToPatch(suggestion, 'opp-bold');
|
|
191
|
+
|
|
192
|
+
expect(patch).to.exist;
|
|
193
|
+
expect(patch.value.type).to.equal('root');
|
|
194
|
+
expect(patch.value.children).to.be.an('array');
|
|
195
|
+
|
|
196
|
+
// Find the paragraph
|
|
197
|
+
const paragraph = patch.value.children.find((child) => child.type === 'element' && child.tagName === 'p');
|
|
198
|
+
expect(paragraph).to.exist;
|
|
199
|
+
expect(paragraph.children).to.be.an('array');
|
|
200
|
+
|
|
201
|
+
// Should contain text and strong elements
|
|
202
|
+
const hasStrong = paragraph.children.some((child) => child.type === 'element' && child.tagName === 'strong');
|
|
203
|
+
expect(hasStrong).to.be.true;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should convert markdown with headings to HAST', () => {
|
|
207
|
+
const suggestion = {
|
|
208
|
+
getId: () => 'sugg-heading',
|
|
209
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
210
|
+
getData: () => ({
|
|
211
|
+
summarizationText: '## Key Points\n\nImportant information.',
|
|
212
|
+
transformRules: {
|
|
213
|
+
action: 'insertAfter',
|
|
214
|
+
selector: '#intro',
|
|
215
|
+
},
|
|
216
|
+
}),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const patch = mapper.suggestionToPatch(suggestion, 'opp-heading');
|
|
220
|
+
|
|
221
|
+
expect(patch).to.exist;
|
|
222
|
+
expect(patch.value.children).to.be.an('array');
|
|
223
|
+
|
|
224
|
+
// Should have h2 and paragraph
|
|
225
|
+
const hasH2 = patch.value.children.some((child) => child.type === 'element' && child.tagName === 'h2');
|
|
226
|
+
const hasP = patch.value.children.some((child) => child.type === 'element' && child.tagName === 'p');
|
|
227
|
+
expect(hasH2).to.be.true;
|
|
228
|
+
expect(hasP).to.be.true;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should convert markdown with lists to HAST', () => {
|
|
232
|
+
const suggestion = {
|
|
233
|
+
getId: () => 'sugg-list',
|
|
234
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
235
|
+
getData: () => ({
|
|
236
|
+
summarizationText: '- Item 1\n- Item 2\n- Item 3',
|
|
237
|
+
transformRules: {
|
|
238
|
+
action: 'insertAfter',
|
|
239
|
+
selector: '#list-section',
|
|
240
|
+
},
|
|
241
|
+
}),
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const patch = mapper.suggestionToPatch(suggestion, 'opp-list');
|
|
245
|
+
|
|
246
|
+
expect(patch).to.exist;
|
|
247
|
+
|
|
248
|
+
// Should have ul element
|
|
249
|
+
const hasList = patch.value.children.some((child) => child.type === 'element' && child.tagName === 'ul');
|
|
250
|
+
expect(hasList).to.be.true;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should return null when summarizationText is missing', () => {
|
|
254
|
+
const suggestion = {
|
|
255
|
+
getId: () => 'sugg-invalid',
|
|
256
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
257
|
+
getData: () => ({
|
|
258
|
+
transformRules: {
|
|
259
|
+
action: 'insertAfter',
|
|
260
|
+
selector: '#selector',
|
|
261
|
+
},
|
|
262
|
+
}),
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const patch = mapper.suggestionToPatch(suggestion, 'opp-invalid');
|
|
266
|
+
|
|
267
|
+
expect(patch).to.be.null;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should return null when transformRules are incomplete', () => {
|
|
271
|
+
const suggestion = {
|
|
272
|
+
getId: () => 'sugg-invalid-2',
|
|
273
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
274
|
+
getData: () => ({
|
|
275
|
+
summarizationText: 'Some content',
|
|
276
|
+
transformRules: {
|
|
277
|
+
selector: '#selector',
|
|
278
|
+
},
|
|
279
|
+
}),
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const patch = mapper.suggestionToPatch(suggestion, 'opp-invalid-2');
|
|
283
|
+
|
|
284
|
+
expect(patch).to.be.null;
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should handle complex markdown with multiple elements', () => {
|
|
288
|
+
const markdownText = `Enter your name exactly as it appears on your **government ID** when booking flights. The same name must be on your **ticket and travel documents**. Rules for names may be different for some countries, such as **Canada, UAE, Australia, and New Zealand**. If your name is spelled wrong, contact support before you travel. There may be a fee to make changes.
|
|
289
|
+
|
|
290
|
+
**Key Points**
|
|
291
|
+
|
|
292
|
+
- Name on booking must match your government-issued ID or passport.
|
|
293
|
+
- Exact spelling is required for both ticket and travel documents.
|
|
294
|
+
- Special requirements may apply for Canada, UAE, Australia, and New Zealand.`;
|
|
295
|
+
|
|
296
|
+
const suggestion = {
|
|
297
|
+
getId: () => 'sugg-complex',
|
|
298
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
299
|
+
getData: () => ({
|
|
300
|
+
summarizationText: markdownText,
|
|
301
|
+
transformRules: {
|
|
302
|
+
action: 'insertAfter',
|
|
303
|
+
selector: '#content-section',
|
|
304
|
+
},
|
|
305
|
+
}),
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const patch = mapper.suggestionToPatch(suggestion, 'opp-complex');
|
|
309
|
+
|
|
310
|
+
expect(patch).to.exist;
|
|
311
|
+
expect(patch.value.children).to.be.an('array');
|
|
312
|
+
expect(patch.value.children.length).to.be.greaterThan(2);
|
|
313
|
+
|
|
314
|
+
// Should have paragraphs and list
|
|
315
|
+
const hasP = patch.value.children.some((child) => child.type === 'element' && child.tagName === 'p');
|
|
316
|
+
const hasList = patch.value.children.some((child) => child.type === 'element' && child.tagName === 'ul');
|
|
317
|
+
expect(hasP).to.be.true;
|
|
318
|
+
expect(hasList).to.be.true;
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should handle markdown parsing errors gracefully', () => {
|
|
322
|
+
let errorMessage = '';
|
|
323
|
+
const errorLog = {
|
|
324
|
+
debug: () => {},
|
|
325
|
+
info: () => {},
|
|
326
|
+
warn: () => {},
|
|
327
|
+
error: (msg) => { errorMessage = msg; },
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const errorMapper = new ContentMapper(errorLog);
|
|
331
|
+
|
|
332
|
+
// Stub the markdownToHast method to throw an error
|
|
333
|
+
const stub = sinon.stub(errorMapper, 'markdownToHast').throws(new Error('Markdown parsing failed'));
|
|
334
|
+
|
|
335
|
+
const suggestion = {
|
|
336
|
+
getId: () => 'sugg-error',
|
|
337
|
+
getData: () => ({
|
|
338
|
+
summarizationText: 'Some content',
|
|
339
|
+
transformRules: {
|
|
340
|
+
action: 'insertAfter',
|
|
341
|
+
selector: '#selector',
|
|
342
|
+
},
|
|
343
|
+
}),
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const patch = errorMapper.suggestionToPatch(suggestion, 'opp-error');
|
|
347
|
+
|
|
348
|
+
expect(patch).to.be.null;
|
|
349
|
+
expect(errorMessage).to.include('Failed to convert markdown to HAST');
|
|
350
|
+
expect(errorMessage).to.include('Markdown parsing failed');
|
|
351
|
+
|
|
352
|
+
stub.restore();
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
});
|