mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
275 lines
7.9 KiB
JavaScript
275 lines
7.9 KiB
JavaScript
require('ts-node/register/transpile-only');
|
|
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { after, test } = require('node:test');
|
|
const { Sequelize } = require('sequelize');
|
|
const { Container } = require('typedi');
|
|
|
|
const dataDirectory = fs.mkdtempSync(
|
|
path.join(os.tmpdir(), 'ql3-legacy-boot-shadow-'),
|
|
);
|
|
fs.mkdirSync(path.join(dataDirectory, 'db'), { recursive: true });
|
|
process.env.QL_DATA_DIR = dataDirectory;
|
|
|
|
const { CrontabModel, CrontabStatus } = require('../../back/data/cron');
|
|
const {
|
|
defineSchemaMigrationModel,
|
|
} = require('../../back/data/schemaMigration');
|
|
const CronService = require('../../back/services/cron').default;
|
|
const { runCrons } = require('../../back/schedule/api');
|
|
const { runSchemaMigration } = require('../../back/migrations/0002-run-schema');
|
|
const {
|
|
runCancellationRequestMigration,
|
|
} = require('../../back/migrations/0004-run-cancellation-request');
|
|
const {
|
|
runAttemptDeadlineMigration,
|
|
} = require('../../back/migrations/0006-run-attempt-deadline');
|
|
const { runMigrations } = require('../../back/migrations/runner');
|
|
const {
|
|
LegacySequelizeRunRepository,
|
|
} = require('../../back/runtime/adapters/legacy-sequelize/runRepository');
|
|
const {
|
|
LegacyShadowRunObserver,
|
|
} = require('../../back/runtime/application/legacyShadowRunObserver');
|
|
const {
|
|
LegacyShadowRunWriter,
|
|
} = require('../../back/runtime/application/legacyShadowRunWriter');
|
|
const {
|
|
installLegacyExecutionObserver,
|
|
} = require('../../back/runtime/compatibility/legacyExecutionBridge');
|
|
const {
|
|
shadowOnlyRollout,
|
|
} = require('../../back/runtime/domain/runtimeRollout');
|
|
|
|
after(() => {
|
|
fs.rmSync(dataDirectory, { recursive: true, force: true });
|
|
});
|
|
|
|
function logger() {
|
|
return {
|
|
info() {},
|
|
warn() {},
|
|
error() {},
|
|
};
|
|
}
|
|
|
|
function successfulCommand() {
|
|
return `${JSON.stringify(process.execPath)} -e "process.exit(0)"`;
|
|
}
|
|
|
|
function cron(overrides = {}) {
|
|
return {
|
|
id: 71,
|
|
name: 'boot probe',
|
|
command: 'credential=must-not-persist',
|
|
schedule: '@boot',
|
|
status: CrontabStatus.queued,
|
|
isDisabled: 0,
|
|
extra_schedules: [],
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
async function withStubbedCronUpdate(work) {
|
|
const original = CrontabModel.update;
|
|
const updates = [];
|
|
CrontabModel.update = async (...args) => {
|
|
updates.push(args);
|
|
return [1];
|
|
};
|
|
try {
|
|
return await work(updates);
|
|
} finally {
|
|
CrontabModel.update = original;
|
|
}
|
|
}
|
|
|
|
test('bootTask selects only enabled @boot entries and fixes their origin before dispatch', async () => {
|
|
await withStubbedCronUpdate(async (updates) => {
|
|
const service = new CronService(logger());
|
|
const dispatched = [];
|
|
service.crontabs = async () => ({
|
|
data: [
|
|
cron({ id: 71 }),
|
|
cron({ id: 72, isDisabled: 1 }),
|
|
cron({ id: 73, schedule: '@once' }),
|
|
cron({ id: 74, schedule: '0 * * * *' }),
|
|
],
|
|
total: 4,
|
|
});
|
|
service.runSingle = (...args) => {
|
|
dispatched.push(args);
|
|
};
|
|
|
|
await service.bootTask();
|
|
|
|
assert.deepEqual(dispatched, [[71, 'boot']]);
|
|
assert.equal(updates.length, 1);
|
|
assert.deepEqual(updates[0][0], { status: CrontabStatus.queued });
|
|
assert.deepEqual(updates[0][1], { where: { id: [71] } });
|
|
});
|
|
});
|
|
|
|
test('persists one legacy-owned terminal Run for the real boot child path', async () => {
|
|
const database = new Sequelize({
|
|
dialect: 'sqlite',
|
|
storage: ':memory:',
|
|
logging: false,
|
|
});
|
|
const observations = [];
|
|
const failures = [];
|
|
let idSequence = 900;
|
|
|
|
try {
|
|
await runMigrations({
|
|
database,
|
|
migrationModel: defineSchemaMigrationModel(database),
|
|
migrations: [
|
|
runSchemaMigration,
|
|
runCancellationRequestMigration,
|
|
runAttemptDeadlineMigration,
|
|
],
|
|
logger: { info() {} },
|
|
});
|
|
const repository = new LegacySequelizeRunRepository(database);
|
|
const writer = new LegacyShadowRunWriter(repository, () => {
|
|
idSequence += 1;
|
|
return `019f7100-0000-7000-8000-${String(idSequence).padStart(12, '0')}`;
|
|
});
|
|
const delegate = new LegacyShadowRunObserver(
|
|
shadowOnlyRollout(['boot']),
|
|
writer,
|
|
{ failure: (failure) => failures.push(failure) },
|
|
);
|
|
const restore = installLegacyExecutionObserver(
|
|
{
|
|
begin(fact) {
|
|
const observation = delegate.begin(fact);
|
|
observations.push({ fact, observation });
|
|
return observation;
|
|
},
|
|
},
|
|
['boot'],
|
|
);
|
|
|
|
try {
|
|
await withStubbedCronUpdate(async () => {
|
|
const service = new CronService(logger());
|
|
service.getDb = async () => cron();
|
|
service.makeCommand = () => successfulCommand();
|
|
|
|
const result = await service.runSingle(71, 'boot');
|
|
assert.equal(result.code, 0);
|
|
assert.equal(observations.length, 1);
|
|
await observations[0].observation.settled();
|
|
});
|
|
} finally {
|
|
restore();
|
|
}
|
|
|
|
const runId = '019f7100-0000-7000-8000-000000000901';
|
|
const attemptId = '019f7100-0000-7000-8000-000000000902';
|
|
const run = await repository.findRunById(runId);
|
|
const attempt = await repository.findAttemptById(attemptId);
|
|
const events = await repository.listEvents(runId);
|
|
const fact = observations[0].fact;
|
|
|
|
assert.deepEqual(failures, []);
|
|
assert.equal(fact.origin, 'boot');
|
|
assert.equal(fact.triggerType, 'boot');
|
|
assert.equal(fact.triggeredBy, 'legacy:boot');
|
|
assert.equal(fact.legacyCronId, 71);
|
|
assert.match(fact.taskRevision, /^sha256:[0-9a-f]{64}$/u);
|
|
assert.equal(run.executionOrigin, 'boot');
|
|
assert.equal(run.executionOwner, 'legacy');
|
|
assert.equal(run.status, 'succeeded');
|
|
assert.equal(attempt.status, 'succeeded');
|
|
assert.equal(attempt.executorType, 'legacy_local');
|
|
assert.equal(attempt.pid > 0, true);
|
|
assert.equal(attempt.exitCode, 0);
|
|
assert.deepEqual(
|
|
events.map((event) => event.type),
|
|
[
|
|
'run.created',
|
|
'run.queued',
|
|
'run.dispatching',
|
|
'attempt.starting',
|
|
'attempt.running',
|
|
'run.running',
|
|
'attempt.succeeded',
|
|
'run.succeeded',
|
|
],
|
|
);
|
|
assert.equal(
|
|
JSON.stringify({ run, attempt, events }).includes('credential='),
|
|
false,
|
|
);
|
|
} finally {
|
|
await database.close();
|
|
}
|
|
});
|
|
|
|
test('@once remains a manual trigger instead of becoming an inferred execution origin', async () => {
|
|
const facts = [];
|
|
const restore = installLegacyExecutionObserver(
|
|
{
|
|
begin(fact) {
|
|
facts.push(fact);
|
|
return {
|
|
spawned() {},
|
|
running() {},
|
|
startFailed() {},
|
|
exited() {},
|
|
cancelled() {},
|
|
};
|
|
},
|
|
},
|
|
['manual', 'boot'],
|
|
);
|
|
|
|
try {
|
|
await withStubbedCronUpdate(async () => {
|
|
const service = new CronService(logger());
|
|
service.getDb = async () => cron({ id: 81, schedule: '@once' });
|
|
service.makeCommand = () => successfulCommand();
|
|
|
|
const result = await service.runSingle(81);
|
|
assert.equal(result.code, 0);
|
|
});
|
|
} finally {
|
|
restore();
|
|
}
|
|
|
|
assert.equal(facts.length, 1);
|
|
assert.equal(facts[0].origin, 'manual');
|
|
assert.equal(facts[0].triggerType, 'manual');
|
|
assert.equal(facts[0].triggeredBy, 'legacy:manual');
|
|
});
|
|
|
|
test('the legacy gRPC run endpoint delegates without inventing a grpc execution origin', async () => {
|
|
await withStubbedCronUpdate(async () => {
|
|
const service = new CronService(logger());
|
|
const dispatched = [];
|
|
service.runSingle = (...args) => {
|
|
dispatched.push(args);
|
|
};
|
|
Container.set(CronService, service);
|
|
|
|
try {
|
|
const response = await new Promise((resolve, reject) => {
|
|
runCrons({ request: { ids: [91] } }, (error, value) =>
|
|
error ? reject(error) : resolve(value),
|
|
);
|
|
});
|
|
assert.deepEqual(response, { code: 200 });
|
|
assert.deepEqual(dispatched, [[91]]);
|
|
assert.equal(dispatched[0].includes('grpc'), false);
|
|
} finally {
|
|
Container.remove(CronService);
|
|
}
|
|
});
|
|
});
|