@jgardner04/ghost-mcp-server 1.1.11 → 1.1.13

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.
@@ -0,0 +1,236 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { mockDotenv } from '../../__tests__/helpers/testUtils.js';
3
+ import { createMockContextLogger } from '../../__tests__/helpers/mockLogger.js';
4
+ import {
5
+ createMockRequest,
6
+ createMockResponse,
7
+ createMockNext,
8
+ } from '../../__tests__/helpers/mockExpress.js';
9
+
10
+ // Mock dotenv
11
+ vi.mock('dotenv', () => mockDotenv());
12
+
13
+ // Mock logger
14
+ vi.mock('../../utils/logger.js', () => ({
15
+ createContextLogger: createMockContextLogger(),
16
+ }));
17
+
18
+ // Mock postService functions
19
+ vi.mock('../../services/postService.js', () => ({
20
+ createPostService: vi.fn(),
21
+ }));
22
+
23
+ // Import after mocks are set up
24
+ import { createPost } from '../postController.js';
25
+ import * as postService from '../../services/postService.js';
26
+
27
+ describe('postController', () => {
28
+ beforeEach(() => {
29
+ vi.clearAllMocks();
30
+ });
31
+
32
+ describe('createPost', () => {
33
+ it('should return 201 with new post on success', async () => {
34
+ const mockPost = {
35
+ id: '1',
36
+ title: 'Test Post',
37
+ html: '<p>Test content</p>',
38
+ status: 'draft',
39
+ };
40
+ postService.createPostService.mockResolvedValue(mockPost);
41
+
42
+ const req = createMockRequest({
43
+ body: {
44
+ title: 'Test Post',
45
+ html: '<p>Test content</p>',
46
+ },
47
+ });
48
+ const res = createMockResponse();
49
+ const next = createMockNext();
50
+
51
+ await createPost(req, res, next);
52
+
53
+ expect(postService.createPostService).toHaveBeenCalledWith({
54
+ title: 'Test Post',
55
+ html: '<p>Test content</p>',
56
+ });
57
+ expect(res.status).toHaveBeenCalledWith(201);
58
+ expect(res.json).toHaveBeenCalledWith(mockPost);
59
+ expect(next).not.toHaveBeenCalled();
60
+ });
61
+
62
+ it('should pass through all post fields including feature_image', async () => {
63
+ const mockPost = {
64
+ id: '1',
65
+ title: 'Post with Image',
66
+ html: '<p>Content</p>',
67
+ feature_image: 'https://example.com/image.jpg',
68
+ status: 'published',
69
+ };
70
+ postService.createPostService.mockResolvedValue(mockPost);
71
+
72
+ const req = createMockRequest({
73
+ body: {
74
+ title: 'Post with Image',
75
+ html: '<p>Content</p>',
76
+ feature_image: 'https://example.com/image.jpg',
77
+ status: 'published',
78
+ },
79
+ });
80
+ const res = createMockResponse();
81
+ const next = createMockNext();
82
+
83
+ await createPost(req, res, next);
84
+
85
+ expect(postService.createPostService).toHaveBeenCalledWith({
86
+ title: 'Post with Image',
87
+ html: '<p>Content</p>',
88
+ feature_image: 'https://example.com/image.jpg',
89
+ status: 'published',
90
+ });
91
+ expect(res.status).toHaveBeenCalledWith(201);
92
+ expect(res.json).toHaveBeenCalledWith(mockPost);
93
+ });
94
+
95
+ it('should handle posts with tags', async () => {
96
+ const mockPost = {
97
+ id: '1',
98
+ title: 'Tagged Post',
99
+ html: '<p>Content</p>',
100
+ tags: [{ name: 'Technology' }, { name: 'Science' }],
101
+ status: 'draft',
102
+ };
103
+ postService.createPostService.mockResolvedValue(mockPost);
104
+
105
+ const req = createMockRequest({
106
+ body: {
107
+ title: 'Tagged Post',
108
+ html: '<p>Content</p>',
109
+ tags: [{ name: 'Technology' }, { name: 'Science' }],
110
+ },
111
+ });
112
+ const res = createMockResponse();
113
+ const next = createMockNext();
114
+
115
+ await createPost(req, res, next);
116
+
117
+ expect(postService.createPostService).toHaveBeenCalledWith({
118
+ title: 'Tagged Post',
119
+ html: '<p>Content</p>',
120
+ tags: [{ name: 'Technology' }, { name: 'Science' }],
121
+ });
122
+ expect(res.status).toHaveBeenCalledWith(201);
123
+ expect(res.json).toHaveBeenCalledWith(mockPost);
124
+ });
125
+
126
+ it('should handle posts with metadata', async () => {
127
+ const mockPost = {
128
+ id: '1',
129
+ title: 'Post with Metadata',
130
+ html: '<p>Content</p>',
131
+ meta_title: 'SEO Title',
132
+ meta_description: 'SEO Description',
133
+ status: 'draft',
134
+ };
135
+ postService.createPostService.mockResolvedValue(mockPost);
136
+
137
+ const req = createMockRequest({
138
+ body: {
139
+ title: 'Post with Metadata',
140
+ html: '<p>Content</p>',
141
+ meta_title: 'SEO Title',
142
+ meta_description: 'SEO Description',
143
+ },
144
+ });
145
+ const res = createMockResponse();
146
+ const next = createMockNext();
147
+
148
+ await createPost(req, res, next);
149
+
150
+ expect(postService.createPostService).toHaveBeenCalledWith({
151
+ title: 'Post with Metadata',
152
+ html: '<p>Content</p>',
153
+ meta_title: 'SEO Title',
154
+ meta_description: 'SEO Description',
155
+ });
156
+ expect(res.status).toHaveBeenCalledWith(201);
157
+ });
158
+
159
+ it('should call next() with error when service throws error', async () => {
160
+ const mockError = new Error('Failed to create post');
161
+ postService.createPostService.mockRejectedValue(mockError);
162
+
163
+ const req = createMockRequest({
164
+ body: {
165
+ title: 'Test Post',
166
+ html: '<p>Test content</p>',
167
+ },
168
+ });
169
+ const res = createMockResponse();
170
+ const next = createMockNext();
171
+
172
+ await createPost(req, res, next);
173
+
174
+ expect(postService.createPostService).toHaveBeenCalledWith({
175
+ title: 'Test Post',
176
+ html: '<p>Test content</p>',
177
+ });
178
+ expect(res.status).not.toHaveBeenCalled();
179
+ expect(res.json).not.toHaveBeenCalled();
180
+ expect(next).toHaveBeenCalledWith(mockError);
181
+ });
182
+
183
+ it('should handle validation errors from service', async () => {
184
+ const validationError = new Error('Validation failed: title is required');
185
+ validationError.name = 'ValidationError';
186
+ postService.createPostService.mockRejectedValue(validationError);
187
+
188
+ const req = createMockRequest({
189
+ body: {
190
+ html: '<p>Content without title</p>',
191
+ },
192
+ });
193
+ const res = createMockResponse();
194
+ const next = createMockNext();
195
+
196
+ await createPost(req, res, next);
197
+
198
+ expect(next).toHaveBeenCalledWith(validationError);
199
+ expect(res.status).not.toHaveBeenCalled();
200
+ });
201
+
202
+ it('should handle scheduled posts', async () => {
203
+ const scheduledDate = '2025-12-31T00:00:00Z';
204
+ const mockPost = {
205
+ id: '1',
206
+ title: 'Scheduled Post',
207
+ html: '<p>Future content</p>',
208
+ status: 'scheduled',
209
+ published_at: scheduledDate,
210
+ };
211
+ postService.createPostService.mockResolvedValue(mockPost);
212
+
213
+ const req = createMockRequest({
214
+ body: {
215
+ title: 'Scheduled Post',
216
+ html: '<p>Future content</p>',
217
+ status: 'scheduled',
218
+ published_at: scheduledDate,
219
+ },
220
+ });
221
+ const res = createMockResponse();
222
+ const next = createMockNext();
223
+
224
+ await createPost(req, res, next);
225
+
226
+ expect(postService.createPostService).toHaveBeenCalledWith({
227
+ title: 'Scheduled Post',
228
+ html: '<p>Future content</p>',
229
+ status: 'scheduled',
230
+ published_at: scheduledDate,
231
+ });
232
+ expect(res.status).toHaveBeenCalledWith(201);
233
+ expect(res.json).toHaveBeenCalledWith(mockPost);
234
+ });
235
+ });
236
+ });
@@ -0,0 +1,222 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { mockDotenv } from '../../__tests__/helpers/testUtils.js';
3
+ import { createMockContextLogger } from '../../__tests__/helpers/mockLogger.js';
4
+ import {
5
+ createMockRequest,
6
+ createMockResponse,
7
+ createMockNext,
8
+ } from '../../__tests__/helpers/mockExpress.js';
9
+
10
+ // Mock dotenv
11
+ vi.mock('dotenv', () => mockDotenv());
12
+
13
+ // Mock logger
14
+ vi.mock('../../utils/logger.js', () => ({
15
+ createContextLogger: createMockContextLogger(),
16
+ }));
17
+
18
+ // Mock ghostService functions
19
+ vi.mock('../../services/ghostService.js', () => ({
20
+ getTags: vi.fn(),
21
+ createTag: vi.fn(),
22
+ }));
23
+
24
+ // Import after mocks are set up
25
+ import { getTags, createTag } from '../tagController.js';
26
+ import * as ghostService from '../../services/ghostService.js';
27
+
28
+ describe('tagController', () => {
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ });
32
+
33
+ describe('getTags', () => {
34
+ it('should return 200 with tags array on success', async () => {
35
+ const mockTags = [
36
+ { id: '1', name: 'Technology', slug: 'technology' },
37
+ { id: '2', name: 'Science', slug: 'science' },
38
+ ];
39
+ ghostService.getTags.mockResolvedValue(mockTags);
40
+
41
+ const req = createMockRequest();
42
+ const res = createMockResponse();
43
+ const next = createMockNext();
44
+
45
+ await getTags(req, res, next);
46
+
47
+ expect(ghostService.getTags).toHaveBeenCalledWith(undefined);
48
+ expect(res.status).toHaveBeenCalledWith(200);
49
+ expect(res.json).toHaveBeenCalledWith(mockTags);
50
+ expect(next).not.toHaveBeenCalled();
51
+ });
52
+
53
+ it('should filter tags by name when name query parameter is provided', async () => {
54
+ const mockTags = [{ id: '1', name: 'Technology', slug: 'technology' }];
55
+ ghostService.getTags.mockResolvedValue(mockTags);
56
+
57
+ const req = createMockRequest({ query: { name: 'Technology' } });
58
+ const res = createMockResponse();
59
+ const next = createMockNext();
60
+
61
+ await getTags(req, res, next);
62
+
63
+ expect(ghostService.getTags).toHaveBeenCalledWith('Technology');
64
+ expect(res.status).toHaveBeenCalledWith(200);
65
+ expect(res.json).toHaveBeenCalledWith(mockTags);
66
+ expect(next).not.toHaveBeenCalled();
67
+ });
68
+
69
+ it('should call next() with error when service throws error', async () => {
70
+ const mockError = new Error('Failed to fetch tags');
71
+ ghostService.getTags.mockRejectedValue(mockError);
72
+
73
+ const req = createMockRequest();
74
+ const res = createMockResponse();
75
+ const next = createMockNext();
76
+
77
+ await getTags(req, res, next);
78
+
79
+ expect(ghostService.getTags).toHaveBeenCalledWith(undefined);
80
+ expect(res.status).not.toHaveBeenCalled();
81
+ expect(res.json).not.toHaveBeenCalled();
82
+ expect(next).toHaveBeenCalledWith(mockError);
83
+ });
84
+ });
85
+
86
+ describe('createTag', () => {
87
+ it('should return 201 with new tag on success', async () => {
88
+ const mockTag = { id: '1', name: 'New Tag', slug: 'new-tag' };
89
+ ghostService.createTag.mockResolvedValue(mockTag);
90
+
91
+ const req = createMockRequest({
92
+ body: {
93
+ name: 'New Tag',
94
+ description: 'A new tag description',
95
+ },
96
+ });
97
+ const res = createMockResponse();
98
+ const next = createMockNext();
99
+
100
+ await createTag(req, res, next);
101
+
102
+ expect(ghostService.createTag).toHaveBeenCalledWith({
103
+ name: 'New Tag',
104
+ description: 'A new tag description',
105
+ slug: undefined,
106
+ });
107
+ expect(res.status).toHaveBeenCalledWith(201);
108
+ expect(res.json).toHaveBeenCalledWith(mockTag);
109
+ expect(next).not.toHaveBeenCalled();
110
+ });
111
+
112
+ it('should return 400 when name is missing', async () => {
113
+ const req = createMockRequest({
114
+ body: {
115
+ description: 'A tag without a name',
116
+ },
117
+ });
118
+ const res = createMockResponse();
119
+ const next = createMockNext();
120
+
121
+ await createTag(req, res, next);
122
+
123
+ expect(ghostService.createTag).not.toHaveBeenCalled();
124
+ expect(res.status).toHaveBeenCalledWith(400);
125
+ expect(res.json).toHaveBeenCalledWith({ message: 'Tag name is required.' });
126
+ expect(next).not.toHaveBeenCalled();
127
+ });
128
+
129
+ it('should return 400 when name is empty string', async () => {
130
+ const req = createMockRequest({
131
+ body: {
132
+ name: '',
133
+ description: 'A tag with empty name',
134
+ },
135
+ });
136
+ const res = createMockResponse();
137
+ const next = createMockNext();
138
+
139
+ await createTag(req, res, next);
140
+
141
+ expect(ghostService.createTag).not.toHaveBeenCalled();
142
+ expect(res.status).toHaveBeenCalledWith(400);
143
+ expect(res.json).toHaveBeenCalledWith({ message: 'Tag name is required.' });
144
+ expect(next).not.toHaveBeenCalled();
145
+ });
146
+
147
+ it('should call next() with error when service throws error', async () => {
148
+ const mockError = new Error('Failed to create tag');
149
+ ghostService.createTag.mockRejectedValue(mockError);
150
+
151
+ const req = createMockRequest({
152
+ body: {
153
+ name: 'Test Tag',
154
+ },
155
+ });
156
+ const res = createMockResponse();
157
+ const next = createMockNext();
158
+
159
+ await createTag(req, res, next);
160
+
161
+ expect(ghostService.createTag).toHaveBeenCalledWith({
162
+ name: 'Test Tag',
163
+ description: undefined,
164
+ slug: undefined,
165
+ });
166
+ expect(res.status).not.toHaveBeenCalled();
167
+ expect(res.json).not.toHaveBeenCalled();
168
+ expect(next).toHaveBeenCalledWith(mockError);
169
+ });
170
+
171
+ it('should include slug when provided', async () => {
172
+ const mockTag = { id: '1', name: 'Custom Tag', slug: 'custom-slug' };
173
+ ghostService.createTag.mockResolvedValue(mockTag);
174
+
175
+ const req = createMockRequest({
176
+ body: {
177
+ name: 'Custom Tag',
178
+ slug: 'custom-slug',
179
+ description: 'A tag with custom slug',
180
+ },
181
+ });
182
+ const res = createMockResponse();
183
+ const next = createMockNext();
184
+
185
+ await createTag(req, res, next);
186
+
187
+ expect(ghostService.createTag).toHaveBeenCalledWith({
188
+ name: 'Custom Tag',
189
+ slug: 'custom-slug',
190
+ description: 'A tag with custom slug',
191
+ });
192
+ expect(res.status).toHaveBeenCalledWith(201);
193
+ expect(res.json).toHaveBeenCalledWith(mockTag);
194
+ });
195
+
196
+ it('should pass through additional fields', async () => {
197
+ const mockTag = { id: '1', name: 'Tag', slug: 'tag', visibility: 'public' };
198
+ ghostService.createTag.mockResolvedValue(mockTag);
199
+
200
+ const req = createMockRequest({
201
+ body: {
202
+ name: 'Tag',
203
+ visibility: 'public',
204
+ meta_title: 'Custom Meta Title',
205
+ },
206
+ });
207
+ const res = createMockResponse();
208
+ const next = createMockNext();
209
+
210
+ await createTag(req, res, next);
211
+
212
+ expect(ghostService.createTag).toHaveBeenCalledWith({
213
+ name: 'Tag',
214
+ description: undefined,
215
+ slug: undefined,
216
+ visibility: 'public',
217
+ meta_title: 'Custom Meta Title',
218
+ });
219
+ expect(res.status).toHaveBeenCalledWith(201);
220
+ });
221
+ });
222
+ });