@jgardner04/ghost-mcp-server 1.11.0 → 1.12.1
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 +199 -43
- package/package.json +2 -1
- package/src/__tests__/mcp_server.test.js +10 -4
- package/src/__tests__/mcp_server_improved.test.js +261 -156
- package/src/__tests__/mcp_server_pages.test.js +72 -68
- package/src/errors/__tests__/index.test.js +70 -0
- package/src/errors/index.js +10 -0
- package/src/mcp_server.js +9 -19
- package/src/mcp_server_improved.js +716 -531
- package/src/schemas/__tests__/common.test.js +84 -0
- package/src/schemas/__tests__/memberSchemas.test.js +447 -0
- package/src/schemas/__tests__/newsletterSchemas.test.js +399 -0
- package/src/schemas/__tests__/pageSchemas.test.js +518 -0
- package/src/schemas/__tests__/tierSchemas.test.js +574 -0
- package/src/schemas/common.js +50 -3
- package/src/services/__tests__/ghostServiceImproved.members.test.js +12 -61
- package/src/services/__tests__/postService.test.js +7 -99
- package/src/services/ghostServiceImproved.js +10 -21
- package/src/services/postService.js +4 -30
- package/src/utils/__tests__/tempFileManager.test.js +316 -0
- package/src/utils/__tests__/validation.test.js +163 -0
- package/src/utils/tempFileManager.js +113 -0
- package/src/utils/validation.js +28 -0
|
@@ -54,25 +54,87 @@ const mockUpdatePost = vi.fn();
|
|
|
54
54
|
const mockDeletePost = vi.fn();
|
|
55
55
|
const mockSearchPosts = vi.fn();
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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();
|
|
64
86
|
|
|
65
87
|
vi.mock('../services/postService.js', () => ({
|
|
66
88
|
createPostService: (...args) => mockCreatePostService(...args),
|
|
67
89
|
}));
|
|
68
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
|
+
|
|
69
99
|
vi.mock('../services/ghostServiceImproved.js', () => ({
|
|
100
|
+
// Posts
|
|
101
|
+
getPosts: (...args) => mockGetPosts(...args),
|
|
102
|
+
getPost: (...args) => mockGetPost(...args),
|
|
70
103
|
updatePost: (...args) => mockUpdatePost(...args),
|
|
71
104
|
deletePost: (...args) => mockDeletePost(...args),
|
|
72
105
|
searchPosts: (...args) => mockSearchPosts(...args),
|
|
106
|
+
// Tags
|
|
107
|
+
getTags: (...args) => mockGetTags(...args),
|
|
73
108
|
getTag: (...args) => mockGetTag(...args),
|
|
109
|
+
createTag: (...args) => mockCreateTag(...args),
|
|
74
110
|
updateTag: (...args) => mockUpdateTag(...args),
|
|
75
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),
|
|
76
138
|
}));
|
|
77
139
|
|
|
78
140
|
vi.mock('../services/imageProcessingService.js', () => ({
|
|
@@ -91,15 +153,21 @@ vi.mock('axios', () => ({
|
|
|
91
153
|
}));
|
|
92
154
|
|
|
93
155
|
// Mock fs
|
|
94
|
-
const mockUnlink = vi.fn((path, cb) => cb(null));
|
|
95
156
|
const mockCreateWriteStream = vi.fn();
|
|
96
157
|
vi.mock('fs', () => ({
|
|
97
158
|
default: {
|
|
98
|
-
unlink: (...args) => mockUnlink(...args),
|
|
99
159
|
createWriteStream: (...args) => mockCreateWriteStream(...args),
|
|
100
160
|
},
|
|
101
161
|
}));
|
|
102
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
|
+
|
|
103
171
|
// Mock os
|
|
104
172
|
vi.mock('os', () => ({
|
|
105
173
|
default: { tmpdir: vi.fn().mockReturnValue('/tmp') },
|
|
@@ -133,12 +201,13 @@ describe('mcp_server_improved - ghost_get_posts tool', () => {
|
|
|
133
201
|
expect(tool).toBeDefined();
|
|
134
202
|
expect(tool.description).toContain('posts');
|
|
135
203
|
expect(tool.schema).toBeDefined();
|
|
136
|
-
|
|
137
|
-
expect(tool.schema.
|
|
138
|
-
expect(tool.schema.
|
|
139
|
-
expect(tool.schema.
|
|
140
|
-
expect(tool.schema.
|
|
141
|
-
expect(tool.schema.
|
|
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();
|
|
142
211
|
});
|
|
143
212
|
|
|
144
213
|
it('should retrieve posts with default options', async () => {
|
|
@@ -168,22 +237,24 @@ describe('mcp_server_improved - ghost_get_posts tool', () => {
|
|
|
168
237
|
|
|
169
238
|
it('should validate limit is between 1 and 100', () => {
|
|
170
239
|
const tool = mockTools.get('ghost_get_posts');
|
|
171
|
-
|
|
240
|
+
// Zod schemas store field definitions in schema.shape
|
|
241
|
+
const shape = tool.schema.shape;
|
|
172
242
|
|
|
173
243
|
// Test that limit schema exists and has proper validation
|
|
174
|
-
expect(
|
|
175
|
-
expect(() =>
|
|
176
|
-
expect(() =>
|
|
177
|
-
expect(
|
|
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);
|
|
178
248
|
});
|
|
179
249
|
|
|
180
250
|
it('should validate page is at least 1', () => {
|
|
181
251
|
const tool = mockTools.get('ghost_get_posts');
|
|
182
|
-
|
|
252
|
+
// Zod schemas store field definitions in schema.shape
|
|
253
|
+
const shape = tool.schema.shape;
|
|
183
254
|
|
|
184
|
-
expect(
|
|
185
|
-
expect(() =>
|
|
186
|
-
expect(
|
|
255
|
+
expect(shape.page).toBeDefined();
|
|
256
|
+
expect(() => shape.page.parse(0)).toThrow();
|
|
257
|
+
expect(shape.page.parse(1)).toBe(1);
|
|
187
258
|
});
|
|
188
259
|
|
|
189
260
|
it('should pass status filter', async () => {
|
|
@@ -198,14 +269,15 @@ describe('mcp_server_improved - ghost_get_posts tool', () => {
|
|
|
198
269
|
|
|
199
270
|
it('should validate status enum values', () => {
|
|
200
271
|
const tool = mockTools.get('ghost_get_posts');
|
|
201
|
-
|
|
272
|
+
// Zod schemas store field definitions in schema.shape
|
|
273
|
+
const shape = tool.schema.shape;
|
|
202
274
|
|
|
203
|
-
expect(
|
|
204
|
-
expect(() =>
|
|
205
|
-
expect(
|
|
206
|
-
expect(
|
|
207
|
-
expect(
|
|
208
|
-
expect(
|
|
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');
|
|
209
281
|
});
|
|
210
282
|
|
|
211
283
|
it('should pass include parameter', async () => {
|
|
@@ -322,14 +394,15 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
|
|
|
322
394
|
expect(tool).toBeDefined();
|
|
323
395
|
expect(tool.description).toContain('post');
|
|
324
396
|
expect(tool.schema).toBeDefined();
|
|
325
|
-
|
|
326
|
-
expect(tool.schema.
|
|
327
|
-
expect(tool.schema.
|
|
397
|
+
// Zod schemas store field definitions in schema.shape
|
|
398
|
+
expect(tool.schema.shape.id).toBeDefined();
|
|
399
|
+
expect(tool.schema.shape.slug).toBeDefined();
|
|
400
|
+
expect(tool.schema.shape.include).toBeDefined();
|
|
328
401
|
});
|
|
329
402
|
|
|
330
403
|
it('should retrieve post by ID', async () => {
|
|
331
404
|
const mockPost = {
|
|
332
|
-
id: '
|
|
405
|
+
id: '507f1f77bcf86cd799439011',
|
|
333
406
|
title: 'Test Post',
|
|
334
407
|
slug: 'test-post',
|
|
335
408
|
html: '<p>Content</p>',
|
|
@@ -338,16 +411,16 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
|
|
|
338
411
|
mockGetPost.mockResolvedValue(mockPost);
|
|
339
412
|
|
|
340
413
|
const tool = mockTools.get('ghost_get_post');
|
|
341
|
-
const result = await tool.handler({ id: '
|
|
414
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
342
415
|
|
|
343
|
-
expect(mockGetPost).toHaveBeenCalledWith('
|
|
344
|
-
expect(result.content[0].text).toContain('"id": "
|
|
416
|
+
expect(mockGetPost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {});
|
|
417
|
+
expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
|
|
345
418
|
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
346
419
|
});
|
|
347
420
|
|
|
348
421
|
it('should retrieve post by slug', async () => {
|
|
349
422
|
const mockPost = {
|
|
350
|
-
id: '
|
|
423
|
+
id: '507f1f77bcf86cd799439011',
|
|
351
424
|
title: 'Test Post',
|
|
352
425
|
slug: 'test-post',
|
|
353
426
|
html: '<p>Content</p>',
|
|
@@ -364,7 +437,7 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
|
|
|
364
437
|
|
|
365
438
|
it('should pass include parameter with ID', async () => {
|
|
366
439
|
const mockPost = {
|
|
367
|
-
id: '
|
|
440
|
+
id: '507f1f77bcf86cd799439011',
|
|
368
441
|
title: 'Post with relations',
|
|
369
442
|
tags: [{ name: 'tech' }],
|
|
370
443
|
authors: [{ name: 'John' }],
|
|
@@ -372,14 +445,16 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
|
|
|
372
445
|
mockGetPost.mockResolvedValue(mockPost);
|
|
373
446
|
|
|
374
447
|
const tool = mockTools.get('ghost_get_post');
|
|
375
|
-
await tool.handler({ id: '
|
|
448
|
+
await tool.handler({ id: '507f1f77bcf86cd799439011', include: 'tags,authors' });
|
|
376
449
|
|
|
377
|
-
expect(mockGetPost).toHaveBeenCalledWith('
|
|
450
|
+
expect(mockGetPost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
451
|
+
include: 'tags,authors',
|
|
452
|
+
});
|
|
378
453
|
});
|
|
379
454
|
|
|
380
455
|
it('should pass include parameter with slug', async () => {
|
|
381
456
|
const mockPost = {
|
|
382
|
-
id: '
|
|
457
|
+
id: '507f1f77bcf86cd799439011',
|
|
383
458
|
title: 'Post with relations',
|
|
384
459
|
slug: 'test-post',
|
|
385
460
|
tags: [{ name: 'tech' }],
|
|
@@ -393,20 +468,20 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
|
|
|
393
468
|
});
|
|
394
469
|
|
|
395
470
|
it('should prefer ID over slug when both provided', async () => {
|
|
396
|
-
const mockPost = { id: '
|
|
471
|
+
const mockPost = { id: '507f1f77bcf86cd799439011', title: 'Test Post', slug: 'test-post' };
|
|
397
472
|
mockGetPost.mockResolvedValue(mockPost);
|
|
398
473
|
|
|
399
474
|
const tool = mockTools.get('ghost_get_post');
|
|
400
|
-
await tool.handler({ id: '
|
|
475
|
+
await tool.handler({ id: '507f1f77bcf86cd799439011', slug: 'wrong-slug' });
|
|
401
476
|
|
|
402
|
-
expect(mockGetPost).toHaveBeenCalledWith('
|
|
477
|
+
expect(mockGetPost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {});
|
|
403
478
|
});
|
|
404
479
|
|
|
405
480
|
it('should handle not found errors', async () => {
|
|
406
481
|
mockGetPost.mockRejectedValue(new Error('Post not found'));
|
|
407
482
|
|
|
408
483
|
const tool = mockTools.get('ghost_get_post');
|
|
409
|
-
const result = await tool.handler({ id: '
|
|
484
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
410
485
|
|
|
411
486
|
expect(result.isError).toBe(true);
|
|
412
487
|
expect(result.content[0].text).toContain('Post not found');
|
|
@@ -424,7 +499,7 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
|
|
|
424
499
|
|
|
425
500
|
it('should return formatted JSON response', async () => {
|
|
426
501
|
const mockPost = {
|
|
427
|
-
id: '
|
|
502
|
+
id: '507f1f77bcf86cd799439011',
|
|
428
503
|
uuid: 'uuid-123',
|
|
429
504
|
title: 'Test Post',
|
|
430
505
|
slug: 'test-post',
|
|
@@ -436,11 +511,11 @@ describe('mcp_server_improved - ghost_get_post tool', () => {
|
|
|
436
511
|
mockGetPost.mockResolvedValue(mockPost);
|
|
437
512
|
|
|
438
513
|
const tool = mockTools.get('ghost_get_post');
|
|
439
|
-
const result = await tool.handler({ id: '
|
|
514
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
440
515
|
|
|
441
516
|
expect(result.content).toBeDefined();
|
|
442
517
|
expect(result.content[0].type).toBe('text');
|
|
443
|
-
expect(result.content[0].text).toContain('"id": "
|
|
518
|
+
expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
|
|
444
519
|
expect(result.content[0].text).toContain('"title": "Test Post"');
|
|
445
520
|
expect(result.content[0].text).toContain('"status": "published"');
|
|
446
521
|
});
|
|
@@ -472,23 +547,24 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
472
547
|
expect(tool).toBeDefined();
|
|
473
548
|
expect(tool.description).toContain('Updates an existing post');
|
|
474
549
|
expect(tool.schema).toBeDefined();
|
|
475
|
-
|
|
476
|
-
expect(tool.schema.
|
|
477
|
-
expect(tool.schema.
|
|
478
|
-
expect(tool.schema.
|
|
479
|
-
expect(tool.schema.
|
|
480
|
-
expect(tool.schema.
|
|
481
|
-
expect(tool.schema.
|
|
482
|
-
expect(tool.schema.
|
|
483
|
-
expect(tool.schema.
|
|
484
|
-
expect(tool.schema.
|
|
485
|
-
expect(tool.schema.
|
|
486
|
-
expect(tool.schema.
|
|
550
|
+
// Zod schemas store field definitions in schema.shape
|
|
551
|
+
expect(tool.schema.shape.id).toBeDefined();
|
|
552
|
+
expect(tool.schema.shape.title).toBeDefined();
|
|
553
|
+
expect(tool.schema.shape.html).toBeDefined();
|
|
554
|
+
expect(tool.schema.shape.status).toBeDefined();
|
|
555
|
+
expect(tool.schema.shape.tags).toBeDefined();
|
|
556
|
+
expect(tool.schema.shape.feature_image).toBeDefined();
|
|
557
|
+
expect(tool.schema.shape.feature_image_alt).toBeDefined();
|
|
558
|
+
expect(tool.schema.shape.feature_image_caption).toBeDefined();
|
|
559
|
+
expect(tool.schema.shape.meta_title).toBeDefined();
|
|
560
|
+
expect(tool.schema.shape.meta_description).toBeDefined();
|
|
561
|
+
expect(tool.schema.shape.published_at).toBeDefined();
|
|
562
|
+
expect(tool.schema.shape.custom_excerpt).toBeDefined();
|
|
487
563
|
});
|
|
488
564
|
|
|
489
565
|
it('should update post title', async () => {
|
|
490
566
|
const mockUpdatedPost = {
|
|
491
|
-
id: '
|
|
567
|
+
id: '507f1f77bcf86cd799439011',
|
|
492
568
|
title: 'Updated Title',
|
|
493
569
|
slug: 'test-post',
|
|
494
570
|
html: '<p>Content</p>',
|
|
@@ -498,15 +574,17 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
498
574
|
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
499
575
|
|
|
500
576
|
const tool = mockTools.get('ghost_update_post');
|
|
501
|
-
const result = await tool.handler({ id: '
|
|
577
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: 'Updated Title' });
|
|
502
578
|
|
|
503
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('
|
|
579
|
+
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
580
|
+
title: 'Updated Title',
|
|
581
|
+
});
|
|
504
582
|
expect(result.content[0].text).toContain('"title": "Updated Title"');
|
|
505
583
|
});
|
|
506
584
|
|
|
507
585
|
it('should update post content', async () => {
|
|
508
586
|
const mockUpdatedPost = {
|
|
509
|
-
id: '
|
|
587
|
+
id: '507f1f77bcf86cd799439011',
|
|
510
588
|
title: 'Test Post',
|
|
511
589
|
html: '<p>Updated content</p>',
|
|
512
590
|
status: 'published',
|
|
@@ -515,15 +593,20 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
515
593
|
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
516
594
|
|
|
517
595
|
const tool = mockTools.get('ghost_update_post');
|
|
518
|
-
const result = await tool.handler({
|
|
596
|
+
const result = await tool.handler({
|
|
597
|
+
id: '507f1f77bcf86cd799439011',
|
|
598
|
+
html: '<p>Updated content</p>',
|
|
599
|
+
});
|
|
519
600
|
|
|
520
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('
|
|
601
|
+
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
602
|
+
html: '<p>Updated content</p>',
|
|
603
|
+
});
|
|
521
604
|
expect(result.content[0].text).toContain('Updated content');
|
|
522
605
|
});
|
|
523
606
|
|
|
524
607
|
it('should update post status', async () => {
|
|
525
608
|
const mockUpdatedPost = {
|
|
526
|
-
id: '
|
|
609
|
+
id: '507f1f77bcf86cd799439011',
|
|
527
610
|
title: 'Test Post',
|
|
528
611
|
html: '<p>Content</p>',
|
|
529
612
|
status: 'published',
|
|
@@ -532,15 +615,17 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
532
615
|
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
533
616
|
|
|
534
617
|
const tool = mockTools.get('ghost_update_post');
|
|
535
|
-
const result = await tool.handler({ id: '
|
|
618
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', status: 'published' });
|
|
536
619
|
|
|
537
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('
|
|
620
|
+
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
621
|
+
status: 'published',
|
|
622
|
+
});
|
|
538
623
|
expect(result.content[0].text).toContain('"status": "published"');
|
|
539
624
|
});
|
|
540
625
|
|
|
541
626
|
it('should update post tags', async () => {
|
|
542
627
|
const mockUpdatedPost = {
|
|
543
|
-
id: '
|
|
628
|
+
id: '507f1f77bcf86cd799439011',
|
|
544
629
|
title: 'Test Post',
|
|
545
630
|
html: '<p>Content</p>',
|
|
546
631
|
tags: [{ name: 'tech' }, { name: 'javascript' }],
|
|
@@ -549,16 +634,21 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
549
634
|
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
550
635
|
|
|
551
636
|
const tool = mockTools.get('ghost_update_post');
|
|
552
|
-
const result = await tool.handler({
|
|
637
|
+
const result = await tool.handler({
|
|
638
|
+
id: '507f1f77bcf86cd799439011',
|
|
639
|
+
tags: ['tech', 'javascript'],
|
|
640
|
+
});
|
|
553
641
|
|
|
554
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('
|
|
642
|
+
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
643
|
+
tags: ['tech', 'javascript'],
|
|
644
|
+
});
|
|
555
645
|
expect(result.content[0].text).toContain('tech');
|
|
556
646
|
expect(result.content[0].text).toContain('javascript');
|
|
557
647
|
});
|
|
558
648
|
|
|
559
649
|
it('should update post featured image', async () => {
|
|
560
650
|
const mockUpdatedPost = {
|
|
561
|
-
id: '
|
|
651
|
+
id: '507f1f77bcf86cd799439011',
|
|
562
652
|
title: 'Test Post',
|
|
563
653
|
feature_image: 'https://example.com/new-image.jpg',
|
|
564
654
|
feature_image_alt: 'New image',
|
|
@@ -568,12 +658,12 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
568
658
|
|
|
569
659
|
const tool = mockTools.get('ghost_update_post');
|
|
570
660
|
const result = await tool.handler({
|
|
571
|
-
id: '
|
|
661
|
+
id: '507f1f77bcf86cd799439011',
|
|
572
662
|
feature_image: 'https://example.com/new-image.jpg',
|
|
573
663
|
feature_image_alt: 'New image',
|
|
574
664
|
});
|
|
575
665
|
|
|
576
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('
|
|
666
|
+
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
577
667
|
feature_image: 'https://example.com/new-image.jpg',
|
|
578
668
|
feature_image_alt: 'New image',
|
|
579
669
|
});
|
|
@@ -582,7 +672,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
582
672
|
|
|
583
673
|
it('should update SEO meta fields', async () => {
|
|
584
674
|
const mockUpdatedPost = {
|
|
585
|
-
id: '
|
|
675
|
+
id: '507f1f77bcf86cd799439011',
|
|
586
676
|
title: 'Test Post',
|
|
587
677
|
meta_title: 'SEO Title',
|
|
588
678
|
meta_description: 'SEO Description',
|
|
@@ -592,12 +682,12 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
592
682
|
|
|
593
683
|
const tool = mockTools.get('ghost_update_post');
|
|
594
684
|
const result = await tool.handler({
|
|
595
|
-
id: '
|
|
685
|
+
id: '507f1f77bcf86cd799439011',
|
|
596
686
|
meta_title: 'SEO Title',
|
|
597
687
|
meta_description: 'SEO Description',
|
|
598
688
|
});
|
|
599
689
|
|
|
600
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('
|
|
690
|
+
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
601
691
|
meta_title: 'SEO Title',
|
|
602
692
|
meta_description: 'SEO Description',
|
|
603
693
|
});
|
|
@@ -607,7 +697,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
607
697
|
|
|
608
698
|
it('should update multiple fields at once', async () => {
|
|
609
699
|
const mockUpdatedPost = {
|
|
610
|
-
id: '
|
|
700
|
+
id: '507f1f77bcf86cd799439011',
|
|
611
701
|
title: 'Updated Title',
|
|
612
702
|
html: '<p>Updated content</p>',
|
|
613
703
|
status: 'published',
|
|
@@ -618,14 +708,14 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
618
708
|
|
|
619
709
|
const tool = mockTools.get('ghost_update_post');
|
|
620
710
|
const result = await tool.handler({
|
|
621
|
-
id: '
|
|
711
|
+
id: '507f1f77bcf86cd799439011',
|
|
622
712
|
title: 'Updated Title',
|
|
623
713
|
html: '<p>Updated content</p>',
|
|
624
714
|
status: 'published',
|
|
625
715
|
tags: ['tech'],
|
|
626
716
|
});
|
|
627
717
|
|
|
628
|
-
expect(mockUpdatePost).toHaveBeenCalledWith('
|
|
718
|
+
expect(mockUpdatePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
629
719
|
title: 'Updated Title',
|
|
630
720
|
html: '<p>Updated content</p>',
|
|
631
721
|
status: 'published',
|
|
@@ -638,7 +728,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
638
728
|
mockUpdatePost.mockRejectedValue(new Error('Post not found'));
|
|
639
729
|
|
|
640
730
|
const tool = mockTools.get('ghost_update_post');
|
|
641
|
-
const result = await tool.handler({ id: '
|
|
731
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439099', title: 'New Title' });
|
|
642
732
|
|
|
643
733
|
expect(result.isError).toBe(true);
|
|
644
734
|
expect(result.content[0].text).toContain('Post not found');
|
|
@@ -648,7 +738,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
648
738
|
mockUpdatePost.mockRejectedValue(new Error('Validation failed: Title is required'));
|
|
649
739
|
|
|
650
740
|
const tool = mockTools.get('ghost_update_post');
|
|
651
|
-
const result = await tool.handler({ id: '
|
|
741
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: '' });
|
|
652
742
|
|
|
653
743
|
expect(result.isError).toBe(true);
|
|
654
744
|
expect(result.content[0].text).toContain('Validation failed');
|
|
@@ -658,7 +748,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
658
748
|
mockUpdatePost.mockRejectedValue(new Error('Ghost API error: Server timeout'));
|
|
659
749
|
|
|
660
750
|
const tool = mockTools.get('ghost_update_post');
|
|
661
|
-
const result = await tool.handler({ id: '
|
|
751
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: 'Updated' });
|
|
662
752
|
|
|
663
753
|
expect(result.isError).toBe(true);
|
|
664
754
|
expect(result.content[0].text).toContain('Ghost API error');
|
|
@@ -666,7 +756,7 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
666
756
|
|
|
667
757
|
it('should return formatted JSON response', async () => {
|
|
668
758
|
const mockUpdatedPost = {
|
|
669
|
-
id: '
|
|
759
|
+
id: '507f1f77bcf86cd799439011',
|
|
670
760
|
uuid: 'uuid-123',
|
|
671
761
|
title: 'Updated Post',
|
|
672
762
|
slug: 'updated-post',
|
|
@@ -678,11 +768,11 @@ describe('mcp_server_improved - ghost_update_post tool', () => {
|
|
|
678
768
|
mockUpdatePost.mockResolvedValue(mockUpdatedPost);
|
|
679
769
|
|
|
680
770
|
const tool = mockTools.get('ghost_update_post');
|
|
681
|
-
const result = await tool.handler({ id: '
|
|
771
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', title: 'Updated Post' });
|
|
682
772
|
|
|
683
773
|
expect(result.content).toBeDefined();
|
|
684
774
|
expect(result.content[0].type).toBe('text');
|
|
685
|
-
expect(result.content[0].text).toContain('"id": "
|
|
775
|
+
expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
|
|
686
776
|
expect(result.content[0].text).toContain('"title": "Updated Post"');
|
|
687
777
|
expect(result.content[0].text).toContain('"status": "published"');
|
|
688
778
|
});
|
|
@@ -707,17 +797,20 @@ describe('mcp_server_improved - ghost_delete_post tool', () => {
|
|
|
707
797
|
expect(tool.description).toContain('Deletes a post');
|
|
708
798
|
expect(tool.description).toContain('permanent');
|
|
709
799
|
expect(tool.schema).toBeDefined();
|
|
710
|
-
|
|
800
|
+
// Zod schemas store field definitions in schema.shape
|
|
801
|
+
expect(tool.schema.shape.id).toBeDefined();
|
|
711
802
|
});
|
|
712
803
|
|
|
713
804
|
it('should delete post by ID', async () => {
|
|
714
805
|
mockDeletePost.mockResolvedValue({ deleted: true });
|
|
715
806
|
|
|
716
807
|
const tool = mockTools.get('ghost_delete_post');
|
|
717
|
-
const result = await tool.handler({ id: '
|
|
808
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
718
809
|
|
|
719
|
-
expect(mockDeletePost).toHaveBeenCalledWith('
|
|
720
|
-
expect(result.content[0].text).toContain(
|
|
810
|
+
expect(mockDeletePost).toHaveBeenCalledWith('507f1f77bcf86cd799439011');
|
|
811
|
+
expect(result.content[0].text).toContain(
|
|
812
|
+
'Post 507f1f77bcf86cd799439011 has been successfully deleted'
|
|
813
|
+
);
|
|
721
814
|
expect(result.isError).toBeUndefined();
|
|
722
815
|
});
|
|
723
816
|
|
|
@@ -725,7 +818,7 @@ describe('mcp_server_improved - ghost_delete_post tool', () => {
|
|
|
725
818
|
mockDeletePost.mockRejectedValue(new Error('Post not found'));
|
|
726
819
|
|
|
727
820
|
const tool = mockTools.get('ghost_delete_post');
|
|
728
|
-
const result = await tool.handler({ id: '
|
|
821
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
729
822
|
|
|
730
823
|
expect(result.isError).toBe(true);
|
|
731
824
|
expect(result.content[0].text).toContain('Post not found');
|
|
@@ -735,7 +828,7 @@ describe('mcp_server_improved - ghost_delete_post tool', () => {
|
|
|
735
828
|
mockDeletePost.mockRejectedValue(new Error('Ghost API error: Permission denied'));
|
|
736
829
|
|
|
737
830
|
const tool = mockTools.get('ghost_delete_post');
|
|
738
|
-
const result = await tool.handler({ id: '
|
|
831
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
739
832
|
|
|
740
833
|
expect(result.isError).toBe(true);
|
|
741
834
|
expect(result.content[0].text).toContain('Ghost API error');
|
|
@@ -745,18 +838,20 @@ describe('mcp_server_improved - ghost_delete_post tool', () => {
|
|
|
745
838
|
mockDeletePost.mockResolvedValue({ deleted: true });
|
|
746
839
|
|
|
747
840
|
const tool = mockTools.get('ghost_delete_post');
|
|
748
|
-
const result = await tool.handler({ id: '
|
|
841
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
749
842
|
|
|
750
843
|
expect(result.content).toBeDefined();
|
|
751
844
|
expect(result.content[0].type).toBe('text');
|
|
752
|
-
expect(result.content[0].text).toBe(
|
|
845
|
+
expect(result.content[0].text).toBe(
|
|
846
|
+
'Post 507f1f77bcf86cd799439011 has been successfully deleted.'
|
|
847
|
+
);
|
|
753
848
|
});
|
|
754
849
|
|
|
755
850
|
it('should handle network errors', async () => {
|
|
756
851
|
mockDeletePost.mockRejectedValue(new Error('Network error: Connection refused'));
|
|
757
852
|
|
|
758
853
|
const tool = mockTools.get('ghost_delete_post');
|
|
759
|
-
const result = await tool.handler({ id: '
|
|
854
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439012' });
|
|
760
855
|
|
|
761
856
|
expect(result.isError).toBe(true);
|
|
762
857
|
expect(result.content[0].text).toContain('Network error');
|
|
@@ -781,9 +876,10 @@ describe('mcp_server_improved - ghost_search_posts tool', () => {
|
|
|
781
876
|
expect(tool).toBeDefined();
|
|
782
877
|
expect(tool.description).toContain('Search');
|
|
783
878
|
expect(tool.schema).toBeDefined();
|
|
784
|
-
|
|
785
|
-
expect(tool.schema.
|
|
786
|
-
expect(tool.schema.
|
|
879
|
+
// Zod schemas store field definitions in schema.shape
|
|
880
|
+
expect(tool.schema.shape.query).toBeDefined();
|
|
881
|
+
expect(tool.schema.shape.status).toBeDefined();
|
|
882
|
+
expect(tool.schema.shape.limit).toBeDefined();
|
|
787
883
|
});
|
|
788
884
|
|
|
789
885
|
it('should search posts with query only', async () => {
|
|
@@ -825,24 +921,26 @@ describe('mcp_server_improved - ghost_search_posts tool', () => {
|
|
|
825
921
|
|
|
826
922
|
it('should validate limit is between 1 and 50', () => {
|
|
827
923
|
const tool = mockTools.get('ghost_search_posts');
|
|
828
|
-
|
|
924
|
+
// Zod schemas store field definitions in schema.shape
|
|
925
|
+
const shape = tool.schema.shape;
|
|
829
926
|
|
|
830
|
-
expect(
|
|
831
|
-
expect(() =>
|
|
832
|
-
expect(() =>
|
|
833
|
-
expect(
|
|
927
|
+
expect(shape.limit).toBeDefined();
|
|
928
|
+
expect(() => shape.limit.parse(0)).toThrow();
|
|
929
|
+
expect(() => shape.limit.parse(51)).toThrow();
|
|
930
|
+
expect(shape.limit.parse(25)).toBe(25);
|
|
834
931
|
});
|
|
835
932
|
|
|
836
933
|
it('should validate status enum values', () => {
|
|
837
934
|
const tool = mockTools.get('ghost_search_posts');
|
|
838
|
-
|
|
935
|
+
// Zod schemas store field definitions in schema.shape
|
|
936
|
+
const shape = tool.schema.shape;
|
|
839
937
|
|
|
840
|
-
expect(
|
|
841
|
-
expect(() =>
|
|
842
|
-
expect(
|
|
843
|
-
expect(
|
|
844
|
-
expect(
|
|
845
|
-
expect(
|
|
938
|
+
expect(shape.status).toBeDefined();
|
|
939
|
+
expect(() => shape.status.parse('invalid')).toThrow();
|
|
940
|
+
expect(shape.status.parse('published')).toBe('published');
|
|
941
|
+
expect(shape.status.parse('draft')).toBe('draft');
|
|
942
|
+
expect(shape.status.parse('scheduled')).toBe('scheduled');
|
|
943
|
+
expect(shape.status.parse('all')).toBe('all');
|
|
846
944
|
});
|
|
847
945
|
|
|
848
946
|
it('should pass all parameters combined', async () => {
|
|
@@ -863,13 +961,12 @@ describe('mcp_server_improved - ghost_search_posts tool', () => {
|
|
|
863
961
|
});
|
|
864
962
|
|
|
865
963
|
it('should handle errors from searchPosts', async () => {
|
|
866
|
-
|
|
867
|
-
|
|
964
|
+
// Empty query is now caught by Zod validation
|
|
868
965
|
const tool = mockTools.get('ghost_search_posts');
|
|
869
966
|
const result = await tool.handler({ query: '' });
|
|
870
967
|
|
|
871
968
|
expect(result.isError).toBe(true);
|
|
872
|
-
expect(result.content[0].text).toContain('
|
|
969
|
+
expect(result.content[0].text).toContain('VALIDATION_ERROR');
|
|
873
970
|
});
|
|
874
971
|
|
|
875
972
|
it('should handle Ghost API errors', async () => {
|
|
@@ -930,14 +1027,15 @@ describe('ghost_get_tag', () => {
|
|
|
930
1027
|
|
|
931
1028
|
it('should have correct schema with id and slug as optional', () => {
|
|
932
1029
|
const tool = mockTools.get('ghost_get_tag');
|
|
933
|
-
|
|
934
|
-
expect(tool.schema.
|
|
935
|
-
expect(tool.schema.
|
|
1030
|
+
// Zod schemas store field definitions in schema.shape
|
|
1031
|
+
expect(tool.schema.shape.id).toBeDefined();
|
|
1032
|
+
expect(tool.schema.shape.slug).toBeDefined();
|
|
1033
|
+
expect(tool.schema.shape.include).toBeDefined();
|
|
936
1034
|
});
|
|
937
1035
|
|
|
938
1036
|
it('should retrieve tag by ID', async () => {
|
|
939
1037
|
const mockTag = {
|
|
940
|
-
id: '
|
|
1038
|
+
id: '507f1f77bcf86cd799439011',
|
|
941
1039
|
name: 'Test Tag',
|
|
942
1040
|
slug: 'test-tag',
|
|
943
1041
|
description: 'A test tag',
|
|
@@ -945,18 +1043,18 @@ describe('ghost_get_tag', () => {
|
|
|
945
1043
|
mockGetTag.mockResolvedValue(mockTag);
|
|
946
1044
|
|
|
947
1045
|
const tool = mockTools.get('ghost_get_tag');
|
|
948
|
-
const result = await tool.handler({ id: '
|
|
1046
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
949
1047
|
|
|
950
|
-
expect(mockGetTag).toHaveBeenCalledWith('
|
|
1048
|
+
expect(mockGetTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {});
|
|
951
1049
|
expect(result.content).toBeDefined();
|
|
952
1050
|
expect(result.content[0].type).toBe('text');
|
|
953
|
-
expect(result.content[0].text).toContain('"id": "
|
|
1051
|
+
expect(result.content[0].text).toContain('"id": "507f1f77bcf86cd799439011"');
|
|
954
1052
|
expect(result.content[0].text).toContain('"name": "Test Tag"');
|
|
955
1053
|
});
|
|
956
1054
|
|
|
957
1055
|
it('should retrieve tag by slug', async () => {
|
|
958
1056
|
const mockTag = {
|
|
959
|
-
id: '
|
|
1057
|
+
id: '507f1f77bcf86cd799439011',
|
|
960
1058
|
name: 'Test Tag',
|
|
961
1059
|
slug: 'test-tag',
|
|
962
1060
|
description: 'A test tag',
|
|
@@ -972,7 +1070,7 @@ describe('ghost_get_tag', () => {
|
|
|
972
1070
|
|
|
973
1071
|
it('should support include parameter for post count', async () => {
|
|
974
1072
|
const mockTag = {
|
|
975
|
-
id: '
|
|
1073
|
+
id: '507f1f77bcf86cd799439011',
|
|
976
1074
|
name: 'Test Tag',
|
|
977
1075
|
slug: 'test-tag',
|
|
978
1076
|
count: { posts: 5 },
|
|
@@ -980,9 +1078,9 @@ describe('ghost_get_tag', () => {
|
|
|
980
1078
|
mockGetTag.mockResolvedValue(mockTag);
|
|
981
1079
|
|
|
982
1080
|
const tool = mockTools.get('ghost_get_tag');
|
|
983
|
-
const result = await tool.handler({ id: '
|
|
1081
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', include: 'count.posts' });
|
|
984
1082
|
|
|
985
|
-
expect(mockGetTag).toHaveBeenCalledWith('
|
|
1083
|
+
expect(mockGetTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', { include: 'count.posts' });
|
|
986
1084
|
expect(result.content[0].text).toContain('"count"');
|
|
987
1085
|
});
|
|
988
1086
|
|
|
@@ -999,7 +1097,7 @@ describe('ghost_get_tag', () => {
|
|
|
999
1097
|
mockGetTag.mockRejectedValue(new Error('Tag not found'));
|
|
1000
1098
|
|
|
1001
1099
|
const tool = mockTools.get('ghost_get_tag');
|
|
1002
|
-
const result = await tool.handler({ id: '
|
|
1100
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
1003
1101
|
|
|
1004
1102
|
expect(result.isError).toBe(true);
|
|
1005
1103
|
expect(result.content[0].text).toContain('Tag not found');
|
|
@@ -1022,48 +1120,54 @@ describe('ghost_update_tag', () => {
|
|
|
1022
1120
|
|
|
1023
1121
|
it('should have correct schema with all update fields', () => {
|
|
1024
1122
|
const tool = mockTools.get('ghost_update_tag');
|
|
1025
|
-
|
|
1026
|
-
expect(tool.schema.
|
|
1027
|
-
expect(tool.schema.
|
|
1028
|
-
expect(tool.schema.
|
|
1029
|
-
expect(tool.schema.
|
|
1030
|
-
expect(tool.schema.
|
|
1031
|
-
expect(tool.schema.
|
|
1123
|
+
// Zod schemas store field definitions in schema.shape
|
|
1124
|
+
expect(tool.schema.shape.id).toBeDefined();
|
|
1125
|
+
expect(tool.schema.shape.name).toBeDefined();
|
|
1126
|
+
expect(tool.schema.shape.slug).toBeDefined();
|
|
1127
|
+
expect(tool.schema.shape.description).toBeDefined();
|
|
1128
|
+
expect(tool.schema.shape.feature_image).toBeDefined();
|
|
1129
|
+
expect(tool.schema.shape.meta_title).toBeDefined();
|
|
1130
|
+
expect(tool.schema.shape.meta_description).toBeDefined();
|
|
1032
1131
|
});
|
|
1033
1132
|
|
|
1034
1133
|
it('should update tag name', async () => {
|
|
1035
1134
|
const mockUpdatedTag = {
|
|
1036
|
-
id: '
|
|
1135
|
+
id: '507f1f77bcf86cd799439011',
|
|
1037
1136
|
name: 'Updated Tag',
|
|
1038
1137
|
slug: 'updated-tag',
|
|
1039
1138
|
};
|
|
1040
1139
|
mockUpdateTag.mockResolvedValue(mockUpdatedTag);
|
|
1041
1140
|
|
|
1042
1141
|
const tool = mockTools.get('ghost_update_tag');
|
|
1043
|
-
const result = await tool.handler({ id: '
|
|
1142
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', name: 'Updated Tag' });
|
|
1044
1143
|
|
|
1045
|
-
expect(mockUpdateTag).toHaveBeenCalledWith('
|
|
1144
|
+
expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', { name: 'Updated Tag' });
|
|
1046
1145
|
expect(result.content[0].text).toContain('"name": "Updated Tag"');
|
|
1047
1146
|
});
|
|
1048
1147
|
|
|
1049
1148
|
it('should update tag description', async () => {
|
|
1050
1149
|
const mockUpdatedTag = {
|
|
1051
|
-
id: '
|
|
1150
|
+
id: '507f1f77bcf86cd799439011',
|
|
1052
1151
|
name: 'Test Tag',
|
|
1053
1152
|
description: 'New description',
|
|
1054
1153
|
};
|
|
1055
1154
|
mockUpdateTag.mockResolvedValue(mockUpdatedTag);
|
|
1056
1155
|
|
|
1057
1156
|
const tool = mockTools.get('ghost_update_tag');
|
|
1058
|
-
const result = await tool.handler({
|
|
1157
|
+
const result = await tool.handler({
|
|
1158
|
+
id: '507f1f77bcf86cd799439011',
|
|
1159
|
+
description: 'New description',
|
|
1160
|
+
});
|
|
1059
1161
|
|
|
1060
|
-
expect(mockUpdateTag).toHaveBeenCalledWith('
|
|
1162
|
+
expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
1163
|
+
description: 'New description',
|
|
1164
|
+
});
|
|
1061
1165
|
expect(result.content[0].text).toContain('"description": "New description"');
|
|
1062
1166
|
});
|
|
1063
1167
|
|
|
1064
1168
|
it('should update multiple fields at once', async () => {
|
|
1065
1169
|
const mockUpdatedTag = {
|
|
1066
|
-
id: '
|
|
1170
|
+
id: '507f1f77bcf86cd799439011',
|
|
1067
1171
|
name: 'Updated Tag',
|
|
1068
1172
|
slug: 'updated-tag',
|
|
1069
1173
|
description: 'Updated description',
|
|
@@ -1073,13 +1177,13 @@ describe('ghost_update_tag', () => {
|
|
|
1073
1177
|
|
|
1074
1178
|
const tool = mockTools.get('ghost_update_tag');
|
|
1075
1179
|
await tool.handler({
|
|
1076
|
-
id: '
|
|
1180
|
+
id: '507f1f77bcf86cd799439011',
|
|
1077
1181
|
name: 'Updated Tag',
|
|
1078
1182
|
description: 'Updated description',
|
|
1079
1183
|
meta_title: 'Updated Meta',
|
|
1080
1184
|
});
|
|
1081
1185
|
|
|
1082
|
-
expect(mockUpdateTag).toHaveBeenCalledWith('
|
|
1186
|
+
expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
1083
1187
|
name: 'Updated Tag',
|
|
1084
1188
|
description: 'Updated description',
|
|
1085
1189
|
meta_title: 'Updated Meta',
|
|
@@ -1088,7 +1192,7 @@ describe('ghost_update_tag', () => {
|
|
|
1088
1192
|
|
|
1089
1193
|
it('should update tag feature image', async () => {
|
|
1090
1194
|
const mockUpdatedTag = {
|
|
1091
|
-
id: '
|
|
1195
|
+
id: '507f1f77bcf86cd799439011',
|
|
1092
1196
|
name: 'Test Tag',
|
|
1093
1197
|
feature_image: 'https://example.com/image.jpg',
|
|
1094
1198
|
};
|
|
@@ -1096,11 +1200,11 @@ describe('ghost_update_tag', () => {
|
|
|
1096
1200
|
|
|
1097
1201
|
const tool = mockTools.get('ghost_update_tag');
|
|
1098
1202
|
await tool.handler({
|
|
1099
|
-
id: '
|
|
1203
|
+
id: '507f1f77bcf86cd799439011',
|
|
1100
1204
|
feature_image: 'https://example.com/image.jpg',
|
|
1101
1205
|
});
|
|
1102
1206
|
|
|
1103
|
-
expect(mockUpdateTag).toHaveBeenCalledWith('
|
|
1207
|
+
expect(mockUpdateTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011', {
|
|
1104
1208
|
feature_image: 'https://example.com/image.jpg',
|
|
1105
1209
|
});
|
|
1106
1210
|
});
|
|
@@ -1110,14 +1214,14 @@ describe('ghost_update_tag', () => {
|
|
|
1110
1214
|
const result = await tool.handler({ name: 'Test' });
|
|
1111
1215
|
|
|
1112
1216
|
expect(result.isError).toBe(true);
|
|
1113
|
-
expect(result.content[0].text).toContain('
|
|
1217
|
+
expect(result.content[0].text).toContain('VALIDATION_ERROR');
|
|
1114
1218
|
});
|
|
1115
1219
|
|
|
1116
1220
|
it('should handle validation error', async () => {
|
|
1117
1221
|
mockUpdateTag.mockRejectedValue(new Error('Validation failed'));
|
|
1118
1222
|
|
|
1119
1223
|
const tool = mockTools.get('ghost_update_tag');
|
|
1120
|
-
const result = await tool.handler({ id: '
|
|
1224
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011', name: '' });
|
|
1121
1225
|
|
|
1122
1226
|
expect(result.isError).toBe(true);
|
|
1123
1227
|
expect(result.content[0].text).toContain('Validation failed');
|
|
@@ -1127,7 +1231,7 @@ describe('ghost_update_tag', () => {
|
|
|
1127
1231
|
mockUpdateTag.mockRejectedValue(new Error('Tag not found'));
|
|
1128
1232
|
|
|
1129
1233
|
const tool = mockTools.get('ghost_update_tag');
|
|
1130
|
-
const result = await tool.handler({ id: '
|
|
1234
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439099', name: 'Test' });
|
|
1131
1235
|
|
|
1132
1236
|
expect(result.isError).toBe(true);
|
|
1133
1237
|
expect(result.content[0].text).toContain('Tag not found');
|
|
@@ -1150,16 +1254,17 @@ describe('ghost_delete_tag', () => {
|
|
|
1150
1254
|
|
|
1151
1255
|
it('should have correct schema with id field', () => {
|
|
1152
1256
|
const tool = mockTools.get('ghost_delete_tag');
|
|
1153
|
-
|
|
1257
|
+
// Zod schemas store field definitions in schema.shape
|
|
1258
|
+
expect(tool.schema.shape.id).toBeDefined();
|
|
1154
1259
|
});
|
|
1155
1260
|
|
|
1156
1261
|
it('should delete tag successfully', async () => {
|
|
1157
1262
|
mockDeleteTag.mockResolvedValue({ success: true });
|
|
1158
1263
|
|
|
1159
1264
|
const tool = mockTools.get('ghost_delete_tag');
|
|
1160
|
-
const result = await tool.handler({ id: '
|
|
1265
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
1161
1266
|
|
|
1162
|
-
expect(mockDeleteTag).toHaveBeenCalledWith('
|
|
1267
|
+
expect(mockDeleteTag).toHaveBeenCalledWith('507f1f77bcf86cd799439011');
|
|
1163
1268
|
expect(result.content[0].text).toContain('successfully deleted');
|
|
1164
1269
|
expect(result.isError).toBeUndefined();
|
|
1165
1270
|
});
|
|
@@ -1169,14 +1274,14 @@ describe('ghost_delete_tag', () => {
|
|
|
1169
1274
|
const result = await tool.handler({});
|
|
1170
1275
|
|
|
1171
1276
|
expect(result.isError).toBe(true);
|
|
1172
|
-
expect(result.content[0].text).toContain('
|
|
1277
|
+
expect(result.content[0].text).toContain('VALIDATION_ERROR');
|
|
1173
1278
|
});
|
|
1174
1279
|
|
|
1175
1280
|
it('should handle not found error', async () => {
|
|
1176
1281
|
mockDeleteTag.mockRejectedValue(new Error('Tag not found'));
|
|
1177
1282
|
|
|
1178
1283
|
const tool = mockTools.get('ghost_delete_tag');
|
|
1179
|
-
const result = await tool.handler({ id: '
|
|
1284
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439099' });
|
|
1180
1285
|
|
|
1181
1286
|
expect(result.isError).toBe(true);
|
|
1182
1287
|
expect(result.content[0].text).toContain('Tag not found');
|
|
@@ -1186,7 +1291,7 @@ describe('ghost_delete_tag', () => {
|
|
|
1186
1291
|
mockDeleteTag.mockRejectedValue(new Error('Failed to delete tag'));
|
|
1187
1292
|
|
|
1188
1293
|
const tool = mockTools.get('ghost_delete_tag');
|
|
1189
|
-
const result = await tool.handler({ id: '
|
|
1294
|
+
const result = await tool.handler({ id: '507f1f77bcf86cd799439011' });
|
|
1190
1295
|
|
|
1191
1296
|
expect(result.isError).toBe(true);
|
|
1192
1297
|
expect(result.content[0].text).toContain('Failed to delete tag');
|