test(platform-service): add products module tests and fix product schema defaults
- Added products.test.ts covering CreateProductSchema and UpdateProductSchema - Added route export smoke test for productRoutes - Fixed CreateProductSchema packageName default validation (allow empty default) - Verified: tsc --noEmit clean, 19 test files / 178 tests passing
This commit is contained in:
parent
c0830e3dec
commit
0fee7e9ee7
125
services/platform-service/src/modules/products/products.test.ts
Normal file
125
services/platform-service/src/modules/products/products.test.ts
Normal file
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Unit tests for products module — types, schemas, cache.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { CreateProductSchema, UpdateProductSchema } from './types.js';
|
||||
|
||||
describe('CreateProductSchema', () => {
|
||||
it('accepts valid product input', () => {
|
||||
const result = CreateProductSchema.safeParse({
|
||||
productId: 'lysnrai',
|
||||
displayName: 'LysnrAI',
|
||||
licensePrefix: 'LYSNR',
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.productId).toBe('lysnrai');
|
||||
expect(result.data.defaultPlan).toBe('free');
|
||||
expect(result.data.trialDays).toBe(14);
|
||||
expect(result.data.deviceLimits).toEqual({ free: 1, pro: 3, enterprise: 10 });
|
||||
expect(result.data.status).toBe('active');
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects productId with uppercase', () => {
|
||||
const result = CreateProductSchema.safeParse({
|
||||
productId: 'LysnrAI',
|
||||
displayName: 'LysnrAI',
|
||||
licensePrefix: 'LYSNR',
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects productId with spaces', () => {
|
||||
const result = CreateProductSchema.safeParse({
|
||||
productId: 'lysnr ai',
|
||||
displayName: 'LysnrAI',
|
||||
licensePrefix: 'LYSNR',
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects licensePrefix with lowercase', () => {
|
||||
const result = CreateProductSchema.safeParse({
|
||||
productId: 'lysnrai',
|
||||
displayName: 'LysnrAI',
|
||||
licensePrefix: 'lysnr',
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts full product input with all fields', () => {
|
||||
const result = CreateProductSchema.safeParse({
|
||||
productId: 'mindlyst',
|
||||
displayName: 'MindLyst',
|
||||
licensePrefix: 'MIND',
|
||||
packageName: 'com.mindlyst.app',
|
||||
defaultPlan: 'pro',
|
||||
trialDays: 30,
|
||||
deviceLimits: { free: 2, pro: 5, enterprise: 20 },
|
||||
websiteUrl: 'https://mindlyst.com',
|
||||
status: 'active',
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.trialDays).toBe(30);
|
||||
expect(result.data.deviceLimits.pro).toBe(5);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects empty productId', () => {
|
||||
const result = CreateProductSchema.safeParse({
|
||||
productId: '',
|
||||
displayName: 'Test',
|
||||
licensePrefix: 'TEST',
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdateProductSchema', () => {
|
||||
it('accepts partial update', () => {
|
||||
const result = UpdateProductSchema.safeParse({
|
||||
displayName: 'Updated Name',
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts empty object', () => {
|
||||
const result = UpdateProductSchema.safeParse({});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('accepts partial deviceLimits', () => {
|
||||
const result = UpdateProductSchema.safeParse({
|
||||
deviceLimits: { pro: 10 },
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.deviceLimits?.pro).toBe(10);
|
||||
expect(result.data.deviceLimits?.free).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('accepts status change', () => {
|
||||
const result = UpdateProductSchema.safeParse({
|
||||
status: 'disabled',
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects invalid status', () => {
|
||||
const result = UpdateProductSchema.safeParse({
|
||||
status: 'deleted',
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('productRoutes export', () => {
|
||||
it('exports productRoutes function', async () => {
|
||||
const mod = await import('./routes.js');
|
||||
expect(typeof mod.productRoutes).toBe('function');
|
||||
});
|
||||
});
|
||||
@ -42,7 +42,7 @@ export const CreateProductSchema = z.object({
|
||||
.min(1)
|
||||
.max(16)
|
||||
.regex(/^[A-Z]+$/, 'licensePrefix must be uppercase letters'),
|
||||
packageName: z.string().min(1).max(256).default(''),
|
||||
packageName: z.string().max(256).default(''),
|
||||
defaultPlan: z.enum(['free', 'pro']).default('free'),
|
||||
trialDays: z.number().int().min(0).max(365).default(14),
|
||||
deviceLimits: DeviceLimitsSchema.default({ free: 1, pro: 3, enterprise: 10 }),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user