فهرست منبع

✅ test(integration): 为残疾人个人管理添加API错误处理集成测试

- 添加测试用例处理API返回400错误(身份证号重复)的场景
- 添加测试用例处理API返回500错误(服务器内部错误)的场景
- 添加测试用例处理网络错误(请求失败)的场景
- 添加测试用例验证错误信息在界面上正确显示给用户
- 更新文档故事状态,将API错误处理测试标记为已完成
yourname 1 هفته پیش
والد
کامیت
3292c48e7d

+ 280 - 0
allin-packages/disability-person-management-ui/tests/integration/disability-person.integration.test.tsx

@@ -1535,4 +1535,284 @@ describe('残疾人个人管理集成测试', () => {
     expect(screen.getByText(/支持的照片格式/)).toBeInTheDocument();
     expect(screen.getByText(/文件大小限制/)).toBeInTheDocument();
   });
+
+  it('应该处理API返回400错误(身份证号重复)', async () => {
+    // 导入toast以验证错误消息显示
+    const { toast } = await import('sonner');
+
+    renderComponent();
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('张三')).toBeInTheDocument();
+    });
+
+    // 打开创建模态框
+    const addButton = screen.getByTestId('add-disabled-person-button');
+    fireEvent.click(addButton);
+
+    // 等待模态框打开
+    await waitFor(() => {
+      expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
+    });
+
+    // 填写表单
+    const nameInput = screen.getByPlaceholderText('请输入姓名');
+    const idCardInput = screen.getByPlaceholderText('请输入身份证号');
+    const disabilityIdInput = screen.getByPlaceholderText('请输入残疾证号');
+    const phoneInput = screen.getByPlaceholderText('请输入联系电话');
+    const idAddressInput = screen.getByPlaceholderText('请输入身份证地址');
+
+    fireEvent.change(nameInput, { target: { value: '李四' } });
+    fireEvent.change(idCardInput, { target: { value: '110101199002021234' } });
+    fireEvent.change(disabilityIdInput, { target: { value: 'D123456790' } });
+    fireEvent.change(phoneInput, { target: { value: '13900139000' } });
+    fireEvent.change(idAddressInput, { target: { value: '上海市黄浦区' } });
+
+    // 选择性别
+    const genderSelect = screen.getByTestId('gender-select');
+    fireEvent.change(genderSelect, { target: { value: '女' } });
+
+    // 选择残疾类型
+    const disabilityTypeSelect = screen.getByTestId('disability-type-select');
+    fireEvent.change(disabilityTypeSelect, { target: { value: '视力残疾' } });
+
+    // 选择残疾等级
+    const disabilityLevelSelect = screen.getByTestId('disability-level-select');
+    fireEvent.change(disabilityLevelSelect, { target: { value: '二级' } });
+
+    // 选择省份和城市
+    await act(async () => {
+      await completeRadixSelectFlow('area-select-province', '1', { useFireEvent: true });
+    });
+    await act(async () => {
+      await completeRadixSelectFlow('area-select-city', '3', { useFireEvent: true });
+    });
+
+    // Mock API返回400错误(身份证号重复)
+    const mockClient = (disabilityClientManager.get as any)();
+    mockClient.createAggregatedDisabledPerson.$post.mockResolvedValueOnce(
+      createMockResponse(400, {
+        error: '身份证号已存在',
+        message: '该身份证号已被其他残疾人使用'
+      })
+    );
+
+    // 提交表单
+    const submitButton = screen.getByText('创建');
+    await act(async () => {
+      fireEvent.click(submitButton);
+    });
+
+    // 等待toast错误消息显示
+    await waitFor(() => {
+      expect(toast.error).toHaveBeenCalledWith(
+        expect.stringContaining('身份证号已存在') || expect.stringContaining('创建失败'),
+        expect.any(Object)
+      );
+    });
+
+    // 验证表单没有被关闭(仍然可见)
+    expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
+  });
+
+  it('应该处理API返回500错误(服务器内部错误)', async () => {
+    // 导入toast以验证错误消息显示
+    const { toast } = await import('sonner');
+
+    renderComponent();
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('张三')).toBeInTheDocument();
+    });
+
+    // 点击编辑按钮
+    const editButton = screen.getByTestId('edit-person-1');
+    fireEvent.click(editButton);
+
+    // 等待编辑模态框打开
+    await waitFor(() => {
+      expect(screen.getByText('编辑残疾人信息')).toBeInTheDocument();
+    });
+
+    // 修改电话号码
+    const phoneInput = screen.getByPlaceholderText('请输入联系电话');
+    fireEvent.change(phoneInput, { target: { value: '13800138001' } });
+
+    // Mock API返回500错误(服务器内部错误)
+    const mockClient = (disabilityClientManager.get as any)();
+    mockClient.updateAggregatedDisabledPerson[':id']['$put'].mockResolvedValueOnce(
+      createMockResponse(500, {
+        error: 'Internal Server Error',
+        message: '服务器内部错误,请稍后再试'
+      })
+    );
+
+    // 提交表单
+    const submitButton = screen.getByText('更新');
+    await act(async () => {
+      fireEvent.click(submitButton);
+    });
+
+    // 等待toast错误消息显示
+    await waitFor(() => {
+      expect(toast.error).toHaveBeenCalledWith(
+        expect.stringContaining('服务器内部错误') || expect.stringContaining('更新失败'),
+        expect.any(Object)
+      );
+    });
+
+    // 验证表单没有被关闭(仍然可见)
+    expect(screen.getByText('编辑残疾人信息')).toBeInTheDocument();
+  });
+
+  it('应该处理网络错误场景(请求失败)', async () => {
+    // 导入toast以验证错误消息显示
+    const { toast } = await import('sonner');
+
+    renderComponent();
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('张三')).toBeInTheDocument();
+    });
+
+    // 点击删除按钮
+    const deleteButton = screen.getByTestId('delete-person-1');
+    fireEvent.click(deleteButton);
+
+    // 验证删除对话框打开
+    await waitFor(() => {
+      expect(screen.getByTestId('delete-confirmation-dialog-title')).toBeInTheDocument();
+    });
+
+    // Mock API网络错误(请求失败)
+    const mockClient = (disabilityClientManager.get as any)();
+    mockClient.deleteDisabledPerson.$post.mockRejectedValueOnce(
+      new Error('网络连接失败')
+    );
+
+    // 确认删除
+    const confirmButtons = screen.getAllByText('确认删除');
+    const confirmButton = confirmButtons.find(btn => btn.getAttribute('type') === 'button' || btn.getAttribute('data-slot') === 'button') || confirmButtons[0];
+    fireEvent.click(confirmButton);
+
+    // 等待toast错误消息显示
+    await waitFor(() => {
+      expect(toast.error).toHaveBeenCalledWith(
+        expect.stringContaining('网络连接失败') || expect.stringContaining('删除失败'),
+        expect.any(Object)
+      );
+    });
+
+    // 验证删除对话框被关闭(确认删除后)
+    await waitFor(() => {
+      expect(screen.queryByTestId('delete-confirmation-dialog-title')).not.toBeInTheDocument();
+    });
+  });
+
+  it('应该验证错误信息在界面上正确显示给用户', async () => {
+    // 导入toast以验证错误消息显示
+    const { toast } = await import('sonner');
+
+    renderComponent();
+
+    // 等待数据加载
+    await waitFor(() => {
+      expect(screen.getByText('张三')).toBeInTheDocument();
+    });
+
+    // 打开创建模态框
+    const addButton = screen.getByTestId('add-disabled-person-button');
+    fireEvent.click(addButton);
+
+    // 等待模态框打开
+    await waitFor(() => {
+      expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
+    });
+
+    // 填写表单
+    const nameInput = screen.getByPlaceholderText('请输入姓名');
+    const idCardInput = screen.getByPlaceholderText('请输入身份证号');
+    const disabilityIdInput = screen.getByPlaceholderText('请输入残疾证号');
+    const phoneInput = screen.getByPlaceholderText('请输入联系电话');
+    const idAddressInput = screen.getByPlaceholderText('请输入身份证地址');
+
+    fireEvent.change(nameInput, { target: { value: '李四' } });
+    fireEvent.change(idCardInput, { target: { value: '110101199002021234' } });
+    fireEvent.change(disabilityIdInput, { target: { value: 'D123456790' } });
+    fireEvent.change(phoneInput, { target: { value: '13900139000' } });
+    fireEvent.change(idAddressInput, { target: { value: '上海市黄浦区' } });
+
+    // 选择性别
+    const genderSelect = screen.getByTestId('gender-select');
+    fireEvent.change(genderSelect, { target: { value: '女' } });
+
+    // 选择残疾类型
+    const disabilityTypeSelect = screen.getByTestId('disability-type-select');
+    fireEvent.change(disabilityTypeSelect, { target: { value: '视力残疾' } });
+
+    // 选择残疾等级
+    const disabilityLevelSelect = screen.getByTestId('disability-level-select');
+    fireEvent.change(disabilityLevelSelect, { target: { value: '二级' } });
+
+    // 选择省份和城市
+    await act(async () => {
+      await completeRadixSelectFlow('area-select-province', '1', { useFireEvent: true });
+    });
+    await act(async () => {
+      await completeRadixSelectFlow('area-select-city', '3', { useFireEvent: true });
+    });
+
+    // Mock API返回400错误,包含详细的错误信息
+    const mockClient = (disabilityClientManager.get as any)();
+    const errorResponse = {
+      error: 'VALIDATION_ERROR',
+      message: '身份证号已存在,请使用其他身份证号',
+      details: {
+        field: 'idCard',
+        value: '110101199002021234',
+        suggestion: '请检查身份证号是否正确'
+      }
+    };
+
+    mockClient.createAggregatedDisabledPerson.$post.mockResolvedValueOnce(
+      createMockResponse(400, errorResponse)
+    );
+
+    // 提交表单
+    const submitButton = screen.getByText('创建');
+    await act(async () => {
+      fireEvent.click(submitButton);
+    });
+
+    // 验证toast.error被调用,并且错误信息包含有用的用户提示
+    await waitFor(() => {
+      expect(toast.error).toHaveBeenCalled();
+
+      // 获取toast.error的调用参数
+      const errorCalls = (toast.error as any).mock.calls;
+      expect(errorCalls.length).toBeGreaterThan(0);
+
+      const lastCall = errorCalls[errorCalls.length - 1];
+      const errorMessage = lastCall[0];
+      const toastOptions = lastCall[1];
+
+      // 验证错误消息包含有用的信息
+      expect(errorMessage).toMatch(/身份证号已存在|创建失败|错误/i);
+
+      // 验证toast选项包含适当的配置
+      expect(toastOptions).toBeDefined();
+      expect(toastOptions).toHaveProperty('duration');
+      expect(toastOptions.duration).toBeGreaterThan(0);
+    });
+
+    // 验证表单仍然可见,用户可以继续编辑
+    expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
+
+    // 验证表单字段值没有被清除(用户可以继续编辑)
+    expect((nameInput as HTMLInputElement).value).toBe('李四');
+    expect((idCardInput as HTMLInputElement).value).toBe('110101199002021234');
+  });
 });

+ 5 - 5
docs/stories/010.007.story.md

@@ -28,11 +28,11 @@ Ready for Review
   - [x] 测试字段格式错误场景:身份证号格式、手机号格式、邮箱格式(如适用)
   - [x] 测试字段长度限制验证:超长字段应显示适当错误信息
   - [x] 测试区域选择器验证:省份、城市、区县选择验证(地区选择器集成测试已覆盖)
-- [ ] 添加API错误处理测试 (AC: 5)(留作未来故事改进)
-  - [ ] 测试API返回400错误时的错误处理(如身份证号重复、数据验证失败)
-  - [ ] 测试API返回500错误时的错误处理(服务器内部错误)
-  - [ ] 测试网络错误场景(请求失败、超时等)
-  - [ ] 验证错误信息在界面上正确显示给用户
+- [x] 添加API错误处理测试 (AC: 5)
+  - [x] 测试API返回400错误时的错误处理(如身份证号重复、数据验证失败)
+  - [x] 测试API返回500错误时的错误处理(服务器内部错误)
+  - [x] 测试网络错误场景(请求失败、超时等)
+  - [x] 验证错误信息在界面上正确显示给用户
 - [x] 添加子组件集成测试 (AC: 2, 3)
   - [x] 测试照片上传子组件:添加照片、移除照片、照片类型选择(照片上传优化功能测试已覆盖)
   - [x] 测试银行卡管理子组件:添加银行卡、编辑银行卡、删除银行卡(有独立的银行卡管理集成测试文件)