194 行
5.9 KiB
TypeScript
194 行
5.9 KiB
TypeScript
/**
|
|
* OmniCard AI (CRMmp) - 用户端离线缓存与断网重放队列单元测试
|
|
* 严格遵循 TDD 测试驱动规范与瑞士国际主义极简原则
|
|
*/
|
|
|
|
describe('Point 41: 移动端弱网离线视觉指示器与断网重放队列 (Offline Queue)', () => {
|
|
let storageMap: Record<string, string>;
|
|
let mod: any;
|
|
|
|
beforeEach(() => {
|
|
storageMap = {};
|
|
|
|
// 模拟全局 uni-app storage 与网络 API
|
|
(global as any).uni = {
|
|
getStorageSync: jest.fn((key: string) => storageMap[key] || ''),
|
|
setStorageSync: jest.fn((key: string, val: string) => {
|
|
storageMap[key] = String(val);
|
|
}),
|
|
removeStorageSync: jest.fn((key: string) => {
|
|
delete storageMap[key];
|
|
}),
|
|
onNetworkStatusChange: jest.fn(),
|
|
};
|
|
|
|
// @ts-ignore
|
|
mod = require('../../../mpuniapp/common/offline-queue.uts');
|
|
mod.clearOfflineQueue();
|
|
mod.setNetworkStatus(true, 'wifi');
|
|
});
|
|
|
|
afterEach(() => {
|
|
delete (global as any).uni;
|
|
});
|
|
|
|
describe('1. 网络在线/断网状态检测与切换', () => {
|
|
it('默认初始状态应为在线 (isOnline = true)', () => {
|
|
expect(mod.isOnline()).toBe(true);
|
|
const status = mod.getOfflineQueueStatus();
|
|
expect(status.isOnline).toBe(true);
|
|
expect(status.networkType).toBe('wifi');
|
|
});
|
|
|
|
it('断网时状态切换为离线并输出正确的国际化横幅文案', () => {
|
|
mod.setNetworkStatus(false, 'none');
|
|
expect(mod.isOnline()).toBe(false);
|
|
|
|
const status = mod.getOfflineQueueStatus();
|
|
expect(status.isOnline).toBe(false);
|
|
expect(status.networkType).toBe('none');
|
|
|
|
// 中文与英文横幅文案
|
|
const zhBanner = mod.formatOfflineBannerText('zh', 2);
|
|
expect(zhBanner).toContain('网络已断开');
|
|
expect(zhBanner).toContain('2 条暂存待同步');
|
|
|
|
const enBanner = mod.formatOfflineBannerText('en', 3);
|
|
expect(enBanner).toContain('Offline');
|
|
expect(enBanner).toContain('3 pending');
|
|
|
|
const arBanner = mod.formatOfflineBannerText('ar', 1);
|
|
expect(arBanner).toContain('غير متصل');
|
|
});
|
|
});
|
|
|
|
describe('2. 离线动作暂存入队 (enqueueOfflineAction)', () => {
|
|
it('应能正确保存离线留资或轨迹提交动作至本地队列', () => {
|
|
const action = {
|
|
id: 'action-001',
|
|
type: 'LEAD_SUBMIT',
|
|
url: '/card/lead/submit',
|
|
method: 'POST',
|
|
data: { name: 'Faisal Al-Otaibi', email: 'faisal@sauditech.sa' },
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
const count = mod.enqueueOfflineAction(action);
|
|
expect(count).toBe(1);
|
|
|
|
const queue = mod.getOfflineQueue();
|
|
expect(queue.length).toBe(1);
|
|
expect(queue[0].id).toBe('action-001');
|
|
expect(queue[0].type).toBe('LEAD_SUBMIT');
|
|
expect(queue[0].retryCount).toBe(0);
|
|
});
|
|
|
|
it('支持多次入队并保持先进先出 (FIFO) 顺序', () => {
|
|
mod.enqueueOfflineAction({
|
|
id: 'act-1',
|
|
type: 'RADAR_TRACK',
|
|
url: '/crm/radar/track',
|
|
timestamp: 1000,
|
|
});
|
|
mod.enqueueOfflineAction({
|
|
id: 'act-2',
|
|
type: 'LEAD_SUBMIT',
|
|
url: '/crm/lead/create',
|
|
timestamp: 2000,
|
|
});
|
|
|
|
const queue = mod.getOfflineQueue();
|
|
expect(queue.length).toBe(2);
|
|
expect(queue[0].id).toBe('act-1');
|
|
expect(queue[1].id).toBe('act-2');
|
|
});
|
|
|
|
it('自动生成缺失的 action.id 与 timestamp', () => {
|
|
mod.enqueueOfflineAction({
|
|
type: 'RADAR_TRACK',
|
|
url: '/crm/radar/ping',
|
|
});
|
|
|
|
const queue = mod.getOfflineQueue();
|
|
expect(queue.length).toBe(1);
|
|
expect(queue[0].id).toBeDefined();
|
|
expect(queue[0].id.length).toBeGreaterThan(0);
|
|
expect(queue[0].timestamp).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('3. 网络恢复自动重放队列 (replayOfflineQueue)', () => {
|
|
it('成功执行所有队列动作后应自动清空队列', async () => {
|
|
mod.enqueueOfflineAction({
|
|
id: 'act-success-1',
|
|
type: 'LEAD_SUBMIT',
|
|
url: '/card/lead/submit',
|
|
data: { test: 1 },
|
|
});
|
|
mod.enqueueOfflineAction({
|
|
id: 'act-success-2',
|
|
type: 'RADAR_TRACK',
|
|
url: '/crm/radar/track',
|
|
data: { test: 2 },
|
|
});
|
|
|
|
expect(mod.getOfflineQueue().length).toBe(2);
|
|
|
|
const executedIds: string[] = [];
|
|
const result = await mod.replayOfflineQueue(async (item: any) => {
|
|
executedIds.push(item.id);
|
|
return true;
|
|
});
|
|
|
|
expect(result.total).toBe(2);
|
|
expect(result.success).toBe(2);
|
|
expect(result.failed).toBe(0);
|
|
expect(executedIds).toEqual(['act-success-1', 'act-success-2']);
|
|
expect(mod.getOfflineQueue().length).toBe(0);
|
|
});
|
|
|
|
it('重放部分失败时应增加重试次数并保留失败项在队列中', async () => {
|
|
mod.enqueueOfflineAction({
|
|
id: 'act-ok',
|
|
type: 'LEAD_SUBMIT',
|
|
url: '/api/ok',
|
|
});
|
|
mod.enqueueOfflineAction({
|
|
id: 'act-fail',
|
|
type: 'LEAD_SUBMIT',
|
|
url: '/api/fail',
|
|
});
|
|
|
|
const result = await mod.replayOfflineQueue(async (item: any) => {
|
|
if (item.id === 'act-fail') {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
expect(result.total).toBe(2);
|
|
expect(result.success).toBe(1);
|
|
expect(result.failed).toBe(1);
|
|
|
|
const remaining = mod.getOfflineQueue();
|
|
expect(remaining.length).toBe(1);
|
|
expect(remaining[0].id).toBe('act-fail');
|
|
expect(remaining[0].retryCount).toBe(1);
|
|
});
|
|
|
|
it('超过最大重试次数 (默认 3 次) 时应安全移除死信项,避免永久阻塞', async () => {
|
|
mod.enqueueOfflineAction({
|
|
id: 'act-dead',
|
|
type: 'DEAD_LETTER',
|
|
url: '/api/dead',
|
|
retryCount: 3, // 已达上限
|
|
});
|
|
|
|
const result = await mod.replayOfflineQueue(async () => false, 3);
|
|
expect(result.total).toBe(1);
|
|
expect(result.failed).toBe(1);
|
|
expect(mod.getOfflineQueue().length).toBe(0); // 超限后被淘汰
|
|
});
|
|
});
|
|
});
|