Browse Source
Zod v4 does not support .omit() on schemas containing .refine(). Restructured the shared schema to define server and client schemas independently while sharing field definitions to avoid duplication. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>feat/registration-page
2 changed files with 107 additions and 12 deletions
@ -0,0 +1,80 @@ |
|||
import { describe, it, expect } from 'vitest' |
|||
import { registerSchema, clientRegisterSchema } from '../auth-schema' |
|||
|
|||
describe('registerSchema', () => { |
|||
const validBody = { |
|||
username: 'testuser', |
|||
password: 'password123', |
|||
confirmPassword: 'password123', |
|||
captchaToken: 'token-abc', |
|||
captchaText: 'abcde', |
|||
} |
|||
|
|||
it('passes with valid input', () => { |
|||
expect(registerSchema.safeParse(validBody).success).toBe(true) |
|||
}) |
|||
|
|||
it('fails on short username', () => { |
|||
const r = registerSchema.safeParse({ ...validBody, username: 'ab' }) |
|||
expect(r.success).toBe(false) |
|||
if (!r.success) { |
|||
expect(r.error.issues[0].path).toContain('username') |
|||
} |
|||
}) |
|||
|
|||
it('fails on short password', () => { |
|||
const r = registerSchema.safeParse({ ...validBody, password: '1234567' }) |
|||
expect(r.success).toBe(false) |
|||
if (!r.success) { |
|||
expect(r.error.issues[0].path).toContain('password') |
|||
} |
|||
}) |
|||
|
|||
it('fails on password mismatch', () => { |
|||
const r = registerSchema.safeParse({ |
|||
...validBody, |
|||
confirmPassword: 'different', |
|||
}) |
|||
expect(r.success).toBe(false) |
|||
if (!r.success) { |
|||
expect(r.error.issues[0].path).toContain('confirmPassword') |
|||
} |
|||
}) |
|||
|
|||
it('fails on empty captchaText', () => { |
|||
const r = registerSchema.safeParse({ ...validBody, captchaText: '' }) |
|||
expect(r.success).toBe(false) |
|||
if (!r.success) { |
|||
expect(r.error.issues[0].path).toContain('captchaText') |
|||
} |
|||
}) |
|||
|
|||
it('fails on empty captchaToken', () => { |
|||
const r = registerSchema.safeParse({ ...validBody, captchaToken: '' }) |
|||
expect(r.success).toBe(false) |
|||
if (!r.success) { |
|||
expect(r.error.issues[0].path).toContain('captchaToken') |
|||
} |
|||
}) |
|||
}) |
|||
|
|||
describe('clientRegisterSchema', () => { |
|||
it('still requires captchaText field', () => { |
|||
const r = clientRegisterSchema.safeParse({ |
|||
username: 'testuser', |
|||
password: 'password123', |
|||
confirmPassword: 'password123', |
|||
}) |
|||
expect(r.success).toBe(false) |
|||
}) |
|||
|
|||
it('validates without captchaToken field', () => { |
|||
const r = clientRegisterSchema.safeParse({ |
|||
username: 'testuser', |
|||
password: 'password123', |
|||
confirmPassword: 'password123', |
|||
captchaText: 'abcde', |
|||
}) |
|||
expect(r.success).toBe(true) |
|||
}) |
|||
}) |
|||
@ -1,23 +1,38 @@ |
|||
import { z } from 'zod' |
|||
|
|||
const usernameField = z.string().trim().min(3, '用户名至少需要3个字符').max(30, '用户名最多30个字符') |
|||
const passwordField = z.string().min(8, '密码至少需要8个字符') |
|||
const confirmPasswordField = z.string() |
|||
const captchaTextField = z.string().min(1, '验证码不能为空') |
|||
const captchaTokenField = z.string().min(1, '验证码令牌不能为空') |
|||
|
|||
const passwordRefine = (data: { password: string; confirmPassword: string }) => |
|||
data.password === data.confirmPassword |
|||
|
|||
export const registerSchema = z |
|||
.object({ |
|||
username: z |
|||
.string() |
|||
.trim() |
|||
.min(3, '用户名至少需要3个字符') |
|||
.max(30, '用户名最多30个字符'), |
|||
password: z.string().min(8, '密码至少需要8个字符'), |
|||
confirmPassword: z.string(), |
|||
captchaToken: z.string().min(1, '验证码令牌不能为空'), |
|||
captchaText: z.string().min(1, '验证码不能为空'), |
|||
username: usernameField, |
|||
password: passwordField, |
|||
confirmPassword: confirmPasswordField, |
|||
captchaToken: captchaTokenField, |
|||
captchaText: captchaTextField, |
|||
}) |
|||
.refine((data) => data.password === data.confirmPassword, { |
|||
.refine(passwordRefine, { |
|||
message: '两次输入的密码不一致', |
|||
path: ['confirmPassword'], |
|||
}) |
|||
|
|||
/** Client-side schema: omits captchaToken (managed by page shell) */ |
|||
export const clientRegisterSchema = registerSchema.omit({ captchaToken: true }) |
|||
/** Client-side schema: excludes captchaToken (managed by page shell) */ |
|||
export const clientRegisterSchema = z |
|||
.object({ |
|||
username: usernameField, |
|||
password: passwordField, |
|||
confirmPassword: confirmPasswordField, |
|||
captchaText: captchaTextField, |
|||
}) |
|||
.refine(passwordRefine, { |
|||
message: '两次输入的密码不一致', |
|||
path: ['confirmPassword'], |
|||
}) |
|||
|
|||
export type RegisterInput = z.infer<typeof clientRegisterSchema> |
|||
|
|||
Loading…
Reference in new issue