From 882e40baa1a5d43747c6faf6174c53947269694e Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Fri, 15 May 2026 16:08:18 +0800 Subject: [PATCH] fix: use Zod v4 compatible schema definition without .omit() on refined schemas 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 --- packages/shared/__tests__/auth-schema.test.ts | 80 +++++++++++++++++++++++++++ packages/shared/auth-schema.ts | 39 +++++++++---- 2 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 packages/shared/__tests__/auth-schema.test.ts diff --git a/packages/shared/__tests__/auth-schema.test.ts b/packages/shared/__tests__/auth-schema.test.ts new file mode 100644 index 0000000..b00c3b7 --- /dev/null +++ b/packages/shared/__tests__/auth-schema.test.ts @@ -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) + }) +}) diff --git a/packages/shared/auth-schema.ts b/packages/shared/auth-schema.ts index d71284b..437d014 100644 --- a/packages/shared/auth-schema.ts +++ b/packages/shared/auth-schema.ts @@ -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