@jgardner04/ghost-mcp-server 1.13.2 → 1.13.3
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 +5 -13
- package/src/__tests__/mcp_server.test.js +204 -117
- package/src/__tests__/mcp_server_pages.test.js +32 -18
- package/src/config/mcp-config.js +1 -1
- package/src/controllers/__tests__/tagController.test.js +12 -8
- package/src/controllers/tagController.js +2 -2
- package/src/errors/__tests__/index.test.js +3 -3
- package/src/errors/index.js +1 -1
- package/src/index.js +1 -1
- package/src/mcp_server.js +35 -31
- package/src/schemas/__tests__/postSchemas.test.js +19 -0
- package/src/schemas/__tests__/tagSchemas.test.js +1 -1
- package/src/schemas/common.js +2 -2
- package/src/schemas/memberSchemas.js +20 -8
- package/src/schemas/newsletterSchemas.js +10 -10
- package/src/schemas/pageSchemas.js +16 -11
- package/src/schemas/postSchemas.js +22 -15
- package/src/schemas/tagSchemas.js +12 -7
- package/src/schemas/tierSchemas.js +17 -8
- package/src/services/__tests__/ghostServiceImproved.members.test.js +7 -2
- package/src/services/__tests__/ghostServiceImproved.newsletters.test.js +10 -6
- package/src/services/__tests__/ghostServiceImproved.pages.test.js +4 -4
- package/src/services/__tests__/ghostServiceImproved.posts.test.js +4 -4
- package/src/services/__tests__/ghostServiceImproved.tags.test.js +2 -2
- package/src/services/__tests__/ghostServiceImproved.tiers.test.js +9 -14
- package/src/services/__tests__/memberService.test.js +0 -28
- package/src/services/__tests__/tierService.test.js +0 -28
- package/src/services/ghostServiceImproved.js +69 -217
- package/src/services/imageProcessingService.js +1 -1
- package/src/services/memberService.js +0 -13
- package/src/services/tierService.js +0 -13
- package/src/utils/__tests__/nqlSanitizer.test.js +38 -0
- package/src/utils/nqlSanitizer.js +11 -0
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
*/
|
|
36
36
|
export const createPostSchema = z.object({
|
|
37
37
|
title: titleSchema,
|
|
38
|
-
html: htmlContentSchema.
|
|
38
|
+
html: htmlContentSchema.meta({ description: 'HTML content of the post' }),
|
|
39
39
|
slug: slugSchema.optional(),
|
|
40
40
|
status: postStatusSchema.default('draft'),
|
|
41
41
|
visibility: visibilitySchema.default('public'),
|
|
@@ -57,17 +57,21 @@ export const createPostSchema = z.object({
|
|
|
57
57
|
.max(500, 'Twitter description cannot exceed 500 characters')
|
|
58
58
|
.optional(),
|
|
59
59
|
canonical_url: canonicalUrlSchema,
|
|
60
|
-
tags: tagsSchema.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
tags: tagsSchema.meta({
|
|
61
|
+
description:
|
|
62
|
+
'Array of tag names or IDs to associate with the post. On update, this fully replaces the existing tags array (not merged).',
|
|
63
|
+
}),
|
|
64
|
+
authors: authorsSchema.meta({
|
|
65
|
+
description:
|
|
66
|
+
'Array of author IDs or emails. On update, this fully replaces the existing authors array (not merged).',
|
|
67
|
+
}),
|
|
68
|
+
published_at: isoDateSchema
|
|
69
|
+
.optional()
|
|
70
|
+
.meta({ description: 'Scheduled publish time (ISO 8601 format)' }),
|
|
67
71
|
codeinjection_head: z.string().optional(),
|
|
68
72
|
codeinjection_foot: z.string().optional(),
|
|
69
|
-
custom_template: z.string().optional().
|
|
70
|
-
email_only: z.boolean().default(false).
|
|
73
|
+
custom_template: z.string().optional().meta({ description: 'Custom template filename' }),
|
|
74
|
+
email_only: z.boolean().default(false).meta({ description: 'Whether post is email-only' }),
|
|
71
75
|
});
|
|
72
76
|
|
|
73
77
|
/**
|
|
@@ -86,17 +90,20 @@ export const postQuerySchema = z.object({
|
|
|
86
90
|
.string()
|
|
87
91
|
.regex(/^[a-zA-Z0-9_\-:.'"\s,[\]<>=!+]+$/, 'Invalid filter: contains disallowed characters')
|
|
88
92
|
.optional()
|
|
89
|
-
.
|
|
93
|
+
.meta({ description: 'NQL filter string (e.g., "status:published+featured:true")' }),
|
|
90
94
|
include: z
|
|
91
95
|
.string()
|
|
92
96
|
.optional()
|
|
93
|
-
.
|
|
94
|
-
fields: z.string().optional().
|
|
97
|
+
.meta({ description: 'Comma-separated list of relations (e.g., "tags,authors")' }),
|
|
98
|
+
fields: z.string().optional().meta({ description: 'Comma-separated list of fields to return' }),
|
|
95
99
|
formats: z
|
|
96
100
|
.string()
|
|
97
101
|
.optional()
|
|
98
|
-
.
|
|
99
|
-
order: z
|
|
102
|
+
.meta({ description: 'Comma-separated list of formats (html, plaintext, mobiledoc)' }),
|
|
103
|
+
order: z
|
|
104
|
+
.string()
|
|
105
|
+
.optional()
|
|
106
|
+
.meta({ description: 'Order results (e.g., "published_at DESC", "title ASC")' }),
|
|
100
107
|
});
|
|
101
108
|
|
|
102
109
|
/**
|
|
@@ -68,9 +68,11 @@ export const tagQueryBaseSchema = z.object({
|
|
|
68
68
|
'Tag name contains invalid characters. Only letters, numbers, spaces, hyphens, underscores, and apostrophes are allowed'
|
|
69
69
|
)
|
|
70
70
|
.optional()
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
.meta({
|
|
72
|
+
description: 'Filter by exact tag name (legacy parameter, converted to filter internally)',
|
|
73
|
+
}),
|
|
74
|
+
slug: z.string().optional().meta({ description: 'Filter by tag slug' }),
|
|
75
|
+
visibility: visibilitySchema.optional().meta({ description: 'Filter by visibility' }),
|
|
74
76
|
limit: z
|
|
75
77
|
.union([
|
|
76
78
|
z.number().int().min(1).max(100),
|
|
@@ -79,7 +81,7 @@ export const tagQueryBaseSchema = z.object({
|
|
|
79
81
|
])
|
|
80
82
|
.default(15)
|
|
81
83
|
.optional()
|
|
82
|
-
.
|
|
84
|
+
.meta({ description: 'Number of tags to return (1-100) or "all" for all tags' }),
|
|
83
85
|
page: z
|
|
84
86
|
.union([z.number().int().min(1), z.string().regex(/^\d+$/).transform(Number)])
|
|
85
87
|
.default(1)
|
|
@@ -88,12 +90,15 @@ export const tagQueryBaseSchema = z.object({
|
|
|
88
90
|
.string()
|
|
89
91
|
.regex(/^[a-zA-Z0-9_\-:.'"\s,[\]<>=!+]+$/, 'Invalid filter: contains disallowed characters')
|
|
90
92
|
.optional()
|
|
91
|
-
.
|
|
93
|
+
.meta({ description: 'NQL filter string' }),
|
|
92
94
|
include: z
|
|
93
95
|
.string()
|
|
94
96
|
.optional()
|
|
95
|
-
.
|
|
96
|
-
order: z
|
|
97
|
+
.meta({ description: 'Comma-separated list of relations to include (e.g., "count.posts")' }),
|
|
98
|
+
order: z
|
|
99
|
+
.string()
|
|
100
|
+
.optional()
|
|
101
|
+
.meta({ description: 'Order results (e.g., "name ASC", "created_at DESC")' }),
|
|
97
102
|
});
|
|
98
103
|
|
|
99
104
|
/**
|
|
@@ -47,16 +47,19 @@ export const createTierSchema = z.object({
|
|
|
47
47
|
name: z.string().min(1, 'Name cannot be empty').max(191, 'Name cannot exceed 191 characters'),
|
|
48
48
|
description: z.string().max(2000, 'Description cannot exceed 2000 characters').optional(),
|
|
49
49
|
slug: slugSchema.optional(),
|
|
50
|
-
active: z
|
|
50
|
+
active: z
|
|
51
|
+
.boolean()
|
|
52
|
+
.default(true)
|
|
53
|
+
.meta({ description: 'Whether tier is currently active/available' }),
|
|
51
54
|
type: z
|
|
52
55
|
.enum(['free', 'paid'], {
|
|
53
|
-
|
|
56
|
+
error: () => ({ message: 'Type must be free or paid' }),
|
|
54
57
|
})
|
|
55
58
|
.default('paid'),
|
|
56
59
|
welcome_page_url: z.string().url('Invalid welcome page URL').optional(),
|
|
57
60
|
visibility: z
|
|
58
61
|
.enum(['public', 'none'], {
|
|
59
|
-
|
|
62
|
+
error: () => ({ message: 'Visibility must be public or none' }),
|
|
60
63
|
})
|
|
61
64
|
.default('public'),
|
|
62
65
|
trial_days: z
|
|
@@ -64,7 +67,7 @@ export const createTierSchema = z.object({
|
|
|
64
67
|
.int()
|
|
65
68
|
.min(0, 'Trial days must be non-negative')
|
|
66
69
|
.default(0)
|
|
67
|
-
.
|
|
70
|
+
.meta({ description: 'Number of trial days for paid tiers' }),
|
|
68
71
|
currency: z
|
|
69
72
|
.string()
|
|
70
73
|
.length(3, 'Currency must be 3-letter ISO code')
|
|
@@ -72,7 +75,10 @@ export const createTierSchema = z.object({
|
|
|
72
75
|
.optional(),
|
|
73
76
|
monthly_price: z.number().int().min(0, 'Monthly price must be non-negative').optional(),
|
|
74
77
|
yearly_price: z.number().int().min(0, 'Yearly price must be non-negative').optional(),
|
|
75
|
-
benefits: z
|
|
78
|
+
benefits: z
|
|
79
|
+
.array(z.string())
|
|
80
|
+
.optional()
|
|
81
|
+
.meta({ description: 'Array of benefit names/descriptions' }),
|
|
76
82
|
});
|
|
77
83
|
|
|
78
84
|
/**
|
|
@@ -91,12 +97,15 @@ export const tierQuerySchema = z.object({
|
|
|
91
97
|
.string()
|
|
92
98
|
.regex(/^[a-zA-Z0-9_\-:.'"\s,[\]<>=!+]+$/, 'Invalid filter: contains disallowed characters')
|
|
93
99
|
.optional()
|
|
94
|
-
.
|
|
95
|
-
include: z
|
|
100
|
+
.meta({ description: 'NQL filter string (e.g., "type:paid+active:true")' }),
|
|
101
|
+
include: z
|
|
102
|
+
.string()
|
|
103
|
+
.optional()
|
|
104
|
+
.meta({ description: 'Comma-separated list of relations to include' }),
|
|
96
105
|
order: z
|
|
97
106
|
.string()
|
|
98
107
|
.optional()
|
|
99
|
-
.
|
|
108
|
+
.meta({ description: 'Order results (e.g., "monthly_price ASC", "created_at DESC")' }),
|
|
100
109
|
});
|
|
101
110
|
|
|
102
111
|
/**
|
|
@@ -173,8 +173,13 @@ describe('ghostServiceImproved - Members', () => {
|
|
|
173
173
|
expect(api.members.read).toHaveBeenCalledWith(expect.any(Object), { id: memberId });
|
|
174
174
|
// Should send ONLY updateData + updated_at, NOT the full existing member
|
|
175
175
|
expect(api.members.edit).toHaveBeenCalledWith(
|
|
176
|
-
{
|
|
177
|
-
|
|
176
|
+
{
|
|
177
|
+
id: memberId,
|
|
178
|
+
name: 'Jane Doe',
|
|
179
|
+
note: 'Updated note',
|
|
180
|
+
updated_at: '2023-01-01T00:00:00.000Z',
|
|
181
|
+
},
|
|
182
|
+
{}
|
|
178
183
|
);
|
|
179
184
|
// Verify read-only fields are NOT sent
|
|
180
185
|
const editCallData = api.members.edit.mock.calls[0][0];
|
|
@@ -214,8 +214,8 @@ describe('ghostServiceImproved - Newsletter Operations', () => {
|
|
|
214
214
|
expect(mockNewslettersApi.read).toHaveBeenCalledWith({}, { id: 'newsletter-123' });
|
|
215
215
|
// Should send ONLY updateData + updated_at, NOT the full existing newsletter
|
|
216
216
|
expect(mockNewslettersApi.edit).toHaveBeenCalledWith(
|
|
217
|
-
{ name: 'New Name', updated_at: '2024-01-01T00:00:00.000Z' },
|
|
218
|
-
{
|
|
217
|
+
{ id: 'newsletter-123', name: 'New Name', updated_at: '2024-01-01T00:00:00.000Z' },
|
|
218
|
+
{}
|
|
219
219
|
);
|
|
220
220
|
// Verify read-only fields are NOT sent
|
|
221
221
|
const editCallData = mockNewslettersApi.edit.mock.calls[0][0];
|
|
@@ -242,8 +242,8 @@ describe('ghostServiceImproved - Newsletter Operations', () => {
|
|
|
242
242
|
|
|
243
243
|
// Should send ONLY updateData + updated_at
|
|
244
244
|
expect(mockNewslettersApi.edit).toHaveBeenCalledWith(
|
|
245
|
-
{ ...updateData, updated_at: '2024-01-01T00:00:00.000Z' },
|
|
246
|
-
{
|
|
245
|
+
{ id: 'newsletter-123', ...updateData, updated_at: '2024-01-01T00:00:00.000Z' },
|
|
246
|
+
{}
|
|
247
247
|
);
|
|
248
248
|
});
|
|
249
249
|
|
|
@@ -278,8 +278,12 @@ describe('ghostServiceImproved - Newsletter Operations', () => {
|
|
|
278
278
|
|
|
279
279
|
// Should send ONLY updateData + updated_at
|
|
280
280
|
expect(mockNewslettersApi.edit).toHaveBeenCalledWith(
|
|
281
|
-
{
|
|
282
|
-
|
|
281
|
+
{
|
|
282
|
+
id: 'newsletter-123',
|
|
283
|
+
description: 'Updated description',
|
|
284
|
+
updated_at: '2024-01-01T00:00:00.000Z',
|
|
285
|
+
},
|
|
286
|
+
{}
|
|
283
287
|
);
|
|
284
288
|
});
|
|
285
289
|
});
|
|
@@ -292,8 +292,8 @@ describe('ghostServiceImproved - Pages', () => {
|
|
|
292
292
|
expect(api.pages.read).toHaveBeenCalledWith({}, { id: pageId });
|
|
293
293
|
// Should send ONLY updateData + updated_at, NOT the full existing page
|
|
294
294
|
expect(api.pages.edit).toHaveBeenCalledWith(
|
|
295
|
-
{ title: 'Updated Title', updated_at: '2024-01-01T00:00:00.000Z' },
|
|
296
|
-
{
|
|
295
|
+
{ id: pageId, title: 'Updated Title', updated_at: '2024-01-01T00:00:00.000Z' },
|
|
296
|
+
{}
|
|
297
297
|
);
|
|
298
298
|
// Verify read-only fields are NOT sent
|
|
299
299
|
const editCallData = api.pages.edit.mock.calls[0][0];
|
|
@@ -345,8 +345,8 @@ describe('ghostServiceImproved - Pages', () => {
|
|
|
345
345
|
|
|
346
346
|
describe('deletePage', () => {
|
|
347
347
|
it('should throw error when page ID is missing', async () => {
|
|
348
|
-
await expect(deletePage(null)).rejects.toThrow('Page ID is required');
|
|
349
|
-
await expect(deletePage('')).rejects.toThrow('Page ID is required');
|
|
348
|
+
await expect(deletePage(null)).rejects.toThrow('Page ID is required for deletion');
|
|
349
|
+
await expect(deletePage('')).rejects.toThrow('Page ID is required for deletion');
|
|
350
350
|
});
|
|
351
351
|
|
|
352
352
|
it('should delete page successfully', async () => {
|
|
@@ -95,8 +95,8 @@ describe('ghostServiceImproved - Posts (updatePost)', () => {
|
|
|
95
95
|
expect(result).toEqual(expectedResult);
|
|
96
96
|
// Should send ONLY updateData + updated_at, NOT the full existing post
|
|
97
97
|
expect(api.posts.edit).toHaveBeenCalledWith(
|
|
98
|
-
{ title: 'Updated Title', updated_at: '2024-01-01T00:00:00.000Z' },
|
|
99
|
-
{
|
|
98
|
+
{ id: postId, title: 'Updated Title', updated_at: '2024-01-01T00:00:00.000Z' },
|
|
99
|
+
{}
|
|
100
100
|
);
|
|
101
101
|
// Verify read-only fields are NOT sent
|
|
102
102
|
const editCallData = api.posts.edit.mock.calls[0][0];
|
|
@@ -173,8 +173,8 @@ describe('ghostServiceImproved - Posts (updatePost)', () => {
|
|
|
173
173
|
|
|
174
174
|
expect(result).toBeDefined();
|
|
175
175
|
expect(api.posts.edit).toHaveBeenCalledWith(
|
|
176
|
-
{ ...updateData, updated_at: existingPost.updated_at },
|
|
177
|
-
{
|
|
176
|
+
{ id: postId, ...updateData, updated_at: existingPost.updated_at },
|
|
177
|
+
{}
|
|
178
178
|
);
|
|
179
179
|
});
|
|
180
180
|
});
|
|
@@ -431,8 +431,8 @@ describe('ghostServiceImproved - Tags', () => {
|
|
|
431
431
|
expect(api.tags.read).toHaveBeenCalled();
|
|
432
432
|
// Should send ONLY updateData, NOT the full existing tag
|
|
433
433
|
expect(api.tags.edit).toHaveBeenCalledWith(
|
|
434
|
-
{ name: 'Updated JavaScript', description: 'Updated description' },
|
|
435
|
-
{
|
|
434
|
+
{ id: tagId, name: 'Updated JavaScript', description: 'Updated description' },
|
|
435
|
+
{}
|
|
436
436
|
);
|
|
437
437
|
// Verify read-only fields are NOT sent
|
|
438
438
|
const editCallData = api.tags.edit.mock.calls[0][0];
|
|
@@ -270,14 +270,7 @@ describe('ghostServiceImproved - Tiers', () => {
|
|
|
270
270
|
|
|
271
271
|
const result = await getTier('tier-1');
|
|
272
272
|
|
|
273
|
-
expect(api.tiers.read).toHaveBeenCalledWith(
|
|
274
|
-
expect.objectContaining({
|
|
275
|
-
id: 'tier-1',
|
|
276
|
-
}),
|
|
277
|
-
expect.objectContaining({
|
|
278
|
-
id: 'tier-1',
|
|
279
|
-
})
|
|
280
|
-
);
|
|
273
|
+
expect(api.tiers.read).toHaveBeenCalledWith({}, { id: 'tier-1' });
|
|
281
274
|
expect(result).toEqual(mockTier);
|
|
282
275
|
});
|
|
283
276
|
|
|
@@ -327,14 +320,16 @@ describe('ghostServiceImproved - Tiers', () => {
|
|
|
327
320
|
|
|
328
321
|
const result = await updateTier('tier-1', updateData);
|
|
329
322
|
|
|
330
|
-
expect(api.tiers.read).toHaveBeenCalledWith(
|
|
331
|
-
expect.objectContaining({ id: 'tier-1' }),
|
|
332
|
-
expect.objectContaining({ id: 'tier-1' })
|
|
333
|
-
);
|
|
323
|
+
expect(api.tiers.read).toHaveBeenCalledWith({}, { id: 'tier-1' });
|
|
334
324
|
// Should send ONLY updateData + updated_at, NOT the full existing tier
|
|
335
325
|
expect(api.tiers.edit).toHaveBeenCalledWith(
|
|
336
|
-
{
|
|
337
|
-
|
|
326
|
+
{
|
|
327
|
+
id: 'tier-1',
|
|
328
|
+
name: 'Premium Plus',
|
|
329
|
+
monthly_price: 1299,
|
|
330
|
+
updated_at: '2024-01-01T00:00:00.000Z',
|
|
331
|
+
},
|
|
332
|
+
{}
|
|
338
333
|
);
|
|
339
334
|
// Verify read-only fields are NOT sent
|
|
340
335
|
const editCallData = api.tiers.edit.mock.calls[0][0];
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
validateMemberLookup,
|
|
7
7
|
validateSearchQuery,
|
|
8
8
|
validateSearchOptions,
|
|
9
|
-
sanitizeNqlValue,
|
|
10
9
|
} from '../memberService.js';
|
|
11
10
|
|
|
12
11
|
describe('memberService - Validation', () => {
|
|
@@ -447,31 +446,4 @@ describe('memberService - Validation', () => {
|
|
|
447
446
|
);
|
|
448
447
|
});
|
|
449
448
|
});
|
|
450
|
-
|
|
451
|
-
describe('sanitizeNqlValue', () => {
|
|
452
|
-
it('should escape backslashes', () => {
|
|
453
|
-
expect(sanitizeNqlValue('test\\value')).toBe('test\\\\value');
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
it('should escape single quotes', () => {
|
|
457
|
-
expect(sanitizeNqlValue("test'value")).toBe("test\\'value");
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
it('should escape double quotes', () => {
|
|
461
|
-
expect(sanitizeNqlValue('test"value')).toBe('test\\"value');
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
it('should handle multiple special characters', () => {
|
|
465
|
-
expect(sanitizeNqlValue('test\'value"with\\chars')).toBe('test\\\'value\\"with\\\\chars');
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
it('should not modify strings without special characters', () => {
|
|
469
|
-
expect(sanitizeNqlValue('normalvalue')).toBe('normalvalue');
|
|
470
|
-
expect(sanitizeNqlValue('test@example.com')).toBe('test@example.com');
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
it('should handle empty string', () => {
|
|
474
|
-
expect(sanitizeNqlValue('')).toBe('');
|
|
475
|
-
});
|
|
476
|
-
});
|
|
477
449
|
});
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
validateTierData,
|
|
4
4
|
validateTierUpdateData,
|
|
5
5
|
validateTierQueryOptions,
|
|
6
|
-
sanitizeNqlValue,
|
|
7
6
|
} from '../tierService.js';
|
|
8
7
|
import { ValidationError } from '../../errors/index.js';
|
|
9
8
|
|
|
@@ -342,31 +341,4 @@ describe('tierService - Validation', () => {
|
|
|
342
341
|
).not.toThrow();
|
|
343
342
|
});
|
|
344
343
|
});
|
|
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
344
|
});
|