Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
153 lines
6.0 KiB
TypeScript
153 lines
6.0 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { hasLowProcessIntegrity, type NativeRuntimeIntegrity } from '../src/runtime-integrity';
|
|
|
|
const native = vi.hoisted(() => ({ load: vi.fn(), error: vi.fn() }));
|
|
vi.mock('../src/injector', () => ({
|
|
getNativeHookAddon: native.load,
|
|
getNativeHookLoadError: native.error,
|
|
}));
|
|
|
|
const platform = process.platform;
|
|
const sample = (): NativeRuntimeIntegrity => ({
|
|
pid: process.pid,
|
|
executablePath: process.execPath,
|
|
process: { status: 'ok', rid: 8192 },
|
|
file: { status: 'ok', label: null },
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
vi.resetAllMocks();
|
|
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
Object.defineProperty(process, 'platform', { value: platform, configurable: true });
|
|
});
|
|
|
|
describe('runtime integrity diagnostics', () => {
|
|
it('warns only for a successfully read process token below Medium', () => {
|
|
const available = { ...sample(), status: 'available' as const };
|
|
|
|
for (const rid of [0, 4096]) {
|
|
expect(hasLowProcessIntegrity({ ...available, process: { status: 'ok', rid } })).toBe(
|
|
true,
|
|
);
|
|
}
|
|
|
|
for (const rid of [8192, 8448, 12288, 16384]) {
|
|
expect(
|
|
hasLowProcessIntegrity({
|
|
...available,
|
|
process: { status: 'ok', rid },
|
|
file: { status: 'ok', label: { rid: 4096, inherited: true, policy: 1 } },
|
|
}),
|
|
).toBe(false);
|
|
}
|
|
|
|
expect(hasLowProcessIntegrity({ status: 'unsupported' })).toBe(false);
|
|
expect(hasLowProcessIntegrity({ status: 'unavailable', error: 'unavailable' })).toBe(false);
|
|
expect(
|
|
hasLowProcessIntegrity({
|
|
...available,
|
|
process: { status: 'error', operation: 'OpenProcessToken', code: 5 },
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it('keeps one startup snapshot for logging and all WebUI polls', async () => {
|
|
const read = vi.fn().mockResolvedValue(sample());
|
|
native.load.mockReturnValue({ getRuntimeIntegrityAsync: read });
|
|
const { getRuntimeIntegrity } = await import('../src/runtime-integrity');
|
|
const pending = getRuntimeIntegrity();
|
|
expect(getRuntimeIntegrity()).toBe(pending);
|
|
const first = await pending;
|
|
expect(first).toMatchObject({
|
|
status: 'available',
|
|
process: { rid: 8192 },
|
|
file: { label: null },
|
|
});
|
|
read.mockResolvedValue({
|
|
...sample(),
|
|
file: { status: 'ok', label: { rid: 4096, inherited: true, policy: 1 } },
|
|
});
|
|
expect(await getRuntimeIntegrity()).toBe(first);
|
|
expect(read).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it.each(['process', 'file'])(
|
|
'retains %s query errors and the other successful result',
|
|
async (field) => {
|
|
native.load.mockReturnValue({
|
|
getRuntimeIntegrityAsync: async () => ({
|
|
...sample(),
|
|
[field]: { status: 'error', operation: 'Win32Query', code: 5 },
|
|
}),
|
|
});
|
|
const { getRuntimeIntegrity } = await import('../src/runtime-integrity');
|
|
expect(await getRuntimeIntegrity()).toMatchObject({
|
|
status: 'available',
|
|
[field]: { status: 'error', operation: 'Win32Query', code: 5 },
|
|
});
|
|
},
|
|
);
|
|
|
|
it('reports old addons as unavailable rather than Medium', async () => {
|
|
native.load.mockReturnValue({});
|
|
const { getRuntimeIntegrity } = await import('../src/runtime-integrity');
|
|
expect(await getRuntimeIntegrity()).toMatchObject({
|
|
status: 'unavailable',
|
|
error: expect.stringContaining('不支持'),
|
|
});
|
|
});
|
|
|
|
it('retains the loader error for the startup log', async () => {
|
|
native.load.mockReturnValue(null);
|
|
native.error.mockReturnValue('dlopen failed: incompatible binary');
|
|
const { getRuntimeIntegrity } = await import('../src/runtime-integrity');
|
|
expect(await getRuntimeIntegrity()).toMatchObject({
|
|
status: 'unavailable',
|
|
error: 'dlopen failed: incompatible binary',
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
null,
|
|
{ ...sample(), pid: process.pid + 1 },
|
|
{ ...sample(), process: { status: 'ok', rid: -1 } },
|
|
{ ...sample(), file: { status: 'ok', label: { rid: 4096, inherited: 'yes', policy: 1 } } },
|
|
])('rejects malformed or wrong-process native results', async (result) => {
|
|
native.load.mockReturnValue({ getRuntimeIntegrityAsync: async () => result });
|
|
const { getRuntimeIntegrity } = await import('../src/runtime-integrity');
|
|
expect(await getRuntimeIntegrity()).toMatchObject({
|
|
status: 'unavailable',
|
|
error: expect.stringContaining('无效数据'),
|
|
});
|
|
});
|
|
|
|
it('preserves rejected errors without repeatedly probing on WebUI polls', async () => {
|
|
const read = vi
|
|
.fn()
|
|
.mockRejectedValueOnce(new Error('allocation failed'))
|
|
.mockResolvedValue(sample());
|
|
native.load.mockReturnValue({ getRuntimeIntegrityAsync: read });
|
|
const { getRuntimeIntegrity } = await import('../src/runtime-integrity');
|
|
expect(await getRuntimeIntegrity()).toMatchObject({
|
|
status: 'unavailable',
|
|
error: 'allocation failed',
|
|
});
|
|
expect(await getRuntimeIntegrity()).toMatchObject({
|
|
status: 'unavailable',
|
|
error: 'allocation failed',
|
|
});
|
|
expect(read).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('does not load the addon on non-Windows hosts', async () => {
|
|
Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
|
|
const { getRuntimeIntegrity } = await import('../src/runtime-integrity');
|
|
expect(await getRuntimeIntegrity()).toEqual({ status: 'unsupported' });
|
|
expect(native.load).not.toHaveBeenCalled();
|
|
});
|
|
});
|