feat(yachts): add zod validators + tests
This commit is contained in:
52
src/lib/validators/yachts.ts
Normal file
52
src/lib/validators/yachts.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { z } from 'zod';
|
||||
import { baseListQuerySchema } from '@/lib/api/route-helpers';
|
||||
|
||||
export const ownerRefSchema = z.object({
|
||||
type: z.enum(['client', 'company']),
|
||||
id: z.string().min(1),
|
||||
});
|
||||
|
||||
export const createYachtSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
hullNumber: z.string().optional(),
|
||||
registration: z.string().optional(),
|
||||
flag: z.string().optional(),
|
||||
yearBuilt: z.number().int().min(1800).max(2100).optional(),
|
||||
builder: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
hullMaterial: z.string().optional(),
|
||||
lengthFt: z.string().optional(),
|
||||
widthFt: z.string().optional(),
|
||||
draftFt: z.string().optional(),
|
||||
lengthM: z.string().optional(),
|
||||
widthM: z.string().optional(),
|
||||
draftM: z.string().optional(),
|
||||
owner: ownerRefSchema, // required; yacht must have an owner
|
||||
status: z.enum(['active', 'retired', 'sold_away']).optional().default('active'),
|
||||
notes: z.string().optional(),
|
||||
tagIds: z.array(z.string()).optional().default([]),
|
||||
});
|
||||
|
||||
export const updateYachtSchema = createYachtSchema.partial().omit({ owner: true });
|
||||
// Owner changes go through /transfer, not PATCH.
|
||||
|
||||
export const transferOwnershipSchema = z.object({
|
||||
newOwner: ownerRefSchema,
|
||||
effectiveDate: z.coerce.date(),
|
||||
transferReason: z
|
||||
.enum(['sale', 'inheritance', 'gift', 'company_restructure', 'other'])
|
||||
.optional(),
|
||||
transferNotes: z.string().optional(),
|
||||
});
|
||||
|
||||
export const listYachtsQuery = baseListQuerySchema.extend({
|
||||
ownerType: z.enum(['client', 'company']).optional(),
|
||||
ownerId: z.string().optional(),
|
||||
status: z.enum(['active', 'retired', 'sold_away']).optional(),
|
||||
search: z.string().optional(),
|
||||
});
|
||||
|
||||
export type CreateYachtInput = z.infer<typeof createYachtSchema>;
|
||||
export type UpdateYachtInput = z.infer<typeof updateYachtSchema>;
|
||||
export type TransferOwnershipInput = z.infer<typeof transferOwnershipSchema>;
|
||||
export type ListYachtsInput = z.infer<typeof listYachtsQuery>;
|
||||
49
tests/unit/validators/yachts.test.ts
Normal file
49
tests/unit/validators/yachts.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createYachtSchema, transferOwnershipSchema } from '@/lib/validators/yachts';
|
||||
|
||||
describe('createYachtSchema', () => {
|
||||
it('rejects empty name', () => {
|
||||
const result = createYachtSchema.safeParse({
|
||||
name: '',
|
||||
owner: { type: 'client', id: 'c1' },
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('requires owner', () => {
|
||||
const result = createYachtSchema.safeParse({ name: 'Sea Breeze' });
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects invalid yearBuilt', () => {
|
||||
const result = createYachtSchema.safeParse({
|
||||
name: 'Sea Breeze',
|
||||
owner: { type: 'client', id: 'c1' },
|
||||
yearBuilt: 1700,
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts minimal valid input', () => {
|
||||
const result = createYachtSchema.safeParse({
|
||||
name: 'Sea Breeze',
|
||||
owner: { type: 'client', id: 'c1' },
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transferOwnershipSchema', () => {
|
||||
it('requires newOwner + effectiveDate', () => {
|
||||
expect(transferOwnershipSchema.safeParse({}).success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts valid input', () => {
|
||||
const result = transferOwnershipSchema.safeParse({
|
||||
newOwner: { type: 'company', id: 'co1' },
|
||||
effectiveDate: new Date(),
|
||||
transferReason: 'sale',
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user