Files
hms/apps/miniprogram/__tests__/services/request.test.ts
iven 6d151bbfb1 refactor(mp): request.ts 模块级状态收编 + AbortSignal + Analytics 受控
- 提取 ConcurrencyLimiter 类(并发限制 8,可 reset)
- 提取 ResponseCache 类(GET 缓存 + 去重 + patientId 绑定)
- 新增 resetForTesting() 测试隔离函数
- api.get/post/put/delete 支持 AbortSignal 请求取消
- app.tsx Analytics 定时器改为 useDidShow/useDidHide 控制后台暂停
- 测试文件接入 resetForTesting()

构建通过,测试 74/75(1 个预存失败)。
2026-05-15 06:58:37 +08:00

152 lines
4.8 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock @tarojs/taro
vi.mock('@tarojs/taro', () => ({
default: {
request: vi.fn(),
getStorageSync: vi.fn(() => ''),
setStorageSync: vi.fn(),
showToast: vi.fn(),
reLaunch: vi.fn(),
getCurrentPages: vi.fn(() => []),
},
}));
// Mock secure-storage to use simple values
vi.mock('@/utils/secure-storage', () => ({
secureGet: vi.fn((key: string) => {
const store: Record<string, string> = { access_token: 'test-token', tenant_id: 'test-tenant' };
return store[key] || '';
}),
secureSet: vi.fn(),
secureRemove: vi.fn(),
}));
import Taro from '@tarojs/taro';
import { api, clearRequestCache, resetForTesting } from '@/services/request';
describe('request module', () => {
beforeEach(() => {
vi.clearAllMocks();
resetForTesting();
});
describe('api.get', () => {
it('should call Taro.request with correct method and url', async () => {
const mockData = { success: true, data: { items: [] } };
vi.mocked(Taro.request).mockResolvedValue({ statusCode: 200, data: mockData } as any);
const result = await api.get('/health/patients', { page: 1 });
expect(Taro.request).toHaveBeenCalledWith(
expect.objectContaining({
url: expect.stringContaining('/health/patients?page=1'),
method: 'GET',
}),
);
});
it('should build query string from params', async () => {
vi.mocked(Taro.request).mockResolvedValue({ statusCode: 200, data: { success: true, data: null } } as any);
await api.get('/test', { foo: 'bar', baz: 42 });
const call = vi.mocked(Taro.request).mock.calls[0][0];
expect(call.url).toContain('foo=bar');
expect(call.url).toContain('baz=42');
});
it('should skip undefined and empty params', async () => {
vi.mocked(Taro.request).mockResolvedValue({ statusCode: 200, data: { success: true, data: null } } as any);
await api.get('/test', { foo: 'bar', empty: '', missing: undefined });
const call = vi.mocked(Taro.request).mock.calls[0][0];
expect(call.url).toContain('foo=bar');
expect(call.url).not.toContain('empty');
expect(call.url).not.toContain('missing');
});
it('should cache GET responses', async () => {
vi.mocked(Taro.request).mockResolvedValue({ statusCode: 200, data: { success: true, data: { id: '1' } } } as any);
const r1 = await api.get('/cached');
const r2 = await api.get('/cached');
expect(Taro.request).toHaveBeenCalledTimes(1);
expect(r1).toEqual(r2);
});
});
describe('api.post', () => {
it('should send POST with body', async () => {
vi.mocked(Taro.request).mockResolvedValue({ statusCode: 200, data: { success: true, data: { id: 'new' } } } as any);
await api.post('/health/patients', { name: 'Test' });
expect(Taro.request).toHaveBeenCalledWith(
expect.objectContaining({
method: 'POST',
data: { name: 'Test' },
}),
);
});
});
describe('api.put', () => {
it('should send PUT with body', async () => {
vi.mocked(Taro.request).mockResolvedValue({ statusCode: 200, data: { success: true, data: {} } } as any);
await api.put('/health/patients/p1', { name: 'Updated', version: 1 });
expect(Taro.request).toHaveBeenCalledWith(
expect.objectContaining({
method: 'PUT',
data: { name: 'Updated', version: 1 },
}),
);
});
});
describe('api.delete', () => {
it('should send DELETE request', async () => {
vi.mocked(Taro.request).mockResolvedValue({ statusCode: 200, data: { success: true, data: null } } as any);
await api.delete('/health/patients/p1');
expect(Taro.request).toHaveBeenCalledWith(
expect.objectContaining({ method: 'DELETE' }),
);
});
});
describe('error handling', () => {
it('should throw on 403 with permission error', async () => {
vi.mocked(Taro.request).mockResolvedValue({ statusCode: 403, data: {} } as any);
await expect(api.get('/forbidden')).rejects.toThrow('权限不足');
});
it('should throw on 500 with server error', async () => {
vi.mocked(Taro.request).mockResolvedValue({ statusCode: 500, data: {} } as any);
await expect(api.get('/error')).rejects.toThrow('服务器错误');
});
it('should throw on network error', async () => {
vi.mocked(Taro.request).mockRejectedValue({ errMsg: 'request:fail timeout' });
await expect(api.get('/timeout')).rejects.toThrow('网络超时');
});
it('should throw on API failure with message', async () => {
vi.mocked(Taro.request).mockResolvedValue({
statusCode: 200,
data: { success: false, message: '参数错误' },
} as any);
await expect(api.get('/bad-params')).rejects.toThrow('参数错误');
});
});
});