feat(v027): AutostartDiagnostic — Windows registry 조회 + silent fallback
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import electron from 'electron';
|
||||
import { execFile } from 'node:child_process';
|
||||
const { app } = electron;
|
||||
|
||||
/**
|
||||
@@ -16,6 +17,9 @@ export interface AutostartState {
|
||||
registryValue?: string | null;
|
||||
}
|
||||
|
||||
const WIN_REGISTRY_PATH = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';
|
||||
const WIN_REGISTRY_KEY = 'Inkling';
|
||||
|
||||
export async function collectAutostartState(): Promise<AutostartState> {
|
||||
const w = app.getLoginItemSettings({ args: ['--hidden'] });
|
||||
const n = app.getLoginItemSettings();
|
||||
@@ -24,6 +28,29 @@ export async function collectAutostartState(): Promise<AutostartState> {
|
||||
noArgs: { openAtLogin: n.openAtLogin, executableWillLaunchAtLogin: n.executableWillLaunchAtLogin },
|
||||
execPath: process.execPath
|
||||
};
|
||||
// Task 20 — Windows registry 조회 (HKCU\...\Run\Inkling) 는 다음 task 에서 추가.
|
||||
if (process.platform === 'win32') {
|
||||
state.registryPath = `${WIN_REGISTRY_PATH}\\${WIN_REGISTRY_KEY}`;
|
||||
state.registryValue = await readRegistrySilent();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* `reg query` 로 HKCU\\...\\Run\\Inkling 의 값을 조회.
|
||||
* 키가 없으면 reg.exe 가 exit 1 → silent fallback (null).
|
||||
*
|
||||
* promisify(execFile) 대신 직접 Promise 로 wrapping — 테스트에서 vi.mock 이
|
||||
* `util.promisify.custom` symbol 을 보전하지 못해 stdout 이 undefined 가 되는 이슈 회피.
|
||||
*/
|
||||
function readRegistrySilent(): Promise<string | null> {
|
||||
return new Promise((resolve) => {
|
||||
execFile('reg', ['query', WIN_REGISTRY_PATH, '/v', WIN_REGISTRY_KEY], (err, stdout) => {
|
||||
if (err) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
const m = stdout.match(/REG_SZ\s+(.+)/);
|
||||
resolve(m && m[1] ? m[1].trim() : null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,21 +1,38 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
const { mockApp } = vi.hoisted(() => ({
|
||||
mockApp: { getLoginItemSettings: vi.fn() }
|
||||
const { mockApp, mockExecFile } = vi.hoisted(() => ({
|
||||
mockApp: { getLoginItemSettings: vi.fn() },
|
||||
mockExecFile: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
default: { app: mockApp }
|
||||
}));
|
||||
|
||||
vi.mock('node:child_process', () => ({
|
||||
execFile: mockExecFile
|
||||
}));
|
||||
|
||||
import { collectAutostartState } from '../../src/main/services/AutostartDiagnostic';
|
||||
|
||||
const ORIGINAL_PLATFORM = process.platform;
|
||||
|
||||
function setPlatform(p: NodeJS.Platform): void {
|
||||
Object.defineProperty(process, 'platform', { value: p, configurable: true });
|
||||
}
|
||||
|
||||
describe('AutostartDiagnostic — collectAutostartState', () => {
|
||||
beforeEach(() => {
|
||||
mockApp.getLoginItemSettings.mockReset();
|
||||
mockExecFile.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setPlatform(ORIGINAL_PLATFORM);
|
||||
});
|
||||
|
||||
it('returns withArgs / noArgs / execPath structure', async () => {
|
||||
setPlatform('darwin');
|
||||
mockApp.getLoginItemSettings
|
||||
.mockReturnValueOnce({ openAtLogin: true, executableWillLaunchAtLogin: true })
|
||||
.mockReturnValueOnce({ openAtLogin: false, executableWillLaunchAtLogin: true });
|
||||
@@ -28,6 +45,7 @@ describe('AutostartDiagnostic — collectAutostartState', () => {
|
||||
});
|
||||
|
||||
it('passes args=["--hidden"] for the first call, no args for the second', async () => {
|
||||
setPlatform('darwin');
|
||||
mockApp.getLoginItemSettings
|
||||
.mockReturnValueOnce({ openAtLogin: true, executableWillLaunchAtLogin: true })
|
||||
.mockReturnValueOnce({ openAtLogin: true, executableWillLaunchAtLogin: true });
|
||||
@@ -37,4 +55,42 @@ describe('AutostartDiagnostic — collectAutostartState', () => {
|
||||
expect(mockApp.getLoginItemSettings).toHaveBeenNthCalledWith(1, { args: ['--hidden'] });
|
||||
expect(mockApp.getLoginItemSettings).toHaveBeenNthCalledWith(2);
|
||||
});
|
||||
|
||||
it('non-win32: does not set registryPath/registryValue', async () => {
|
||||
setPlatform('darwin');
|
||||
mockApp.getLoginItemSettings.mockReturnValue({ openAtLogin: true, executableWillLaunchAtLogin: true });
|
||||
|
||||
const state = await collectAutostartState();
|
||||
|
||||
expect(state.registryPath).toBeUndefined();
|
||||
expect(state.registryValue).toBeUndefined();
|
||||
expect(mockExecFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Windows: returns registryPath + registryValue when reg.exe succeeds', async () => {
|
||||
setPlatform('win32');
|
||||
mockApp.getLoginItemSettings.mockReturnValue({ openAtLogin: true, executableWillLaunchAtLogin: true });
|
||||
mockExecFile.mockImplementation((_cmd: string, _args: string[], cb: (err: Error | null, stdout: string, stderr: string) => void) => {
|
||||
cb(null, '\r\nHKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\r\n Inkling REG_SZ "C:\\Users\\u\\Inkling.exe" --hidden\r\n', '');
|
||||
});
|
||||
|
||||
const state = await collectAutostartState();
|
||||
|
||||
expect(state.registryPath).toBe('HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\Inkling');
|
||||
expect(state.registryValue).toContain('Inkling.exe');
|
||||
expect(state.registryValue).toContain('--hidden');
|
||||
});
|
||||
|
||||
it('Windows: silent fallback on reg.exe error', async () => {
|
||||
setPlatform('win32');
|
||||
mockApp.getLoginItemSettings.mockReturnValue({ openAtLogin: true, executableWillLaunchAtLogin: true });
|
||||
mockExecFile.mockImplementation((_cmd: string, _args: string[], cb: (err: Error | null, stdout: string, stderr: string) => void) => {
|
||||
cb(new Error('not found'), '', '');
|
||||
});
|
||||
|
||||
const state = await collectAutostartState();
|
||||
|
||||
expect(state.registryPath).toBe('HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\Inkling');
|
||||
expect(state.registryValue).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user