mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-28 09:02:12 +08:00
159 lines
4.7 KiB
JavaScript
159 lines
4.7 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const load = require('../helpers/load-security-module.cjs');
|
|
|
|
const logger = { info() {}, error() {} };
|
|
|
|
function cronFixture(killTask) {
|
|
const events = [];
|
|
const { runCron } = load('back/shared/runCron.ts', {
|
|
'cross-spawn': {
|
|
spawn: () => {
|
|
events.push('spawn');
|
|
return {};
|
|
},
|
|
},
|
|
'./childProcess': {
|
|
observeChildProcess: () => ({ completed: Promise.resolve({ code: 0 }) }),
|
|
asError: (e) => e,
|
|
},
|
|
'./pLimit': {
|
|
runWithCronLimit: async (_, fn) => fn(),
|
|
removeQueuedCron: () => events.push('release'),
|
|
},
|
|
'../loaders/logger': logger,
|
|
'../config/util': { killTask },
|
|
'../data/cron': {
|
|
CrontabModel: {
|
|
findOne: async () => ({
|
|
pid: 100,
|
|
status: 0,
|
|
allow_multiple_instances: 0,
|
|
}),
|
|
update: async () => events.push('idle'),
|
|
},
|
|
CrontabStatus: { running: 0, idle: 1, queued: 3 },
|
|
},
|
|
'../data/runningInstance': {
|
|
RunningInstanceModel: { update: async () => events.push('stopped') },
|
|
InstanceStatus: { running: 0, stopped: 2 },
|
|
},
|
|
});
|
|
return { run: () => runCron('ignored', { id: '1' }), events };
|
|
}
|
|
|
|
test('single-instance replacement does not spawn or finalize when termination fails', async () => {
|
|
const f = cronFixture(async () => {
|
|
throw Error('EPERM');
|
|
});
|
|
await f.run();
|
|
assert.deepEqual(f.events, ['release']);
|
|
});
|
|
|
|
test('single-instance replacement waits for verified exit before spawning', async () => {
|
|
let finish, entered, verified;
|
|
const pending = new Promise((resolve) => {
|
|
entered = resolve;
|
|
});
|
|
const gate = new Promise((resolve) => {
|
|
finish = resolve;
|
|
});
|
|
const f = cronFixture(async (pid, wait) => {
|
|
assert.equal(pid, 100);
|
|
verified = wait;
|
|
entered();
|
|
await gate;
|
|
});
|
|
const running = f.run();
|
|
await pending;
|
|
assert.deepEqual(f.events, []);
|
|
finish();
|
|
await running;
|
|
assert.equal(verified, true);
|
|
assert.deepEqual(f.events, ['stopped', 'idle', 'spawn', 'release']);
|
|
});
|
|
|
|
function subscriptionFixture(killTask) {
|
|
const updates = [];
|
|
const Service = load('back/services/subscription.ts', {
|
|
'../config': {},
|
|
'../data/cron': {},
|
|
'../config/const': {},
|
|
'../data/subscription': {
|
|
SubscriptionModel: {
|
|
findAll: async () => [
|
|
{ id: 1, pid: 101 },
|
|
{ id: 2, pid: 102 },
|
|
],
|
|
update: async (values, query) => updates.push({ values, query }),
|
|
},
|
|
SubscriptionStatus: { idle: 1 },
|
|
},
|
|
'../config/util': { killTask },
|
|
'../config/subscription': {},
|
|
'../shared/i18n': {},
|
|
'../shared/pLimit': {},
|
|
'../shared/logReader': {},
|
|
'../shared/logStreamManager': {},
|
|
'./schedule': {},
|
|
'./sock': {},
|
|
'./sshKey': {},
|
|
'./cron': {},
|
|
}).default;
|
|
return { service: new Service(logger, {}, {}, {}, {}), updates };
|
|
}
|
|
|
|
test('batch subscription stop preserves failed items and reports failure while stopping others', async () => {
|
|
const calls = [];
|
|
const denied = Error('EPERM');
|
|
const f = subscriptionFixture(async (pid, wait) => {
|
|
calls.push([pid, wait]);
|
|
if (pid === 101) throw denied;
|
|
});
|
|
await assert.rejects(f.service.stop([1, 2]), (e) => e === denied);
|
|
assert.deepEqual(calls, [
|
|
[101, true],
|
|
[102, true],
|
|
]);
|
|
assert.equal(f.updates.length, 1);
|
|
assert.equal(f.updates[0].query.where.id, 2);
|
|
assert.equal(f.updates[0].values.status, 1);
|
|
});
|
|
|
|
function scriptFixture(killTask, pid = 100) {
|
|
const Service = load('back/services/script.ts', {
|
|
'../config': { scriptPath: '/scripts' },
|
|
'../config/const': { TASK_COMMAND: 'task' },
|
|
'../config/util': { killTask, getPid: async () => pid },
|
|
'../shared/pLimit': { removeQueuedCron() {} },
|
|
'./sock': {},
|
|
'./cron': {},
|
|
'./schedule': {},
|
|
'../shared/fileAccess': {},
|
|
}).default;
|
|
return new Service(logger, {}, {}, {});
|
|
}
|
|
|
|
test('script stop propagates failure instead of returning success', async () => {
|
|
const denied = Error('EPERM');
|
|
const service = scriptFixture(async () => {
|
|
throw denied;
|
|
});
|
|
await assert.rejects(
|
|
service.stopScript('/scripts/test.js', 100),
|
|
(e) => e === denied,
|
|
);
|
|
});
|
|
|
|
test('script stop verifies exit and treats an absent queued process as already stopped', async () => {
|
|
const calls = [];
|
|
const service = scriptFixture(async (...args) => calls.push(args), undefined);
|
|
assert.equal((await service.stopScript('/scripts/test.js', 100)).code, 200);
|
|
assert.deepEqual(calls, [[100, true]]);
|
|
const absent = scriptFixture(async () => assert.fail('no PID to stop'), null);
|
|
assert.equal(
|
|
(await absent.stopScript('/scripts/test.js', undefined)).code,
|
|
200,
|
|
);
|
|
});
|