@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.
@@ -0,0 +1,399 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ createNewsletterSchema,
4
+ updateNewsletterSchema,
5
+ newsletterQuerySchema,
6
+ newsletterIdSchema,
7
+ newsletterSlugSchema,
8
+ newsletterOutputSchema,
9
+ } from '../newsletterSchemas.js';
10
+
11
+ describe('Newsletter Schemas', () => {
12
+ describe('createNewsletterSchema', () => {
13
+ it('should accept valid newsletter creation data', () => {
14
+ const validNewsletter = {
15
+ name: 'Weekly Newsletter',
16
+ description: 'Our weekly updates',
17
+ slug: 'weekly-newsletter',
18
+ };
19
+
20
+ expect(() => createNewsletterSchema.parse(validNewsletter)).not.toThrow();
21
+ });
22
+
23
+ it('should accept minimal newsletter creation data (name only)', () => {
24
+ const minimalNewsletter = {
25
+ name: 'Simple Newsletter',
26
+ };
27
+
28
+ const result = createNewsletterSchema.parse(minimalNewsletter);
29
+ expect(result.name).toBe('Simple Newsletter');
30
+ expect(result.sender_reply_to).toBe('newsletter'); // default
31
+ expect(result.status).toBe('active'); // default
32
+ expect(result.visibility).toBe('members'); // default
33
+ expect(result.subscribe_on_signup).toBe(true); // default
34
+ expect(result.show_header_icon).toBe(true); // default
35
+ expect(result.show_header_title).toBe(true); // default
36
+ expect(result.title_font_category).toBe('sans-serif'); // default
37
+ expect(result.title_alignment).toBe('center'); // default
38
+ expect(result.show_feature_image).toBe(true); // default
39
+ expect(result.body_font_category).toBe('sans-serif'); // default
40
+ expect(result.show_badge).toBe(true); // default
41
+ });
42
+
43
+ it('should accept newsletter with all fields', () => {
44
+ const fullNewsletter = {
45
+ name: 'Complete Newsletter',
46
+ description: 'Full description',
47
+ slug: 'complete-newsletter',
48
+ sender_name: 'Blog Team',
49
+ sender_email: 'team@example.com',
50
+ sender_reply_to: 'support',
51
+ status: 'archived',
52
+ visibility: 'paid',
53
+ subscribe_on_signup: false,
54
+ sort_order: 5,
55
+ header_image: 'https://example.com/header.jpg',
56
+ show_header_icon: false,
57
+ show_header_title: false,
58
+ title_font_category: 'serif',
59
+ title_alignment: 'left',
60
+ show_feature_image: false,
61
+ body_font_category: 'serif',
62
+ footer_content: 'Custom footer',
63
+ show_badge: false,
64
+ show_header_name: false,
65
+ show_post_title_section: false,
66
+ };
67
+
68
+ expect(() => createNewsletterSchema.parse(fullNewsletter)).not.toThrow();
69
+ });
70
+
71
+ it('should reject newsletter without name', () => {
72
+ const invalidNewsletter = {
73
+ description: 'Missing name',
74
+ };
75
+
76
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
77
+ });
78
+
79
+ it('should reject newsletter with empty name', () => {
80
+ const invalidNewsletter = {
81
+ name: '',
82
+ };
83
+
84
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
85
+ });
86
+
87
+ it('should reject newsletter with too long name', () => {
88
+ const invalidNewsletter = {
89
+ name: 'A'.repeat(192),
90
+ };
91
+
92
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
93
+ });
94
+
95
+ it('should reject newsletter with too long description', () => {
96
+ const invalidNewsletter = {
97
+ name: 'Newsletter',
98
+ description: 'A'.repeat(2001),
99
+ };
100
+
101
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
102
+ });
103
+
104
+ it('should reject newsletter with invalid slug', () => {
105
+ const invalidNewsletter = {
106
+ name: 'Newsletter',
107
+ slug: 'Invalid_Slug', // uppercase and underscore not allowed
108
+ };
109
+
110
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
111
+ });
112
+
113
+ it('should reject newsletter with too long sender name', () => {
114
+ const invalidNewsletter = {
115
+ name: 'Newsletter',
116
+ sender_name: 'A'.repeat(192),
117
+ };
118
+
119
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
120
+ });
121
+
122
+ it('should reject newsletter with invalid sender email', () => {
123
+ const invalidNewsletter = {
124
+ name: 'Newsletter',
125
+ sender_email: 'not-an-email',
126
+ };
127
+
128
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
129
+ });
130
+
131
+ it('should reject newsletter with invalid sender_reply_to', () => {
132
+ const invalidNewsletter = {
133
+ name: 'Newsletter',
134
+ sender_reply_to: 'invalid',
135
+ };
136
+
137
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
138
+ });
139
+
140
+ it('should reject newsletter with invalid status', () => {
141
+ const invalidNewsletter = {
142
+ name: 'Newsletter',
143
+ status: 'invalid',
144
+ };
145
+
146
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
147
+ });
148
+
149
+ it('should reject newsletter with invalid visibility', () => {
150
+ const invalidNewsletter = {
151
+ name: 'Newsletter',
152
+ visibility: 'public',
153
+ };
154
+
155
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
156
+ });
157
+
158
+ it('should reject newsletter with negative sort_order', () => {
159
+ const invalidNewsletter = {
160
+ name: 'Newsletter',
161
+ sort_order: -1,
162
+ };
163
+
164
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
165
+ });
166
+
167
+ it('should reject newsletter with invalid header_image URL', () => {
168
+ const invalidNewsletter = {
169
+ name: 'Newsletter',
170
+ header_image: 'not-a-url',
171
+ };
172
+
173
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
174
+ });
175
+
176
+ it('should reject newsletter with invalid title_font_category', () => {
177
+ const invalidNewsletter = {
178
+ name: 'Newsletter',
179
+ title_font_category: 'invalid',
180
+ };
181
+
182
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
183
+ });
184
+
185
+ it('should reject newsletter with invalid title_alignment', () => {
186
+ const invalidNewsletter = {
187
+ name: 'Newsletter',
188
+ title_alignment: 'right',
189
+ };
190
+
191
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
192
+ });
193
+
194
+ it('should reject newsletter with invalid body_font_category', () => {
195
+ const invalidNewsletter = {
196
+ name: 'Newsletter',
197
+ body_font_category: 'monospace',
198
+ };
199
+
200
+ expect(() => createNewsletterSchema.parse(invalidNewsletter)).toThrow();
201
+ });
202
+ });
203
+
204
+ describe('updateNewsletterSchema', () => {
205
+ it('should accept partial newsletter updates', () => {
206
+ const update = {
207
+ description: 'Updated description',
208
+ };
209
+
210
+ expect(() => updateNewsletterSchema.parse(update)).not.toThrow();
211
+ });
212
+
213
+ it('should accept empty update object', () => {
214
+ expect(() => updateNewsletterSchema.parse({})).not.toThrow();
215
+ });
216
+
217
+ it('should accept full newsletter update', () => {
218
+ const update = {
219
+ name: 'Updated Newsletter',
220
+ description: 'Updated description',
221
+ status: 'archived',
222
+ visibility: 'paid',
223
+ };
224
+
225
+ expect(() => updateNewsletterSchema.parse(update)).not.toThrow();
226
+ });
227
+ });
228
+
229
+ describe('newsletterQuerySchema', () => {
230
+ it('should accept valid query parameters', () => {
231
+ const query = {
232
+ limit: 20,
233
+ page: 2,
234
+ filter: 'status:active',
235
+ };
236
+
237
+ expect(() => newsletterQuerySchema.parse(query)).not.toThrow();
238
+ });
239
+
240
+ it('should accept query with order parameter', () => {
241
+ const query = {
242
+ order: 'sort_order ASC',
243
+ };
244
+
245
+ expect(() => newsletterQuerySchema.parse(query)).not.toThrow();
246
+ });
247
+
248
+ it('should reject query with invalid filter characters', () => {
249
+ const query = {
250
+ filter: 'status;DROP TABLE',
251
+ };
252
+
253
+ expect(() => newsletterQuerySchema.parse(query)).toThrow();
254
+ });
255
+
256
+ it('should accept empty query object', () => {
257
+ const result = newsletterQuerySchema.parse({});
258
+ expect(result).toBeDefined();
259
+ });
260
+ });
261
+
262
+ describe('newsletterIdSchema', () => {
263
+ it('should accept valid Ghost ID', () => {
264
+ const validId = {
265
+ id: '507f1f77bcf86cd799439011',
266
+ };
267
+
268
+ expect(() => newsletterIdSchema.parse(validId)).not.toThrow();
269
+ });
270
+
271
+ it('should reject invalid Ghost ID', () => {
272
+ const invalidId = {
273
+ id: 'invalid-id',
274
+ };
275
+
276
+ expect(() => newsletterIdSchema.parse(invalidId)).toThrow();
277
+ });
278
+ });
279
+
280
+ describe('newsletterSlugSchema', () => {
281
+ it('should accept valid slug', () => {
282
+ const validSlug = {
283
+ slug: 'weekly-newsletter',
284
+ };
285
+
286
+ expect(() => newsletterSlugSchema.parse(validSlug)).not.toThrow();
287
+ });
288
+
289
+ it('should reject invalid slug', () => {
290
+ const invalidSlug = {
291
+ slug: 'Invalid_Slug',
292
+ };
293
+
294
+ expect(() => newsletterSlugSchema.parse(invalidSlug)).toThrow();
295
+ });
296
+ });
297
+
298
+ describe('newsletterOutputSchema', () => {
299
+ it('should accept valid newsletter output from Ghost API', () => {
300
+ const apiNewsletter = {
301
+ id: '507f1f77bcf86cd799439011',
302
+ uuid: '550e8400-e29b-41d4-a716-446655440000',
303
+ name: 'Weekly Newsletter',
304
+ description: 'Our weekly updates',
305
+ slug: 'weekly-newsletter',
306
+ sender_name: 'Blog Team',
307
+ sender_email: 'team@example.com',
308
+ sender_reply_to: 'newsletter',
309
+ status: 'active',
310
+ visibility: 'members',
311
+ subscribe_on_signup: true,
312
+ sort_order: 0,
313
+ header_image: 'https://example.com/header.jpg',
314
+ show_header_icon: true,
315
+ show_header_title: true,
316
+ title_font_category: 'sans-serif',
317
+ title_alignment: 'center',
318
+ show_feature_image: true,
319
+ body_font_category: 'sans-serif',
320
+ footer_content: 'Footer text',
321
+ show_badge: true,
322
+ show_header_name: true,
323
+ show_post_title_section: true,
324
+ created_at: '2024-01-15T10:30:00.000Z',
325
+ updated_at: '2024-01-15T10:30:00.000Z',
326
+ count: {
327
+ members: 150,
328
+ posts: 25,
329
+ },
330
+ };
331
+
332
+ expect(() => newsletterOutputSchema.parse(apiNewsletter)).not.toThrow();
333
+ });
334
+
335
+ it('should accept newsletter with null optional fields', () => {
336
+ const apiNewsletter = {
337
+ id: '507f1f77bcf86cd799439011',
338
+ uuid: '550e8400-e29b-41d4-a716-446655440000',
339
+ name: 'Newsletter',
340
+ description: null,
341
+ slug: 'newsletter',
342
+ sender_name: null,
343
+ sender_email: null,
344
+ sender_reply_to: 'newsletter',
345
+ status: 'active',
346
+ visibility: 'members',
347
+ subscribe_on_signup: true,
348
+ sort_order: 0,
349
+ header_image: null,
350
+ show_header_icon: true,
351
+ show_header_title: true,
352
+ title_font_category: 'sans-serif',
353
+ title_alignment: 'center',
354
+ show_feature_image: true,
355
+ body_font_category: 'sans-serif',
356
+ footer_content: null,
357
+ show_badge: true,
358
+ created_at: '2024-01-15T10:30:00.000Z',
359
+ updated_at: '2024-01-15T10:30:00.000Z',
360
+ };
361
+
362
+ expect(() => newsletterOutputSchema.parse(apiNewsletter)).not.toThrow();
363
+ });
364
+
365
+ it('should reject newsletter output without required fields', () => {
366
+ const invalidNewsletter = {
367
+ name: 'Newsletter',
368
+ slug: 'newsletter',
369
+ };
370
+
371
+ expect(() => newsletterOutputSchema.parse(invalidNewsletter)).toThrow();
372
+ });
373
+
374
+ it('should reject newsletter output with invalid status', () => {
375
+ const invalidNewsletter = {
376
+ id: '507f1f77bcf86cd799439011',
377
+ uuid: '550e8400-e29b-41d4-a716-446655440000',
378
+ name: 'Newsletter',
379
+ slug: 'newsletter',
380
+ sender_reply_to: 'newsletter',
381
+ status: 'invalid_status',
382
+ visibility: 'members',
383
+ subscribe_on_signup: true,
384
+ sort_order: 0,
385
+ show_header_icon: true,
386
+ show_header_title: true,
387
+ title_font_category: 'sans-serif',
388
+ title_alignment: 'center',
389
+ show_feature_image: true,
390
+ body_font_category: 'sans-serif',
391
+ show_badge: true,
392
+ created_at: '2024-01-15T10:30:00.000Z',
393
+ updated_at: '2024-01-15T10:30:00.000Z',
394
+ };
395
+
396
+ expect(() => newsletterOutputSchema.parse(invalidNewsletter)).toThrow();
397
+ });
398
+ });
399
+ });