- 添加 ImageAudit 图片审核组件,支持图片上传和内容安全审核 - 集成 useImageDetectionTaskManager hook 处理审核任务管理 - 新增 imageaudit-demo 演示页面展示组件功能 - 添加图片上传工具 imageUploader - 支持审核进度跟踪、结果显示和历史记录 - 优化用户交互体验,包括上传进度、错误处理等
359 lines
10 KiB
TypeScript
359 lines
10 KiB
TypeScript
// Mock Taro 在导入之前
|
|
import { AsyncTaskManager, TaskStatus, TaskConfig } from '../AsyncTaskManager';
|
|
|
|
const mockTaro = {
|
|
request: jest.fn(),
|
|
setStorage: jest.fn().mockResolvedValue({}),
|
|
getStorage: jest.fn().mockResolvedValue({ data: {} }),
|
|
removeStorage: jest.fn().mockResolvedValue({})
|
|
};
|
|
|
|
// Mock '@tarojs/taro' 模块
|
|
jest.mock('@tarojs/taro', () => mockTaro);
|
|
|
|
describe('AsyncTaskManager', () => {
|
|
let taskManager: AsyncTaskManager;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
jest.useFakeTimers();
|
|
// 确保 mock 函数正确重置
|
|
mockTaro.setStorage.mockResolvedValue({});
|
|
mockTaro.getStorage.mockResolvedValue({ data: {} });
|
|
|
|
// 注入 storage mock
|
|
taskManager = new AsyncTaskManager(mockTaro);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
describe('任务提交和状态管理', () => {
|
|
it('应该能创建新任务', async () => {
|
|
const config: TaskConfig = {
|
|
taskId: 'test-task-001',
|
|
pollingInterval: 2000,
|
|
maxRetries: 3,
|
|
timeout: 60000
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
|
|
expect(taskManager.hasTask('test-task-001')).toBe(true);
|
|
expect(taskManager.getTaskStatus('test-task-001')).toBe(TaskStatus.RUNNING);
|
|
});
|
|
|
|
it('应该能获取任务状态', async () => {
|
|
const config: TaskConfig = {
|
|
taskId: 'test-task-002',
|
|
pollingInterval: 1000,
|
|
maxRetries: 2,
|
|
timeout: 30000
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
|
|
const status = taskManager.getTaskStatus('test-task-002');
|
|
expect(status).toBe(TaskStatus.RUNNING);
|
|
});
|
|
|
|
it('应该能取消正在运行的任务', async () => {
|
|
const config: TaskConfig = {
|
|
taskId: 'test-task-003',
|
|
pollingInterval: 2000,
|
|
maxRetries: 3,
|
|
timeout: 60000
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
await taskManager.cancelTask('test-task-003');
|
|
|
|
expect(taskManager.getTaskStatus('test-task-003')).toBe(TaskStatus.CANCELLED);
|
|
});
|
|
});
|
|
|
|
describe('轮询机制', () => {
|
|
it('应该开始轮询任务状态', async () => {
|
|
const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.RUNNING);
|
|
taskManager.setStatusChecker(mockStatusCheck);
|
|
|
|
const config: TaskConfig = {
|
|
taskId: 'test-poll-001',
|
|
pollingInterval: 1000,
|
|
maxRetries: 3,
|
|
timeout: 60000
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
|
|
// 快进1秒触发轮询
|
|
jest.advanceTimersByTime(1000);
|
|
await Promise.resolve(); // 等待异步操作
|
|
|
|
expect(mockStatusCheck).toHaveBeenCalledWith('test-poll-001');
|
|
});
|
|
|
|
it('应该在任务完成时停止轮询', async () => {
|
|
const mockStatusCheck = jest.fn()
|
|
.mockResolvedValueOnce(TaskStatus.RUNNING)
|
|
.mockResolvedValueOnce(TaskStatus.SUCCESS);
|
|
|
|
taskManager.setStatusChecker(mockStatusCheck);
|
|
|
|
const config: TaskConfig = {
|
|
taskId: 'test-poll-002',
|
|
pollingInterval: 1000,
|
|
maxRetries: 3,
|
|
timeout: 60000
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
|
|
// 触发两次轮询
|
|
jest.advanceTimersByTime(1000);
|
|
await Promise.resolve();
|
|
jest.advanceTimersByTime(1000);
|
|
await Promise.resolve();
|
|
|
|
expect(taskManager.getTaskStatus('test-poll-002')).toBe(TaskStatus.SUCCESS);
|
|
});
|
|
});
|
|
|
|
describe('回调处理', () => {
|
|
it('应该在任务成功时调用成功回调', async () => {
|
|
const onSuccess = jest.fn();
|
|
const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.SUCCESS);
|
|
taskManager.setStatusChecker(mockStatusCheck);
|
|
|
|
const config: TaskConfig = {
|
|
taskId: 'test-callback-001',
|
|
pollingInterval: 1000,
|
|
maxRetries: 3,
|
|
timeout: 60000,
|
|
onSuccess
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
|
|
jest.advanceTimersByTime(1000);
|
|
await Promise.resolve();
|
|
|
|
expect(onSuccess).toHaveBeenCalled();
|
|
});
|
|
|
|
it('应该在任务失败时调用错误回调', async () => {
|
|
const onError = jest.fn();
|
|
const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.FAILED);
|
|
taskManager.setStatusChecker(mockStatusCheck);
|
|
|
|
const config: TaskConfig = {
|
|
taskId: 'test-callback-002',
|
|
pollingInterval: 1000,
|
|
maxRetries: 3,
|
|
timeout: 60000,
|
|
onError
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
|
|
jest.advanceTimersByTime(1000);
|
|
await Promise.resolve();
|
|
|
|
expect(onError).toHaveBeenCalled();
|
|
});
|
|
|
|
it('应该在进度更新时调用进度回调', async () => {
|
|
const onProgress = jest.fn();
|
|
const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.RUNNING);
|
|
taskManager.setStatusChecker(mockStatusCheck);
|
|
|
|
const config: TaskConfig = {
|
|
taskId: 'test-callback-003',
|
|
pollingInterval: 1000,
|
|
maxRetries: 3,
|
|
timeout: 60000,
|
|
onProgress
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
|
|
jest.advanceTimersByTime(1000);
|
|
await Promise.resolve();
|
|
|
|
expect(onProgress).toHaveBeenCalledWith(TaskStatus.RUNNING);
|
|
});
|
|
});
|
|
|
|
describe('超时处理', () => {
|
|
it('应该在超时后将任务标记为超时状态', async () => {
|
|
const onTimeout = jest.fn();
|
|
const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.RUNNING);
|
|
taskManager.setStatusChecker(mockStatusCheck);
|
|
|
|
const config: TaskConfig = {
|
|
taskId: 'test-timeout-001',
|
|
pollingInterval: 100,
|
|
maxRetries: 3,
|
|
timeout: 150, // 150毫秒超时
|
|
onTimeout
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
|
|
// 快进到第一次轮询时间
|
|
jest.advanceTimersByTime(100);
|
|
await Promise.resolve();
|
|
|
|
// 再快进到超时时间
|
|
jest.advanceTimersByTime(100);
|
|
await Promise.resolve();
|
|
|
|
expect(taskManager.getTaskStatus('test-timeout-001')).toBe(TaskStatus.TIMEOUT);
|
|
expect(onTimeout).toHaveBeenCalled();
|
|
}, 2000); // 2秒超时限制
|
|
});
|
|
|
|
describe('持久化存储', () => {
|
|
it('应该将任务状态保存到本地存储', async () => {
|
|
const config: TaskConfig = {
|
|
taskId: 'test-storage-001',
|
|
pollingInterval: 2000,
|
|
maxRetries: 3,
|
|
timeout: 60000
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
|
|
expect(mockTaro.setStorage).toHaveBeenCalledWith({
|
|
key: 'async_tasks',
|
|
data: expect.objectContaining({
|
|
'test-storage-001': expect.objectContaining({
|
|
id: 'test-storage-001',
|
|
status: TaskStatus.RUNNING
|
|
})
|
|
})
|
|
});
|
|
}, 2000);
|
|
|
|
it('应该从本地存储恢复任务', async () => {
|
|
const storedTasks = {
|
|
'restored-task-001': {
|
|
id: 'restored-task-001',
|
|
status: TaskStatus.RUNNING,
|
|
startTime: Date.now() - 10000,
|
|
retryCount: 0,
|
|
lastPollTime: Date.now() - 5000,
|
|
config: {
|
|
taskId: 'restored-task-001',
|
|
pollingInterval: 2000,
|
|
maxRetries: 3,
|
|
timeout: 60000
|
|
}
|
|
}
|
|
};
|
|
|
|
mockTaro.getStorage.mockResolvedValueOnce({ data: storedTasks });
|
|
|
|
await taskManager.resumeTasks();
|
|
|
|
expect(taskManager.hasTask('restored-task-001')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('错误处理和重试', () => {
|
|
it('应该在网络错误时进行重试', async () => {
|
|
const mockStatusCheck = jest.fn()
|
|
.mockRejectedValueOnce(new Error('网络错误'))
|
|
.mockRejectedValueOnce(new Error('网络错误'))
|
|
.mockResolvedValueOnce(TaskStatus.SUCCESS);
|
|
|
|
taskManager.setStatusChecker(mockStatusCheck);
|
|
|
|
const config: TaskConfig = {
|
|
taskId: 'test-retry-001',
|
|
pollingInterval: 1000,
|
|
maxRetries: 3,
|
|
timeout: 60000
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
|
|
// 触发多次轮询以测试重试
|
|
for (let i = 0; i < 3; i++) {
|
|
jest.advanceTimersByTime(1000);
|
|
await Promise.resolve();
|
|
}
|
|
|
|
expect(mockStatusCheck).toHaveBeenCalledTimes(3);
|
|
expect(taskManager.getTaskStatus('test-retry-001')).toBe(TaskStatus.SUCCESS);
|
|
});
|
|
|
|
it('应该在达到最大重试次数后标记为失败', async () => {
|
|
const mockStatusCheck = jest.fn().mockRejectedValue(new Error('持续网络错误'));
|
|
taskManager.setStatusChecker(mockStatusCheck);
|
|
|
|
const config: TaskConfig = {
|
|
taskId: 'test-retry-002',
|
|
pollingInterval: 1000,
|
|
maxRetries: 2,
|
|
timeout: 60000
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
|
|
// 触发足够的轮询次数以超过重试限制
|
|
for (let i = 0; i < 4; i++) {
|
|
jest.advanceTimersByTime(1000);
|
|
await Promise.resolve();
|
|
}
|
|
|
|
expect(taskManager.getTaskStatus('test-retry-002')).toBe(TaskStatus.FAILED);
|
|
});
|
|
});
|
|
|
|
describe('任务暂停和恢复', () => {
|
|
it('应该能暂停所有活跃任务的轮询', async () => {
|
|
const config: TaskConfig = {
|
|
taskId: 'test-pause-001',
|
|
pollingInterval: 1000,
|
|
maxRetries: 3,
|
|
timeout: 60000
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
taskManager.pausePolling();
|
|
|
|
const mockStatusCheck = jest.fn();
|
|
taskManager.setStatusChecker(mockStatusCheck);
|
|
|
|
// 即使时间前进,暂停状态下不应该触发轮询
|
|
jest.advanceTimersByTime(2000);
|
|
await Promise.resolve();
|
|
|
|
expect(mockStatusCheck).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('应该能恢复所有任务的轮询', async () => {
|
|
const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.RUNNING);
|
|
taskManager.setStatusChecker(mockStatusCheck);
|
|
|
|
const config: TaskConfig = {
|
|
taskId: 'test-resume-001',
|
|
pollingInterval: 1000,
|
|
maxRetries: 3,
|
|
timeout: 60000
|
|
};
|
|
|
|
await taskManager.startTask(config);
|
|
taskManager.pausePolling();
|
|
taskManager.activatePolling();
|
|
|
|
jest.advanceTimersByTime(1000);
|
|
await Promise.resolve();
|
|
|
|
expect(mockStatusCheck).toHaveBeenCalled();
|
|
});
|
|
});
|
|
}); |