@jgardner04/ghost-mcp-server 1.10.0 → 1.12.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/package.json +1 -1
- package/src/__tests__/mcp_server.test.js +10 -4
- package/src/__tests__/mcp_server_improved.test.js +192 -149
- package/src/__tests__/mcp_server_pages.test.js +72 -68
- package/src/errors/__tests__/index.test.js +70 -0
- package/src/errors/index.js +10 -0
- package/src/mcp_server.js +9 -19
- package/src/mcp_server_improved.js +815 -424
- package/src/schemas/__tests__/common.test.js +84 -0
- package/src/schemas/common.js +50 -3
- package/src/services/__tests__/ghostServiceImproved.members.test.js +12 -61
- package/src/services/__tests__/ghostServiceImproved.tiers.test.js +392 -0
- package/src/services/__tests__/postService.test.js +7 -99
- package/src/services/__tests__/tierService.test.js +372 -0
- package/src/services/ghostServiceImproved.js +140 -21
- package/src/services/postService.js +4 -30
- package/src/services/tierService.js +304 -0
- package/src/utils/__tests__/tempFileManager.test.js +316 -0
- package/src/utils/__tests__/validation.test.js +163 -0
- package/src/utils/tempFileManager.js +113 -0
- package/src/utils/validation.js +28 -0
|
@@ -26,7 +26,11 @@ describe('postService', () => {
|
|
|
26
26
|
vi.clearAllMocks();
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// NOTE: Input validation tests have been moved to MCP layer tests.
|
|
30
|
+
// The postService no longer performs Joi validation - input is validated
|
|
31
|
+
// by Zod schemas at the MCP tool layer (see mcp_server_improved.js).
|
|
32
|
+
|
|
33
|
+
describe('createPostService - basic functionality', () => {
|
|
30
34
|
it('should accept valid input and create a post', async () => {
|
|
31
35
|
const validInput = {
|
|
32
36
|
title: 'Test Post',
|
|
@@ -47,41 +51,6 @@ describe('postService', () => {
|
|
|
47
51
|
);
|
|
48
52
|
});
|
|
49
53
|
|
|
50
|
-
it('should reject input with missing title', async () => {
|
|
51
|
-
const invalidInput = {
|
|
52
|
-
html: '<p>Test content</p>',
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
await expect(createPostService(invalidInput)).rejects.toThrow(
|
|
56
|
-
'Invalid post input: "title" is required'
|
|
57
|
-
);
|
|
58
|
-
expect(createPost).not.toHaveBeenCalled();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should reject input with missing html', async () => {
|
|
62
|
-
const invalidInput = {
|
|
63
|
-
title: 'Test Post',
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
await expect(createPostService(invalidInput)).rejects.toThrow(
|
|
67
|
-
'Invalid post input: "html" is required'
|
|
68
|
-
);
|
|
69
|
-
expect(createPost).not.toHaveBeenCalled();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should reject input with invalid status', async () => {
|
|
73
|
-
const invalidInput = {
|
|
74
|
-
title: 'Test Post',
|
|
75
|
-
html: '<p>Content</p>',
|
|
76
|
-
status: 'invalid-status',
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
await expect(createPostService(invalidInput)).rejects.toThrow(
|
|
80
|
-
'Invalid post input: "status" must be one of [draft, published, scheduled]'
|
|
81
|
-
);
|
|
82
|
-
expect(createPost).not.toHaveBeenCalled();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
54
|
it('should accept valid status values', async () => {
|
|
86
55
|
const statuses = ['draft', 'published', 'scheduled'];
|
|
87
56
|
createPost.mockResolvedValue({ id: '1', title: 'Test' });
|
|
@@ -100,39 +69,6 @@ describe('postService', () => {
|
|
|
100
69
|
}
|
|
101
70
|
});
|
|
102
71
|
|
|
103
|
-
it('should validate tags array with maximum length', async () => {
|
|
104
|
-
const invalidInput = {
|
|
105
|
-
title: 'Test Post',
|
|
106
|
-
html: '<p>Content</p>',
|
|
107
|
-
tags: Array(11).fill('tag'), // 11 tags exceeds max of 10
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
await expect(createPostService(invalidInput)).rejects.toThrow('Invalid post input:');
|
|
111
|
-
expect(createPost).not.toHaveBeenCalled();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should validate tag string max length', async () => {
|
|
115
|
-
const invalidInput = {
|
|
116
|
-
title: 'Test Post',
|
|
117
|
-
html: '<p>Content</p>',
|
|
118
|
-
tags: ['a'.repeat(51)], // 51 chars exceeds max of 50
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
await expect(createPostService(invalidInput)).rejects.toThrow('Invalid post input:');
|
|
122
|
-
expect(createPost).not.toHaveBeenCalled();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should validate feature_image is a valid URI', async () => {
|
|
126
|
-
const invalidInput = {
|
|
127
|
-
title: 'Test Post',
|
|
128
|
-
html: '<p>Content</p>',
|
|
129
|
-
feature_image: 'not-a-valid-url',
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
await expect(createPostService(invalidInput)).rejects.toThrow('Invalid post input:');
|
|
133
|
-
expect(createPost).not.toHaveBeenCalled();
|
|
134
|
-
});
|
|
135
|
-
|
|
136
72
|
it('should accept valid feature_image URI', async () => {
|
|
137
73
|
const validInput = {
|
|
138
74
|
title: 'Test Post',
|
|
@@ -217,27 +153,7 @@ describe('postService', () => {
|
|
|
217
153
|
);
|
|
218
154
|
});
|
|
219
155
|
|
|
220
|
-
|
|
221
|
-
const input = {
|
|
222
|
-
title: 'Test Post',
|
|
223
|
-
html: '<p>Content</p>',
|
|
224
|
-
tags: [null, 'valid-tag'],
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
await expect(createPostService(input)).rejects.toThrow('Invalid post input:');
|
|
228
|
-
expect(createPost).not.toHaveBeenCalled();
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('should reject tags array with empty strings', async () => {
|
|
232
|
-
const input = {
|
|
233
|
-
title: 'Test Post',
|
|
234
|
-
html: '<p>Content</p>',
|
|
235
|
-
tags: ['', 'valid-tag'],
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
await expect(createPostService(input)).rejects.toThrow('Invalid post input:');
|
|
239
|
-
expect(createPost).not.toHaveBeenCalled();
|
|
240
|
-
});
|
|
156
|
+
// NOTE: Tag validation tests (non-string values, empty strings) moved to MCP layer
|
|
241
157
|
|
|
242
158
|
it('should trim whitespace from tag names', async () => {
|
|
243
159
|
const input = {
|
|
@@ -383,15 +299,7 @@ describe('postService', () => {
|
|
|
383
299
|
expect(calledDescription).toBe('a'.repeat(497) + '...');
|
|
384
300
|
});
|
|
385
301
|
|
|
386
|
-
|
|
387
|
-
const input = {
|
|
388
|
-
title: 'Test Post',
|
|
389
|
-
html: '',
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
await expect(createPostService(input)).rejects.toThrow('Invalid post input:');
|
|
393
|
-
expect(createPost).not.toHaveBeenCalled();
|
|
394
|
-
});
|
|
302
|
+
// NOTE: Empty HTML validation test moved to MCP layer
|
|
395
303
|
|
|
396
304
|
it('should strip HTML tags and truncate when generating meta_description', async () => {
|
|
397
305
|
const longHtml = '<p>' + 'word '.repeat(200) + '</p>';
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
validateTierData,
|
|
4
|
+
validateTierUpdateData,
|
|
5
|
+
validateTierQueryOptions,
|
|
6
|
+
sanitizeNqlValue,
|
|
7
|
+
} from '../tierService.js';
|
|
8
|
+
import { ValidationError } from '../../errors/index.js';
|
|
9
|
+
|
|
10
|
+
describe('tierService - Validation', () => {
|
|
11
|
+
describe('validateTierData', () => {
|
|
12
|
+
it('should validate required name field', () => {
|
|
13
|
+
expect(() => validateTierData({})).toThrow(ValidationError);
|
|
14
|
+
expect(() => validateTierData({})).toThrow('Tier validation failed');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should validate required currency field', () => {
|
|
18
|
+
expect(() => validateTierData({ name: 'Premium' })).toThrow(ValidationError);
|
|
19
|
+
expect(() => validateTierData({ name: 'Premium' })).toThrow('Tier validation failed');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should accept valid tier data with name and currency', () => {
|
|
23
|
+
expect(() =>
|
|
24
|
+
validateTierData({
|
|
25
|
+
name: 'Premium',
|
|
26
|
+
currency: 'USD',
|
|
27
|
+
})
|
|
28
|
+
).not.toThrow();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should validate name is a non-empty string', () => {
|
|
32
|
+
expect(() =>
|
|
33
|
+
validateTierData({
|
|
34
|
+
name: '',
|
|
35
|
+
currency: 'USD',
|
|
36
|
+
})
|
|
37
|
+
).toThrow('Tier validation failed');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should validate name does not exceed max length', () => {
|
|
41
|
+
const longName = 'a'.repeat(192);
|
|
42
|
+
expect(() =>
|
|
43
|
+
validateTierData({
|
|
44
|
+
name: longName,
|
|
45
|
+
currency: 'USD',
|
|
46
|
+
})
|
|
47
|
+
).toThrow('Tier validation failed');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should validate currency is a 3-letter uppercase code', () => {
|
|
51
|
+
expect(() =>
|
|
52
|
+
validateTierData({
|
|
53
|
+
name: 'Premium',
|
|
54
|
+
currency: 'us',
|
|
55
|
+
})
|
|
56
|
+
).toThrow('Tier validation failed');
|
|
57
|
+
|
|
58
|
+
expect(() =>
|
|
59
|
+
validateTierData({
|
|
60
|
+
name: 'Premium',
|
|
61
|
+
currency: 'USDD',
|
|
62
|
+
})
|
|
63
|
+
).toThrow('Tier validation failed');
|
|
64
|
+
|
|
65
|
+
expect(() =>
|
|
66
|
+
validateTierData({
|
|
67
|
+
name: 'Premium',
|
|
68
|
+
currency: '123',
|
|
69
|
+
})
|
|
70
|
+
).toThrow('Tier validation failed');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should validate description does not exceed max length', () => {
|
|
74
|
+
const longDescription = 'a'.repeat(2001);
|
|
75
|
+
expect(() =>
|
|
76
|
+
validateTierData({
|
|
77
|
+
name: 'Premium',
|
|
78
|
+
currency: 'USD',
|
|
79
|
+
description: longDescription,
|
|
80
|
+
})
|
|
81
|
+
).toThrow('Tier validation failed');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should validate monthly_price is a non-negative number', () => {
|
|
85
|
+
expect(() =>
|
|
86
|
+
validateTierData({
|
|
87
|
+
name: 'Premium',
|
|
88
|
+
currency: 'USD',
|
|
89
|
+
monthly_price: -100,
|
|
90
|
+
})
|
|
91
|
+
).toThrow('Tier validation failed');
|
|
92
|
+
|
|
93
|
+
expect(() =>
|
|
94
|
+
validateTierData({
|
|
95
|
+
name: 'Premium',
|
|
96
|
+
currency: 'USD',
|
|
97
|
+
monthly_price: 'invalid',
|
|
98
|
+
})
|
|
99
|
+
).toThrow('Tier validation failed');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should validate yearly_price is a non-negative number', () => {
|
|
103
|
+
expect(() =>
|
|
104
|
+
validateTierData({
|
|
105
|
+
name: 'Premium',
|
|
106
|
+
currency: 'USD',
|
|
107
|
+
yearly_price: -1000,
|
|
108
|
+
})
|
|
109
|
+
).toThrow('Tier validation failed');
|
|
110
|
+
|
|
111
|
+
expect(() =>
|
|
112
|
+
validateTierData({
|
|
113
|
+
name: 'Premium',
|
|
114
|
+
currency: 'USD',
|
|
115
|
+
yearly_price: 'invalid',
|
|
116
|
+
})
|
|
117
|
+
).toThrow('Tier validation failed');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should validate benefits is an array of strings', () => {
|
|
121
|
+
expect(() =>
|
|
122
|
+
validateTierData({
|
|
123
|
+
name: 'Premium',
|
|
124
|
+
currency: 'USD',
|
|
125
|
+
benefits: 'not an array',
|
|
126
|
+
})
|
|
127
|
+
).toThrow('Tier validation failed');
|
|
128
|
+
|
|
129
|
+
expect(() =>
|
|
130
|
+
validateTierData({
|
|
131
|
+
name: 'Premium',
|
|
132
|
+
currency: 'USD',
|
|
133
|
+
benefits: [123, 456],
|
|
134
|
+
})
|
|
135
|
+
).toThrow('Tier validation failed');
|
|
136
|
+
|
|
137
|
+
expect(() =>
|
|
138
|
+
validateTierData({
|
|
139
|
+
name: 'Premium',
|
|
140
|
+
currency: 'USD',
|
|
141
|
+
benefits: ['Benefit 1', ''],
|
|
142
|
+
})
|
|
143
|
+
).toThrow('Tier validation failed');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should validate welcome_page_url is a valid URL', () => {
|
|
147
|
+
expect(() =>
|
|
148
|
+
validateTierData({
|
|
149
|
+
name: 'Premium',
|
|
150
|
+
currency: 'USD',
|
|
151
|
+
welcome_page_url: 'not-a-url',
|
|
152
|
+
})
|
|
153
|
+
).toThrow('Tier validation failed');
|
|
154
|
+
|
|
155
|
+
expect(() =>
|
|
156
|
+
validateTierData({
|
|
157
|
+
name: 'Premium',
|
|
158
|
+
currency: 'USD',
|
|
159
|
+
welcome_page_url: 'ftp://example.com',
|
|
160
|
+
})
|
|
161
|
+
).toThrow('Tier validation failed');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should accept valid welcome_page_url', () => {
|
|
165
|
+
expect(() =>
|
|
166
|
+
validateTierData({
|
|
167
|
+
name: 'Premium',
|
|
168
|
+
currency: 'USD',
|
|
169
|
+
welcome_page_url: 'https://example.com/welcome',
|
|
170
|
+
})
|
|
171
|
+
).not.toThrow();
|
|
172
|
+
|
|
173
|
+
expect(() =>
|
|
174
|
+
validateTierData({
|
|
175
|
+
name: 'Premium',
|
|
176
|
+
currency: 'USD',
|
|
177
|
+
welcome_page_url: 'http://example.com/welcome',
|
|
178
|
+
})
|
|
179
|
+
).not.toThrow();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should accept complete valid tier data', () => {
|
|
183
|
+
expect(() =>
|
|
184
|
+
validateTierData({
|
|
185
|
+
name: 'Premium Membership',
|
|
186
|
+
description: 'Access to premium content',
|
|
187
|
+
currency: 'USD',
|
|
188
|
+
monthly_price: 999,
|
|
189
|
+
yearly_price: 9999,
|
|
190
|
+
benefits: ['Ad-free experience', 'Exclusive content', 'Priority support'],
|
|
191
|
+
welcome_page_url: 'https://example.com/welcome',
|
|
192
|
+
})
|
|
193
|
+
).not.toThrow();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('validateTierUpdateData', () => {
|
|
198
|
+
it('should accept empty update data', () => {
|
|
199
|
+
expect(() => validateTierUpdateData({})).not.toThrow();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should validate name if provided', () => {
|
|
203
|
+
expect(() =>
|
|
204
|
+
validateTierUpdateData({
|
|
205
|
+
name: '',
|
|
206
|
+
})
|
|
207
|
+
).toThrow('Tier validation failed');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should validate currency if provided', () => {
|
|
211
|
+
expect(() =>
|
|
212
|
+
validateTierUpdateData({
|
|
213
|
+
currency: 'us',
|
|
214
|
+
})
|
|
215
|
+
).toThrow('Tier validation failed');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should validate description length if provided', () => {
|
|
219
|
+
const longDescription = 'a'.repeat(2001);
|
|
220
|
+
expect(() =>
|
|
221
|
+
validateTierUpdateData({
|
|
222
|
+
description: longDescription,
|
|
223
|
+
})
|
|
224
|
+
).toThrow('Tier validation failed');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should validate monthly_price if provided', () => {
|
|
228
|
+
expect(() =>
|
|
229
|
+
validateTierUpdateData({
|
|
230
|
+
monthly_price: -100,
|
|
231
|
+
})
|
|
232
|
+
).toThrow('Tier validation failed');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should validate yearly_price if provided', () => {
|
|
236
|
+
expect(() =>
|
|
237
|
+
validateTierUpdateData({
|
|
238
|
+
yearly_price: -1000,
|
|
239
|
+
})
|
|
240
|
+
).toThrow('Tier validation failed');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should validate benefits if provided', () => {
|
|
244
|
+
expect(() =>
|
|
245
|
+
validateTierUpdateData({
|
|
246
|
+
benefits: 'not an array',
|
|
247
|
+
})
|
|
248
|
+
).toThrow('Tier validation failed');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should validate welcome_page_url if provided', () => {
|
|
252
|
+
expect(() =>
|
|
253
|
+
validateTierUpdateData({
|
|
254
|
+
welcome_page_url: 'not-a-url',
|
|
255
|
+
})
|
|
256
|
+
).toThrow('Tier validation failed');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should accept valid update data', () => {
|
|
260
|
+
expect(() =>
|
|
261
|
+
validateTierUpdateData({
|
|
262
|
+
name: 'Updated Premium',
|
|
263
|
+
monthly_price: 1299,
|
|
264
|
+
benefits: ['New benefit'],
|
|
265
|
+
})
|
|
266
|
+
).not.toThrow();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('validateTierQueryOptions', () => {
|
|
271
|
+
it('should accept empty options', () => {
|
|
272
|
+
expect(() => validateTierQueryOptions({})).not.toThrow();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should validate limit is within range', () => {
|
|
276
|
+
expect(() => validateTierQueryOptions({ limit: 0 })).toThrow('Tier query validation failed');
|
|
277
|
+
|
|
278
|
+
expect(() => validateTierQueryOptions({ limit: 101 })).toThrow(
|
|
279
|
+
'Tier query validation failed'
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
expect(() => validateTierQueryOptions({ limit: 50 })).not.toThrow();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should validate limit is a number', () => {
|
|
286
|
+
expect(() => validateTierQueryOptions({ limit: 'invalid' })).toThrow(
|
|
287
|
+
'Tier query validation failed'
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should validate page is >= 1', () => {
|
|
292
|
+
expect(() => validateTierQueryOptions({ page: 0 })).toThrow('Tier query validation failed');
|
|
293
|
+
|
|
294
|
+
expect(() => validateTierQueryOptions({ page: -1 })).toThrow('Tier query validation failed');
|
|
295
|
+
|
|
296
|
+
expect(() => validateTierQueryOptions({ page: 1 })).not.toThrow();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should validate page is a number', () => {
|
|
300
|
+
expect(() => validateTierQueryOptions({ page: 'invalid' })).toThrow(
|
|
301
|
+
'Tier query validation failed'
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should validate filter is a non-empty string', () => {
|
|
306
|
+
expect(() => validateTierQueryOptions({ filter: '' })).toThrow(
|
|
307
|
+
'Tier query validation failed'
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
expect(() => validateTierQueryOptions({ filter: ' ' })).toThrow(
|
|
311
|
+
'Tier query validation failed'
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
expect(() => validateTierQueryOptions({ filter: 'type:paid' })).not.toThrow();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should validate order is a non-empty string', () => {
|
|
318
|
+
expect(() => validateTierQueryOptions({ order: '' })).toThrow('Tier query validation failed');
|
|
319
|
+
|
|
320
|
+
expect(() => validateTierQueryOptions({ order: 'created_at desc' })).not.toThrow();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should validate include is a non-empty string', () => {
|
|
324
|
+
expect(() => validateTierQueryOptions({ include: '' })).toThrow(
|
|
325
|
+
'Tier query validation failed'
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
expect(() =>
|
|
329
|
+
validateTierQueryOptions({ include: 'monthly_price,yearly_price' })
|
|
330
|
+
).not.toThrow();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should accept valid query options', () => {
|
|
334
|
+
expect(() =>
|
|
335
|
+
validateTierQueryOptions({
|
|
336
|
+
limit: 50,
|
|
337
|
+
page: 2,
|
|
338
|
+
filter: 'type:paid',
|
|
339
|
+
order: 'created_at desc',
|
|
340
|
+
include: 'monthly_price,yearly_price',
|
|
341
|
+
})
|
|
342
|
+
).not.toThrow();
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
describe('sanitizeNqlValue', () => {
|
|
347
|
+
it('should return value if undefined or null', () => {
|
|
348
|
+
expect(sanitizeNqlValue(null)).toBe(null);
|
|
349
|
+
expect(sanitizeNqlValue(undefined)).toBe(undefined);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should escape backslashes', () => {
|
|
353
|
+
expect(sanitizeNqlValue('test\\value')).toBe('test\\\\value');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('should escape single quotes', () => {
|
|
357
|
+
expect(sanitizeNqlValue("test'value")).toBe("test\\'value");
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('should escape double quotes', () => {
|
|
361
|
+
expect(sanitizeNqlValue('test"value')).toBe('test\\"value');
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should escape multiple special characters', () => {
|
|
365
|
+
expect(sanitizeNqlValue('test\\value"with\'quotes')).toBe('test\\\\value\\"with\\\'quotes');
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should handle strings without special characters', () => {
|
|
369
|
+
expect(sanitizeNqlValue('simple-value')).toBe('simple-value');
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
});
|