- DataBuffer: 离线持久化缓冲(分桶存储 + 去重 + 容量管理) - GenericBleAdapter: 基于 Bluetooth SIG 标准 Health Profile 的通用适配器 (Heart Rate 0x180D / Health Thermometer 0x1809 / Blood Pressure 0x1810) - DataSyncScheduler: 定时自动同步调度(基于时间间隔判断是否需要同步) - BLEManager: 集成 DataBuffer 替换简单 Storage 缓存 - device-sync 页面: 注册 CustomBandAdapter + 自动同步 + 状态显示 - 新增 vitest 单元测试配置,30 个测试全部通过
92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import { DataSyncScheduler } from '@/services/ble/DataSyncScheduler';
|
|
|
|
const storage = new Map<string, string>();
|
|
vi.mock('@tarojs/taro', () => ({
|
|
default: {
|
|
getStorageSync: vi.fn((key: string) => storage.get(key) || ''),
|
|
setStorageSync: vi.fn((key: string, value: string) => { storage.set(key, value); }),
|
|
removeStorageSync: vi.fn((key: string) => { storage.delete(key); }),
|
|
},
|
|
}));
|
|
|
|
describe('DataSyncScheduler', () => {
|
|
let scheduler: DataSyncScheduler;
|
|
let syncFn: ReturnType<typeof vi.fn>;
|
|
|
|
beforeEach(() => {
|
|
storage.clear();
|
|
syncFn = vi.fn().mockResolvedValue({ success: true, uploadedCount: 5 });
|
|
scheduler = new DataSyncScheduler({
|
|
intervalMs: 60 * 60 * 1000,
|
|
storageKey: 'last_ble_sync',
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
scheduler.destroy();
|
|
});
|
|
|
|
it('首次同步:无记录时立即需要同步', () => {
|
|
expect(scheduler.needsSync()).toBe(true);
|
|
});
|
|
|
|
it('同步后记录时间戳', async () => {
|
|
await scheduler.recordSync(syncFn);
|
|
expect(storage.has('last_ble_sync')).toBe(true);
|
|
expect(syncFn).toHaveBeenCalled();
|
|
});
|
|
|
|
it('同步后不需要再次同步', async () => {
|
|
await scheduler.recordSync(syncFn);
|
|
expect(scheduler.needsSync()).toBe(false);
|
|
});
|
|
|
|
it('超过间隔后需要再次同步', async () => {
|
|
const twoHoursAgo = Date.now() - 2 * 60 * 60 * 1000;
|
|
storage.set('last_ble_sync', JSON.stringify({ lastSyncAt: twoHoursAgo }));
|
|
scheduler = new DataSyncScheduler({ intervalMs: 60 * 60 * 1000, storageKey: 'last_ble_sync' });
|
|
|
|
expect(scheduler.needsSync()).toBe(true);
|
|
});
|
|
|
|
it('同步失败不更新时间戳', async () => {
|
|
const failFn = vi.fn().mockRejectedValue(new Error('network error'));
|
|
const oneHourAgo = Date.now() - 60 * 60 * 1000;
|
|
storage.set('last_ble_sync', JSON.stringify({ lastSyncAt: oneHourAgo }));
|
|
|
|
await scheduler.recordSync(failFn);
|
|
const stored = JSON.parse(storage.get('last_ble_sync') || '{}');
|
|
expect(stored.lastSyncAt).toBe(oneHourAgo);
|
|
});
|
|
|
|
it('tryAutoSync 首次时触发同步', async () => {
|
|
const result = await scheduler.tryAutoSync(syncFn);
|
|
expect(result).toBe(true);
|
|
expect(syncFn).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('tryAutoSync 未超时不触发', async () => {
|
|
await scheduler.recordSync(syncFn);
|
|
syncFn.mockClear();
|
|
const result = await scheduler.tryAutoSync(syncFn);
|
|
expect(result).toBe(false);
|
|
expect(syncFn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('destroy 清理定时器', () => {
|
|
const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
|
|
scheduler.startPeriodicCheck(syncFn, 30000);
|
|
scheduler.destroy();
|
|
expect(clearIntervalSpy).toHaveBeenCalled();
|
|
clearIntervalSpy.mockRestore();
|
|
});
|
|
|
|
it('getLastSyncAt 返回上次同步时间', async () => {
|
|
await scheduler.recordSync(syncFn);
|
|
const lastSync = scheduler.getLastSyncAt();
|
|
expect(lastSync).toBeTruthy();
|
|
expect(typeof lastSync).toBe('number');
|
|
});
|
|
});
|