Integrate the current development branch, preserve the album comment fixes, and align the changes with repository formatting and dependency tooling.
153 lines
5.9 KiB
TypeScript
153 lines
5.9 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().mockReturnValue(sample());
|
|
native.load.mockReturnValue({ getRuntimeIntegrity: read });
|
|
const { getRuntimeIntegrity } = await import('../src/runtime-integrity');
|
|
const first = getRuntimeIntegrity();
|
|
expect(first).toMatchObject({
|
|
status: 'available',
|
|
process: { rid: 8192 },
|
|
file: { label: null },
|
|
});
|
|
read.mockReturnValue({
|
|
...sample(),
|
|
file: { status: 'ok', label: { rid: 4096, inherited: true, policy: 1 } },
|
|
});
|
|
expect(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({
|
|
getRuntimeIntegrity: () => ({
|
|
...sample(),
|
|
[field]: { status: 'error', operation: 'Win32Query', code: 5 },
|
|
}),
|
|
});
|
|
const { getRuntimeIntegrity } = await import('../src/runtime-integrity');
|
|
expect(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(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(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({ getRuntimeIntegrity: () => result });
|
|
const { getRuntimeIntegrity } = await import('../src/runtime-integrity');
|
|
expect(getRuntimeIntegrity()).toMatchObject({
|
|
status: 'unavailable',
|
|
error: expect.stringContaining('无效数据'),
|
|
});
|
|
});
|
|
|
|
it('preserves thrown errors without repeatedly probing on WebUI polls', async () => {
|
|
const read = vi
|
|
.fn()
|
|
.mockImplementationOnce(() => {
|
|
throw new Error('allocation failed');
|
|
})
|
|
.mockReturnValue(sample());
|
|
native.load.mockReturnValue({ getRuntimeIntegrity: read });
|
|
const { getRuntimeIntegrity } = await import('../src/runtime-integrity');
|
|
expect(getRuntimeIntegrity()).toMatchObject({
|
|
status: 'unavailable',
|
|
error: 'allocation failed',
|
|
});
|
|
expect(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(getRuntimeIntegrity()).toEqual({ status: 'unsupported' });
|
|
expect(native.load).not.toHaveBeenCalled();
|
|
});
|
|
});
|