mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
require('ts-node/register/transpile-only');
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const fs = require('node:fs/promises');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { afterEach, test } = require('node:test');
|
||||
const { DataTypes, Sequelize } = require('sequelize');
|
||||
const {
|
||||
defineSchemaMigrationModel,
|
||||
} = require('../../back/data/schemaMigration');
|
||||
const {
|
||||
RUN_ATTEMPT_TABLE,
|
||||
RUN_EVENT_TABLE,
|
||||
RUN_TABLE,
|
||||
} = require('../../back/migrations/0002-run-schema');
|
||||
const {
|
||||
RUN_CANCELLATION_DISPATCH_TABLE,
|
||||
} = require('../../back/migrations/0005-run-cancellation-dispatch');
|
||||
const { migrations } = require('../../back/migrations');
|
||||
const { runMigrations } = require('../../back/migrations/runner');
|
||||
const {
|
||||
parseArguments: parseLegacySchemaAuditArguments,
|
||||
} = require('../../scripts/ql3-schema-audit.cjs');
|
||||
|
||||
let nodeSqlite;
|
||||
try {
|
||||
nodeSqlite = require('node:sqlite');
|
||||
} catch {
|
||||
nodeSqlite = undefined;
|
||||
}
|
||||
|
||||
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
||||
const temporaryDirectories = [];
|
||||
|
||||
test('legacy schema audit never selects a live database implicitly', () => {
|
||||
assert.throws(
|
||||
() => parseLegacySchemaAuditArguments([]),
|
||||
/requires an explicit --database path/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
parseLegacySchemaAuditArguments([
|
||||
'--database=/tmp/a.sqlite',
|
||||
'--database=/tmp/b.sqlite',
|
||||
]),
|
||||
/must not be duplicated/,
|
||||
);
|
||||
});
|
||||
|
||||
async function createMigratedDatabase() {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql3-node-sqlite-'));
|
||||
temporaryDirectories.push(root);
|
||||
const storage = path.join(root, 'database.sqlite');
|
||||
const database = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage,
|
||||
logging: false,
|
||||
});
|
||||
const queryInterface = database.getQueryInterface();
|
||||
for (const table of ['CrontabViews', 'Subscriptions', 'Crontabs', 'Envs']) {
|
||||
await queryInterface.createTable(table, {
|
||||
id: { type: DataTypes.INTEGER, primaryKey: true },
|
||||
});
|
||||
}
|
||||
await queryInterface.createTable('RunningInstances', {
|
||||
id: { type: DataTypes.INTEGER, primaryKey: true },
|
||||
started_at: { type: DataTypes.INTEGER, allowNull: false },
|
||||
});
|
||||
await runMigrations({
|
||||
database,
|
||||
migrationModel: defineSchemaMigrationModel(database),
|
||||
migrations,
|
||||
logger: { info() {} },
|
||||
});
|
||||
await database.close();
|
||||
return storage;
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(
|
||||
temporaryDirectories
|
||||
.splice(0)
|
||||
.map((directory) => fs.rm(directory, { recursive: true, force: true })),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'Node 24 opens the Sequelize migration chain with defensive node:sqlite options',
|
||||
{ skip: nodeMajor < 24 },
|
||||
async () => {
|
||||
assert.equal(typeof nodeSqlite?.DatabaseSync, 'function');
|
||||
const storage = await createMigratedDatabase();
|
||||
const database = new nodeSqlite.DatabaseSync(storage, {
|
||||
allowExtension: false,
|
||||
allowUnknownNamedParameters: false,
|
||||
defensive: true,
|
||||
enableDoubleQuotedStringLiterals: false,
|
||||
enableForeignKeyConstraints: true,
|
||||
readOnly: true,
|
||||
timeout: 1_000,
|
||||
});
|
||||
|
||||
try {
|
||||
assert.equal(
|
||||
database.prepare('PRAGMA integrity_check').get().integrity_check,
|
||||
'ok',
|
||||
);
|
||||
const tables = database
|
||||
.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name",
|
||||
)
|
||||
.all()
|
||||
.map((row) => row.name);
|
||||
for (const table of [
|
||||
RUN_TABLE,
|
||||
RUN_ATTEMPT_TABLE,
|
||||
RUN_EVENT_TABLE,
|
||||
RUN_CANCELLATION_DISPATCH_TABLE,
|
||||
'SchemaMigrations',
|
||||
]) {
|
||||
assert.ok(tables.includes(table), `missing ${table}`);
|
||||
}
|
||||
|
||||
const runColumns = database
|
||||
.prepare(`PRAGMA table_info(${RUN_TABLE})`)
|
||||
.all()
|
||||
.map((row) => row.name);
|
||||
for (const column of [
|
||||
'id',
|
||||
'status',
|
||||
'version',
|
||||
'event_sequence',
|
||||
'cancel_requested_at_ms',
|
||||
]) {
|
||||
assert.ok(runColumns.includes(column), `missing Runs.${column}`);
|
||||
}
|
||||
const attemptColumns = database
|
||||
.prepare(`PRAGMA table_info(${RUN_ATTEMPT_TABLE})`)
|
||||
.all()
|
||||
.map((row) => row.name);
|
||||
assert.ok(
|
||||
attemptColumns.includes('deadline_at_ms'),
|
||||
'missing RunAttempts.deadline_at_ms',
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => database.exec('CREATE TABLE must_not_write(id INTEGER)'),
|
||||
/read.?only/i,
|
||||
);
|
||||
} finally {
|
||||
database.close();
|
||||
}
|
||||
|
||||
const audit = JSON.parse(
|
||||
execFileSync(
|
||||
process.execPath,
|
||||
[
|
||||
path.resolve(__dirname, '../../scripts/ql3-schema-audit.cjs'),
|
||||
'--json',
|
||||
`--database=${storage}`,
|
||||
],
|
||||
{ encoding: 'utf8' },
|
||||
),
|
||||
);
|
||||
assert.equal(audit.compatible, true);
|
||||
assert.equal(audit.driftDetected, false);
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user