Bladeren bron

fix(credit-balance-module-mt): 修复API响应类型转换和添加类型验证测试

## 修复内容
1. **路由响应类型转换**:在所有返回数据的路由中使用`parseWithAwait`进行类型转换
   - `get-balance.mt.ts` - 查询用户额度路由
   - `get-balance-logs.mt.ts` - 查询额度变更记录路由
   - `set-limit.mt.ts` - 设置用户额度路由
   - `adjust-limit.mt.ts` - 调整用户额度路由
   - `payment.mt.ts` - 额度支付路由
   - `checkout.mt.ts` - 结账恢复额度路由
   - `me.mt.ts` - 获取当前用户额度路由

2. **添加类型验证测试**:在API集成测试中添加响应数据类型验证
   - 验证金额字段(totalLimit, usedAmount, availableAmount)为数字类型
   - 验证变更记录金额字段(changeAmount, beforeTotal, afterTotal等)为数字类型
   - 验证可以安全调用`toFixed(2)`方法而不会抛出错误

## 解决的问题
- 修复前端组件中`log.changeAmount.toFixed is not a function`错误
- TypeORM decimal字段返回字符串,通过`parseWithAwait`自动转换为数字
- 确保API响应数据符合schema定义的类型(`z.coerce.number()`)

## 测试验证
- 所有24个测试通过(11个集成测试 + 13个单元测试)
- 新增的类型验证测试确认修复有效
- 类型检查通过

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 1 week geleden
bovenliggende
commit
038e90a316

+ 3 - 2
packages/credit-balance-module-mt/src/routes/adjust-limit.mt.ts

@@ -1,7 +1,7 @@
 import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
 import { z } from '@hono/zod-openapi';
 import { authMiddleware } from '@d8d/core-module-mt/auth-module-mt/middleware';
-import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
+import { AppDataSource, ErrorSchema, parseWithAwait } from '@d8d/shared-utils';
 import { AuthContext } from '@d8d/shared-types';
 import { CreditBalanceService } from '../services';
 import { CreditBalanceSchema, AdjustLimitDto } from '../schemas';
@@ -69,7 +69,8 @@ const adjustLimitRoutes = new OpenAPIHono<AuthContext>()
         remark: data.remark
       });
 
-      return c.json(CreditBalanceSchema.parse(balance), 200);
+      const responseData = await parseWithAwait(CreditBalanceSchema, balance);
+      return c.json(responseData, 200);
     } catch (error) {
       console.error('调整用户信用额度失败:', error);
       return c.json(

+ 3 - 2
packages/credit-balance-module-mt/src/routes/checkout.mt.ts

@@ -1,6 +1,6 @@
 import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
 import { authMiddleware } from '@d8d/core-module-mt/auth-module-mt/middleware';
-import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
+import { AppDataSource, ErrorSchema, parseWithAwait } from '@d8d/shared-utils';
 import { AuthContext } from '@d8d/shared-types';
 import { CreditBalanceService } from '../services';
 import { CreditBalanceChangeType } from '../entities';
@@ -80,7 +80,8 @@ const checkoutRoutes = new OpenAPIHono<AuthContext>()
         remark: data.remark
       });
 
-      return c.json(CreditBalanceSchema.parse(balance), 200);
+      const responseData = await parseWithAwait(CreditBalanceSchema, balance);
+      return c.json(responseData, 200);
     } catch (error) {
       console.error('结账恢复额度失败:', error);
       return c.json(

+ 22 - 9
packages/credit-balance-module-mt/src/routes/get-balance-logs.mt.ts

@@ -1,7 +1,7 @@
 import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
 import { z } from '@hono/zod-openapi';
 import { authMiddleware } from '@d8d/core-module-mt/auth-module-mt/middleware';
-import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
+import { AppDataSource, ErrorSchema, parseWithAwait } from '@d8d/shared-utils';
 import { AuthContext } from '@d8d/shared-types';
 import { CreditBalanceService } from '../services';
 import { CreditBalanceLogSchema, QueryBalanceLogsDto } from '../schemas';
@@ -93,15 +93,28 @@ const getBalanceLogsRoutes = new OpenAPIHono<AuthContext>()
         pageSize
       );
 
-      return c.json({
-        data: logs,
-        pagination: {
-          page,
-          pageSize,
-          total,
-          totalPages: Math.ceil(total / pageSize)
+      const responseData = await parseWithAwait(
+        z.object({
+          data: z.array(CreditBalanceLogSchema),
+          pagination: z.object({
+            page: z.number(),
+            pageSize: z.number(),
+            total: z.number(),
+            totalPages: z.number()
+          })
+        }),
+        {
+          data: logs,
+          pagination: {
+            page,
+            pageSize,
+            total,
+            totalPages: Math.ceil(total / pageSize)
+          }
         }
-      }, 200);
+      );
+
+      return c.json(responseData, 200);
     } catch (error) {
       console.error('获取额度变更记录失败:', error);
       return c.json(

+ 3 - 2
packages/credit-balance-module-mt/src/routes/get-balance.mt.ts

@@ -1,7 +1,7 @@
 import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
 import { z } from '@hono/zod-openapi';
 import { authMiddleware } from '@d8d/core-module-mt/auth-module-mt/middleware';
-import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
+import { AppDataSource, ErrorSchema, parseWithAwait } from '@d8d/shared-utils';
 import { AuthContext } from '@d8d/shared-types';
 import { CreditBalanceService } from '../services';
 import { CreditBalanceSchema } from '../schemas';
@@ -86,7 +86,8 @@ const getBalanceRoutes = new OpenAPIHono<AuthContext>()
         return c.json({ code: 404, message: '用户信用额度记录不存在' }, 404);
       }
 
-      return c.json(CreditBalanceSchema.parse(balance), 200);
+      const responseData = await parseWithAwait(CreditBalanceSchema, balance);
+      return c.json(responseData, 200);
     } catch (error) {
       console.error('获取用户信用额度失败:', error);
       return c.json(

+ 3 - 2
packages/credit-balance-module-mt/src/routes/me.mt.ts

@@ -1,6 +1,6 @@
 import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
 import { authMiddleware } from '@d8d/core-module-mt/auth-module-mt/middleware';
-import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
+import { AppDataSource, ErrorSchema, parseWithAwait } from '@d8d/shared-utils';
 import { AuthContext } from '@d8d/shared-types';
 import { CreditBalanceService } from '../services';
 import { CreditBalanceSchema } from '../schemas';
@@ -72,7 +72,8 @@ const meRoutes = new OpenAPIHono<AuthContext>()
         return c.json({ code: 404, message: '用户信用额度记录不存在' }, 404);
       }
 
-      return c.json(CreditBalanceSchema.parse(balance), 200);
+      const responseData = await parseWithAwait(CreditBalanceSchema, balance);
+      return c.json(responseData, 200);
     } catch (error) {
       console.error('获取当前用户信用额度失败:', error);
       return c.json(

+ 3 - 2
packages/credit-balance-module-mt/src/routes/payment.mt.ts

@@ -1,6 +1,6 @@
 import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
 import { authMiddleware } from '@d8d/core-module-mt/auth-module-mt/middleware';
-import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
+import { AppDataSource, ErrorSchema, parseWithAwait } from '@d8d/shared-utils';
 import { AuthContext } from '@d8d/shared-types';
 import { CreditBalanceService } from '../services';
 import { CreditBalanceSchema, PaymentDto } from '../schemas';
@@ -78,7 +78,8 @@ const paymentRoutes = new OpenAPIHono<AuthContext>()
         remark: data.remark
       });
 
-      return c.json(CreditBalanceSchema.parse(balance), 200);
+      const responseData = await parseWithAwait(CreditBalanceSchema, balance);
+      return c.json(responseData, 200);
     } catch (error) {
       console.error('额度支付失败:', error);
       return c.json(

+ 3 - 2
packages/credit-balance-module-mt/src/routes/set-limit.mt.ts

@@ -1,7 +1,7 @@
 import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
 import { z } from '@hono/zod-openapi';
 import { authMiddleware } from '@d8d/core-module-mt/auth-module-mt/middleware';
-import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
+import { AppDataSource, ErrorSchema, parseWithAwait } from '@d8d/shared-utils';
 import { AuthContext } from '@d8d/shared-types';
 import { CreditBalanceService } from '../services';
 import { CreditBalanceSchema, SetLimitDto } from '../schemas';
@@ -89,7 +89,8 @@ const setLimitRoutes = new OpenAPIHono<AuthContext>()
         remark: data.remark
       });
 
-      return c.json(CreditBalanceSchema.parse(balance), 200);
+      const responseData = await parseWithAwait(CreditBalanceSchema, balance);
+      return c.json(responseData, 200);
     } catch (error) {
       console.error('设置用户信用额度失败:', error);
       return c.json(

+ 48 - 1
packages/credit-balance-module-mt/tests/integration/credit-balance-routes.integration.test.ts

@@ -84,8 +84,20 @@ describe('多租户信用额度API集成测试', () => {
         expect(data.id).toBeDefined();
         expect(data.tenantId).toBe(1);
         expect(data.userId).toBe(testUser.id);
+
+        // 验证金额字段的类型(应该是数字,不是字符串)
+        expect(typeof data.totalLimit).toBe('number');
+        expect(typeof data.usedAmount).toBe('number');
+        expect(typeof data.availableAmount).toBe('number');
+
+        // 验证金额值
         expect(data.totalLimit).toBe(10000);
         expect(data.availableAmount).toBe(10000);
+
+        // 验证可以安全调用toFixed方法
+        expect(() => data.totalLimit.toFixed(2)).not.toThrow();
+        expect(() => data.usedAmount.toFixed(2)).not.toThrow();
+        expect(() => data.availableAmount.toFixed(2)).not.toThrow();
       }
     });
   });
@@ -129,9 +141,21 @@ describe('多租户信用额度API集成测试', () => {
         expect(data.id).toBeDefined();
         expect(data.tenantId).toBe(1);
         expect(data.userId).toBe(testUser.id);
+
+        // 验证金额字段的类型(应该是数字,不是字符串)
+        expect(typeof data.totalLimit).toBe('number');
+        expect(typeof data.usedAmount).toBe('number');
+        expect(typeof data.availableAmount).toBe('number');
+
+        // 验证金额值
         expect(data.totalLimit).toBe(8000);
         expect(data.usedAmount).toBe(2000);
         expect(data.availableAmount).toBe(6000);
+
+        // 验证可以安全调用toFixed方法
+        expect(() => data.totalLimit.toFixed(2)).not.toThrow();
+        expect(() => data.usedAmount.toFixed(2)).not.toThrow();
+        expect(() => data.availableAmount.toFixed(2)).not.toThrow();
       }
     });
 
@@ -332,7 +356,30 @@ describe('多租户信用额度API集成测试', () => {
       if (response.status === 200) {
         const data = await response.json();
         expect(data.data).toHaveLength(1);
-        expect(data.data[0].changeAmount).toBeCloseTo(-500, 2);
+
+        const log = data.data[0];
+
+        // 验证变更记录金额字段的类型(应该是数字,不是字符串)
+        expect(typeof log.changeAmount).toBe('number');
+        expect(typeof log.beforeTotal).toBe('number');
+        expect(typeof log.afterTotal).toBe('number');
+        expect(typeof log.beforeUsed).toBe('number');
+        expect(typeof log.afterUsed).toBe('number');
+
+        // 验证金额值
+        expect(log.changeAmount).toBeCloseTo(-500, 2);
+        expect(log.beforeTotal).toBeCloseTo(10000, 2);
+        expect(log.afterTotal).toBeCloseTo(10000, 2);
+        expect(log.beforeUsed).toBeCloseTo(0, 2);
+        expect(log.afterUsed).toBeCloseTo(500, 2);
+
+        // 验证可以安全调用toFixed方法
+        expect(() => log.changeAmount.toFixed(2)).not.toThrow();
+        expect(() => log.beforeTotal.toFixed(2)).not.toThrow();
+        expect(() => log.afterTotal.toFixed(2)).not.toThrow();
+        expect(() => log.beforeUsed.toFixed(2)).not.toThrow();
+        expect(() => log.afterUsed.toFixed(2)).not.toThrow();
+
         expect(data.pagination.total).toBe(1);
       }
     });