mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:29:13 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,880 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { test } = require('node:test');
|
||||
const {
|
||||
TaskDefinitionConflictError,
|
||||
TaskDefinitionUnavailableError,
|
||||
createTaskDefinitionRecord,
|
||||
} = require('@qinglong/runtime-core/task-definition');
|
||||
const {
|
||||
createBuiltInTaskSpecSemanticRegistry,
|
||||
} = require('@qinglong/runtime-core/task-spec-semantic');
|
||||
const {
|
||||
TriggerConflictError,
|
||||
TriggerUnavailableError,
|
||||
createBuiltInTriggerSpecSemanticRegistry,
|
||||
createTriggerRecord,
|
||||
} = require('@qinglong/runtime-core/trigger');
|
||||
const {
|
||||
compileClusterCommandTaskDefinition,
|
||||
} = require('@qinglong/runtime-core/cluster-execution-revision');
|
||||
const {
|
||||
TaskDefinitionAdministrationAuthorizationFenceConflictError,
|
||||
TaskDefinitionAdministrationMutationConflictError,
|
||||
TaskDefinitionAdministrationReadConflictError,
|
||||
} = require('@qinglong/runtime-core/task-definition-administration');
|
||||
const {
|
||||
TriggerAdministrationAuthorizationFenceConflictError,
|
||||
TriggerAdministrationReadConflictError,
|
||||
} = require('@qinglong/runtime-core/trigger-administration');
|
||||
const {
|
||||
PostgresTaskDefinitionRepository,
|
||||
PostgresTaskDefinitionSource,
|
||||
PostgresTaskExecutionRevisionSource,
|
||||
} = require('../dist/automation/taskDefinitionRepository');
|
||||
const {
|
||||
PostgresTriggerRepository,
|
||||
PostgresTriggerSource,
|
||||
} = require('../dist/scheduling/triggerRepository');
|
||||
const {
|
||||
PostgresTaskDefinitionAdministrationRepository,
|
||||
PostgresTriggerAdministrationRepository,
|
||||
} = require('../dist/automation/automationAdministrationRepository');
|
||||
|
||||
const TASK_COMMAND = Object.freeze({
|
||||
projectId: 'default',
|
||||
taskId: 'task-00001',
|
||||
expectedRevision: null,
|
||||
mutationId: '123e4567-e89b-42d3-a456-426614174001',
|
||||
name: 'Task 1',
|
||||
kind: 'command',
|
||||
spec: Object.freeze({
|
||||
schema: 'qinglong/command@v1',
|
||||
config: Object.freeze({
|
||||
command: Object.freeze({
|
||||
kind: 'argv',
|
||||
file: '/bin/echo',
|
||||
args: Object.freeze(['1']),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
labels: Object.freeze({ source: 'postgres-test' }),
|
||||
enabled: true,
|
||||
occurredAtMs: 101,
|
||||
});
|
||||
|
||||
const NORMALIZED_TASK_COMMAND = Object.freeze({
|
||||
...TASK_COMMAND,
|
||||
spec: createBuiltInTaskSpecSemanticRegistry().normalize({
|
||||
projectId: TASK_COMMAND.projectId,
|
||||
taskId: TASK_COMMAND.taskId,
|
||||
kind: TASK_COMMAND.kind,
|
||||
spec: TASK_COMMAND.spec,
|
||||
}),
|
||||
});
|
||||
const TASK = createTaskDefinitionRecord(
|
||||
NORMALIZED_TASK_COMMAND,
|
||||
TASK_COMMAND.occurredAtMs,
|
||||
);
|
||||
const DRIFT_TASK = createTaskDefinitionRecord(
|
||||
Object.freeze({ ...NORMALIZED_TASK_COMMAND, name: 'drift' }),
|
||||
TASK_COMMAND.occurredAtMs,
|
||||
);
|
||||
const TASK_EXECUTION = compileClusterCommandTaskDefinition(
|
||||
TASK,
|
||||
createBuiltInTaskSpecSemanticRegistry(),
|
||||
);
|
||||
|
||||
const TRIGGER_COMMAND = Object.freeze({
|
||||
projectId: 'default',
|
||||
triggerId: 'trigger-00001',
|
||||
expectedRevision: null,
|
||||
mutationId: '123e4567-e89b-42d3-a456-426614174002',
|
||||
taskId: TASK.taskId,
|
||||
taskRevision: TASK.revision,
|
||||
taskContentDigest: TASK.contentDigest,
|
||||
spec: Object.freeze({
|
||||
schema: 'qinglong/cron@v1',
|
||||
config: Object.freeze({
|
||||
expression: '*/5 * * * *',
|
||||
timezone: 'Etc/UTC',
|
||||
misfirePolicy: 'skip',
|
||||
}),
|
||||
}),
|
||||
enabled: true,
|
||||
occurredAtMs: 201,
|
||||
});
|
||||
const NORMALIZED_TRIGGER_COMMAND = Object.freeze({
|
||||
...TRIGGER_COMMAND,
|
||||
spec: createBuiltInTriggerSpecSemanticRegistry().normalize({
|
||||
projectId: TRIGGER_COMMAND.projectId,
|
||||
triggerId: TRIGGER_COMMAND.triggerId,
|
||||
taskId: TRIGGER_COMMAND.taskId,
|
||||
taskRevision: TRIGGER_COMMAND.taskRevision,
|
||||
spec: TRIGGER_COMMAND.spec,
|
||||
}),
|
||||
});
|
||||
const TRIGGER = createTriggerRecord(
|
||||
NORMALIZED_TRIGGER_COMMAND,
|
||||
TRIGGER_COMMAND.occurredAtMs,
|
||||
);
|
||||
const DRIFT_TRIGGER = createTriggerRecord(
|
||||
Object.freeze({ ...NORMALIZED_TRIGGER_COMMAND, enabled: false }),
|
||||
TRIGGER_COMMAND.occurredAtMs,
|
||||
);
|
||||
const ACTOR = Object.freeze({ type: 'user', id: 'usr_cluster_admin' });
|
||||
const FENCE = Object.freeze({ projectVersion: 1, bindingVersion: 1 });
|
||||
const TASK_AUDIT = Object.freeze({
|
||||
eventId: TASK_COMMAND.mutationId,
|
||||
requestId: 'request-task-create-00001',
|
||||
operationId: 'task.create',
|
||||
projectId: TASK_COMMAND.projectId,
|
||||
subject: ACTOR,
|
||||
authenticationId: 'oidc:session-task-00001',
|
||||
outcome: 'allowed',
|
||||
reasons: Object.freeze(['role_grant']),
|
||||
fence: FENCE,
|
||||
occurredAtMs: 102,
|
||||
});
|
||||
const TRIGGER_AUDIT = Object.freeze({
|
||||
...TASK_AUDIT,
|
||||
eventId: TRIGGER_COMMAND.mutationId,
|
||||
requestId: 'request-trigger-create-00001',
|
||||
operationId: 'trigger.create',
|
||||
occurredAtMs: 202,
|
||||
});
|
||||
const TASK_READ_AUDIT = Object.freeze({
|
||||
...TASK_AUDIT,
|
||||
eventId: '123e4567-e89b-42d3-a456-426614174010',
|
||||
requestId: 'request-task-read-00001',
|
||||
operationId: 'task.read',
|
||||
occurredAtMs: 302,
|
||||
});
|
||||
const TRIGGER_READ_AUDIT = Object.freeze({
|
||||
...TASK_AUDIT,
|
||||
eventId: '123e4567-e89b-42d3-a456-426614174011',
|
||||
requestId: 'request-trigger-read-00001',
|
||||
operationId: 'trigger.read',
|
||||
occurredAtMs: 303,
|
||||
});
|
||||
|
||||
function administrationAuditRow(audit) {
|
||||
return {
|
||||
auditEventId: audit.eventId,
|
||||
auditRequestId: audit.requestId,
|
||||
auditOperationId: audit.operationId,
|
||||
auditProjectId: audit.projectId,
|
||||
auditSubjectType: audit.subject.type,
|
||||
auditSubjectId: audit.subject.id,
|
||||
auditAuthenticationId: audit.authenticationId,
|
||||
auditOutcome: audit.outcome,
|
||||
auditReasons: [...audit.reasons],
|
||||
auditProjectVersion: audit.fence.projectVersion,
|
||||
auditBindingVersion: audit.fence.bindingVersion,
|
||||
auditOccurredAtMs: String(audit.occurredAtMs),
|
||||
};
|
||||
}
|
||||
|
||||
function taskRow(overrides = {}) {
|
||||
return {
|
||||
projectId: TASK.projectId,
|
||||
taskId: TASK.taskId,
|
||||
revision: TASK.revision,
|
||||
mutationId: TASK.mutationId,
|
||||
name: TASK.name,
|
||||
description: null,
|
||||
kind: TASK.kind,
|
||||
specJson: TASK.spec,
|
||||
labelsJson: TASK.labels,
|
||||
enabled: TASK.enabled,
|
||||
contentDigest: TASK.contentDigest,
|
||||
createdAtMs: String(TASK.createdAtMs),
|
||||
updatedAtMs: String(TASK.updatedAtMs),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function triggerRow(overrides = {}) {
|
||||
return {
|
||||
projectId: TRIGGER.projectId,
|
||||
triggerId: TRIGGER.triggerId,
|
||||
revision: TRIGGER.revision,
|
||||
mutationId: TRIGGER.mutationId,
|
||||
taskId: TRIGGER.taskId,
|
||||
taskRevision: TRIGGER.taskRevision,
|
||||
taskContentDigest: TRIGGER.taskContentDigest,
|
||||
specJson: TRIGGER.spec,
|
||||
enabled: TRIGGER.enabled,
|
||||
contentDigest: TRIGGER.contentDigest,
|
||||
createdAtMs: String(TRIGGER.createdAtMs),
|
||||
updatedAtMs: String(TRIGGER.updatedAtMs),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function taskExecutionRow(overrides = {}) {
|
||||
return {
|
||||
projectId: TASK_EXECUTION.projectId,
|
||||
taskId: TASK_EXECUTION.taskId,
|
||||
sourceRevision: TASK_EXECUTION.sourceRevision,
|
||||
taskRevision: TASK_EXECUTION.taskRevision,
|
||||
sourceContentDigest: TASK_EXECUTION.sourceContentDigest,
|
||||
executorType: TASK_EXECUTION.executorType,
|
||||
planSchema: TASK_EXECUTION.planSchema,
|
||||
planJson: {
|
||||
command: TASK_EXECUTION.command,
|
||||
environment: TASK_EXECUTION.environment,
|
||||
...(TASK_EXECUTION.workingDirectory === undefined
|
||||
? {}
|
||||
: { workingDirectory: TASK_EXECUTION.workingDirectory }),
|
||||
...(TASK_EXECUTION.timeoutMs === undefined
|
||||
? {}
|
||||
: { timeoutMs: TASK_EXECUTION.timeoutMs }),
|
||||
...(TASK_EXECUTION.placement === undefined
|
||||
? {}
|
||||
: { placement: TASK_EXECUTION.placement }),
|
||||
},
|
||||
contentDigest: TASK_EXECUTION.contentDigest,
|
||||
createdAtMs: String(TASK_EXECUTION.createdAtMs),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function sourcePool(rows) {
|
||||
const queries = [];
|
||||
return {
|
||||
queries,
|
||||
pool: {
|
||||
async query(text, values) {
|
||||
queries.push({ text, values });
|
||||
return { rows };
|
||||
},
|
||||
async connect() {
|
||||
throw new Error('not used');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function appendPool(kind, options = {}) {
|
||||
const events = [];
|
||||
const queries = [];
|
||||
let connection = 0;
|
||||
return {
|
||||
events,
|
||||
queries,
|
||||
pool: {
|
||||
async query() {
|
||||
throw new Error('not used');
|
||||
},
|
||||
async connect() {
|
||||
connection += 1;
|
||||
return {
|
||||
async query(text, values) {
|
||||
events.push(text.trim().split('\n', 1)[0]);
|
||||
queries.push({ text, values });
|
||||
if (text.includes('WHERE revision.mutation_id = $1')) {
|
||||
return {
|
||||
rows: options.replay
|
||||
? [kind === 'task' ? taskRow(options.replay) : triggerRow(options.replay)]
|
||||
: [],
|
||||
};
|
||||
}
|
||||
if (text.includes('FROM "ql3"."task_execution_revisions"')) {
|
||||
return {
|
||||
rows: options.missingExecution ? [] : [taskExecutionRow()],
|
||||
};
|
||||
}
|
||||
if (text.includes('FROM "ql3"."trigger_schedules"')) {
|
||||
return {
|
||||
rows: options.missingSchedule
|
||||
? []
|
||||
: [{ triggerRevision: TRIGGER.revision }],
|
||||
};
|
||||
}
|
||||
if (text.includes('FROM "ql3"."projects"')) {
|
||||
return {
|
||||
rows: [{
|
||||
status: options.projectStatus ?? 'active',
|
||||
version: options.projectVersion ?? 1,
|
||||
}],
|
||||
};
|
||||
}
|
||||
if (text.includes('FROM "ql3"."project_role_bindings"')) {
|
||||
return {
|
||||
rows: options.missingBinding
|
||||
? []
|
||||
: [{
|
||||
version: options.bindingVersion ?? 1,
|
||||
state: options.bindingState ?? 'active',
|
||||
}],
|
||||
};
|
||||
}
|
||||
if (text.includes('FROM "ql3"."security_audit_events"')) {
|
||||
return {
|
||||
rows: options.audit ? [administrationAuditRow(options.audit)] : [],
|
||||
};
|
||||
}
|
||||
if (text.includes('INSERT INTO "ql3"."task_definitions"')) {
|
||||
return { rows: [{ taskId: TASK.taskId }], rowCount: 1 };
|
||||
}
|
||||
if (text.includes('FROM "ql3"."task_definitions"') &&
|
||||
text.includes('FOR UPDATE')) {
|
||||
return {
|
||||
rows: [{
|
||||
currentRevision: 1,
|
||||
createdAtMs: '101',
|
||||
updatedAtMs: '101',
|
||||
}],
|
||||
};
|
||||
}
|
||||
if (text.includes('INSERT INTO "ql3"."task_definition_revisions"')) {
|
||||
if (options.retryOnce && connection === 1) {
|
||||
throw Object.assign(new Error('serialization'), { code: '40001' });
|
||||
}
|
||||
return { rows: [], rowCount: 1 };
|
||||
}
|
||||
if (text.includes('INSERT INTO "ql3"."triggers"')) {
|
||||
return { rows: [{ triggerId: TRIGGER.triggerId }], rowCount: 1 };
|
||||
}
|
||||
if (text.includes('FROM "ql3"."triggers"') &&
|
||||
text.includes('FOR UPDATE')) {
|
||||
return {
|
||||
rows: [{
|
||||
taskId: TASK.taskId,
|
||||
currentRevision: 1,
|
||||
createdAtMs: '201',
|
||||
updatedAtMs: '201',
|
||||
}],
|
||||
};
|
||||
}
|
||||
if (text.includes('FROM "ql3"."task_definitions"') &&
|
||||
text.includes('revision.revision = $3')) {
|
||||
return { rows: options.missingTask ? [] : [taskRow(options.task)] };
|
||||
}
|
||||
if (text.includes('FROM "ql3"."task_definitions"') &&
|
||||
text.includes('JOIN "ql3"."task_definition_revisions"')) {
|
||||
return { rows: options.readAbsent ? [] : [taskRow(options.task)] };
|
||||
}
|
||||
if (text.includes('FROM "ql3"."triggers"') &&
|
||||
text.includes('JOIN "ql3"."trigger_revisions"')) {
|
||||
return {
|
||||
rows: options.readAbsent ? [] : [triggerRow(options.trigger)],
|
||||
};
|
||||
}
|
||||
if (text.includes('INSERT INTO "ql3"."trigger_revisions"')) {
|
||||
return { rows: [], rowCount: 1 };
|
||||
}
|
||||
return { rows: [], rowCount: 1 };
|
||||
},
|
||||
release() {
|
||||
events.push(`release:${connection}`);
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('runtime sources read normalized immutable facts without write authority', async () => {
|
||||
const taskFixture = sourcePool([taskRow()]);
|
||||
const taskSource = new PostgresTaskDefinitionSource(taskFixture.pool);
|
||||
assert.deepEqual(
|
||||
await taskSource.findCurrentTaskDefinition(TASK.projectId, TASK.taskId),
|
||||
TASK,
|
||||
);
|
||||
assert.equal('appendTaskDefinitionRevision' in taskSource, false);
|
||||
assert.deepEqual(taskFixture.queries[0].values, [TASK.projectId, TASK.taskId]);
|
||||
const listed = sourcePool([taskRow()]);
|
||||
assert.equal(
|
||||
(await new PostgresTaskDefinitionSource(listed.pool).listTaskDefinitions({
|
||||
projectId: TASK.projectId,
|
||||
limit: 1,
|
||||
})).definitions.length,
|
||||
1,
|
||||
);
|
||||
assert.equal(
|
||||
(listed.queries[0].text.match(/ORDER BY/g) ?? []).length,
|
||||
1,
|
||||
);
|
||||
|
||||
const executionSource = new PostgresTaskExecutionRevisionSource(
|
||||
sourcePool([taskExecutionRow()]).pool,
|
||||
);
|
||||
assert.deepEqual(
|
||||
await executionSource.resolveClusterTaskExecutionRevision({
|
||||
projectId: TASK.projectId,
|
||||
taskId: TASK.taskId,
|
||||
sourceRevision: TASK.revision,
|
||||
}),
|
||||
TASK_EXECUTION,
|
||||
);
|
||||
|
||||
const triggerFixture = sourcePool([triggerRow()]);
|
||||
const triggerSource = new PostgresTriggerSource(triggerFixture.pool);
|
||||
assert.deepEqual(
|
||||
await triggerSource.findCurrentTrigger(TRIGGER.projectId, TRIGGER.triggerId),
|
||||
TRIGGER,
|
||||
);
|
||||
assert.equal('appendTriggerRevision' in triggerSource, false);
|
||||
});
|
||||
|
||||
test('publishes TaskDefinition atomically and replays the exact mutation', async () => {
|
||||
const fixture = appendPool('task');
|
||||
const created = await new PostgresTaskDefinitionRepository(
|
||||
fixture.pool,
|
||||
).appendTaskDefinitionRevision(TASK_COMMAND);
|
||||
assert.deepEqual(created, { status: 'created', definition: TASK });
|
||||
assert.ok(fixture.events.includes('BEGIN ISOLATION LEVEL SERIALIZABLE'));
|
||||
assert.ok(fixture.events.includes('COMMIT'));
|
||||
assert.equal(
|
||||
fixture.events.some((event) =>
|
||||
event.includes('INSERT INTO "ql3"."task_execution_revisions"'),
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(fixture.events.at(-1), 'release:1');
|
||||
|
||||
const replayFixture = appendPool('task', { replay: {} });
|
||||
assert.deepEqual(
|
||||
await new PostgresTaskDefinitionRepository(
|
||||
replayFixture.pool,
|
||||
).appendTaskDefinitionRevision(TASK_COMMAND),
|
||||
{ status: 'existing', definition: TASK },
|
||||
);
|
||||
assert.equal(
|
||||
replayFixture.events.some((event) => event.includes('INSERT INTO')),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test('runs TaskDefinition transaction hooks for create and replay before COMMIT', async () => {
|
||||
const createdFixture = appendPool('task');
|
||||
const createdHook = [];
|
||||
await new PostgresTaskDefinitionRepository(
|
||||
createdFixture.pool,
|
||||
).appendTaskDefinitionRevision(TASK_COMMAND, async (client, context) => {
|
||||
createdHook.push(context);
|
||||
await client.query('SELECT 1 AS task_hook');
|
||||
});
|
||||
assert.equal(createdHook.length, 1);
|
||||
assert.equal(createdHook[0].replay, null);
|
||||
assert.deepEqual(createdHook[0].record, TASK);
|
||||
assert.ok(
|
||||
createdFixture.events.indexOf('SELECT 1 AS task_hook') <
|
||||
createdFixture.events.indexOf('COMMIT'),
|
||||
);
|
||||
|
||||
const replayFixture = appendPool('task', { replay: {} });
|
||||
const replayHook = [];
|
||||
await new PostgresTaskDefinitionRepository(
|
||||
replayFixture.pool,
|
||||
).appendTaskDefinitionRevision(TASK_COMMAND, async (_client, context) => {
|
||||
replayHook.push(context);
|
||||
});
|
||||
assert.deepEqual(replayHook[0].replay, TASK);
|
||||
assert.deepEqual(replayHook[0].record, TASK);
|
||||
|
||||
const deniedFixture = appendPool('task');
|
||||
const denial = new Error('task authorization fence changed');
|
||||
await assert.rejects(
|
||||
new PostgresTaskDefinitionRepository(
|
||||
deniedFixture.pool,
|
||||
).appendTaskDefinitionRevision(TASK_COMMAND, async () => {
|
||||
throw denial;
|
||||
}),
|
||||
(error) => error === denial,
|
||||
);
|
||||
assert.equal(deniedFixture.events.includes('ROLLBACK'), true);
|
||||
assert.equal(deniedFixture.events.includes('COMMIT'), false);
|
||||
});
|
||||
|
||||
test('retries serialization and fails closed on TaskDefinition drift or corruption', async () => {
|
||||
const retry = appendPool('task', { retryOnce: true });
|
||||
assert.equal(
|
||||
(
|
||||
await new PostgresTaskDefinitionRepository(
|
||||
retry.pool,
|
||||
).appendTaskDefinitionRevision(TASK_COMMAND)
|
||||
).status,
|
||||
'created',
|
||||
);
|
||||
assert.equal(retry.events.filter((event) => event.startsWith('release:')).length, 2);
|
||||
|
||||
await assert.rejects(
|
||||
new PostgresTaskDefinitionRepository(
|
||||
appendPool('task', {
|
||||
replay: {
|
||||
name: DRIFT_TASK.name,
|
||||
contentDigest: DRIFT_TASK.contentDigest,
|
||||
},
|
||||
}).pool,
|
||||
).appendTaskDefinitionRevision(TASK_COMMAND),
|
||||
TaskDefinitionConflictError,
|
||||
);
|
||||
await assert.rejects(
|
||||
new PostgresTaskDefinitionRepository(
|
||||
appendPool('task', { replay: {}, missingExecution: true }).pool,
|
||||
).appendTaskDefinitionRevision(TASK_COMMAND),
|
||||
TaskDefinitionUnavailableError,
|
||||
);
|
||||
await assert.rejects(
|
||||
new PostgresTaskDefinitionSource(sourcePool([
|
||||
taskRow({ contentDigest: 'b'.repeat(64) }),
|
||||
]).pool).findCurrentTaskDefinition(TASK.projectId, TASK.taskId),
|
||||
TaskDefinitionUnavailableError,
|
||||
);
|
||||
});
|
||||
|
||||
test('publishes Trigger only when its immutable task pin is valid', async () => {
|
||||
const fixture = appendPool('trigger');
|
||||
const created = await new PostgresTriggerRepository(
|
||||
fixture.pool,
|
||||
).appendTriggerRevision(TRIGGER_COMMAND);
|
||||
assert.deepEqual(created, { status: 'created', trigger: TRIGGER });
|
||||
assert.ok(
|
||||
fixture.events.findIndex((event) => event.includes('task_definitions')) <
|
||||
fixture.events.findIndex((event) => event.includes('trigger_revisions')),
|
||||
);
|
||||
assert.equal(
|
||||
fixture.events.some((event) =>
|
||||
event.includes('INSERT INTO "ql3"."trigger_schedules"'),
|
||||
),
|
||||
true,
|
||||
);
|
||||
const createdPin = fixture.queries.find(({ text }) =>
|
||||
text.includes('$4::boolean = false'),
|
||||
);
|
||||
assert.equal(createdPin.values[3], true);
|
||||
|
||||
const replayFixture = appendPool('trigger', { replay: {} });
|
||||
assert.deepEqual(
|
||||
await new PostgresTriggerRepository(
|
||||
replayFixture.pool,
|
||||
).appendTriggerRevision(TRIGGER_COMMAND),
|
||||
{ status: 'existing', trigger: TRIGGER },
|
||||
);
|
||||
const replayPin = replayFixture.queries.find(({ text }) =>
|
||||
text.includes('$4::boolean = false'),
|
||||
);
|
||||
assert.equal(replayPin.values[3], false);
|
||||
|
||||
await assert.rejects(
|
||||
new PostgresTriggerRepository(
|
||||
appendPool('trigger', { missingTask: true }).pool,
|
||||
).appendTriggerRevision(TRIGGER_COMMAND),
|
||||
TriggerConflictError,
|
||||
);
|
||||
await assert.rejects(
|
||||
new PostgresTriggerRepository(
|
||||
appendPool('trigger', { task: { contentDigest: 'b'.repeat(64) } }).pool,
|
||||
).appendTriggerRevision(TRIGGER_COMMAND),
|
||||
TriggerUnavailableError,
|
||||
);
|
||||
});
|
||||
|
||||
test('runs Trigger transaction hooks atomically and preserves hook failures', async () => {
|
||||
const createdFixture = appendPool('trigger');
|
||||
const contexts = [];
|
||||
await new PostgresTriggerRepository(
|
||||
createdFixture.pool,
|
||||
).appendTriggerRevision(TRIGGER_COMMAND, async (client, context) => {
|
||||
contexts.push(context);
|
||||
await client.query('SELECT 1 AS trigger_hook');
|
||||
});
|
||||
assert.equal(contexts.length, 1);
|
||||
assert.equal(contexts[0].replay, null);
|
||||
assert.deepEqual(contexts[0].record, TRIGGER);
|
||||
assert.ok(
|
||||
createdFixture.events.indexOf('SELECT 1 AS trigger_hook') <
|
||||
createdFixture.events.indexOf('COMMIT'),
|
||||
);
|
||||
|
||||
const replayFixture = appendPool('trigger', { replay: {} });
|
||||
await new PostgresTriggerRepository(
|
||||
replayFixture.pool,
|
||||
).appendTriggerRevision(TRIGGER_COMMAND, async (_client, context) => {
|
||||
contexts.push(context);
|
||||
});
|
||||
assert.deepEqual(contexts[1].replay, TRIGGER);
|
||||
|
||||
const deniedFixture = appendPool('trigger');
|
||||
const denial = new Error('trigger authorization fence changed');
|
||||
await assert.rejects(
|
||||
new PostgresTriggerRepository(
|
||||
deniedFixture.pool,
|
||||
).appendTriggerRevision(TRIGGER_COMMAND, async () => {
|
||||
throw denial;
|
||||
}),
|
||||
(error) => error === denial,
|
||||
);
|
||||
assert.equal(deniedFixture.events.includes('ROLLBACK'), true);
|
||||
assert.equal(deniedFixture.events.includes('COMMIT'), false);
|
||||
});
|
||||
|
||||
test('Trigger replay rejects mutation drift and corrupt durable records', async () => {
|
||||
await assert.rejects(
|
||||
new PostgresTriggerRepository(
|
||||
appendPool('trigger', {
|
||||
replay: {
|
||||
enabled: DRIFT_TRIGGER.enabled,
|
||||
contentDigest: DRIFT_TRIGGER.contentDigest,
|
||||
},
|
||||
}).pool,
|
||||
).appendTriggerRevision(TRIGGER_COMMAND),
|
||||
TriggerConflictError,
|
||||
);
|
||||
await assert.rejects(
|
||||
new PostgresTriggerRepository(
|
||||
appendPool('trigger', {
|
||||
replay: {},
|
||||
missingSchedule: true,
|
||||
}).pool,
|
||||
).appendTriggerRevision(TRIGGER_COMMAND),
|
||||
TriggerUnavailableError,
|
||||
);
|
||||
await assert.rejects(
|
||||
new PostgresTriggerSource(sourcePool([
|
||||
triggerRow({ contentDigest: 'b'.repeat(64) }),
|
||||
]).pool).findCurrentTrigger(TRIGGER.projectId, TRIGGER.triggerId),
|
||||
TriggerUnavailableError,
|
||||
);
|
||||
});
|
||||
|
||||
test('atomically fences and audits authorized PostgreSQL Task mutations', async () => {
|
||||
const createdFixture = appendPool('task');
|
||||
assert.deepEqual(
|
||||
await new PostgresTaskDefinitionAdministrationRepository(
|
||||
createdFixture.pool,
|
||||
).appendAuthorizedTaskDefinitionRevision({
|
||||
command: TASK_COMMAND,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TASK_AUDIT,
|
||||
}),
|
||||
{ status: 'created', definition: TASK },
|
||||
);
|
||||
const createdSql = createdFixture.queries.map(({ text }) => text);
|
||||
assert.ok(
|
||||
createdSql.findIndex((text) => text.includes('project_role_bindings')) <
|
||||
createdSql.findIndex((text) => text.includes('security_audit_events')),
|
||||
);
|
||||
assert.ok(
|
||||
createdSql.findIndex((text) => text.includes('security_audit_events')) <
|
||||
createdSql.indexOf('COMMIT'),
|
||||
);
|
||||
|
||||
const replayFixture = appendPool('task', {
|
||||
replay: {},
|
||||
audit: TASK_AUDIT,
|
||||
});
|
||||
assert.equal(
|
||||
(
|
||||
await new PostgresTaskDefinitionAdministrationRepository(
|
||||
replayFixture.pool,
|
||||
).appendAuthorizedTaskDefinitionRevision({
|
||||
command: TASK_COMMAND,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: { ...TASK_AUDIT, occurredAtMs: 999 },
|
||||
})
|
||||
).status,
|
||||
'existing',
|
||||
);
|
||||
assert.equal(
|
||||
replayFixture.queries.some(({ text }) =>
|
||||
text.includes('INSERT INTO "ql3"."security_audit_events"'),
|
||||
),
|
||||
false,
|
||||
);
|
||||
|
||||
const fencedFixture = appendPool('task', { bindingVersion: 2 });
|
||||
await assert.rejects(
|
||||
new PostgresTaskDefinitionAdministrationRepository(
|
||||
fencedFixture.pool,
|
||||
).appendAuthorizedTaskDefinitionRevision({
|
||||
command: TASK_COMMAND,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TASK_AUDIT,
|
||||
}),
|
||||
TaskDefinitionAdministrationAuthorizationFenceConflictError,
|
||||
);
|
||||
assert.equal(fencedFixture.events.includes('ROLLBACK'), true);
|
||||
|
||||
await assert.rejects(
|
||||
new PostgresTaskDefinitionAdministrationRepository(
|
||||
appendPool('task', { audit: TASK_AUDIT }).pool,
|
||||
).appendAuthorizedTaskDefinitionRevision({
|
||||
command: TASK_COMMAND,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TASK_AUDIT,
|
||||
}),
|
||||
TaskDefinitionAdministrationMutationConflictError,
|
||||
);
|
||||
});
|
||||
|
||||
test('atomically fences and audits authorized PostgreSQL Trigger mutations', async () => {
|
||||
const createdFixture = appendPool('trigger');
|
||||
assert.equal(
|
||||
(
|
||||
await new PostgresTriggerAdministrationRepository(
|
||||
createdFixture.pool,
|
||||
).appendAuthorizedTriggerRevision({
|
||||
command: TRIGGER_COMMAND,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TRIGGER_AUDIT,
|
||||
})
|
||||
).status,
|
||||
'created',
|
||||
);
|
||||
assert.equal(
|
||||
createdFixture.queries.some(({ text }) =>
|
||||
text.includes('INSERT INTO "ql3"."security_audit_events"'),
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
const fencedFixture = appendPool('trigger', { projectVersion: 2 });
|
||||
await assert.rejects(
|
||||
new PostgresTriggerAdministrationRepository(
|
||||
fencedFixture.pool,
|
||||
).appendAuthorizedTriggerRevision({
|
||||
command: TRIGGER_COMMAND,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TRIGGER_AUDIT,
|
||||
}),
|
||||
TriggerAdministrationAuthorizationFenceConflictError,
|
||||
);
|
||||
assert.equal(fencedFixture.events.includes('ROLLBACK'), true);
|
||||
});
|
||||
|
||||
test('atomically fences, reads and audits current PostgreSQL automation facts', async () => {
|
||||
const taskFixture = appendPool('task');
|
||||
const taskRepository = new PostgresTaskDefinitionAdministrationRepository(
|
||||
taskFixture.pool,
|
||||
);
|
||||
assert.deepEqual(
|
||||
await taskRepository.findAuthorizedCurrentTaskDefinition({
|
||||
projectId: TASK.projectId,
|
||||
taskId: TASK.taskId,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TASK_READ_AUDIT,
|
||||
}),
|
||||
TASK,
|
||||
);
|
||||
const taskSql = taskFixture.queries.map(({ text }) => text);
|
||||
assert.ok(
|
||||
taskSql.findIndex((text) => text.includes('FROM "ql3"."projects"')) <
|
||||
taskSql.findIndex((text) =>
|
||||
text.includes('JOIN "ql3"."task_definition_revisions"'),
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
taskSql.findIndex((text) =>
|
||||
text.includes('JOIN "ql3"."task_definition_revisions"'),
|
||||
) <
|
||||
taskSql.findIndex((text) =>
|
||||
text.includes('INSERT INTO "ql3"."security_audit_events"'),
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
taskSql.findIndex((text) =>
|
||||
text.includes('INSERT INTO "ql3"."security_audit_events"'),
|
||||
) < taskSql.indexOf('COMMIT'),
|
||||
);
|
||||
|
||||
const triggerFixture = appendPool('trigger');
|
||||
assert.deepEqual(
|
||||
await new PostgresTriggerAdministrationRepository(
|
||||
triggerFixture.pool,
|
||||
).findAuthorizedCurrentTrigger({
|
||||
projectId: TRIGGER.projectId,
|
||||
triggerId: TRIGGER.triggerId,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TRIGGER_READ_AUDIT,
|
||||
}),
|
||||
TRIGGER,
|
||||
);
|
||||
assert.equal(
|
||||
triggerFixture.queries.some(({ text }) =>
|
||||
text.includes('INSERT INTO "ql3"."security_audit_events"'),
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('reads bounded automation pages and fails closed on fence or audit replay', async () => {
|
||||
const taskFixture = appendPool('task');
|
||||
const tasks = await new PostgresTaskDefinitionAdministrationRepository(
|
||||
taskFixture.pool,
|
||||
).listAuthorizedTaskDefinitions({
|
||||
projectId: TASK.projectId,
|
||||
limit: 1,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TASK_READ_AUDIT,
|
||||
});
|
||||
assert.deepEqual(tasks, { definitions: [TASK], truncated: false });
|
||||
|
||||
const triggerFixture = appendPool('trigger');
|
||||
const triggers = await new PostgresTriggerAdministrationRepository(
|
||||
triggerFixture.pool,
|
||||
).listAuthorizedTriggers({
|
||||
projectId: TRIGGER.projectId,
|
||||
limit: 1,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TRIGGER_READ_AUDIT,
|
||||
});
|
||||
assert.deepEqual(triggers, { triggers: [TRIGGER], truncated: false });
|
||||
|
||||
const fenced = appendPool('task', { bindingVersion: 2 });
|
||||
await assert.rejects(
|
||||
new PostgresTaskDefinitionAdministrationRepository(
|
||||
fenced.pool,
|
||||
).findAuthorizedCurrentTaskDefinition({
|
||||
projectId: TASK.projectId,
|
||||
taskId: TASK.taskId,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TASK_READ_AUDIT,
|
||||
}),
|
||||
TaskDefinitionAdministrationAuthorizationFenceConflictError,
|
||||
);
|
||||
assert.equal(
|
||||
fenced.queries.some(({ text }) =>
|
||||
text.includes('JOIN "ql3"."task_definition_revisions"'),
|
||||
),
|
||||
false,
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
new PostgresTaskDefinitionAdministrationRepository(
|
||||
appendPool('task', { audit: TASK_READ_AUDIT }).pool,
|
||||
).findAuthorizedCurrentTaskDefinition({
|
||||
projectId: TASK.projectId,
|
||||
taskId: TASK.taskId,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TASK_READ_AUDIT,
|
||||
}),
|
||||
TaskDefinitionAdministrationReadConflictError,
|
||||
);
|
||||
await assert.rejects(
|
||||
new PostgresTriggerAdministrationRepository(
|
||||
appendPool('trigger', { audit: TRIGGER_READ_AUDIT }).pool,
|
||||
).findAuthorizedCurrentTrigger({
|
||||
projectId: TRIGGER.projectId,
|
||||
triggerId: TRIGGER.triggerId,
|
||||
actor: ACTOR,
|
||||
fence: FENCE,
|
||||
audit: TRIGGER_READ_AUDIT,
|
||||
}),
|
||||
TriggerAdministrationReadConflictError,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user