@jgardner04/ghost-mcp-server 1.1.12 → 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.
- package/package.json +2 -1
- package/src/__tests__/helpers/mockExpress.js +38 -0
- package/src/__tests__/index.test.js +312 -0
- package/src/__tests__/mcp_server.test.js +381 -0
- package/src/__tests__/mcp_server_improved.test.js +440 -0
- package/src/config/__tests__/mcp-config.test.js +311 -0
- package/src/controllers/__tests__/imageController.test.js +572 -0
- package/src/controllers/__tests__/postController.test.js +236 -0
- package/src/controllers/__tests__/tagController.test.js +222 -0
- package/src/mcp_server_improved.js +105 -1
- package/src/middleware/__tests__/errorMiddleware.test.js +1113 -0
- package/src/resources/__tests__/ResourceManager.test.js +977 -0
- package/src/routes/__tests__/imageRoutes.test.js +117 -0
- package/src/routes/__tests__/postRoutes.test.js +262 -0
- package/src/routes/__tests__/tagRoutes.test.js +175 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// Mock the McpServer to capture tool registrations
|
|
4
|
+
const mockTools = new Map();
|
|
5
|
+
|
|
6
|
+
vi.mock('@modelcontextprotocol/sdk/server/mcp.js', () => {
|
|
7
|
+
return {
|
|
8
|
+
McpServer: class MockMcpServer {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
tool(name, description, schema, handler) {
|
|
14
|
+
mockTools.set(name, { name, description, schema, handler });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
connect(_transport) {
|
|
18
|
+
return Promise.resolve();
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
vi.mock('@modelcontextprotocol/sdk/server/stdio.js', () => {
|
|
25
|
+
return {
|
|
26
|
+
StdioServerTransport: class MockStdioServerTransport {},
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Mock dotenv
|
|
31
|
+
vi.mock('dotenv', () => ({
|
|
32
|
+
default: { config: vi.fn() },
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
// Mock crypto
|
|
36
|
+
vi.mock('crypto', () => ({
|
|
37
|
+
default: { randomUUID: vi.fn().mockReturnValue('test-uuid-1234') },
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
// Mock services - will be lazy loaded
|
|
41
|
+
const mockGetPosts = vi.fn();
|
|
42
|
+
const mockGetPost = vi.fn();
|
|
43
|
+
const mockGetTags = vi.fn();
|
|
44
|
+
const mockCreateTag = vi.fn();
|
|
45
|
+
const mockUploadImage = vi.fn();
|
|
46
|
+
const mockCreatePostService = vi.fn();
|
|
47
|
+
const mockProcessImage = vi.fn();
|
|
48
|
+
const mockValidateImageUrl = vi.fn();
|
|
49
|
+
const mockCreateSecureAxiosConfig = vi.fn();
|
|
50
|
+
|
|
51
|
+
vi.mock('../services/ghostService.js', () => ({
|
|
52
|
+
getPosts: (...args) => mockGetPosts(...args),
|
|
53
|
+
getPost: (...args) => mockGetPost(...args),
|
|
54
|
+
getTags: (...args) => mockGetTags(...args),
|
|
55
|
+
createTag: (...args) => mockCreateTag(...args),
|
|
56
|
+
uploadImage: (...args) => mockUploadImage(...args),
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
vi.mock('../services/postService.js', () => ({
|
|
60
|
+
createPostService: (...args) => mockCreatePostService(...args),
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
vi.mock('../services/imageProcessingService.js', () => ({
|
|
64
|
+
processImage: (...args) => mockProcessImage(...args),
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
vi.mock('../utils/urlValidator.js', () => ({
|
|
68
|
+
validateImageUrl: (...args) => mockValidateImageUrl(...args),
|
|
69
|
+
createSecureAxiosConfig: (...args) => mockCreateSecureAxiosConfig(...args),
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
// Mock axios
|
|
73
|
+
const mockAxios = vi.fn();
|
|
74
|
+
vi.mock('axios', () => ({
|
|
75
|
+
default: (...args) => mockAxios(...args),
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
// Mock fs
|
|
79
|
+
const mockUnlink = vi.fn((path, cb) => cb(null));
|
|
80
|
+
const mockCreateWriteStream = vi.fn();
|
|
81
|
+
vi.mock('fs', () => ({
|
|
82
|
+
default: {
|
|
83
|
+
unlink: (...args) => mockUnlink(...args),
|
|
84
|
+
createWriteStream: (...args) => mockCreateWriteStream(...args),
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
// Mock os
|
|
89
|
+
vi.mock('os', () => ({
|
|
90
|
+
default: { tmpdir: vi.fn().mockReturnValue('/tmp') },
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
// Mock path
|
|
94
|
+
vi.mock('path', async () => {
|
|
95
|
+
const actual = await vi.importActual('path');
|
|
96
|
+
return {
|
|
97
|
+
default: actual,
|
|
98
|
+
...actual,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('mcp_server_improved - ghost_get_posts tool', () => {
|
|
103
|
+
beforeEach(async () => {
|
|
104
|
+
vi.clearAllMocks();
|
|
105
|
+
// Don't clear mockTools - they're registered once on module load
|
|
106
|
+
// Import the module to register tools (only first time)
|
|
107
|
+
if (mockTools.size === 0) {
|
|
108
|
+
await import('../mcp_server_improved.js');
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should register ghost_get_posts tool', () => {
|
|
113
|
+
expect(mockTools.has('ghost_get_posts')).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should have correct schema with all optional parameters', () => {
|
|
117
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
118
|
+
expect(tool).toBeDefined();
|
|
119
|
+
expect(tool.description).toContain('posts');
|
|
120
|
+
expect(tool.schema).toBeDefined();
|
|
121
|
+
expect(tool.schema.limit).toBeDefined();
|
|
122
|
+
expect(tool.schema.page).toBeDefined();
|
|
123
|
+
expect(tool.schema.status).toBeDefined();
|
|
124
|
+
expect(tool.schema.include).toBeDefined();
|
|
125
|
+
expect(tool.schema.filter).toBeDefined();
|
|
126
|
+
expect(tool.schema.order).toBeDefined();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should retrieve posts with default options', async () => {
|
|
130
|
+
const mockPosts = [
|
|
131
|
+
{ id: '1', title: 'Post 1', slug: 'post-1', status: 'published' },
|
|
132
|
+
{ id: '2', title: 'Post 2', slug: 'post-2', status: 'draft' },
|
|
133
|
+
];
|
|
134
|
+
mockGetPosts.mockResolvedValue(mockPosts);
|
|
135
|
+
|
|
136
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
137
|
+
const result = await tool.handler({});
|
|
138
|
+
|
|
139
|
+
expect(mockGetPosts).toHaveBeenCalledWith({});
|
|
140
|
+
expect(result.content[0].text).toContain('Post 1');
|
|
141
|
+
expect(result.content[0].text).toContain('Post 2');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should pass limit and page parameters', async () => {
|
|
145
|
+
const mockPosts = [{ id: '1', title: 'Post 1', slug: 'post-1' }];
|
|
146
|
+
mockGetPosts.mockResolvedValue(mockPosts);
|
|
147
|
+
|
|
148
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
149
|
+
await tool.handler({ limit: 10, page: 2 });
|
|
150
|
+
|
|
151
|
+
expect(mockGetPosts).toHaveBeenCalledWith({ limit: 10, page: 2 });
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should validate limit is between 1 and 100', () => {
|
|
155
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
156
|
+
const schema = tool.schema;
|
|
157
|
+
|
|
158
|
+
// Test that limit schema exists and has proper validation
|
|
159
|
+
expect(schema.limit).toBeDefined();
|
|
160
|
+
expect(() => schema.limit.parse(0)).toThrow();
|
|
161
|
+
expect(() => schema.limit.parse(101)).toThrow();
|
|
162
|
+
expect(schema.limit.parse(50)).toBe(50);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should validate page is at least 1', () => {
|
|
166
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
167
|
+
const schema = tool.schema;
|
|
168
|
+
|
|
169
|
+
expect(schema.page).toBeDefined();
|
|
170
|
+
expect(() => schema.page.parse(0)).toThrow();
|
|
171
|
+
expect(schema.page.parse(1)).toBe(1);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should pass status filter', async () => {
|
|
175
|
+
const mockPosts = [{ id: '1', title: 'Published Post', status: 'published' }];
|
|
176
|
+
mockGetPosts.mockResolvedValue(mockPosts);
|
|
177
|
+
|
|
178
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
179
|
+
await tool.handler({ status: 'published' });
|
|
180
|
+
|
|
181
|
+
expect(mockGetPosts).toHaveBeenCalledWith({ status: 'published' });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should validate status enum values', () => {
|
|
185
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
186
|
+
const schema = tool.schema;
|
|
187
|
+
|
|
188
|
+
expect(schema.status).toBeDefined();
|
|
189
|
+
expect(() => schema.status.parse('invalid')).toThrow();
|
|
190
|
+
expect(schema.status.parse('published')).toBe('published');
|
|
191
|
+
expect(schema.status.parse('draft')).toBe('draft');
|
|
192
|
+
expect(schema.status.parse('scheduled')).toBe('scheduled');
|
|
193
|
+
expect(schema.status.parse('all')).toBe('all');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should pass include parameter', async () => {
|
|
197
|
+
const mockPosts = [
|
|
198
|
+
{
|
|
199
|
+
id: '1',
|
|
200
|
+
title: 'Post with tags',
|
|
201
|
+
tags: [{ name: 'tech' }],
|
|
202
|
+
authors: [{ name: 'John' }],
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
mockGetPosts.mockResolvedValue(mockPosts);
|
|
206
|
+
|
|
207
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
208
|
+
await tool.handler({ include: 'tags,authors' });
|
|
209
|
+
|
|
210
|
+
expect(mockGetPosts).toHaveBeenCalledWith({ include: 'tags,authors' });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should pass filter parameter (NQL)', async () => {
|
|
214
|
+
const mockPosts = [{ id: '1', title: 'Featured Post', featured: true }];
|
|
215
|
+
mockGetPosts.mockResolvedValue(mockPosts);
|
|
216
|
+
|
|
217
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
218
|
+
await tool.handler({ filter: 'featured:true' });
|
|
219
|
+
|
|
220
|
+
expect(mockGetPosts).toHaveBeenCalledWith({ filter: 'featured:true' });
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should pass order parameter', async () => {
|
|
224
|
+
const mockPosts = [
|
|
225
|
+
{ id: '1', title: 'Newest', published_at: '2025-12-10' },
|
|
226
|
+
{ id: '2', title: 'Older', published_at: '2025-12-01' },
|
|
227
|
+
];
|
|
228
|
+
mockGetPosts.mockResolvedValue(mockPosts);
|
|
229
|
+
|
|
230
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
231
|
+
await tool.handler({ order: 'published_at DESC' });
|
|
232
|
+
|
|
233
|
+
expect(mockGetPosts).toHaveBeenCalledWith({ order: 'published_at DESC' });
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should pass all parameters combined', async () => {
|
|
237
|
+
const mockPosts = [{ id: '1', title: 'Test Post' }];
|
|
238
|
+
mockGetPosts.mockResolvedValue(mockPosts);
|
|
239
|
+
|
|
240
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
241
|
+
await tool.handler({
|
|
242
|
+
limit: 20,
|
|
243
|
+
page: 1,
|
|
244
|
+
status: 'published',
|
|
245
|
+
include: 'tags,authors',
|
|
246
|
+
filter: 'featured:true',
|
|
247
|
+
order: 'published_at DESC',
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(mockGetPosts).toHaveBeenCalledWith({
|
|
251
|
+
limit: 20,
|
|
252
|
+
page: 1,
|
|
253
|
+
status: 'published',
|
|
254
|
+
include: 'tags,authors',
|
|
255
|
+
filter: 'featured:true',
|
|
256
|
+
order: 'published_at DESC',
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should handle errors from ghostService', async () => {
|
|
261
|
+
mockGetPosts.mockRejectedValue(new Error('Ghost API error'));
|
|
262
|
+
|
|
263
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
264
|
+
const result = await tool.handler({});
|
|
265
|
+
|
|
266
|
+
expect(result.isError).toBe(true);
|
|
267
|
+
expect(result.content[0].text).toContain('Ghost API error');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should return formatted JSON response', async () => {
|
|
271
|
+
const mockPosts = [
|
|
272
|
+
{
|
|
273
|
+
id: '1',
|
|
274
|
+
title: 'Test Post',
|
|
275
|
+
slug: 'test-post',
|
|
276
|
+
html: '<p>Content</p>',
|
|
277
|
+
status: 'published',
|
|
278
|
+
},
|
|
279
|
+
];
|
|
280
|
+
mockGetPosts.mockResolvedValue(mockPosts);
|
|
281
|
+
|
|
282
|
+
const tool = mockTools.get('ghost_get_posts');
|
|
283
|
+
const result = await tool.handler({});
|
|
284
|
+
|
|
285
|
+
expect(result.content).toBeDefined();
|
|
286
|
+
expect(result.content[0].type).toBe('text');
|
|
287
|
+
expect(result.content[0].text).toContain('"id": "1"');
|
|
288
|
+
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('mcp_server_improved - ghost_get_post tool', () => {
|
|
293
|
+
beforeEach(async () => {
|
|
294
|
+
vi.clearAllMocks();
|
|
295
|
+
// Don't clear mockTools - they're registered once on module load
|
|
296
|
+
if (mockTools.size === 0) {
|
|
297
|
+
await import('../mcp_server_improved.js');
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('should register ghost_get_post tool', () => {
|
|
302
|
+
expect(mockTools.has('ghost_get_post')).toBe(true);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should have correct schema requiring one of id or slug', () => {
|
|
306
|
+
const tool = mockTools.get('ghost_get_post');
|
|
307
|
+
expect(tool).toBeDefined();
|
|
308
|
+
expect(tool.description).toContain('post');
|
|
309
|
+
expect(tool.schema).toBeDefined();
|
|
310
|
+
expect(tool.schema.id).toBeDefined();
|
|
311
|
+
expect(tool.schema.slug).toBeDefined();
|
|
312
|
+
expect(tool.schema.include).toBeDefined();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should retrieve post by ID', async () => {
|
|
316
|
+
const mockPost = {
|
|
317
|
+
id: '123',
|
|
318
|
+
title: 'Test Post',
|
|
319
|
+
slug: 'test-post',
|
|
320
|
+
html: '<p>Content</p>',
|
|
321
|
+
status: 'published',
|
|
322
|
+
};
|
|
323
|
+
mockGetPost.mockResolvedValue(mockPost);
|
|
324
|
+
|
|
325
|
+
const tool = mockTools.get('ghost_get_post');
|
|
326
|
+
const result = await tool.handler({ id: '123' });
|
|
327
|
+
|
|
328
|
+
expect(mockGetPost).toHaveBeenCalledWith('123', {});
|
|
329
|
+
expect(result.content[0].text).toContain('"id": "123"');
|
|
330
|
+
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should retrieve post by slug', async () => {
|
|
334
|
+
const mockPost = {
|
|
335
|
+
id: '123',
|
|
336
|
+
title: 'Test Post',
|
|
337
|
+
slug: 'test-post',
|
|
338
|
+
html: '<p>Content</p>',
|
|
339
|
+
status: 'published',
|
|
340
|
+
};
|
|
341
|
+
mockGetPost.mockResolvedValue(mockPost);
|
|
342
|
+
|
|
343
|
+
const tool = mockTools.get('ghost_get_post');
|
|
344
|
+
const result = await tool.handler({ slug: 'test-post' });
|
|
345
|
+
|
|
346
|
+
expect(mockGetPost).toHaveBeenCalledWith('slug/test-post', {});
|
|
347
|
+
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should pass include parameter with ID', async () => {
|
|
351
|
+
const mockPost = {
|
|
352
|
+
id: '123',
|
|
353
|
+
title: 'Post with relations',
|
|
354
|
+
tags: [{ name: 'tech' }],
|
|
355
|
+
authors: [{ name: 'John' }],
|
|
356
|
+
};
|
|
357
|
+
mockGetPost.mockResolvedValue(mockPost);
|
|
358
|
+
|
|
359
|
+
const tool = mockTools.get('ghost_get_post');
|
|
360
|
+
await tool.handler({ id: '123', include: 'tags,authors' });
|
|
361
|
+
|
|
362
|
+
expect(mockGetPost).toHaveBeenCalledWith('123', { include: 'tags,authors' });
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should pass include parameter with slug', async () => {
|
|
366
|
+
const mockPost = {
|
|
367
|
+
id: '123',
|
|
368
|
+
title: 'Post with relations',
|
|
369
|
+
slug: 'test-post',
|
|
370
|
+
tags: [{ name: 'tech' }],
|
|
371
|
+
};
|
|
372
|
+
mockGetPost.mockResolvedValue(mockPost);
|
|
373
|
+
|
|
374
|
+
const tool = mockTools.get('ghost_get_post');
|
|
375
|
+
await tool.handler({ slug: 'test-post', include: 'tags' });
|
|
376
|
+
|
|
377
|
+
expect(mockGetPost).toHaveBeenCalledWith('slug/test-post', { include: 'tags' });
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should prefer ID over slug when both provided', async () => {
|
|
381
|
+
const mockPost = { id: '123', title: 'Test Post', slug: 'test-post' };
|
|
382
|
+
mockGetPost.mockResolvedValue(mockPost);
|
|
383
|
+
|
|
384
|
+
const tool = mockTools.get('ghost_get_post');
|
|
385
|
+
await tool.handler({ id: '123', slug: 'wrong-slug' });
|
|
386
|
+
|
|
387
|
+
expect(mockGetPost).toHaveBeenCalledWith('123', {});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('should handle not found errors', async () => {
|
|
391
|
+
mockGetPost.mockRejectedValue(new Error('Post not found'));
|
|
392
|
+
|
|
393
|
+
const tool = mockTools.get('ghost_get_post');
|
|
394
|
+
const result = await tool.handler({ id: 'nonexistent' });
|
|
395
|
+
|
|
396
|
+
expect(result.isError).toBe(true);
|
|
397
|
+
expect(result.content[0].text).toContain('Post not found');
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('should handle errors from ghostService', async () => {
|
|
401
|
+
mockGetPost.mockRejectedValue(new Error('Ghost API error'));
|
|
402
|
+
|
|
403
|
+
const tool = mockTools.get('ghost_get_post');
|
|
404
|
+
const result = await tool.handler({ slug: 'test' });
|
|
405
|
+
|
|
406
|
+
expect(result.isError).toBe(true);
|
|
407
|
+
expect(result.content[0].text).toContain('Ghost API error');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('should return formatted JSON response', async () => {
|
|
411
|
+
const mockPost = {
|
|
412
|
+
id: '1',
|
|
413
|
+
uuid: 'uuid-123',
|
|
414
|
+
title: 'Test Post',
|
|
415
|
+
slug: 'test-post',
|
|
416
|
+
html: '<p>Content</p>',
|
|
417
|
+
status: 'published',
|
|
418
|
+
created_at: '2025-12-10T00:00:00.000Z',
|
|
419
|
+
updated_at: '2025-12-10T00:00:00.000Z',
|
|
420
|
+
};
|
|
421
|
+
mockGetPost.mockResolvedValue(mockPost);
|
|
422
|
+
|
|
423
|
+
const tool = mockTools.get('ghost_get_post');
|
|
424
|
+
const result = await tool.handler({ id: '1' });
|
|
425
|
+
|
|
426
|
+
expect(result.content).toBeDefined();
|
|
427
|
+
expect(result.content[0].type).toBe('text');
|
|
428
|
+
expect(result.content[0].text).toContain('"id": "1"');
|
|
429
|
+
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
430
|
+
expect(result.content[0].text).toContain('"status": "published"');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('should handle validation error when neither id nor slug provided', async () => {
|
|
434
|
+
const tool = mockTools.get('ghost_get_post');
|
|
435
|
+
const result = await tool.handler({});
|
|
436
|
+
|
|
437
|
+
expect(result.isError).toBe(true);
|
|
438
|
+
expect(result.content[0].text).toContain('Either id or slug is required');
|
|
439
|
+
});
|
|
440
|
+
});
|