mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
- Run `subminer stats -b` as a dedicated daemon process, independent from the overlay app - Stop Anki progress spinner before showing final `✓`/`x` mine result so it is not overwritten - Keep grammar/noise subtitle tokens hoverable while stripping annotation metadata
159 lines
4.8 KiB
TypeScript
159 lines
4.8 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { createRunStatsDaemonControlHandler } from './stats-daemon-control';
|
|
|
|
test('stats daemon control reuses live daemon and writes launcher response', async () => {
|
|
const calls: string[] = [];
|
|
const responses: Array<{ path: string; payload: { ok: boolean; url?: string; error?: string } }> =
|
|
[];
|
|
const handler = createRunStatsDaemonControlHandler({
|
|
statePath: '/tmp/stats-daemon.json',
|
|
readState: () => ({ pid: 4242, port: 5175, startedAtMs: 1 }),
|
|
removeState: () => {
|
|
calls.push('removeState');
|
|
},
|
|
isProcessAlive: (pid) => {
|
|
calls.push(`isProcessAlive:${pid}`);
|
|
return true;
|
|
},
|
|
resolveUrl: (state) => `http://127.0.0.1:${state.port}`,
|
|
spawnDaemon: async () => {
|
|
calls.push('spawnDaemon');
|
|
return 1;
|
|
},
|
|
waitForDaemonResponse: async () => {
|
|
calls.push('waitForDaemonResponse');
|
|
return { ok: true, url: 'http://127.0.0.1:5175' };
|
|
},
|
|
openExternal: async (url) => {
|
|
calls.push(`openExternal:${url}`);
|
|
},
|
|
writeResponse: (responsePath, payload) => {
|
|
responses.push({ path: responsePath, payload });
|
|
},
|
|
killProcess: () => {
|
|
calls.push('killProcess');
|
|
},
|
|
sleep: async () => {},
|
|
});
|
|
|
|
const exitCode = await handler({
|
|
action: 'start',
|
|
responsePath: '/tmp/response.json',
|
|
openBrowser: true,
|
|
daemonScriptPath: '/tmp/stats-daemon-runner.js',
|
|
userDataPath: '/tmp/SubMiner',
|
|
});
|
|
|
|
assert.equal(exitCode, 0);
|
|
assert.deepEqual(calls, ['isProcessAlive:4242', 'openExternal:http://127.0.0.1:5175']);
|
|
assert.deepEqual(responses, [
|
|
{
|
|
path: '/tmp/response.json',
|
|
payload: { ok: true, url: 'http://127.0.0.1:5175' },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('stats daemon control clears stale state, starts daemon, and waits for response', async () => {
|
|
const calls: string[] = [];
|
|
const handler = createRunStatsDaemonControlHandler({
|
|
statePath: '/tmp/stats-daemon.json',
|
|
readState: () => ({ pid: 4242, port: 5175, startedAtMs: 1 }),
|
|
removeState: () => {
|
|
calls.push('removeState');
|
|
},
|
|
isProcessAlive: (pid) => {
|
|
calls.push(`isProcessAlive:${pid}`);
|
|
return false;
|
|
},
|
|
resolveUrl: (state) => `http://127.0.0.1:${state.port}`,
|
|
spawnDaemon: async (options) => {
|
|
calls.push(`spawnDaemon:${options.scriptPath}:${options.responsePath}:${options.userDataPath}`);
|
|
return 999;
|
|
},
|
|
waitForDaemonResponse: async (responsePath) => {
|
|
calls.push(`waitForDaemonResponse:${responsePath}`);
|
|
return { ok: true, url: 'http://127.0.0.1:5175' };
|
|
},
|
|
openExternal: async (url) => {
|
|
calls.push(`openExternal:${url}`);
|
|
},
|
|
writeResponse: () => {
|
|
calls.push('writeResponse');
|
|
},
|
|
killProcess: () => {
|
|
calls.push('killProcess');
|
|
},
|
|
sleep: async () => {},
|
|
});
|
|
|
|
const exitCode = await handler({
|
|
action: 'start',
|
|
responsePath: '/tmp/response.json',
|
|
openBrowser: false,
|
|
daemonScriptPath: '/tmp/stats-daemon-runner.js',
|
|
userDataPath: '/tmp/SubMiner',
|
|
});
|
|
|
|
assert.equal(exitCode, 0);
|
|
assert.deepEqual(calls, [
|
|
'isProcessAlive:4242',
|
|
'removeState',
|
|
'spawnDaemon:/tmp/stats-daemon-runner.js:/tmp/response.json:/tmp/SubMiner',
|
|
'waitForDaemonResponse:/tmp/response.json',
|
|
]);
|
|
});
|
|
|
|
test('stats daemon control stops live daemon and treats stale state as success', async () => {
|
|
const responses: Array<{ path: string; payload: { ok: boolean; url?: string; error?: string } }> =
|
|
[];
|
|
const calls: string[] = [];
|
|
let aliveChecks = 0;
|
|
const handler = createRunStatsDaemonControlHandler({
|
|
statePath: '/tmp/stats-daemon.json',
|
|
readState: () => ({ pid: 4242, port: 5175, startedAtMs: 1 }),
|
|
removeState: () => {
|
|
calls.push('removeState');
|
|
},
|
|
isProcessAlive: (pid) => {
|
|
aliveChecks += 1;
|
|
calls.push(`isProcessAlive:${pid}:${aliveChecks}`);
|
|
return aliveChecks === 1;
|
|
},
|
|
resolveUrl: (state) => `http://127.0.0.1:${state.port}`,
|
|
spawnDaemon: async () => 1,
|
|
waitForDaemonResponse: async () => ({ ok: true, url: 'http://127.0.0.1:5175' }),
|
|
openExternal: async () => {},
|
|
writeResponse: (responsePath, payload) => {
|
|
responses.push({ path: responsePath, payload });
|
|
},
|
|
killProcess: (pid, signal) => {
|
|
calls.push(`killProcess:${pid}:${signal}`);
|
|
},
|
|
sleep: async () => {},
|
|
});
|
|
|
|
const exitCode = await handler({
|
|
action: 'stop',
|
|
responsePath: '/tmp/response.json',
|
|
openBrowser: false,
|
|
daemonScriptPath: '/tmp/stats-daemon-runner.js',
|
|
userDataPath: '/tmp/SubMiner',
|
|
});
|
|
|
|
assert.equal(exitCode, 0);
|
|
assert.deepEqual(calls, [
|
|
'isProcessAlive:4242:1',
|
|
'killProcess:4242:SIGTERM',
|
|
'isProcessAlive:4242:2',
|
|
'removeState',
|
|
]);
|
|
assert.deepEqual(responses, [
|
|
{
|
|
path: '/tmp/response.json',
|
|
payload: { ok: true },
|
|
},
|
|
]);
|
|
});
|