mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 06:22:45 -08:00
refactor(main): modularize runtime and harden anilist setup flow
This commit is contained in:
119
src/main/runtime/startup-config.test.ts
Normal file
119
src/main/runtime/startup-config.test.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
createCriticalConfigErrorHandler,
|
||||
createReloadConfigHandler,
|
||||
} from './startup-config';
|
||||
|
||||
test('createReloadConfigHandler runs success flow with warnings', async () => {
|
||||
const calls: string[] = [];
|
||||
const refreshCalls: { force: boolean }[] = [];
|
||||
|
||||
const reloadConfig = createReloadConfigHandler({
|
||||
reloadConfigStrict: () => ({
|
||||
ok: true,
|
||||
path: '/tmp/config.jsonc',
|
||||
warnings: [
|
||||
{
|
||||
path: 'ankiConnect.pollingRate',
|
||||
message: 'must be >= 50',
|
||||
value: 10,
|
||||
fallback: 250,
|
||||
},
|
||||
],
|
||||
}),
|
||||
logInfo: (message) => calls.push(`info:${message}`),
|
||||
logWarning: (message) => calls.push(`warn:${message}`),
|
||||
showDesktopNotification: (title, options) => calls.push(`notify:${title}:${options.body}`),
|
||||
startConfigHotReload: () => calls.push('hotReload:start'),
|
||||
refreshAnilistClientSecretState: async (options) => {
|
||||
refreshCalls.push(options);
|
||||
},
|
||||
failHandlers: {
|
||||
logError: (details) => calls.push(`error:${details}`),
|
||||
showErrorBox: (title, details) => calls.push(`dialog:${title}:${details}`),
|
||||
quit: () => calls.push('quit'),
|
||||
},
|
||||
});
|
||||
|
||||
reloadConfig();
|
||||
await Promise.resolve();
|
||||
|
||||
assert.ok(calls.some((entry) => entry.startsWith('info:Using config file: /tmp/config.jsonc')));
|
||||
assert.ok(calls.some((entry) => entry.startsWith('warn:[config] Validation found 1 issue(s)')));
|
||||
assert.ok(
|
||||
calls.some((entry) =>
|
||||
entry.includes('notify:SubMiner:1 config validation issue(s) detected.'),
|
||||
),
|
||||
);
|
||||
assert.ok(calls.some((entry) => entry.includes('1. ankiConnect.pollingRate: must be >= 50')));
|
||||
assert.ok(calls.includes('hotReload:start'));
|
||||
assert.deepEqual(refreshCalls, [{ force: true }]);
|
||||
});
|
||||
|
||||
test('createReloadConfigHandler fails startup for parse errors', () => {
|
||||
const calls: string[] = [];
|
||||
const previousExitCode = process.exitCode;
|
||||
process.exitCode = 0;
|
||||
|
||||
const reloadConfig = createReloadConfigHandler({
|
||||
reloadConfigStrict: () => ({
|
||||
ok: false,
|
||||
path: '/tmp/config.jsonc',
|
||||
error: 'unexpected token',
|
||||
}),
|
||||
logInfo: (message) => calls.push(`info:${message}`),
|
||||
logWarning: (message) => calls.push(`warn:${message}`),
|
||||
showDesktopNotification: (title, options) => calls.push(`notify:${title}:${options.body}`),
|
||||
startConfigHotReload: () => calls.push('hotReload:start'),
|
||||
refreshAnilistClientSecretState: async () => {
|
||||
calls.push('refresh');
|
||||
},
|
||||
failHandlers: {
|
||||
logError: (details) => calls.push(`error:${details}`),
|
||||
showErrorBox: (title, details) => calls.push(`dialog:${title}:${details}`),
|
||||
quit: () => calls.push('quit'),
|
||||
},
|
||||
});
|
||||
|
||||
assert.throws(() => reloadConfig(), /Failed to parse config file at:/);
|
||||
assert.equal(process.exitCode, 1);
|
||||
assert.ok(calls.some((entry) => entry.startsWith('error:Failed to parse config file at:')));
|
||||
assert.ok(
|
||||
calls.some((entry) =>
|
||||
entry.startsWith('dialog:SubMiner config parse error:Failed to parse config file at:'),
|
||||
),
|
||||
);
|
||||
assert.ok(calls.includes('quit'));
|
||||
assert.equal(calls.includes('hotReload:start'), false);
|
||||
|
||||
process.exitCode = previousExitCode;
|
||||
});
|
||||
|
||||
test('createCriticalConfigErrorHandler formats and fails', () => {
|
||||
const calls: string[] = [];
|
||||
const previousExitCode = process.exitCode;
|
||||
process.exitCode = 0;
|
||||
|
||||
const handleCriticalErrors = createCriticalConfigErrorHandler({
|
||||
getConfigPath: () => '/tmp/config.jsonc',
|
||||
failHandlers: {
|
||||
logError: (details) => calls.push(`error:${details}`),
|
||||
showErrorBox: (title, details) => calls.push(`dialog:${title}:${details}`),
|
||||
quit: () => calls.push('quit'),
|
||||
},
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() => handleCriticalErrors(['foo invalid', 'bar invalid']),
|
||||
/Critical config validation failed/,
|
||||
);
|
||||
|
||||
assert.equal(process.exitCode, 1);
|
||||
assert.ok(calls.some((entry) => entry.includes('/tmp/config.jsonc')));
|
||||
assert.ok(calls.some((entry) => entry.includes('1. foo invalid')));
|
||||
assert.ok(calls.some((entry) => entry.includes('2. bar invalid')));
|
||||
assert.ok(calls.includes('quit'));
|
||||
|
||||
process.exitCode = previousExitCode;
|
||||
});
|
||||
Reference in New Issue
Block a user