Files
qinglong/test/back/clusterControlRuntimeBootstrap.test.cjs
T

302 lines
8.6 KiB
JavaScript

require('ts-node/register/transpile-only');
const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
postgresqlMainMigrationStream,
} = require('../../back/migrations/postgresql');
const {
postgresqlControlSchemaContract,
} = require('../../back/migrations/postgresql/schemaContract');
const {
PostgresSchemaReadinessError,
} = require('../../back/migrations/postgresql/schemaReadiness');
const {
PostgresRunRepository,
} = require('../../back/runtime/adapters/postgresql/runRepository');
const {
bootstrapClusterControlRuntime,
} = require('../../back/runtime/adapters/postgresql/clusterControlRuntimeBootstrap');
function migrationHistory() {
return postgresqlMainMigrationStream.migrations.map((migration, index) => ({
streamId: 'postgresql-main',
dialect: 'postgresql',
migrationId: migration.id,
checksum: migration.checksum,
appliedAtMs: index + 1,
}));
}
function runtimePrivileges() {
const privileges = {
schema_migrations: [true, false, false, false],
schema_capabilities: [true, false, false, false],
runs: [true, true, true, false],
run_attempts: [true, true, true, false],
run_events: [true, true, false, false],
run_retry_policies: [true, true, true, false],
};
return Object.entries(privileges).map(
([
tableName,
[selectAllowed, insertAllowed, updateAllowed, deleteAllowed],
]) => ({
tableName,
selectAllowed,
insertAllowed,
updateAllowed,
deleteAllowed,
isOwner: false,
}),
);
}
function databaseResource(events, overrides = {}) {
const contract = postgresqlControlSchemaContract;
const pool = {
async query(text) {
events.push(
`query:${
events.filter((event) => event.startsWith('query:')).length + 1
}`,
);
if (text.includes("current_setting('server_version_num')")) {
return {
rows: [
{
serverVersionNum: overrides.serverVersionNum ?? '160014',
currentUser: 'ql3_runtime',
inRecovery: false,
transactionReadOnly: 'off',
},
],
};
}
if (text.includes('FROM "ql3"."schema_migrations"')) {
return { rows: migrationHistory() };
}
if (text.includes('FROM "ql3"."schema_capabilities"')) {
return {
rows: [
{
contractName: contract.contractName,
contractVersion: contract.contractVersion,
migrationId: contract.migrationId,
capabilities: contract.capabilities,
},
],
};
}
if (text.includes('FROM information_schema.columns')) {
return {
rows: contract.tables.flatMap((table) =>
table.columns.map((columnName) => ({
tableName: table.name,
columnName,
})),
),
};
}
if (text.includes('FROM pg_indexes')) {
return {
rows: contract.indexes.map((indexName) => ({ indexName })),
};
}
if (text.includes('FROM pg_constraint')) {
return {
rows: [
...contract.checks.map((constraintName) => ({
constraintName,
constraintType: 'check',
})),
...contract.foreignKeys.map((constraintName) => ({
constraintName,
constraintType: 'foreign_key',
})),
],
};
}
if (text.includes('FROM pg_catalog.pg_roles')) {
return {
rows: [
{
canLogin: true,
superuser: false,
createDatabase: false,
createRole: false,
replication: false,
bypassRowLevelSecurity: false,
databaseConnect: true,
},
],
};
}
if (text.includes('has_schema_privilege')) {
return { rows: [{ schemaUsage: true, schemaCreate: false }] };
}
if (text.includes('has_table_privilege')) {
return { rows: runtimePrivileges() };
}
throw new Error(`unexpected query: ${text}`);
},
async connect() {
throw new Error('Repository connections are not used during bootstrap');
},
};
return {
pool,
async close() {
events.push('close-database');
if (overrides.closeError) throw overrides.closeError;
},
};
}
function activationStack(
events,
recovery = { safe: true, remaining: 0, failed: 0 },
) {
return {
async reconcile() {
events.push('reconcile');
return recovery;
},
async startLifecycles() {
events.push('start-lifecycles');
return true;
},
installAdmission() {
events.push('install-admission');
return () => events.push('dispose-admission');
},
async stop() {
events.push('stop-stack');
return 'stopped';
},
};
}
function bootstrapOptions(events, overrides = {}) {
return {
enabled: true,
profile: 'cluster-control',
async openDatabase() {
events.push('open-database');
return databaseResource(events);
},
create({ evidence, runs }) {
events.push('create-stack');
assert.equal(evidence.contractVersion, 2);
assert.equal(runs instanceof PostgresRunRepository, true);
return activationStack(events);
},
audit(record) {
events.push(`audit:${record.state}`);
},
...overrides,
};
}
test('disabled and wrong-profile bootstrap never opens PostgreSQL', async () => {
const disabledEvents = [];
const disabled = await bootstrapClusterControlRuntime(
bootstrapOptions(disabledEvents, { enabled: false }),
);
assert.equal(disabled.status, 'disabled');
assert.deepEqual(disabledEvents, ['audit:disabled']);
const wrongProfileEvents = [];
await assert.rejects(
bootstrapClusterControlRuntime(
bootstrapOptions(wrongProfileEvents, { profile: 'edge' }),
),
/cannot activate cluster-control/,
);
assert.deepEqual(wrongProfileEvents, []);
});
test('readiness failure closes the database before returning the root error', async () => {
const events = [];
await assert.rejects(
bootstrapClusterControlRuntime(
bootstrapOptions(events, {
async openDatabase() {
events.push('open-database');
return databaseResource(events, { serverVersionNum: '150018' });
},
}),
),
(error) =>
error instanceof PostgresSchemaReadinessError &&
error.code === 'server_version_unsupported',
);
assert.equal(events.includes('create-stack'), false);
assert.deepEqual(events.slice(-2), ['audit:failed', 'close-database']);
});
test('opens once, assembles after readiness, and closes after stack shutdown', async () => {
const events = [];
const result = await bootstrapClusterControlRuntime(bootstrapOptions(events));
assert.equal(result.status, 'active');
assert.equal(events.filter((event) => event === 'open-database').length, 1);
assert.ok(events.indexOf('create-stack') > events.lastIndexOf('query:7'));
const first = result.stop();
assert.equal(first, result.stop());
assert.equal(await first, 'stopped');
assert.deepEqual(events.slice(-4), [
'dispose-admission',
'stop-stack',
'audit:stopped',
'close-database',
]);
});
test('unsafe recovery stops the stack and closes the database', async () => {
const events = [];
await assert.rejects(
bootstrapClusterControlRuntime(
bootstrapOptions(events, {
create({ runs }) {
events.push('create-stack');
assert.equal(runs instanceof PostgresRunRepository, true);
return activationStack(events, {
safe: false,
remaining: 1,
failed: 0,
});
},
}),
),
/did not converge safely/,
);
assert.equal(events.includes('install-admission'), false);
assert.deepEqual(events.slice(-3), [
'stop-stack',
'audit:failed',
'close-database',
]);
});
test('database close failure does not skip stack shutdown and remains idempotent', async () => {
const events = [];
const closeError = new Error('database close failed');
const result = await bootstrapClusterControlRuntime(
bootstrapOptions(events, {
async openDatabase() {
events.push('open-database');
return databaseResource(events, { closeError });
},
}),
);
const first = result.stop();
assert.equal(first, result.stop());
await assert.rejects(first, (error) => error === closeError);
assert.deepEqual(events.slice(-4), [
'dispose-admission',
'stop-stack',
'audit:stopped',
'close-database',
]);
});