Просмотр исходного кода

fix(salary): 修复Zod v4错误消息参数和添加中文错误消息验证测试

- 修复Zod v4中coerce.number()的正确参数为error
- 添加中文错误消息验证测试:
  - 缺少必填字段(省份、城市)
  - 基本工资为0
  - 津贴补贴为负数
  - 类型转换错误
- 确保所有验证错误显示中文提示

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 2 недель назад
Родитель
Сommit
9e6689560f

+ 36 - 12
allin-packages/salary-module/src/schemas/salary.schema.ts

@@ -65,13 +65,17 @@ export const SalaryLevelSchema = z.object({
 
 // 创建薪资DTO
 export const CreateSalarySchema = z.object({
-  provinceId: z.coerce.number<number>().int().positive({
+  provinceId: z.coerce.number<number>({
+    error: '请选择省份'
+  }).int().positive({
     message: '请选择省份'
   }).openapi({
     description: '省份ID',
     example: 110000
   }),
-  cityId: z.coerce.number<number>().int().positive({
+  cityId: z.coerce.number<number>({
+    error: '请选择城市'
+  }).int().positive({
     message: '请选择城市'
   }).openapi({
     description: '城市ID',
@@ -81,25 +85,33 @@ export const CreateSalarySchema = z.object({
     description: '区县ID',
     example: 110101
   }),
-  basicSalary: z.coerce.number<number>().positive({
+  basicSalary: z.coerce.number<number>({
+    error: '基本工资必须大于0'
+  }).positive({
     message: '基本工资必须大于0'
   }).openapi({
     description: '基本工资',
     example: 5000.00
   }),
-  allowance: z.coerce.number<number>().min(0, {
+  allowance: z.coerce.number<number>({
+    error: '津贴补贴不能为负数'
+  }).min(0, {
     message: '津贴补贴不能为负数'
   }).optional().openapi({
     description: '津贴补贴',
     example: 1000.00
   }),
-  insurance: z.coerce.number<number>().min(0, {
+  insurance: z.coerce.number<number>({
+    error: '保险费用不能为负数'
+  }).min(0, {
     message: '保险费用不能为负数'
   }).optional().openapi({
     description: '保险费用',
     example: 500.00
   }),
-  housingFund: z.coerce.number<number>().min(0, {
+  housingFund: z.coerce.number<number>({
+    error: '住房公积金不能为负数'
+  }).min(0, {
     message: '住房公积金不能为负数'
   }).optional().openapi({
     description: '住房公积金',
@@ -113,13 +125,17 @@ export const UpdateSalarySchema = z.object({
     description: '薪资ID',
     example: 1
   }),
-  provinceId: z.coerce.number<number>().int().positive({
+  provinceId: z.coerce.number<number>({
+    error: '请选择省份'
+  }).int().positive({
     message: '请选择省份'
   }).optional().openapi({
     description: '省份ID',
     example: 110000
   }),
-  cityId: z.coerce.number<number>().int().positive({
+  cityId: z.coerce.number<number>({
+    error: '请选择城市'
+  }).int().positive({
     message: '请选择城市'
   }).optional().openapi({
     description: '城市ID',
@@ -129,25 +145,33 @@ export const UpdateSalarySchema = z.object({
     description: '区县ID',
     example: 110101
   }),
-  basicSalary: z.coerce.number<number>().positive({
+  basicSalary: z.coerce.number<number>({
+    error: '基本工资必须大于0'
+  }).positive({
     message: '基本工资必须大于0'
   }).optional().openapi({
     description: '基本工资',
     example: 5000.00
   }),
-  allowance: z.coerce.number<number>().min(0, {
+  allowance: z.coerce.number<number>({
+    error: '津贴补贴不能为负数'
+  }).min(0, {
     message: '津贴补贴不能为负数'
   }).optional().openapi({
     description: '津贴补贴',
     example: 1000.00
   }),
-  insurance: z.coerce.number<number>().min(0, {
+  insurance: z.coerce.number<number>({
+    error: '保险费用不能为负数'
+  }).min(0, {
     message: '保险费用不能为负数'
   }).optional().openapi({
     description: '保险费用',
     example: 500.00
   }),
-  housingFund: z.coerce.number<number>().min(0, {
+  housingFund: z.coerce.number<number>({
+    error: '住房公积金不能为负数'
+  }).min(0, {
     message: '住房公积金不能为负数'
   }).optional().openapi({
     description: '住房公积金',

+ 149 - 0
allin-packages/salary-module/tests/integration/salary.integration.test.ts

@@ -148,6 +148,155 @@ describe('薪资管理API集成测试', () => {
       expect(response.status).toBe(400);
     });
 
+    it('应该验证中文错误消息 - 缺少必填字段', async () => {
+      const createData = {
+        provinceId: null, // 发送null而不是省略
+        cityId: null, // 发送null而不是省略
+        basicSalary: 5000.00
+      };
+
+      const response = await client.create.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+      const errorData = await response.json();
+      console.debug('缺少必填字段错误响应:', JSON.stringify(errorData, null, 2));
+
+      // 验证错误响应格式
+      expect(errorData).toHaveProperty('success');
+      expect(errorData.success).toBe(false);
+      expect(errorData).toHaveProperty('error');
+      expect(errorData.error).toHaveProperty('name');
+      expect(errorData.error.name).toBe('ZodError');
+      expect(errorData.error).toHaveProperty('message');
+
+      // 解析错误消息
+      const errorMessages = JSON.parse(errorData.error.message);
+      expect(Array.isArray(errorMessages)).toBe(true);
+
+      // 验证具体的错误消息
+      const errorTexts = errorMessages.map((err: any) => err.message);
+      expect(errorTexts).toContain('请选择省份');
+      expect(errorTexts).toContain('请选择城市');
+    });
+
+    it('应该验证中文错误消息 - 基本工资为0', async () => {
+      const createData = {
+        provinceId: testProvince.id,
+        cityId: testCity.id,
+        basicSalary: 0 // 基本工资为0,应该触发错误
+      };
+
+      const response = await client.create.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+      const errorData = await response.json();
+      console.debug('基本工资为0错误响应:', JSON.stringify(errorData, null, 2));
+
+      // 验证错误响应格式
+      expect(errorData).toHaveProperty('success');
+      expect(errorData.success).toBe(false);
+      expect(errorData).toHaveProperty('error');
+      expect(errorData.error).toHaveProperty('name');
+      expect(errorData.error.name).toBe('ZodError');
+      expect(errorData.error).toHaveProperty('message');
+
+      // 解析错误消息
+      const errorMessages = JSON.parse(errorData.error.message);
+      expect(Array.isArray(errorMessages)).toBe(true);
+
+      // 验证具体的错误消息
+      const errorTexts = errorMessages.map((err: any) => err.message);
+      expect(errorTexts).toContain('基本工资必须大于0');
+    });
+
+    it('应该验证中文错误消息 - 津贴补贴为负数', async () => {
+      const createData = {
+        provinceId: testProvince.id,
+        cityId: testCity.id,
+        basicSalary: 5000.00,
+        allowance: -100 // 津贴补贴为负数,应该触发错误
+      };
+
+      const response = await client.create.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+      const errorData = await response.json();
+      console.debug('津贴补贴为负数错误响应:', JSON.stringify(errorData, null, 2));
+
+      // 验证错误响应格式
+      expect(errorData).toHaveProperty('success');
+      expect(errorData.success).toBe(false);
+      expect(errorData).toHaveProperty('error');
+      expect(errorData.error).toHaveProperty('name');
+      expect(errorData.error.name).toBe('ZodError');
+      expect(errorData.error).toHaveProperty('message');
+
+      // 解析错误消息
+      const errorMessages = JSON.parse(errorData.error.message);
+      expect(Array.isArray(errorMessages)).toBe(true);
+
+      // 验证具体的错误消息
+      const errorTexts = errorMessages.map((err: any) => err.message);
+      expect(errorTexts).toContain('津贴补贴不能为负数');
+    });
+
+    it('应该验证中文错误消息 - 类型转换错误', async () => {
+      const createData = {
+        provinceId: testProvince.id,
+        cityId: testCity.id,
+        basicSalary: '不是数字', // 字符串,应该触发类型转换错误
+        allowance: 100
+      };
+
+      const response = await client.create.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+      const errorData = await response.json();
+      console.debug('类型转换错误响应:', JSON.stringify(errorData, null, 2));
+
+      // 验证错误响应格式
+      expect(errorData).toHaveProperty('success');
+      expect(errorData.success).toBe(false);
+      expect(errorData).toHaveProperty('error');
+      expect(errorData.error).toHaveProperty('name');
+      expect(errorData.error.name).toBe('ZodError');
+      expect(errorData.error).toHaveProperty('message');
+
+      // 解析错误消息
+      const errorMessages = JSON.parse(errorData.error.message);
+      expect(Array.isArray(errorMessages)).toBe(true);
+
+      // 验证具体的错误消息
+      const errorTexts = errorMessages.map((err: any) => err.message);
+      console.debug('类型转换错误消息:', errorTexts);
+      // 类型转换错误显示我们设置的中文错误消息
+      expect(errorTexts).toContain('基本工资必须大于0');
+    });
+
     it('应该验证区域唯一性约束', async () => {
       // 第一次创建
       const createData = {