@jgardner04/ghost-mcp-server 1.12.0 → 1.12.2
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_improved.test.js +80 -16
- package/src/errors/index.js +7 -0
- package/src/mcp_server_improved.js +55 -80
- 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
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createTierSchema,
|
|
4
|
+
updateTierSchema,
|
|
5
|
+
tierQuerySchema,
|
|
6
|
+
tierIdSchema,
|
|
7
|
+
tierSlugSchema,
|
|
8
|
+
tierOutputSchema,
|
|
9
|
+
tierBenefitSchema,
|
|
10
|
+
monthlyPriceSchema,
|
|
11
|
+
yearlyPriceSchema,
|
|
12
|
+
benefitOutputSchema,
|
|
13
|
+
monthlyPriceOutputSchema,
|
|
14
|
+
yearlyPriceOutputSchema,
|
|
15
|
+
} from '../tierSchemas.js';
|
|
16
|
+
|
|
17
|
+
describe('Tier Schemas', () => {
|
|
18
|
+
describe('tierBenefitSchema', () => {
|
|
19
|
+
it('should accept valid benefit', () => {
|
|
20
|
+
const benefit = {
|
|
21
|
+
name: 'Access to premium content',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
expect(() => tierBenefitSchema.parse(benefit)).not.toThrow();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should reject empty benefit name', () => {
|
|
28
|
+
const benefit = {
|
|
29
|
+
name: '',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
expect(() => tierBenefitSchema.parse(benefit)).toThrow();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should reject too long benefit name', () => {
|
|
36
|
+
const benefit = {
|
|
37
|
+
name: 'A'.repeat(192),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
expect(() => tierBenefitSchema.parse(benefit)).toThrow();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('monthlyPriceSchema', () => {
|
|
45
|
+
it('should accept valid monthly price', () => {
|
|
46
|
+
const price = {
|
|
47
|
+
amount: 999,
|
|
48
|
+
currency: 'USD',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
expect(() => monthlyPriceSchema.parse(price)).not.toThrow();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should reject negative amount', () => {
|
|
55
|
+
const price = {
|
|
56
|
+
amount: -100,
|
|
57
|
+
currency: 'USD',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
expect(() => monthlyPriceSchema.parse(price)).toThrow();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should reject invalid currency code length', () => {
|
|
64
|
+
const price = {
|
|
65
|
+
amount: 999,
|
|
66
|
+
currency: 'US',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
expect(() => monthlyPriceSchema.parse(price)).toThrow();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should reject lowercase currency code', () => {
|
|
73
|
+
const price = {
|
|
74
|
+
amount: 999,
|
|
75
|
+
currency: 'usd',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
expect(() => monthlyPriceSchema.parse(price)).toThrow();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('yearlyPriceSchema', () => {
|
|
83
|
+
it('should accept valid yearly price', () => {
|
|
84
|
+
const price = {
|
|
85
|
+
amount: 9999,
|
|
86
|
+
currency: 'EUR',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
expect(() => yearlyPriceSchema.parse(price)).not.toThrow();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should reject negative amount', () => {
|
|
93
|
+
const price = {
|
|
94
|
+
amount: -1000,
|
|
95
|
+
currency: 'EUR',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
expect(() => yearlyPriceSchema.parse(price)).toThrow();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should reject invalid currency format', () => {
|
|
102
|
+
const price = {
|
|
103
|
+
amount: 9999,
|
|
104
|
+
currency: 'EURO',
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
expect(() => yearlyPriceSchema.parse(price)).toThrow();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('createTierSchema', () => {
|
|
112
|
+
it('should accept valid tier creation data', () => {
|
|
113
|
+
const validTier = {
|
|
114
|
+
name: 'Premium Membership',
|
|
115
|
+
description: 'Access to all premium content',
|
|
116
|
+
monthly_price: 999,
|
|
117
|
+
yearly_price: 9999,
|
|
118
|
+
currency: 'USD',
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
expect(() => createTierSchema.parse(validTier)).not.toThrow();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should accept minimal tier creation data (name only)', () => {
|
|
125
|
+
const minimalTier = {
|
|
126
|
+
name: 'Basic Tier',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const result = createTierSchema.parse(minimalTier);
|
|
130
|
+
expect(result.name).toBe('Basic Tier');
|
|
131
|
+
expect(result.active).toBe(true); // default
|
|
132
|
+
expect(result.type).toBe('paid'); // default
|
|
133
|
+
expect(result.visibility).toBe('public'); // default
|
|
134
|
+
expect(result.trial_days).toBe(0); // default
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should accept tier with all fields', () => {
|
|
138
|
+
const fullTier = {
|
|
139
|
+
name: 'VIP Membership',
|
|
140
|
+
description: 'Exclusive VIP access',
|
|
141
|
+
slug: 'vip-membership',
|
|
142
|
+
active: true,
|
|
143
|
+
type: 'paid',
|
|
144
|
+
welcome_page_url: 'https://example.com/welcome',
|
|
145
|
+
visibility: 'public',
|
|
146
|
+
trial_days: 7,
|
|
147
|
+
currency: 'USD',
|
|
148
|
+
monthly_price: 1999,
|
|
149
|
+
yearly_price: 19999,
|
|
150
|
+
benefits: ['Premium content', 'Early access', 'Exclusive newsletter'],
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
expect(() => createTierSchema.parse(fullTier)).not.toThrow();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should accept free tier', () => {
|
|
157
|
+
const freeTier = {
|
|
158
|
+
name: 'Free Membership',
|
|
159
|
+
type: 'free',
|
|
160
|
+
benefits: ['Basic access', 'Monthly newsletter'],
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
expect(() => createTierSchema.parse(freeTier)).not.toThrow();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should reject tier without name', () => {
|
|
167
|
+
const invalidTier = {
|
|
168
|
+
description: 'Missing name',
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should reject tier with empty name', () => {
|
|
175
|
+
const invalidTier = {
|
|
176
|
+
name: '',
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should reject tier with too long name', () => {
|
|
183
|
+
const invalidTier = {
|
|
184
|
+
name: 'A'.repeat(192),
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should reject tier with too long description', () => {
|
|
191
|
+
const invalidTier = {
|
|
192
|
+
name: 'Tier',
|
|
193
|
+
description: 'A'.repeat(2001),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should reject tier with invalid slug', () => {
|
|
200
|
+
const invalidTier = {
|
|
201
|
+
name: 'Tier',
|
|
202
|
+
slug: 'Invalid_Slug',
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should reject tier with invalid type', () => {
|
|
209
|
+
const invalidTier = {
|
|
210
|
+
name: 'Tier',
|
|
211
|
+
type: 'premium',
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should reject tier with invalid welcome_page_url', () => {
|
|
218
|
+
const invalidTier = {
|
|
219
|
+
name: 'Tier',
|
|
220
|
+
welcome_page_url: 'not-a-url',
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should reject tier with invalid visibility', () => {
|
|
227
|
+
const invalidTier = {
|
|
228
|
+
name: 'Tier',
|
|
229
|
+
visibility: 'private',
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should reject tier with negative trial_days', () => {
|
|
236
|
+
const invalidTier = {
|
|
237
|
+
name: 'Tier',
|
|
238
|
+
trial_days: -1,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should reject tier with invalid currency', () => {
|
|
245
|
+
const invalidTier = {
|
|
246
|
+
name: 'Tier',
|
|
247
|
+
currency: 'usd',
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should reject tier with negative monthly_price', () => {
|
|
254
|
+
const invalidTier = {
|
|
255
|
+
name: 'Tier',
|
|
256
|
+
monthly_price: -100,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should reject tier with negative yearly_price', () => {
|
|
263
|
+
const invalidTier = {
|
|
264
|
+
name: 'Tier',
|
|
265
|
+
yearly_price: -1000,
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
expect(() => createTierSchema.parse(invalidTier)).toThrow();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('updateTierSchema', () => {
|
|
273
|
+
it('should accept partial tier updates', () => {
|
|
274
|
+
const update = {
|
|
275
|
+
description: 'Updated description',
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
expect(() => updateTierSchema.parse(update)).not.toThrow();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should accept empty update object', () => {
|
|
282
|
+
expect(() => updateTierSchema.parse({})).not.toThrow();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should accept full tier update', () => {
|
|
286
|
+
const update = {
|
|
287
|
+
name: 'Updated Tier',
|
|
288
|
+
description: 'Updated description',
|
|
289
|
+
monthly_price: 1499,
|
|
290
|
+
active: false,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
expect(() => updateTierSchema.parse(update)).not.toThrow();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe('tierQuerySchema', () => {
|
|
298
|
+
it('should accept valid query parameters', () => {
|
|
299
|
+
const query = {
|
|
300
|
+
limit: 20,
|
|
301
|
+
page: 2,
|
|
302
|
+
filter: 'type:paid+active:true',
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
expect(() => tierQuerySchema.parse(query)).not.toThrow();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should accept query with include parameter', () => {
|
|
309
|
+
const query = {
|
|
310
|
+
include: 'benefits,monthly_price',
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
expect(() => tierQuerySchema.parse(query)).not.toThrow();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should accept query with order parameter', () => {
|
|
317
|
+
const query = {
|
|
318
|
+
order: 'monthly_price ASC',
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
expect(() => tierQuerySchema.parse(query)).not.toThrow();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('should reject query with invalid filter characters', () => {
|
|
325
|
+
const query = {
|
|
326
|
+
filter: 'type;DROP TABLE',
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
expect(() => tierQuerySchema.parse(query)).toThrow();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should accept empty query object', () => {
|
|
333
|
+
const result = tierQuerySchema.parse({});
|
|
334
|
+
expect(result).toBeDefined();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe('tierIdSchema', () => {
|
|
339
|
+
it('should accept valid Ghost ID', () => {
|
|
340
|
+
const validId = {
|
|
341
|
+
id: '507f1f77bcf86cd799439011',
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
expect(() => tierIdSchema.parse(validId)).not.toThrow();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('should reject invalid Ghost ID', () => {
|
|
348
|
+
const invalidId = {
|
|
349
|
+
id: 'invalid-id',
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
expect(() => tierIdSchema.parse(invalidId)).toThrow();
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
describe('tierSlugSchema', () => {
|
|
357
|
+
it('should accept valid slug', () => {
|
|
358
|
+
const validSlug = {
|
|
359
|
+
slug: 'premium-tier',
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
expect(() => tierSlugSchema.parse(validSlug)).not.toThrow();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should reject invalid slug', () => {
|
|
366
|
+
const invalidSlug = {
|
|
367
|
+
slug: 'Premium_Tier',
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
expect(() => tierSlugSchema.parse(invalidSlug)).toThrow();
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe('benefitOutputSchema', () => {
|
|
375
|
+
it('should accept valid benefit output from Ghost API', () => {
|
|
376
|
+
const apiBenefit = {
|
|
377
|
+
id: '507f1f77bcf86cd799439011',
|
|
378
|
+
name: 'Premium content access',
|
|
379
|
+
slug: 'premium-content-access',
|
|
380
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
381
|
+
updated_at: '2024-01-15T10:30:00.000Z',
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
expect(() => benefitOutputSchema.parse(apiBenefit)).not.toThrow();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('should reject benefit output without required fields', () => {
|
|
388
|
+
const invalidBenefit = {
|
|
389
|
+
name: 'Premium content',
|
|
390
|
+
slug: 'premium-content',
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
expect(() => benefitOutputSchema.parse(invalidBenefit)).toThrow();
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
describe('monthlyPriceOutputSchema', () => {
|
|
398
|
+
it('should accept valid monthly price output from Ghost API', () => {
|
|
399
|
+
const apiPrice = {
|
|
400
|
+
id: 'price_123',
|
|
401
|
+
tier_id: '507f1f77bcf86cd799439011',
|
|
402
|
+
nickname: 'Monthly Premium',
|
|
403
|
+
amount: 999,
|
|
404
|
+
interval: 'month',
|
|
405
|
+
type: 'recurring',
|
|
406
|
+
currency: 'USD',
|
|
407
|
+
active: true,
|
|
408
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
409
|
+
updated_at: '2024-01-15T10:30:00.000Z',
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
expect(() => monthlyPriceOutputSchema.parse(apiPrice)).not.toThrow();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('should reject price with wrong interval', () => {
|
|
416
|
+
const invalidPrice = {
|
|
417
|
+
id: 'price_123',
|
|
418
|
+
tier_id: '507f1f77bcf86cd799439011',
|
|
419
|
+
nickname: 'Monthly Premium',
|
|
420
|
+
amount: 999,
|
|
421
|
+
interval: 'year',
|
|
422
|
+
type: 'recurring',
|
|
423
|
+
currency: 'USD',
|
|
424
|
+
active: true,
|
|
425
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
426
|
+
updated_at: '2024-01-15T10:30:00.000Z',
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
expect(() => monthlyPriceOutputSchema.parse(invalidPrice)).toThrow();
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
describe('yearlyPriceOutputSchema', () => {
|
|
434
|
+
it('should accept valid yearly price output from Ghost API', () => {
|
|
435
|
+
const apiPrice = {
|
|
436
|
+
id: 'price_456',
|
|
437
|
+
tier_id: '507f1f77bcf86cd799439011',
|
|
438
|
+
nickname: 'Yearly Premium',
|
|
439
|
+
amount: 9999,
|
|
440
|
+
interval: 'year',
|
|
441
|
+
type: 'recurring',
|
|
442
|
+
currency: 'EUR',
|
|
443
|
+
active: true,
|
|
444
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
445
|
+
updated_at: '2024-01-15T10:30:00.000Z',
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
expect(() => yearlyPriceOutputSchema.parse(apiPrice)).not.toThrow();
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('should reject price with wrong interval', () => {
|
|
452
|
+
const invalidPrice = {
|
|
453
|
+
id: 'price_456',
|
|
454
|
+
tier_id: '507f1f77bcf86cd799439011',
|
|
455
|
+
nickname: 'Yearly Premium',
|
|
456
|
+
amount: 9999,
|
|
457
|
+
interval: 'month',
|
|
458
|
+
type: 'recurring',
|
|
459
|
+
currency: 'EUR',
|
|
460
|
+
active: true,
|
|
461
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
462
|
+
updated_at: '2024-01-15T10:30:00.000Z',
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
expect(() => yearlyPriceOutputSchema.parse(invalidPrice)).toThrow();
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
describe('tierOutputSchema', () => {
|
|
470
|
+
it('should accept valid tier output from Ghost API', () => {
|
|
471
|
+
const apiTier = {
|
|
472
|
+
id: '507f1f77bcf86cd799439011',
|
|
473
|
+
name: 'Premium Membership',
|
|
474
|
+
slug: 'premium-membership',
|
|
475
|
+
description: 'Access to premium content',
|
|
476
|
+
active: true,
|
|
477
|
+
type: 'paid',
|
|
478
|
+
welcome_page_url: 'https://example.com/welcome',
|
|
479
|
+
visibility: 'public',
|
|
480
|
+
trial_days: 7,
|
|
481
|
+
currency: 'USD',
|
|
482
|
+
monthly_price: 999,
|
|
483
|
+
yearly_price: 9999,
|
|
484
|
+
monthly_price_id: 'price_123',
|
|
485
|
+
yearly_price_id: 'price_456',
|
|
486
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
487
|
+
updated_at: '2024-01-15T10:30:00.000Z',
|
|
488
|
+
benefits: [
|
|
489
|
+
{
|
|
490
|
+
id: '507f1f77bcf86cd799439012',
|
|
491
|
+
name: 'Premium content',
|
|
492
|
+
slug: 'premium-content',
|
|
493
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
494
|
+
updated_at: '2024-01-15T10:30:00.000Z',
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
monthly_price_object: {
|
|
498
|
+
id: 'price_123',
|
|
499
|
+
tier_id: '507f1f77bcf86cd799439011',
|
|
500
|
+
nickname: 'Monthly Premium',
|
|
501
|
+
amount: 999,
|
|
502
|
+
interval: 'month',
|
|
503
|
+
type: 'recurring',
|
|
504
|
+
currency: 'USD',
|
|
505
|
+
active: true,
|
|
506
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
507
|
+
updated_at: '2024-01-15T10:30:00.000Z',
|
|
508
|
+
},
|
|
509
|
+
yearly_price_object: {
|
|
510
|
+
id: 'price_456',
|
|
511
|
+
tier_id: '507f1f77bcf86cd799439011',
|
|
512
|
+
nickname: 'Yearly Premium',
|
|
513
|
+
amount: 9999,
|
|
514
|
+
interval: 'year',
|
|
515
|
+
type: 'recurring',
|
|
516
|
+
currency: 'USD',
|
|
517
|
+
active: true,
|
|
518
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
519
|
+
updated_at: '2024-01-15T10:30:00.000Z',
|
|
520
|
+
},
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
expect(() => tierOutputSchema.parse(apiTier)).not.toThrow();
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('should accept tier with null optional fields', () => {
|
|
527
|
+
const apiTier = {
|
|
528
|
+
id: '507f1f77bcf86cd799439011',
|
|
529
|
+
name: 'Free Tier',
|
|
530
|
+
slug: 'free-tier',
|
|
531
|
+
description: null,
|
|
532
|
+
active: true,
|
|
533
|
+
type: 'free',
|
|
534
|
+
welcome_page_url: null,
|
|
535
|
+
visibility: 'public',
|
|
536
|
+
trial_days: 0,
|
|
537
|
+
currency: null,
|
|
538
|
+
monthly_price: null,
|
|
539
|
+
yearly_price: null,
|
|
540
|
+
monthly_price_id: null,
|
|
541
|
+
yearly_price_id: null,
|
|
542
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
543
|
+
updated_at: '2024-01-15T10:30:00.000Z',
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
expect(() => tierOutputSchema.parse(apiTier)).not.toThrow();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('should reject tier output without required fields', () => {
|
|
550
|
+
const invalidTier = {
|
|
551
|
+
name: 'Premium',
|
|
552
|
+
slug: 'premium',
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
expect(() => tierOutputSchema.parse(invalidTier)).toThrow();
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('should reject tier output with invalid type', () => {
|
|
559
|
+
const invalidTier = {
|
|
560
|
+
id: '507f1f77bcf86cd799439011',
|
|
561
|
+
name: 'Premium',
|
|
562
|
+
slug: 'premium',
|
|
563
|
+
active: true,
|
|
564
|
+
type: 'premium',
|
|
565
|
+
visibility: 'public',
|
|
566
|
+
trial_days: 0,
|
|
567
|
+
created_at: '2024-01-15T10:30:00.000Z',
|
|
568
|
+
updated_at: '2024-01-15T10:30:00.000Z',
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
expect(() => tierOutputSchema.parse(invalidTier)).toThrow();
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
});
|