@jgardner04/ghost-mcp-server 1.12.2 → 1.12.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/README.md +2 -2
- package/package.json +8 -8
- package/src/__tests__/mcp_server.test.js +1165 -251
- package/src/__tests__/mcp_server_pages.test.js +12 -12
- package/src/index.js +3 -16
- package/src/mcp_server.js +1655 -407
- package/src/mcp_server_enhanced.js +1 -1
- package/src/services/__tests__/postService.test.js +1 -1
- package/src/__tests__/mcp_server_improved.test.js +0 -1301
- package/src/mcp_server_improved.js +0 -1720
|
@@ -1,1301 +0,0 @@
|
|
|
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 mockGetTag = vi.fn();
|
|
45
|
-
const mockCreateTag = vi.fn();
|
|
46
|
-
const mockUpdateTag = vi.fn();
|
|
47
|
-
const mockDeleteTag = vi.fn();
|
|
48
|
-
const mockUploadImage = vi.fn();
|
|
49
|
-
const mockCreatePostService = vi.fn();
|
|
50
|
-
const mockProcessImage = vi.fn();
|
|
51
|
-
const mockValidateImageUrl = vi.fn();
|
|
52
|
-
const mockCreateSecureAxiosConfig = vi.fn();
|
|
53
|
-
const mockUpdatePost = vi.fn();
|
|
54
|
-
const mockDeletePost = vi.fn();
|
|
55
|
-
const mockSearchPosts = vi.fn();
|
|
56
|
-
|
|
57
|
-
// Page mocks
|
|
58
|
-
const mockGetPages = vi.fn();
|
|
59
|
-
const mockGetPage = vi.fn();
|
|
60
|
-
const mockCreatePageService = vi.fn();
|
|
61
|
-
const mockUpdatePage = vi.fn();
|
|
62
|
-
const mockDeletePage = vi.fn();
|
|
63
|
-
const mockSearchPages = vi.fn();
|
|
64
|
-
|
|
65
|
-
// Member mocks
|
|
66
|
-
const mockCreateMember = vi.fn();
|
|
67
|
-
const mockUpdateMember = vi.fn();
|
|
68
|
-
const mockDeleteMember = vi.fn();
|
|
69
|
-
const mockGetMembers = vi.fn();
|
|
70
|
-
const mockGetMember = vi.fn();
|
|
71
|
-
const mockSearchMembers = vi.fn();
|
|
72
|
-
|
|
73
|
-
// Newsletter mocks
|
|
74
|
-
const mockGetNewsletters = vi.fn();
|
|
75
|
-
const mockGetNewsletter = vi.fn();
|
|
76
|
-
const mockCreateNewsletterService = vi.fn();
|
|
77
|
-
const mockUpdateNewsletter = vi.fn();
|
|
78
|
-
const mockDeleteNewsletter = vi.fn();
|
|
79
|
-
|
|
80
|
-
// Tier mocks
|
|
81
|
-
const mockGetTiers = vi.fn();
|
|
82
|
-
const mockGetTier = vi.fn();
|
|
83
|
-
const mockCreateTier = vi.fn();
|
|
84
|
-
const mockUpdateTier = vi.fn();
|
|
85
|
-
const mockDeleteTier = vi.fn();
|
|
86
|
-
|
|
87
|
-
vi.mock('../services/postService.js', () => ({
|
|
88
|
-
createPostService: (...args) => mockCreatePostService(...args),
|
|
89
|
-
}));
|
|
90
|
-
|
|
91
|
-
vi.mock('../services/pageService.js', () => ({
|
|
92
|
-
createPageService: (...args) => mockCreatePageService(...args),
|
|
93
|
-
}));
|
|
94
|
-
|
|
95
|
-
vi.mock('../services/newsletterService.js', () => ({
|
|
96
|
-
createNewsletterService: (...args) => mockCreateNewsletterService(...args),
|
|
97
|
-
}));
|
|
98
|
-
|
|
99
|
-
vi.mock('../services/ghostServiceImproved.js', () => ({
|
|
100
|
-
// Posts
|
|
101
|
-
getPosts: (...args) => mockGetPosts(...args),
|
|
102
|
-
getPost: (...args) => mockGetPost(...args),
|
|
103
|
-
updatePost: (...args) => mockUpdatePost(...args),
|
|
104
|
-
deletePost: (...args) => mockDeletePost(...args),
|
|
105
|
-
searchPosts: (...args) => mockSearchPosts(...args),
|
|
106
|
-
// Tags
|
|
107
|
-
getTags: (...args) => mockGetTags(...args),
|
|
108
|
-
getTag: (...args) => mockGetTag(...args),
|
|
109
|
-
createTag: (...args) => mockCreateTag(...args),
|
|
110
|
-
updateTag: (...args) => mockUpdateTag(...args),
|
|
111
|
-
deleteTag: (...args) => mockDeleteTag(...args),
|
|
112
|
-
// Images
|
|
113
|
-
uploadImage: (...args) => mockUploadImage(...args),
|
|
114
|
-
// Pages
|
|
115
|
-
getPages: (...args) => mockGetPages(...args),
|
|
116
|
-
getPage: (...args) => mockGetPage(...args),
|
|
117
|
-
updatePage: (...args) => mockUpdatePage(...args),
|
|
118
|
-
deletePage: (...args) => mockDeletePage(...args),
|
|
119
|
-
searchPages: (...args) => mockSearchPages(...args),
|
|
120
|
-
// Members
|
|
121
|
-
createMember: (...args) => mockCreateMember(...args),
|
|
122
|
-
updateMember: (...args) => mockUpdateMember(...args),
|
|
123
|
-
deleteMember: (...args) => mockDeleteMember(...args),
|
|
124
|
-
getMembers: (...args) => mockGetMembers(...args),
|
|
125
|
-
getMember: (...args) => mockGetMember(...args),
|
|
126
|
-
searchMembers: (...args) => mockSearchMembers(...args),
|
|
127
|
-
// Newsletters
|
|
128
|
-
getNewsletters: (...args) => mockGetNewsletters(...args),
|
|
129
|
-
getNewsletter: (...args) => mockGetNewsletter(...args),
|
|
130
|
-
updateNewsletter: (...args) => mockUpdateNewsletter(...args),
|
|
131
|
-
deleteNewsletter: (...args) => mockDeleteNewsletter(...args),
|
|
132
|
-
// Tiers
|
|
133
|
-
getTiers: (...args) => mockGetTiers(...args),
|
|
134
|
-
getTier: (...args) => mockGetTier(...args),
|
|
135
|
-
createTier: (...args) => mockCreateTier(...args),
|
|
136
|
-
updateTier: (...args) => mockUpdateTier(...args),
|
|
137
|
-
deleteTier: (...args) => mockDeleteTier(...args),
|
|
138
|
-
}));
|
|
139
|
-
|
|
140
|
-
vi.mock('../services/imageProcessingService.js', () => ({
|
|
141
|
-
processImage: (...args) => mockProcessImage(...args),
|
|
142
|
-
}));
|
|
143
|
-
|
|
144
|
-
vi.mock('../utils/urlValidator.js', () => ({
|
|
145
|
-
validateImageUrl: (...args) => mockValidateImageUrl(...args),
|
|
146
|
-
createSecureAxiosConfig: (...args) => mockCreateSecureAxiosConfig(...args),
|
|
147
|
-
}));
|
|
148
|
-
|
|
149
|
-
// Mock axios
|
|
150
|
-
const mockAxios = vi.fn();
|
|
151
|
-
vi.mock('axios', () => ({
|
|
152
|
-
default: (...args) => mockAxios(...args),
|
|
153
|
-
}));
|
|
154
|
-
|
|
155
|
-
// Mock fs
|
|
156
|
-
const mockCreateWriteStream = vi.fn();
|
|
157
|
-
vi.mock('fs', () => ({
|
|
158
|
-
default: {
|
|
159
|
-
createWriteStream: (...args) => mockCreateWriteStream(...args),
|
|
160
|
-
},
|
|
161
|
-
}));
|
|
162
|
-
|
|
163
|
-
// Mock tempFileManager
|
|
164
|
-
const mockTrackTempFile = vi.fn();
|
|
165
|
-
const mockCleanupTempFiles = vi.fn().mockResolvedValue(undefined);
|
|
166
|
-
vi.mock('../utils/tempFileManager.js', () => ({
|
|
167
|
-
trackTempFile: (...args) => mockTrackTempFile(...args),
|
|
168
|
-
cleanupTempFiles: (...args) => mockCleanupTempFiles(...args),
|
|
169
|
-
}));
|
|
170
|
-
|
|
171
|
-
// Mock os
|
|
172
|
-
vi.mock('os', () => ({
|
|
173
|
-
default: { tmpdir: vi.fn().mockReturnValue('/tmp') },
|
|
174
|
-
}));
|
|
175
|
-
|
|
176
|
-
// Mock path
|
|
177
|
-
vi.mock('path', async () => {
|
|
178
|
-
const actual = await vi.importActual('path');
|
|
179
|
-
return {
|
|
180
|
-
default: actual,
|
|
181
|
-
...actual,
|
|
182
|
-
};
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe('mcp_server_improved - ghost_get_posts tool', () => {
|
|
186
|
-
beforeEach(async () => {
|
|
187
|
-
vi.clearAllMocks();
|
|
188
|
-
// Don't clear mockTools - they're registered once on module load
|
|
189
|
-
// Import the module to register tools (only first time)
|
|
190
|
-
if (mockTools.size === 0) {
|
|
191
|
-
await import('../mcp_server_improved.js');
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('should register ghost_get_posts tool', () => {
|
|
196
|
-
expect(mockTools.has('ghost_get_posts')).toBe(true);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('should have correct schema with all optional parameters', () => {
|
|
200
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
201
|
-
expect(tool).toBeDefined();
|
|
202
|
-
expect(tool.description).toContain('posts');
|
|
203
|
-
expect(tool.schema).toBeDefined();
|
|
204
|
-
// Zod schemas store field definitions in schema.shape
|
|
205
|
-
expect(tool.schema.shape.limit).toBeDefined();
|
|
206
|
-
expect(tool.schema.shape.page).toBeDefined();
|
|
207
|
-
expect(tool.schema.shape.status).toBeDefined();
|
|
208
|
-
expect(tool.schema.shape.include).toBeDefined();
|
|
209
|
-
expect(tool.schema.shape.filter).toBeDefined();
|
|
210
|
-
expect(tool.schema.shape.order).toBeDefined();
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('should retrieve posts with default options', async () => {
|
|
214
|
-
const mockPosts = [
|
|
215
|
-
{ id: '1', title: 'Post 1', slug: 'post-1', status: 'published' },
|
|
216
|
-
{ id: '2', title: 'Post 2', slug: 'post-2', status: 'draft' },
|
|
217
|
-
];
|
|
218
|
-
mockGetPosts.mockResolvedValue(mockPosts);
|
|
219
|
-
|
|
220
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
221
|
-
const result = await tool.handler({});
|
|
222
|
-
|
|
223
|
-
expect(mockGetPosts).toHaveBeenCalledWith({});
|
|
224
|
-
expect(result.content[0].text).toContain('Post 1');
|
|
225
|
-
expect(result.content[0].text).toContain('Post 2');
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('should pass limit and page parameters', async () => {
|
|
229
|
-
const mockPosts = [{ id: '1', title: 'Post 1', slug: 'post-1' }];
|
|
230
|
-
mockGetPosts.mockResolvedValue(mockPosts);
|
|
231
|
-
|
|
232
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
233
|
-
await tool.handler({ limit: 10, page: 2 });
|
|
234
|
-
|
|
235
|
-
expect(mockGetPosts).toHaveBeenCalledWith({ limit: 10, page: 2 });
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it('should validate limit is between 1 and 100', () => {
|
|
239
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
240
|
-
// Zod schemas store field definitions in schema.shape
|
|
241
|
-
const shape = tool.schema.shape;
|
|
242
|
-
|
|
243
|
-
// Test that limit schema exists and has proper validation
|
|
244
|
-
expect(shape.limit).toBeDefined();
|
|
245
|
-
expect(() => shape.limit.parse(0)).toThrow();
|
|
246
|
-
expect(() => shape.limit.parse(101)).toThrow();
|
|
247
|
-
expect(shape.limit.parse(50)).toBe(50);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('should validate page is at least 1', () => {
|
|
251
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
252
|
-
// Zod schemas store field definitions in schema.shape
|
|
253
|
-
const shape = tool.schema.shape;
|
|
254
|
-
|
|
255
|
-
expect(shape.page).toBeDefined();
|
|
256
|
-
expect(() => shape.page.parse(0)).toThrow();
|
|
257
|
-
expect(shape.page.parse(1)).toBe(1);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it('should pass status filter', async () => {
|
|
261
|
-
const mockPosts = [{ id: '1', title: 'Published Post', status: 'published' }];
|
|
262
|
-
mockGetPosts.mockResolvedValue(mockPosts);
|
|
263
|
-
|
|
264
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
265
|
-
await tool.handler({ status: 'published' });
|
|
266
|
-
|
|
267
|
-
expect(mockGetPosts).toHaveBeenCalledWith({ status: 'published' });
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it('should validate status enum values', () => {
|
|
271
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
272
|
-
// Zod schemas store field definitions in schema.shape
|
|
273
|
-
const shape = tool.schema.shape;
|
|
274
|
-
|
|
275
|
-
expect(shape.status).toBeDefined();
|
|
276
|
-
expect(() => shape.status.parse('invalid')).toThrow();
|
|
277
|
-
expect(shape.status.parse('published')).toBe('published');
|
|
278
|
-
expect(shape.status.parse('draft')).toBe('draft');
|
|
279
|
-
expect(shape.status.parse('scheduled')).toBe('scheduled');
|
|
280
|
-
expect(shape.status.parse('all')).toBe('all');
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('should pass include parameter', async () => {
|
|
284
|
-
const mockPosts = [
|
|
285
|
-
{
|
|
286
|
-
id: '1',
|
|
287
|
-
title: 'Post with tags',
|
|
288
|
-
tags: [{ name: 'tech' }],
|
|
289
|
-
authors: [{ name: 'John' }],
|
|
290
|
-
},
|
|
291
|
-
];
|
|
292
|
-
mockGetPosts.mockResolvedValue(mockPosts);
|
|
293
|
-
|
|
294
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
295
|
-
await tool.handler({ include: 'tags,authors' });
|
|
296
|
-
|
|
297
|
-
expect(mockGetPosts).toHaveBeenCalledWith({ include: 'tags,authors' });
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it('should pass filter parameter (NQL)', async () => {
|
|
301
|
-
const mockPosts = [{ id: '1', title: 'Featured Post', featured: true }];
|
|
302
|
-
mockGetPosts.mockResolvedValue(mockPosts);
|
|
303
|
-
|
|
304
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
305
|
-
await tool.handler({ filter: 'featured:true' });
|
|
306
|
-
|
|
307
|
-
expect(mockGetPosts).toHaveBeenCalledWith({ filter: 'featured:true' });
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('should pass order parameter', async () => {
|
|
311
|
-
const mockPosts = [
|
|
312
|
-
{ id: '1', title: 'Newest', published_at: '2025-12-10' },
|
|
313
|
-
{ id: '2', title: 'Older', published_at: '2025-12-01' },
|
|
314
|
-
];
|
|
315
|
-
mockGetPosts.mockResolvedValue(mockPosts);
|
|
316
|
-
|
|
317
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
318
|
-
await tool.handler({ order: 'published_at DESC' });
|
|
319
|
-
|
|
320
|
-
expect(mockGetPosts).toHaveBeenCalledWith({ order: 'published_at DESC' });
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('should pass all parameters combined', async () => {
|
|
324
|
-
const mockPosts = [{ id: '1', title: 'Test Post' }];
|
|
325
|
-
mockGetPosts.mockResolvedValue(mockPosts);
|
|
326
|
-
|
|
327
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
328
|
-
await tool.handler({
|
|
329
|
-
limit: 20,
|
|
330
|
-
page: 1,
|
|
331
|
-
status: 'published',
|
|
332
|
-
include: 'tags,authors',
|
|
333
|
-
filter: 'featured:true',
|
|
334
|
-
order: 'published_at DESC',
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
expect(mockGetPosts).toHaveBeenCalledWith({
|
|
338
|
-
limit: 20,
|
|
339
|
-
page: 1,
|
|
340
|
-
status: 'published',
|
|
341
|
-
include: 'tags,authors',
|
|
342
|
-
filter: 'featured:true',
|
|
343
|
-
order: 'published_at DESC',
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it('should handle errors from ghostService', async () => {
|
|
348
|
-
mockGetPosts.mockRejectedValue(new Error('Ghost API error'));
|
|
349
|
-
|
|
350
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
351
|
-
const result = await tool.handler({});
|
|
352
|
-
|
|
353
|
-
expect(result.isError).toBe(true);
|
|
354
|
-
expect(result.content[0].text).toContain('Ghost API error');
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
it('should return formatted JSON response', async () => {
|
|
358
|
-
const mockPosts = [
|
|
359
|
-
{
|
|
360
|
-
id: '1',
|
|
361
|
-
title: 'Test Post',
|
|
362
|
-
slug: 'test-post',
|
|
363
|
-
html: '<p>Content</p>',
|
|
364
|
-
status: 'published',
|
|
365
|
-
},
|
|
366
|
-
];
|
|
367
|
-
mockGetPosts.mockResolvedValue(mockPosts);
|
|
368
|
-
|
|
369
|
-
const tool = mockTools.get('ghost_get_posts');
|
|
370
|
-
const result = await tool.handler({});
|
|
371
|
-
|
|
372
|
-
expect(result.content).toBeDefined();
|
|
373
|
-
expect(result.content[0].type).toBe('text');
|
|
374
|
-
expect(result.content[0].text).toContain('"id": "1"');
|
|
375
|
-
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
describe('mcp_server_improved - ghost_get_post tool', () => {
|
|
380
|
-
beforeEach(async () => {
|
|
381
|
-
vi.clearAllMocks();
|
|
382
|
-
// Don't clear mockTools - they're registered once on module load
|
|
383
|
-
if (mockTools.size === 0) {
|
|
384
|
-
await import('../mcp_server_improved.js');
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it('should register ghost_get_post tool', () => {
|
|
389
|
-
expect(mockTools.has('ghost_get_post')).toBe(true);
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
it('should have correct schema requiring one of id or slug', () => {
|
|
393
|
-
const tool = mockTools.get('ghost_get_post');
|
|
394
|
-
expect(tool).toBeDefined();
|
|
395
|
-
expect(tool.description).toContain('post');
|
|
396
|
-
expect(tool.schema).toBeDefined();
|
|
397
|
-
// ghost_get_post uses a refined schema, access via _def.schema.shape
|
|
398
|
-
const shape = tool.schema._def.schema.shape;
|
|
399
|
-
expect(shape.id).toBeDefined();
|
|
400
|
-
expect(shape.slug).toBeDefined();
|
|
401
|
-
expect(shape.include).toBeDefined();
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
it('should retrieve post by ID', async () => {
|
|
405
|
-
const mockPost = {
|
|
406
|
-
id: '507f1f77bcf86cd799439011',
|
|
407
|
-
title: 'Test Post',
|
|
408
|
-
slug: 'test-post',
|
|
409
|
-
html: '<p>Content</p>',
|
|
410
|
-
status: 'published',
|
|
411
|
-
};
|
|
412
|
-
mockGetPost.mockResolvedValue(mockPost);
|
|
413
|
-
|
|
414
|
-
const tool = mockTools.get('ghost_get_post');
|
|
415
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
416
|
-
|
|
417
|
-
expect(mockGetPost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {});
|
|
418
|
-
expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
|
|
419
|
-
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it('should retrieve post by slug', async () => {
|
|
423
|
-
const mockPost = {
|
|
424
|
-
id: '507f1f77bcf86cd799439011',
|
|
425
|
-
title: 'Test Post',
|
|
426
|
-
slug: 'test-post',
|
|
427
|
-
html: '<p>Content</p>',
|
|
428
|
-
status: 'published',
|
|
429
|
-
};
|
|
430
|
-
mockGetPost.mockResolvedValue(mockPost);
|
|
431
|
-
|
|
432
|
-
const tool = mockTools.get('ghost_get_post');
|
|
433
|
-
const result = await tool.handler({ slug: 'test-post' });
|
|
434
|
-
|
|
435
|
-
expect(mockGetPost).toHaveBeenCalledWith('slug/test-post', {});
|
|
436
|
-
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
it('should pass include parameter with ID', async () => {
|
|
440
|
-
const mockPost = {
|
|
441
|
-
id: '507f1f77bcf86cd799439011',
|
|
442
|
-
title: 'Post with relations',
|
|
443
|
-
tags: [{ name: 'tech' }],
|
|
444
|
-
authors: [{ name: 'John' }],
|
|
445
|
-
};
|
|
446
|
-
mockGetPost.mockResolvedValue(mockPost);
|
|
447
|
-
|
|
448
|
-
const tool = mockTools.get('ghost_get_post');
|
|
449
|
-
await tool.handler({ id: '507f1f77bcf86cd799439011', include: 'tags,authors' });
|
|
450
|
-
|
|
451
|
-
expect(mockGetPost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
452
|
-
include: 'tags,authors',
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
it('should pass include parameter with slug', async () => {
|
|
457
|
-
const mockPost = {
|
|
458
|
-
id: '507f1f77bcf86cd799439011',
|
|
459
|
-
title: 'Post with relations',
|
|
460
|
-
slug: 'test-post',
|
|
461
|
-
tags: [{ name: 'tech' }],
|
|
462
|
-
};
|
|
463
|
-
mockGetPost.mockResolvedValue(mockPost);
|
|
464
|
-
|
|
465
|
-
const tool = mockTools.get('ghost_get_post');
|
|
466
|
-
await tool.handler({ slug: 'test-post', include: 'tags' });
|
|
467
|
-
|
|
468
|
-
expect(mockGetPost).toHaveBeenCalledWith('slug/test-post', { include: 'tags' });
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
it('should prefer ID over slug when both provided', async () => {
|
|
472
|
-
const mockPost = { id: '507f1f77bcf86cd799439011', title: 'Test Post', slug: 'test-post' };
|
|
473
|
-
mockGetPost.mockResolvedValue(mockPost);
|
|
474
|
-
|
|
475
|
-
const tool = mockTools.get('ghost_get_post');
|
|
476
|
-
await tool.handler({ id: '507f1f77bcf86cd799439011', slug: 'wrong-slug' });
|
|
477
|
-
|
|
478
|
-
expect(mockGetPost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {});
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
it('should handle not found errors', async () => {
|
|
482
|
-
mockGetPost.mockRejectedValue(new Error('Post not found'));
|
|
483
|
-
|
|
484
|
-
const tool = mockTools.get('ghost_get_post');
|
|
485
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
486
|
-
|
|
487
|
-
expect(result.isError).toBe(true);
|
|
488
|
-
expect(result.content[0].text).toContain('Post not found');
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
it('should handle errors from ghostService', async () => {
|
|
492
|
-
mockGetPost.mockRejectedValue(new Error('Ghost API error'));
|
|
493
|
-
|
|
494
|
-
const tool = mockTools.get('ghost_get_post');
|
|
495
|
-
const result = await tool.handler({ slug: 'test' });
|
|
496
|
-
|
|
497
|
-
expect(result.isError).toBe(true);
|
|
498
|
-
expect(result.content[0].text).toContain('Ghost API error');
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it('should return formatted JSON response', async () => {
|
|
502
|
-
const mockPost = {
|
|
503
|
-
id: '507f1f77bcf86cd799439011',
|
|
504
|
-
uuid: 'uuid-123',
|
|
505
|
-
title: 'Test Post',
|
|
506
|
-
slug: 'test-post',
|
|
507
|
-
html: '<p>Content</p>',
|
|
508
|
-
status: 'published',
|
|
509
|
-
created_at: '2025-12-10T00:00:00.000Z',
|
|
510
|
-
updated_at: '2025-12-10T00:00:00.000Z',
|
|
511
|
-
};
|
|
512
|
-
mockGetPost.mockResolvedValue(mockPost);
|
|
513
|
-
|
|
514
|
-
const tool = mockTools.get('ghost_get_post');
|
|
515
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
516
|
-
|
|
517
|
-
expect(result.content).toBeDefined();
|
|
518
|
-
expect(result.content[0].type).toBe('text');
|
|
519
|
-
expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
|
|
520
|
-
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
521
|
-
expect(result.content[0].text).toContain('"status": "published"');
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
it('should handle validation error when neither id nor slug provided', async () => {
|
|
525
|
-
const tool = mockTools.get('ghost_get_post');
|
|
526
|
-
const result = await tool.handler({});
|
|
527
|
-
|
|
528
|
-
expect(result.isError).toBe(true);
|
|
529
|
-
expect(result.content[0].text).toContain('Either id or slug is required');
|
|
530
|
-
});
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
534
|
-
beforeEach(async () => {
|
|
535
|
-
vi.clearAllMocks();
|
|
536
|
-
// Don't clear mockTools - they're registered once on module load
|
|
537
|
-
if (mockTools.size === 0) {
|
|
538
|
-
await import('../mcp_server_improved.js');
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
it('should register ghost_update_post tool', () => {
|
|
543
|
-
expect(mockTools.has('ghost_update_post')).toBe(true);
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
it('should have correct schema with required id field', () => {
|
|
547
|
-
const tool = mockTools.get('ghost_update_post');
|
|
548
|
-
expect(tool).toBeDefined();
|
|
549
|
-
expect(tool.description).toContain('Updates an existing post');
|
|
550
|
-
expect(tool.schema).toBeDefined();
|
|
551
|
-
// Zod schemas store field definitions in schema.shape
|
|
552
|
-
expect(tool.schema.shape.id).toBeDefined();
|
|
553
|
-
expect(tool.schema.shape.title).toBeDefined();
|
|
554
|
-
expect(tool.schema.shape.html).toBeDefined();
|
|
555
|
-
expect(tool.schema.shape.status).toBeDefined();
|
|
556
|
-
expect(tool.schema.shape.tags).toBeDefined();
|
|
557
|
-
expect(tool.schema.shape.feature_image).toBeDefined();
|
|
558
|
-
expect(tool.schema.shape.feature_image_alt).toBeDefined();
|
|
559
|
-
expect(tool.schema.shape.feature_image_caption).toBeDefined();
|
|
560
|
-
expect(tool.schema.shape.meta_title).toBeDefined();
|
|
561
|
-
expect(tool.schema.shape.meta_description).toBeDefined();
|
|
562
|
-
expect(tool.schema.shape.published_at).toBeDefined();
|
|
563
|
-
expect(tool.schema.shape.custom_excerpt).toBeDefined();
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
it('should update post title', async () => {
|
|
567
|
-
const mockUpdatedPost = {
|
|
568
|
-
id: '507f1f77bcf86cd799439011',
|
|
569
|
-
title: 'Updated Title',
|
|
570
|
-
slug: 'test-post',
|
|
571
|
-
html: '<p>Content</p>',
|
|
572
|
-
status: 'published',
|
|
573
|
-
updated_at: '2025-12-10T12:00:00.000Z',
|
|
574
|
-
};
|
|
575
|
-
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
576
|
-
|
|
577
|
-
const tool = mockTools.get('ghost_update_post');
|
|
578
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: 'Updated Title' });
|
|
579
|
-
|
|
580
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
581
|
-
title: 'Updated Title',
|
|
582
|
-
});
|
|
583
|
-
expect(result.content[0].text).toContain('"title": "Updated Title"');
|
|
584
|
-
});
|
|
585
|
-
|
|
586
|
-
it('should update post content', async () => {
|
|
587
|
-
const mockUpdatedPost = {
|
|
588
|
-
id: '507f1f77bcf86cd799439011',
|
|
589
|
-
title: 'Test Post',
|
|
590
|
-
html: '<p>Updated content</p>',
|
|
591
|
-
status: 'published',
|
|
592
|
-
updated_at: '2025-12-10T12:00:00.000Z',
|
|
593
|
-
};
|
|
594
|
-
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
595
|
-
|
|
596
|
-
const tool = mockTools.get('ghost_update_post');
|
|
597
|
-
const result = await tool.handler({
|
|
598
|
-
id: '507f1f77bcf86cd799439011',
|
|
599
|
-
html: '<p>Updated content</p>',
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
603
|
-
html: '<p>Updated content</p>',
|
|
604
|
-
});
|
|
605
|
-
expect(result.content[0].text).toContain('Updated content');
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
it('should update post status', async () => {
|
|
609
|
-
const mockUpdatedPost = {
|
|
610
|
-
id: '507f1f77bcf86cd799439011',
|
|
611
|
-
title: 'Test Post',
|
|
612
|
-
html: '<p>Content</p>',
|
|
613
|
-
status: 'published',
|
|
614
|
-
updated_at: '2025-12-10T12:00:00.000Z',
|
|
615
|
-
};
|
|
616
|
-
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
617
|
-
|
|
618
|
-
const tool = mockTools.get('ghost_update_post');
|
|
619
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', status: 'published' });
|
|
620
|
-
|
|
621
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
622
|
-
status: 'published',
|
|
623
|
-
});
|
|
624
|
-
expect(result.content[0].text).toContain('"status": "published"');
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
it('should update post tags', async () => {
|
|
628
|
-
const mockUpdatedPost = {
|
|
629
|
-
id: '507f1f77bcf86cd799439011',
|
|
630
|
-
title: 'Test Post',
|
|
631
|
-
html: '<p>Content</p>',
|
|
632
|
-
tags: [{ name: 'tech' }, { name: 'javascript' }],
|
|
633
|
-
updated_at: '2025-12-10T12:00:00.000Z',
|
|
634
|
-
};
|
|
635
|
-
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
636
|
-
|
|
637
|
-
const tool = mockTools.get('ghost_update_post');
|
|
638
|
-
const result = await tool.handler({
|
|
639
|
-
id: '507f1f77bcf86cd799439011',
|
|
640
|
-
tags: ['tech', 'javascript'],
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
644
|
-
tags: ['tech', 'javascript'],
|
|
645
|
-
});
|
|
646
|
-
expect(result.content[0].text).toContain('tech');
|
|
647
|
-
expect(result.content[0].text).toContain('javascript');
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
it('should update post featured image', async () => {
|
|
651
|
-
const mockUpdatedPost = {
|
|
652
|
-
id: '507f1f77bcf86cd799439011',
|
|
653
|
-
title: 'Test Post',
|
|
654
|
-
feature_image: 'https://example.com/new-image.jpg',
|
|
655
|
-
feature_image_alt: 'New image',
|
|
656
|
-
updated_at: '2025-12-10T12:00:00.000Z',
|
|
657
|
-
};
|
|
658
|
-
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
659
|
-
|
|
660
|
-
const tool = mockTools.get('ghost_update_post');
|
|
661
|
-
const result = await tool.handler({
|
|
662
|
-
id: '507f1f77bcf86cd799439011',
|
|
663
|
-
feature_image: 'https://example.com/new-image.jpg',
|
|
664
|
-
feature_image_alt: 'New image',
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
668
|
-
feature_image: 'https://example.com/new-image.jpg',
|
|
669
|
-
feature_image_alt: 'New image',
|
|
670
|
-
});
|
|
671
|
-
expect(result.content[0].text).toContain('new-image.jpg');
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
it('should update SEO meta fields', async () => {
|
|
675
|
-
const mockUpdatedPost = {
|
|
676
|
-
id: '507f1f77bcf86cd799439011',
|
|
677
|
-
title: 'Test Post',
|
|
678
|
-
meta_title: 'SEO Title',
|
|
679
|
-
meta_description: 'SEO Description',
|
|
680
|
-
updated_at: '2025-12-10T12:00:00.000Z',
|
|
681
|
-
};
|
|
682
|
-
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
683
|
-
|
|
684
|
-
const tool = mockTools.get('ghost_update_post');
|
|
685
|
-
const result = await tool.handler({
|
|
686
|
-
id: '507f1f77bcf86cd799439011',
|
|
687
|
-
meta_title: 'SEO Title',
|
|
688
|
-
meta_description: 'SEO Description',
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
692
|
-
meta_title: 'SEO Title',
|
|
693
|
-
meta_description: 'SEO Description',
|
|
694
|
-
});
|
|
695
|
-
expect(result.content[0].text).toContain('SEO Title');
|
|
696
|
-
expect(result.content[0].text).toContain('SEO Description');
|
|
697
|
-
});
|
|
698
|
-
|
|
699
|
-
it('should update multiple fields at once', async () => {
|
|
700
|
-
const mockUpdatedPost = {
|
|
701
|
-
id: '507f1f77bcf86cd799439011',
|
|
702
|
-
title: 'Updated Title',
|
|
703
|
-
html: '<p>Updated content</p>',
|
|
704
|
-
status: 'published',
|
|
705
|
-
tags: [{ name: 'tech' }],
|
|
706
|
-
updated_at: '2025-12-10T12:00:00.000Z',
|
|
707
|
-
};
|
|
708
|
-
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
709
|
-
|
|
710
|
-
const tool = mockTools.get('ghost_update_post');
|
|
711
|
-
const result = await tool.handler({
|
|
712
|
-
id: '507f1f77bcf86cd799439011',
|
|
713
|
-
title: 'Updated Title',
|
|
714
|
-
html: '<p>Updated content</p>',
|
|
715
|
-
status: 'published',
|
|
716
|
-
tags: ['tech'],
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
720
|
-
title: 'Updated Title',
|
|
721
|
-
html: '<p>Updated content</p>',
|
|
722
|
-
status: 'published',
|
|
723
|
-
tags: ['tech'],
|
|
724
|
-
});
|
|
725
|
-
expect(result.content[0].text).toContain('Updated Title');
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
it('should handle not found errors', async () => {
|
|
729
|
-
mockUpdatePost.mockRejectedValue(new Error('Post not found'));
|
|
730
|
-
|
|
731
|
-
const tool = mockTools.get('ghost_update_post');
|
|
732
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439099', title: 'New Title' });
|
|
733
|
-
|
|
734
|
-
expect(result.isError).toBe(true);
|
|
735
|
-
expect(result.content[0].text).toContain('Post not found');
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
it('should handle validation errors', async () => {
|
|
739
|
-
mockUpdatePost.mockRejectedValue(new Error('Validation failed: Title is required'));
|
|
740
|
-
|
|
741
|
-
const tool = mockTools.get('ghost_update_post');
|
|
742
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: '' });
|
|
743
|
-
|
|
744
|
-
expect(result.isError).toBe(true);
|
|
745
|
-
expect(result.content[0].text).toContain('Validation failed');
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
it('should handle Ghost API errors', async () => {
|
|
749
|
-
mockUpdatePost.mockRejectedValue(new Error('Ghost API error: Server timeout'));
|
|
750
|
-
|
|
751
|
-
const tool = mockTools.get('ghost_update_post');
|
|
752
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: 'Updated' });
|
|
753
|
-
|
|
754
|
-
expect(result.isError).toBe(true);
|
|
755
|
-
expect(result.content[0].text).toContain('Ghost API error');
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
it('should return formatted JSON response', async () => {
|
|
759
|
-
const mockUpdatedPost = {
|
|
760
|
-
id: '507f1f77bcf86cd799439011',
|
|
761
|
-
uuid: 'uuid-123',
|
|
762
|
-
title: 'Updated Post',
|
|
763
|
-
slug: 'updated-post',
|
|
764
|
-
html: '<p>Updated content</p>',
|
|
765
|
-
status: 'published',
|
|
766
|
-
created_at: '2025-12-09T00:00:00.000Z',
|
|
767
|
-
updated_at: '2025-12-10T12:00:00.000Z',
|
|
768
|
-
};
|
|
769
|
-
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
770
|
-
|
|
771
|
-
const tool = mockTools.get('ghost_update_post');
|
|
772
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: 'Updated Post' });
|
|
773
|
-
|
|
774
|
-
expect(result.content).toBeDefined();
|
|
775
|
-
expect(result.content[0].type).toBe('text');
|
|
776
|
-
expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
|
|
777
|
-
expect(result.content[0].text).toContain('"title": "Updated Post"');
|
|
778
|
-
expect(result.content[0].text).toContain('"status": "published"');
|
|
779
|
-
});
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
describe('mcp_server_improved - ghost_delete_post tool', () => {
|
|
783
|
-
beforeEach(async () => {
|
|
784
|
-
vi.clearAllMocks();
|
|
785
|
-
// Don't clear mockTools - they're registered once on module load
|
|
786
|
-
if (mockTools.size === 0) {
|
|
787
|
-
await import('../mcp_server_improved.js');
|
|
788
|
-
}
|
|
789
|
-
});
|
|
790
|
-
|
|
791
|
-
it('should register ghost_delete_post tool', () => {
|
|
792
|
-
expect(mockTools.has('ghost_delete_post')).toBe(true);
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
it('should have correct schema with required id field', () => {
|
|
796
|
-
const tool = mockTools.get('ghost_delete_post');
|
|
797
|
-
expect(tool).toBeDefined();
|
|
798
|
-
expect(tool.description).toContain('Deletes a post');
|
|
799
|
-
expect(tool.description).toContain('permanent');
|
|
800
|
-
expect(tool.schema).toBeDefined();
|
|
801
|
-
// Zod schemas store field definitions in schema.shape
|
|
802
|
-
expect(tool.schema.shape.id).toBeDefined();
|
|
803
|
-
});
|
|
804
|
-
|
|
805
|
-
it('should delete post by ID', async () => {
|
|
806
|
-
mockDeletePost.mockResolvedValue({ deleted: true });
|
|
807
|
-
|
|
808
|
-
const tool = mockTools.get('ghost_delete_post');
|
|
809
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
810
|
-
|
|
811
|
-
expect(mockDeletePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011');
|
|
812
|
-
expect(result.content[0].text).toContain(
|
|
813
|
-
'Post 507f1f77bcf86cd799439011 has been successfully deleted'
|
|
814
|
-
);
|
|
815
|
-
expect(result.isError).toBeUndefined();
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
it('should handle not found errors', async () => {
|
|
819
|
-
mockDeletePost.mockRejectedValue(new Error('Post not found'));
|
|
820
|
-
|
|
821
|
-
const tool = mockTools.get('ghost_delete_post');
|
|
822
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
823
|
-
|
|
824
|
-
expect(result.isError).toBe(true);
|
|
825
|
-
expect(result.content[0].text).toContain('Post not found');
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
it('should handle Ghost API errors', async () => {
|
|
829
|
-
mockDeletePost.mockRejectedValue(new Error('Ghost API error: Permission denied'));
|
|
830
|
-
|
|
831
|
-
const tool = mockTools.get('ghost_delete_post');
|
|
832
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
833
|
-
|
|
834
|
-
expect(result.isError).toBe(true);
|
|
835
|
-
expect(result.content[0].text).toContain('Ghost API error');
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
it('should return success message on successful deletion', async () => {
|
|
839
|
-
mockDeletePost.mockResolvedValue({ deleted: true });
|
|
840
|
-
|
|
841
|
-
const tool = mockTools.get('ghost_delete_post');
|
|
842
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
843
|
-
|
|
844
|
-
expect(result.content).toBeDefined();
|
|
845
|
-
expect(result.content[0].type).toBe('text');
|
|
846
|
-
expect(result.content[0].text).toBe(
|
|
847
|
-
'Post 507f1f77bcf86cd799439011 has been successfully deleted.'
|
|
848
|
-
);
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
it('should handle network errors', async () => {
|
|
852
|
-
mockDeletePost.mockRejectedValue(new Error('Network error: Connection refused'));
|
|
853
|
-
|
|
854
|
-
const tool = mockTools.get('ghost_delete_post');
|
|
855
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439012' });
|
|
856
|
-
|
|
857
|
-
expect(result.isError).toBe(true);
|
|
858
|
-
expect(result.content[0].text).toContain('Network error');
|
|
859
|
-
});
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
describe('mcp_server_improved - ghost_search_posts tool', () => {
|
|
863
|
-
beforeEach(async () => {
|
|
864
|
-
vi.clearAllMocks();
|
|
865
|
-
// Don't clear mockTools - they're registered once on module load
|
|
866
|
-
if (mockTools.size === 0) {
|
|
867
|
-
await import('../mcp_server_improved.js');
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
|
|
871
|
-
it('should register ghost_search_posts tool', () => {
|
|
872
|
-
expect(mockTools.has('ghost_search_posts')).toBe(true);
|
|
873
|
-
});
|
|
874
|
-
|
|
875
|
-
it('should have correct schema with required query and optional parameters', () => {
|
|
876
|
-
const tool = mockTools.get('ghost_search_posts');
|
|
877
|
-
expect(tool).toBeDefined();
|
|
878
|
-
expect(tool.description).toContain('Search');
|
|
879
|
-
expect(tool.schema).toBeDefined();
|
|
880
|
-
// Zod schemas store field definitions in schema.shape
|
|
881
|
-
expect(tool.schema.shape.query).toBeDefined();
|
|
882
|
-
expect(tool.schema.shape.status).toBeDefined();
|
|
883
|
-
expect(tool.schema.shape.limit).toBeDefined();
|
|
884
|
-
});
|
|
885
|
-
|
|
886
|
-
it('should search posts with query only', async () => {
|
|
887
|
-
const mockPosts = [
|
|
888
|
-
{ id: '1', title: 'JavaScript Tips', slug: 'javascript-tips', status: 'published' },
|
|
889
|
-
{ id: '2', title: 'JavaScript Tricks', slug: 'javascript-tricks', status: 'published' },
|
|
890
|
-
];
|
|
891
|
-
mockSearchPosts.mockResolvedValue(mockPosts);
|
|
892
|
-
|
|
893
|
-
const tool = mockTools.get('ghost_search_posts');
|
|
894
|
-
const result = await tool.handler({ query: 'JavaScript' });
|
|
895
|
-
|
|
896
|
-
expect(mockSearchPosts).toHaveBeenCalledWith('JavaScript', {});
|
|
897
|
-
expect(result.content[0].text).toContain('JavaScript Tips');
|
|
898
|
-
expect(result.content[0].text).toContain('JavaScript Tricks');
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
it('should search posts with query and status filter', async () => {
|
|
902
|
-
const mockPosts = [
|
|
903
|
-
{ id: '1', title: 'Published Post', slug: 'published-post', status: 'published' },
|
|
904
|
-
];
|
|
905
|
-
mockSearchPosts.mockResolvedValue(mockPosts);
|
|
906
|
-
|
|
907
|
-
const tool = mockTools.get('ghost_search_posts');
|
|
908
|
-
await tool.handler({ query: 'test', status: 'published' });
|
|
909
|
-
|
|
910
|
-
expect(mockSearchPosts).toHaveBeenCalledWith('test', { status: 'published' });
|
|
911
|
-
});
|
|
912
|
-
|
|
913
|
-
it('should search posts with query and limit', async () => {
|
|
914
|
-
const mockPosts = [{ id: '1', title: 'Test Post', slug: 'test-post' }];
|
|
915
|
-
mockSearchPosts.mockResolvedValue(mockPosts);
|
|
916
|
-
|
|
917
|
-
const tool = mockTools.get('ghost_search_posts');
|
|
918
|
-
await tool.handler({ query: 'test', limit: 10 });
|
|
919
|
-
|
|
920
|
-
expect(mockSearchPosts).toHaveBeenCalledWith('test', { limit: 10 });
|
|
921
|
-
});
|
|
922
|
-
|
|
923
|
-
it('should validate limit is between 1 and 50', () => {
|
|
924
|
-
const tool = mockTools.get('ghost_search_posts');
|
|
925
|
-
// Zod schemas store field definitions in schema.shape
|
|
926
|
-
const shape = tool.schema.shape;
|
|
927
|
-
|
|
928
|
-
expect(shape.limit).toBeDefined();
|
|
929
|
-
expect(() => shape.limit.parse(0)).toThrow();
|
|
930
|
-
expect(() => shape.limit.parse(51)).toThrow();
|
|
931
|
-
expect(shape.limit.parse(25)).toBe(25);
|
|
932
|
-
});
|
|
933
|
-
|
|
934
|
-
it('should validate status enum values', () => {
|
|
935
|
-
const tool = mockTools.get('ghost_search_posts');
|
|
936
|
-
// Zod schemas store field definitions in schema.shape
|
|
937
|
-
const shape = tool.schema.shape;
|
|
938
|
-
|
|
939
|
-
expect(shape.status).toBeDefined();
|
|
940
|
-
expect(() => shape.status.parse('invalid')).toThrow();
|
|
941
|
-
expect(shape.status.parse('published')).toBe('published');
|
|
942
|
-
expect(shape.status.parse('draft')).toBe('draft');
|
|
943
|
-
expect(shape.status.parse('scheduled')).toBe('scheduled');
|
|
944
|
-
expect(shape.status.parse('all')).toBe('all');
|
|
945
|
-
});
|
|
946
|
-
|
|
947
|
-
it('should pass all parameters combined', async () => {
|
|
948
|
-
const mockPosts = [{ id: '1', title: 'Test Post' }];
|
|
949
|
-
mockSearchPosts.mockResolvedValue(mockPosts);
|
|
950
|
-
|
|
951
|
-
const tool = mockTools.get('ghost_search_posts');
|
|
952
|
-
await tool.handler({
|
|
953
|
-
query: 'JavaScript',
|
|
954
|
-
status: 'published',
|
|
955
|
-
limit: 20,
|
|
956
|
-
});
|
|
957
|
-
|
|
958
|
-
expect(mockSearchPosts).toHaveBeenCalledWith('JavaScript', {
|
|
959
|
-
status: 'published',
|
|
960
|
-
limit: 20,
|
|
961
|
-
});
|
|
962
|
-
});
|
|
963
|
-
|
|
964
|
-
it('should handle errors from searchPosts', async () => {
|
|
965
|
-
// Empty query is now caught by Zod validation
|
|
966
|
-
const tool = mockTools.get('ghost_search_posts');
|
|
967
|
-
const result = await tool.handler({ query: '' });
|
|
968
|
-
|
|
969
|
-
expect(result.isError).toBe(true);
|
|
970
|
-
expect(result.content[0].text).toContain('VALIDATION_ERROR');
|
|
971
|
-
});
|
|
972
|
-
|
|
973
|
-
it('should handle Ghost API errors', async () => {
|
|
974
|
-
mockSearchPosts.mockRejectedValue(new Error('Ghost API error'));
|
|
975
|
-
|
|
976
|
-
const tool = mockTools.get('ghost_search_posts');
|
|
977
|
-
const result = await tool.handler({ query: 'test' });
|
|
978
|
-
|
|
979
|
-
expect(result.isError).toBe(true);
|
|
980
|
-
expect(result.content[0].text).toContain('Ghost API error');
|
|
981
|
-
});
|
|
982
|
-
|
|
983
|
-
it('should return formatted JSON response', async () => {
|
|
984
|
-
const mockPosts = [
|
|
985
|
-
{
|
|
986
|
-
id: '1',
|
|
987
|
-
title: 'Test Post',
|
|
988
|
-
slug: 'test-post',
|
|
989
|
-
html: '<p>Content</p>',
|
|
990
|
-
status: 'published',
|
|
991
|
-
},
|
|
992
|
-
];
|
|
993
|
-
mockSearchPosts.mockResolvedValue(mockPosts);
|
|
994
|
-
|
|
995
|
-
const tool = mockTools.get('ghost_search_posts');
|
|
996
|
-
const result = await tool.handler({ query: 'Test' });
|
|
997
|
-
|
|
998
|
-
expect(result.content).toBeDefined();
|
|
999
|
-
expect(result.content[0].type).toBe('text');
|
|
1000
|
-
expect(result.content[0].text).toContain('"id": "1"');
|
|
1001
|
-
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
it('should return empty array when no results found', async () => {
|
|
1005
|
-
mockSearchPosts.mockResolvedValue([]);
|
|
1006
|
-
|
|
1007
|
-
const tool = mockTools.get('ghost_search_posts');
|
|
1008
|
-
const result = await tool.handler({ query: 'nonexistent' });
|
|
1009
|
-
|
|
1010
|
-
expect(result.content[0].text).toBe('[]');
|
|
1011
|
-
expect(result.isError).toBeUndefined();
|
|
1012
|
-
});
|
|
1013
|
-
});
|
|
1014
|
-
|
|
1015
|
-
describe('ghost_get_tag', () => {
|
|
1016
|
-
beforeEach(() => {
|
|
1017
|
-
vi.clearAllMocks();
|
|
1018
|
-
});
|
|
1019
|
-
|
|
1020
|
-
it('should be registered as a tool', () => {
|
|
1021
|
-
expect(mockTools.has('ghost_get_tag')).toBe(true);
|
|
1022
|
-
const tool = mockTools.get('ghost_get_tag');
|
|
1023
|
-
expect(tool.name).toBe('ghost_get_tag');
|
|
1024
|
-
expect(tool.description).toBeDefined();
|
|
1025
|
-
expect(tool.schema).toBeDefined();
|
|
1026
|
-
expect(tool.handler).toBeDefined();
|
|
1027
|
-
});
|
|
1028
|
-
|
|
1029
|
-
it('should have correct schema with id and slug as optional', () => {
|
|
1030
|
-
const tool = mockTools.get('ghost_get_tag');
|
|
1031
|
-
// ghost_get_tag uses a refined schema, access via _def.schema.shape
|
|
1032
|
-
const shape = tool.schema._def.schema.shape;
|
|
1033
|
-
expect(shape.id).toBeDefined();
|
|
1034
|
-
expect(shape.slug).toBeDefined();
|
|
1035
|
-
expect(shape.include).toBeDefined();
|
|
1036
|
-
});
|
|
1037
|
-
|
|
1038
|
-
it('should retrieve tag by ID', async () => {
|
|
1039
|
-
const mockTag = {
|
|
1040
|
-
id: '507f1f77bcf86cd799439011',
|
|
1041
|
-
name: 'Test Tag',
|
|
1042
|
-
slug: 'test-tag',
|
|
1043
|
-
description: 'A test tag',
|
|
1044
|
-
};
|
|
1045
|
-
mockGetTag.mockResolvedValue(mockTag);
|
|
1046
|
-
|
|
1047
|
-
const tool = mockTools.get('ghost_get_tag');
|
|
1048
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
1049
|
-
|
|
1050
|
-
expect(mockGetTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {});
|
|
1051
|
-
expect(result.content).toBeDefined();
|
|
1052
|
-
expect(result.content[0].type).toBe('text');
|
|
1053
|
-
expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
|
|
1054
|
-
expect(result.content[0].text).toContain('"name": "Test Tag"');
|
|
1055
|
-
});
|
|
1056
|
-
|
|
1057
|
-
it('should retrieve tag by slug', async () => {
|
|
1058
|
-
const mockTag = {
|
|
1059
|
-
id: '507f1f77bcf86cd799439011',
|
|
1060
|
-
name: 'Test Tag',
|
|
1061
|
-
slug: 'test-tag',
|
|
1062
|
-
description: 'A test tag',
|
|
1063
|
-
};
|
|
1064
|
-
mockGetTag.mockResolvedValue(mockTag);
|
|
1065
|
-
|
|
1066
|
-
const tool = mockTools.get('ghost_get_tag');
|
|
1067
|
-
const result = await tool.handler({ slug: 'test-tag' });
|
|
1068
|
-
|
|
1069
|
-
expect(mockGetTag).toHaveBeenCalledWith('slug/test-tag', {});
|
|
1070
|
-
expect(result.content[0].text).toContain('"slug": "test-tag"');
|
|
1071
|
-
});
|
|
1072
|
-
|
|
1073
|
-
it('should support include parameter for post count', async () => {
|
|
1074
|
-
const mockTag = {
|
|
1075
|
-
id: '507f1f77bcf86cd799439011',
|
|
1076
|
-
name: 'Test Tag',
|
|
1077
|
-
slug: 'test-tag',
|
|
1078
|
-
count: { posts: 5 },
|
|
1079
|
-
};
|
|
1080
|
-
mockGetTag.mockResolvedValue(mockTag);
|
|
1081
|
-
|
|
1082
|
-
const tool = mockTools.get('ghost_get_tag');
|
|
1083
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', include: 'count.posts' });
|
|
1084
|
-
|
|
1085
|
-
expect(mockGetTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', { include: 'count.posts' });
|
|
1086
|
-
expect(result.content[0].text).toContain('"count"');
|
|
1087
|
-
});
|
|
1088
|
-
|
|
1089
|
-
it('should return error when neither id nor slug provided', async () => {
|
|
1090
|
-
const tool = mockTools.get('ghost_get_tag');
|
|
1091
|
-
const result = await tool.handler({});
|
|
1092
|
-
|
|
1093
|
-
expect(result.content[0].type).toBe('text');
|
|
1094
|
-
expect(result.content[0].text).toContain('Either id or slug is required');
|
|
1095
|
-
expect(result.isError).toBe(true);
|
|
1096
|
-
});
|
|
1097
|
-
|
|
1098
|
-
it('should handle not found error', async () => {
|
|
1099
|
-
mockGetTag.mockRejectedValue(new Error('Tag not found'));
|
|
1100
|
-
|
|
1101
|
-
const tool = mockTools.get('ghost_get_tag');
|
|
1102
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
1103
|
-
|
|
1104
|
-
expect(result.isError).toBe(true);
|
|
1105
|
-
expect(result.content[0].text).toContain('Tag not found');
|
|
1106
|
-
});
|
|
1107
|
-
});
|
|
1108
|
-
|
|
1109
|
-
describe('ghost_update_tag', () => {
|
|
1110
|
-
beforeEach(() => {
|
|
1111
|
-
vi.clearAllMocks();
|
|
1112
|
-
});
|
|
1113
|
-
|
|
1114
|
-
it('should be registered as a tool', () => {
|
|
1115
|
-
expect(mockTools.has('ghost_update_tag')).toBe(true);
|
|
1116
|
-
const tool = mockTools.get('ghost_update_tag');
|
|
1117
|
-
expect(tool.name).toBe('ghost_update_tag');
|
|
1118
|
-
expect(tool.description).toBeDefined();
|
|
1119
|
-
expect(tool.schema).toBeDefined();
|
|
1120
|
-
expect(tool.handler).toBeDefined();
|
|
1121
|
-
});
|
|
1122
|
-
|
|
1123
|
-
it('should have correct schema with all update fields', () => {
|
|
1124
|
-
const tool = mockTools.get('ghost_update_tag');
|
|
1125
|
-
// Zod schemas store field definitions in schema.shape
|
|
1126
|
-
expect(tool.schema.shape.id).toBeDefined();
|
|
1127
|
-
expect(tool.schema.shape.name).toBeDefined();
|
|
1128
|
-
expect(tool.schema.shape.slug).toBeDefined();
|
|
1129
|
-
expect(tool.schema.shape.description).toBeDefined();
|
|
1130
|
-
expect(tool.schema.shape.feature_image).toBeDefined();
|
|
1131
|
-
expect(tool.schema.shape.meta_title).toBeDefined();
|
|
1132
|
-
expect(tool.schema.shape.meta_description).toBeDefined();
|
|
1133
|
-
});
|
|
1134
|
-
|
|
1135
|
-
it('should update tag name', async () => {
|
|
1136
|
-
const mockUpdatedTag = {
|
|
1137
|
-
id: '507f1f77bcf86cd799439011',
|
|
1138
|
-
name: 'Updated Tag',
|
|
1139
|
-
slug: 'updated-tag',
|
|
1140
|
-
};
|
|
1141
|
-
mockUpdateTag.mockResolvedValue(mockUpdatedTag);
|
|
1142
|
-
|
|
1143
|
-
const tool = mockTools.get('ghost_update_tag');
|
|
1144
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', name: 'Updated Tag' });
|
|
1145
|
-
|
|
1146
|
-
expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', { name: 'Updated Tag' });
|
|
1147
|
-
expect(result.content[0].text).toContain('"name": "Updated Tag"');
|
|
1148
|
-
});
|
|
1149
|
-
|
|
1150
|
-
it('should update tag description', async () => {
|
|
1151
|
-
const mockUpdatedTag = {
|
|
1152
|
-
id: '507f1f77bcf86cd799439011',
|
|
1153
|
-
name: 'Test Tag',
|
|
1154
|
-
description: 'New description',
|
|
1155
|
-
};
|
|
1156
|
-
mockUpdateTag.mockResolvedValue(mockUpdatedTag);
|
|
1157
|
-
|
|
1158
|
-
const tool = mockTools.get('ghost_update_tag');
|
|
1159
|
-
const result = await tool.handler({
|
|
1160
|
-
id: '507f1f77bcf86cd799439011',
|
|
1161
|
-
description: 'New description',
|
|
1162
|
-
});
|
|
1163
|
-
|
|
1164
|
-
expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
1165
|
-
description: 'New description',
|
|
1166
|
-
});
|
|
1167
|
-
expect(result.content[0].text).toContain('"description": "New description"');
|
|
1168
|
-
});
|
|
1169
|
-
|
|
1170
|
-
it('should update multiple fields at once', async () => {
|
|
1171
|
-
const mockUpdatedTag = {
|
|
1172
|
-
id: '507f1f77bcf86cd799439011',
|
|
1173
|
-
name: 'Updated Tag',
|
|
1174
|
-
slug: 'updated-tag',
|
|
1175
|
-
description: 'Updated description',
|
|
1176
|
-
meta_title: 'Updated Meta',
|
|
1177
|
-
};
|
|
1178
|
-
mockUpdateTag.mockResolvedValue(mockUpdatedTag);
|
|
1179
|
-
|
|
1180
|
-
const tool = mockTools.get('ghost_update_tag');
|
|
1181
|
-
await tool.handler({
|
|
1182
|
-
id: '507f1f77bcf86cd799439011',
|
|
1183
|
-
name: 'Updated Tag',
|
|
1184
|
-
description: 'Updated description',
|
|
1185
|
-
meta_title: 'Updated Meta',
|
|
1186
|
-
});
|
|
1187
|
-
|
|
1188
|
-
expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
1189
|
-
name: 'Updated Tag',
|
|
1190
|
-
description: 'Updated description',
|
|
1191
|
-
meta_title: 'Updated Meta',
|
|
1192
|
-
});
|
|
1193
|
-
});
|
|
1194
|
-
|
|
1195
|
-
it('should update tag feature image', async () => {
|
|
1196
|
-
const mockUpdatedTag = {
|
|
1197
|
-
id: '507f1f77bcf86cd799439011',
|
|
1198
|
-
name: 'Test Tag',
|
|
1199
|
-
feature_image: 'https://example.com/image.jpg',
|
|
1200
|
-
};
|
|
1201
|
-
mockUpdateTag.mockResolvedValue(mockUpdatedTag);
|
|
1202
|
-
|
|
1203
|
-
const tool = mockTools.get('ghost_update_tag');
|
|
1204
|
-
await tool.handler({
|
|
1205
|
-
id: '507f1f77bcf86cd799439011',
|
|
1206
|
-
feature_image: 'https://example.com/image.jpg',
|
|
1207
|
-
});
|
|
1208
|
-
|
|
1209
|
-
expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
1210
|
-
feature_image: 'https://example.com/image.jpg',
|
|
1211
|
-
});
|
|
1212
|
-
});
|
|
1213
|
-
|
|
1214
|
-
it('should return error when id is missing', async () => {
|
|
1215
|
-
const tool = mockTools.get('ghost_update_tag');
|
|
1216
|
-
const result = await tool.handler({ name: 'Test' });
|
|
1217
|
-
|
|
1218
|
-
expect(result.isError).toBe(true);
|
|
1219
|
-
expect(result.content[0].text).toContain('VALIDATION_ERROR');
|
|
1220
|
-
});
|
|
1221
|
-
|
|
1222
|
-
it('should handle validation error', async () => {
|
|
1223
|
-
mockUpdateTag.mockRejectedValue(new Error('Validation failed'));
|
|
1224
|
-
|
|
1225
|
-
const tool = mockTools.get('ghost_update_tag');
|
|
1226
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', name: '' });
|
|
1227
|
-
|
|
1228
|
-
expect(result.isError).toBe(true);
|
|
1229
|
-
expect(result.content[0].text).toContain('Validation failed');
|
|
1230
|
-
});
|
|
1231
|
-
|
|
1232
|
-
it('should handle not found error', async () => {
|
|
1233
|
-
mockUpdateTag.mockRejectedValue(new Error('Tag not found'));
|
|
1234
|
-
|
|
1235
|
-
const tool = mockTools.get('ghost_update_tag');
|
|
1236
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439099', name: 'Test' });
|
|
1237
|
-
|
|
1238
|
-
expect(result.isError).toBe(true);
|
|
1239
|
-
expect(result.content[0].text).toContain('Tag not found');
|
|
1240
|
-
});
|
|
1241
|
-
});
|
|
1242
|
-
|
|
1243
|
-
describe('ghost_delete_tag', () => {
|
|
1244
|
-
beforeEach(() => {
|
|
1245
|
-
vi.clearAllMocks();
|
|
1246
|
-
});
|
|
1247
|
-
|
|
1248
|
-
it('should be registered as a tool', () => {
|
|
1249
|
-
expect(mockTools.has('ghost_delete_tag')).toBe(true);
|
|
1250
|
-
const tool = mockTools.get('ghost_delete_tag');
|
|
1251
|
-
expect(tool.name).toBe('ghost_delete_tag');
|
|
1252
|
-
expect(tool.description).toBeDefined();
|
|
1253
|
-
expect(tool.schema).toBeDefined();
|
|
1254
|
-
expect(tool.handler).toBeDefined();
|
|
1255
|
-
});
|
|
1256
|
-
|
|
1257
|
-
it('should have correct schema with id field', () => {
|
|
1258
|
-
const tool = mockTools.get('ghost_delete_tag');
|
|
1259
|
-
// Zod schemas store field definitions in schema.shape
|
|
1260
|
-
expect(tool.schema.shape.id).toBeDefined();
|
|
1261
|
-
});
|
|
1262
|
-
|
|
1263
|
-
it('should delete tag successfully', async () => {
|
|
1264
|
-
mockDeleteTag.mockResolvedValue({ success: true });
|
|
1265
|
-
|
|
1266
|
-
const tool = mockTools.get('ghost_delete_tag');
|
|
1267
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
1268
|
-
|
|
1269
|
-
expect(mockDeleteTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011');
|
|
1270
|
-
expect(result.content[0].text).toContain('successfully deleted');
|
|
1271
|
-
expect(result.isError).toBeUndefined();
|
|
1272
|
-
});
|
|
1273
|
-
|
|
1274
|
-
it('should return error when id is missing', async () => {
|
|
1275
|
-
const tool = mockTools.get('ghost_delete_tag');
|
|
1276
|
-
const result = await tool.handler({});
|
|
1277
|
-
|
|
1278
|
-
expect(result.isError).toBe(true);
|
|
1279
|
-
expect(result.content[0].text).toContain('VALIDATION_ERROR');
|
|
1280
|
-
});
|
|
1281
|
-
|
|
1282
|
-
it('should handle not found error', async () => {
|
|
1283
|
-
mockDeleteTag.mockRejectedValue(new Error('Tag not found'));
|
|
1284
|
-
|
|
1285
|
-
const tool = mockTools.get('ghost_delete_tag');
|
|
1286
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
1287
|
-
|
|
1288
|
-
expect(result.isError).toBe(true);
|
|
1289
|
-
expect(result.content[0].text).toContain('Tag not found');
|
|
1290
|
-
});
|
|
1291
|
-
|
|
1292
|
-
it('should handle deletion error', async () => {
|
|
1293
|
-
mockDeleteTag.mockRejectedValue(new Error('Failed to delete tag'));
|
|
1294
|
-
|
|
1295
|
-
const tool = mockTools.get('ghost_delete_tag');
|
|
1296
|
-
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
1297
|
-
|
|
1298
|
-
expect(result.isError).toBe(true);
|
|
1299
|
-
expect(result.content[0].text).toContain('Failed to delete tag');
|
|
1300
|
-
});
|
|
1301
|
-
});
|