Files
hms/apps/miniprogram/__tests__/services/analytics-pii.test.ts
iven 2aa393dd65 fix(miniprogram): Analytics PII 清理 — 移除 userId/patientId 字段 + sanitizeProperties (S2-1)
- 移除 AnalyticsEvent 接口中的 userId/patientId 字段
- 新增 sanitizeProperties 运行时过滤 14 种 PII 标识字段
- trackEvent 自动清理 properties 中的 PII
- 3 个单元测试覆盖 PII 过滤场景
2026-05-22 08:17:58 +08:00

100 lines
3.1 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest';
vi.mock('@tarojs/taro', () => ({
default: {
getStorageSync: vi.fn(() => []),
setStorage: vi.fn(),
removeStorageSync: vi.fn(),
},
}));
vi.mock('@/services/request', () => ({
api: { post: vi.fn().mockResolvedValue({ success: true }) },
}));
describe('Analytics PII 清理', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('flushEvents 发送的 batch 不含 PII', async () => {
const { trackEvent, flushEvents } = await import('@/services/analytics');
const { api } = await import('@/services/request');
trackEvent('page_view', {
page: 'health',
userId: 'should-be-removed',
patientId: 'should-be-removed',
user_name: 'should-be-removed',
phone: 'should-be-removed',
id_card: 'should-be-removed',
});
await flushEvents();
const postCall = vi.mocked(api.post).mock.calls[0];
const body = postCall[1] as { events: Array<Record<string, unknown>> };
const evt = body.events[0];
// 事件级别不应有 userId/patientId
expect(evt).not.toHaveProperty('userId');
expect(evt).not.toHaveProperty('patientId');
// properties 中不应有 PII 字段
const props = evt.properties as Record<string, unknown>;
expect(props).not.toHaveProperty('userId');
expect(props).not.toHaveProperty('patientId');
expect(props).not.toHaveProperty('user_name');
expect(props).not.toHaveProperty('phone');
expect(props).not.toHaveProperty('id_card');
// 正常字段保留
expect(props.page).toBe('health');
});
it('trackEvent 不在事件级别包含 userId/patientId', async () => {
const { trackEvent, flushEvents } = await import('@/services/analytics');
const { api } = await import('@/services/request');
trackEvent('test_event');
await flushEvents();
const postCall = vi.mocked(api.post).mock.calls[0];
const body = postCall[1] as { events: Array<Record<string, unknown>> };
const evt = body.events[0];
expect(evt).not.toHaveProperty('userId');
expect(evt).not.toHaveProperty('patientId');
});
it('sanitizeProperties 过滤所有 PII 标识字段', async () => {
const { trackEvent, flushEvents } = await import('@/services/analytics');
const { api } = await import('@/services/request');
trackEvent('test', {
openid: 'oXXX',
access_token: 'tok',
refresh_token: 'ref',
email: 'test@test.com',
address: '某地',
mobile: '13800001111',
page: 'settings', // 非 PII 字段
});
await flushEvents();
const postCall = vi.mocked(api.post).mock.calls[0];
const body = postCall[1] as { events: Array<Record<string, unknown>> };
const props = body.events[0].properties as Record<string, unknown>;
// 全部 PII 被过滤,只剩 page
expect(props).not.toHaveProperty('openid');
expect(props).not.toHaveProperty('access_token');
expect(props).not.toHaveProperty('refresh_token');
expect(props).not.toHaveProperty('email');
expect(props).not.toHaveProperty('address');
expect(props).not.toHaveProperty('mobile');
expect(props.page).toBe('settings');
});
});