NapCatQQ source snapshot
Build NapCat Artifacts / Type Check & Test (push) Waiting to run
Release Tag / validate-version (push) Waiting to run

This commit is contained in:
TanTan committed 2026-11-13 09:01:47 +08:00
commit f954252a34
1111 files changed
+139867

No files matched your search

@@ -0,0 +1,104 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import fs from 'node:fs';
import { NativePacketHandler } from '@/napcat-core/packet/handler/client';
import { Napi2NativeLoader } from '@/napcat-core/packet/handler/napi2nativeLoader';
import { NativePacketClient } from '@/napcat-core/packet/client/nativeClient';
import type { LogWrapper } from '@/napcat-core/helper/log';
import type { NapCoreContext } from '@/napcat-core/packet/context/napCoreContext';
import type { PacketLogger } from '@/napcat-core/packet/context/loggerContext';
import type { LogStack } from '@/napcat-core/packet/context/clientContext';
import type { PacketBuf } from '@/napcat-core/packet/transformer/base';
const logger = { log: vi.fn(), logWarn: vi.fn(), logError: vi.fn() } as unknown as LogWrapper;
const platformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform')!;
const archDescriptor = Object.getOwnPropertyDescriptor(process, 'arch')!;
beforeEach(() => {
Object.defineProperty(process, 'platform', { value: 'darwin' });
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
});
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
Object.defineProperty(process, 'platform', platformDescriptor);
Object.defineProperty(process, 'arch', archDescriptor);
});
describe('macOS native loaders', () => {
it.each(['arm64', 'x64'])('loads both packet modules for %s', async (arch) => {
Object.defineProperty(process, 'arch', { value: arch });
const dlopen = vi.spyOn(process, 'dlopen').mockImplementation((module) => {
Object.assign(module, { exports: { initHook: vi.fn().mockReturnValue('success') } });
});
const handler = new NativePacketHandler({ logger });
expect(await handler.init('7.0.2-53644')).toBe(true);
expect(dlopen.mock.calls[0]![1]).toContain('MoeHoo.darwin.' + arch + '.node');
const loader = new Napi2NativeLoader({ logger });
expect(loader.loaded).toBe(true);
expect(dlopen.mock.calls[1]![1]).toContain('napi2native.darwin.' + arch + '.node');
});
it.each(['error hook', 'error search', undefined])('does not report native failure %s as success', async (result) => {
Object.defineProperty(process, 'arch', { value: 'arm64' });
vi.spyOn(process, 'dlopen').mockImplementation(module => {
Object.assign(module, { exports: { initHook: vi.fn().mockReturnValue(result) } });
});
expect(await new NativePacketHandler({ logger }).init('7.0.2-53644')).toBe(false);
});
it('does not attempt dlopen when a native binary is absent', async () => {
vi.mocked(fs.existsSync).mockReturnValue(false);
const dlopen = vi.spyOn(process, 'dlopen').mockImplementation(() => {});
const handler = new NativePacketHandler({ logger });
expect(await handler.init('7.0.2-53644')).toBe(false);
expect(new Napi2NativeLoader({ logger }).loaded).toBe(false);
expect(dlopen).not.toHaveBeenCalled();
});
});
describe('NativePacketClient', () => {
function fixture () {
const send = vi.fn();
const minimum = vi.fn().mockImplementation(version => Number(version) <= 40768);
const native = { initHook: vi.fn().mockReturnValue(true) };
const core = { basicInfo: { requireMinNTQQBuild: minimum }, sendSsoCmdReqByContend: send };
const logs = { pushLogWarn: vi.fn(), pushLogInfo: vi.fn(), pushLogError: vi.fn() };
const client = new NativePacketClient(
core as unknown as NapCoreContext,
{ error: vi.fn() } as unknown as PacketLogger,
logs as unknown as LogStack,
native as unknown as Napi2NativeLoader
);
return { client, send, minimum, native };
}
it('initializes the oldest supported build and clears availability after failure', async () => {
const { client, minimum, native } = fixture();
await client.init(0, '10', '20');
expect(minimum).toHaveBeenCalledWith('40768');
expect(client.available).toBe(true);
native.initHook.mockReturnValue(false);
await client.init(0, '10', '20');
expect(client.available).toBe(false);
});
it.each(['success', 'failure', 'timeout'])('clears request timers after %s', async (outcome) => {
vi.useFakeTimers();
const { client, send } = fixture();
const payload = Buffer.from([0, 255, 128]) as PacketBuf;
if (outcome === 'success') send.mockResolvedValue({ rspbuffer: payload });
if (outcome === 'failure') send.mockRejectedValue(new Error('network unavailable'));
if (outcome === 'timeout') send.mockReturnValue(new Promise(() => {}));
const pending = client.sendPacket('Test.Command', payload, true, 50);
if (outcome === 'success') {
await expect(pending).resolves.toEqual({ seq: 0, cmd: 'Test.Command', data: payload });
} else {
const assertion = expect(pending).rejects.toThrow(outcome === 'timeout' ? '超时' : 'network unavailable');
if (outcome === 'timeout') await vi.advanceTimersByTimeAsync(50);
await assertion;
}
expect(vi.getTimerCount()).toBe(0);
expect(send).toHaveBeenCalledWith('Test.Command', payload);
});
});