216 lines
6.7 KiB
TypeScript
216 lines
6.7 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||
|
||
const {
|
||
sendJsonRpcMock,
|
||
getDeviceIdMock,
|
||
getDeviceModelMock,
|
||
getChannelIdMock,
|
||
ensureApiClientReadyMock,
|
||
buglyInitMock,
|
||
buglySetDeviceIdMock,
|
||
buglySetDeviceModelMock,
|
||
setOaidMock,
|
||
setPrivacyStatusMock,
|
||
initBaiduOcpcMock,
|
||
} = vi.hoisted(() => ({
|
||
sendJsonRpcMock: vi.fn(),
|
||
getDeviceIdMock: vi.fn(),
|
||
getDeviceModelMock: vi.fn(),
|
||
getChannelIdMock: vi.fn(),
|
||
ensureApiClientReadyMock: vi.fn(),
|
||
buglyInitMock: vi.fn(),
|
||
buglySetDeviceIdMock: vi.fn(),
|
||
buglySetDeviceModelMock: vi.fn(),
|
||
setOaidMock: vi.fn(),
|
||
setPrivacyStatusMock: vi.fn(),
|
||
initBaiduOcpcMock: vi.fn(),
|
||
}));
|
||
|
||
vi.mock('react-native-gesture-handler', () => ({}));
|
||
vi.mock('react-native-url-polyfill/auto', () => ({}));
|
||
vi.mock('../src/App', () => ({
|
||
__esModule: true,
|
||
default: () => null,
|
||
}));
|
||
|
||
vi.mock('@telescope/api-client', () => ({
|
||
sendJsonRpc: sendJsonRpcMock,
|
||
}));
|
||
|
||
vi.mock('@telescope/oaid', () => ({
|
||
getDeviceId: getDeviceIdMock,
|
||
}));
|
||
|
||
vi.mock('@telescope/device-model', () => ({
|
||
getDeviceModel: getDeviceModelMock,
|
||
}));
|
||
|
||
vi.mock('@telescope/channel-id', () => ({
|
||
getChannelId: getChannelIdMock,
|
||
}));
|
||
|
||
vi.mock('../src/api', () => ({
|
||
ensureApiClientReady: ensureApiClientReadyMock,
|
||
}));
|
||
|
||
vi.mock('@telescope/bugly', () => ({
|
||
__esModule: true,
|
||
default: {
|
||
init: buglyInitMock,
|
||
setDeviceId: buglySetDeviceIdMock,
|
||
setDeviceModel: buglySetDeviceModelMock,
|
||
},
|
||
BuglyTransport: class BuglyTransport {
|
||
options: unknown;
|
||
|
||
constructor(options?: unknown) {
|
||
this.options = options;
|
||
}
|
||
},
|
||
}));
|
||
|
||
vi.mock('@telescope/baidu-ocpc', () => ({
|
||
setOaid: setOaidMock,
|
||
setPrivacyStatus: setPrivacyStatusMock,
|
||
init: initBaiduOcpcMock,
|
||
BaiduOcpcTransport: class BaiduOcpcTransport {
|
||
options: unknown;
|
||
|
||
constructor(options?: unknown) {
|
||
this.options = options;
|
||
}
|
||
},
|
||
}));
|
||
|
||
async function flushPromises(times = 4) {
|
||
for (let i = 0; i < times; i += 1) {
|
||
await Promise.resolve();
|
||
}
|
||
}
|
||
|
||
describe('main', () => {
|
||
beforeEach(() => {
|
||
vi.resetModules();
|
||
vi.clearAllMocks();
|
||
(globalThis as any).__DEV__ = true;
|
||
|
||
sendJsonRpcMock.mockResolvedValue(undefined);
|
||
getDeviceIdMock.mockResolvedValue('device-1');
|
||
getDeviceModelMock.mockResolvedValue('Pixel-9');
|
||
getChannelIdMock.mockResolvedValue('GF');
|
||
ensureApiClientReadyMock.mockResolvedValue(undefined);
|
||
initBaiduOcpcMock.mockResolvedValue(undefined);
|
||
setOaidMock.mockResolvedValue(undefined);
|
||
setPrivacyStatusMock.mockResolvedValue(undefined);
|
||
});
|
||
|
||
it('会初始化 logger/第三方 SDK 并注册 RN 入口', async () => {
|
||
await import('../src/main');
|
||
await flushPromises();
|
||
|
||
const { initLogger } = await import('@telescope/logger');
|
||
const { AppRegistry } = await import('react-native');
|
||
|
||
expect(initLogger).toHaveBeenCalledTimes(1);
|
||
|
||
const [loggerConfig] = (initLogger as any).mock.calls[0] as [{ transports: unknown[] }];
|
||
expect(loggerConfig.transports).toHaveLength(4);
|
||
|
||
expect(AppRegistry.registerComponent).toHaveBeenCalledWith('XingyunReactNative', expect.any(Function));
|
||
|
||
// Bugly 初始化在 App.tsx 的 useEffect 中,main.tsx 只注册 BuglyTransport,
|
||
// 所以此处不验证 Bugly.init(在 App.test.tsx 中测试)。
|
||
expect(buglyInitMock).not.toHaveBeenCalled();
|
||
|
||
expect(setOaidMock).not.toHaveBeenCalled();
|
||
expect(setPrivacyStatusMock).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it('BD 渠道会先设置 OAID 再上报隐私同意状态', async () => {
|
||
// OCPC 逻辑在 App.tsx 的 useEffect 中,main.tsx 只注册组件工厂不渲染,
|
||
// 所以此场景在 App.test.tsx 中测试。此处仅验证 main 不会抛错。
|
||
getChannelIdMock.mockResolvedValue('BD');
|
||
|
||
await import('../src/main');
|
||
await flushPromises();
|
||
|
||
expect(getChannelIdMock).toHaveBeenCalled();
|
||
});
|
||
|
||
it('BatchTransport.submit 会做等待、脱敏与截断后上报', async () => {
|
||
await import('../src/main');
|
||
await flushPromises();
|
||
|
||
const { initLogger } = await import('@telescope/logger');
|
||
const { InteractionManager } = await import('react-native');
|
||
|
||
const [loggerConfig] = (initLogger as any).mock.calls[0] as [{ transports: Array<{ options?: any }> }];
|
||
const batchTransport = loggerConfig.transports[3];
|
||
const submit = batchTransport.options.submit as (entries: any[]) => Promise<void>;
|
||
|
||
ensureApiClientReadyMock.mockClear();
|
||
getDeviceIdMock.mockClear();
|
||
sendJsonRpcMock.mockClear();
|
||
InteractionManager.runAfterInteractions.mockClear();
|
||
|
||
const circular: Record<string, unknown> = { id: 'self' };
|
||
circular.self = circular;
|
||
|
||
await submit([
|
||
{
|
||
message: 'm'.repeat(250),
|
||
context: {
|
||
token: 'plain-token',
|
||
password: 'plain-password',
|
||
normal: 'ok',
|
||
arr: ['x'.repeat(1100)],
|
||
deep: { a: { b: { c: { d: { e: { f: { g: 'too-deep' } } } } } } },
|
||
circular,
|
||
err: new Error('E'.repeat(1100)),
|
||
},
|
||
timestamp: 0,
|
||
},
|
||
{
|
||
message: 'short',
|
||
context: 42,
|
||
timestamp: 1000,
|
||
},
|
||
]);
|
||
|
||
expect(ensureApiClientReadyMock).toHaveBeenCalledTimes(1);
|
||
expect(InteractionManager.runAfterInteractions).toHaveBeenCalledTimes(1);
|
||
expect(getDeviceIdMock).toHaveBeenCalledTimes(1);
|
||
expect(sendJsonRpcMock).toHaveBeenCalledTimes(1);
|
||
|
||
const [method, payload] = sendJsonRpcMock.mock.calls[0] as [string, { items: Array<Record<string, any>> }];
|
||
expect(method).toBe('BatchSubmitCrmTrackLog');
|
||
expect(payload.items).toHaveLength(2);
|
||
|
||
expect(payload.items[0].uniqueId).toBe('device-1');
|
||
expect(payload.items[0].event).toBe(`${'m'.repeat(200)}...`);
|
||
expect(payload.items[0].time).toBe(new Date(0).toISOString());
|
||
expect(payload.items[0].params.token).toBe('[REDACTED]');
|
||
expect(payload.items[0].params.password).toBe('[REDACTED]');
|
||
expect(payload.items[0].params.arr[0].length).toBe(1003);
|
||
expect(payload.items[0].params.circular.self).toBe('[Circular]');
|
||
expect(payload.items[0].params.deep.a.b.c.d.e.f).toBe('[MaxDepth]');
|
||
expect(payload.items[0].params.err.name).toBe('Error');
|
||
expect(payload.items[0].params.err.message.length).toBe(1003);
|
||
|
||
expect(payload.items[1].event).toBe('short');
|
||
expect(payload.items[1].params).toEqual({ value: 42 });
|
||
expect(payload.items[1].time).toBe(new Date(1000).toISOString());
|
||
});
|
||
|
||
it('Bugly / 百度 OCPC 初始化失败时不抛错', async () => {
|
||
getChannelIdMock.mockRejectedValue(new Error('channel-failed'));
|
||
setOaidMock.mockRejectedValueOnce(new Error('oaid-failed'));
|
||
|
||
await expect(import('../src/main')).resolves.toBeDefined();
|
||
await flushPromises();
|
||
|
||
expect(buglyInitMock).not.toHaveBeenCalled();
|
||
expect(setPrivacyStatusMock).not.toHaveBeenCalled();
|
||
});
|
||
});
|