Files
qinglong/test/contracts/projectToolDefinitionSnapshotRepositoryContract.cjs

227 lines
7.6 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const {
createProjectToolDefinitionSnapshot,
projectToolDefinitionSnapshotContribution,
InvalidProjectToolDefinitionSnapshotError,
ProjectToolDefinitionSnapshotConflictError,
} = require('../../packages/ql3-runtime-core/dist/tool-execution/tool-registry/projectToolDefinitionSnapshot');
const {
createBuiltInTaskSpecSemanticRegistry,
} = require('../../packages/ql3-runtime-core/dist/task-definition/taskSpecSemantic');
const {
activateInstall,
pluginPackageTaskReconciliationFixture,
} = require('./pluginPackageTaskReconciliationRepositoryContract.cjs');
function snapshotFor(value) {
return createProjectToolDefinitionSnapshot({
projectId: value.projectId,
contributions: [
projectToolDefinitionSnapshotContribution(value.revision, value.registry),
],
});
}
async function pendingProjectIds(repository) {
const projects = [];
let after;
for (let pageNumber = 0; pageNumber < 8; pageNumber += 1) {
const page = await repository.listPendingProjectPage({
limit: 64,
...(after ? { after } : {}),
});
projects.push(...page.projectIds);
if (!page.truncated) return projects;
after = page.next;
}
throw new Error('snapshot pending Project contract did not converge');
}
function registerProjectToolDefinitionSnapshotRepositoryContract(options) {
test(`${options.name}: publishes and exactly replays an empty active vector`, async (t) => {
const projectId = `${options.namespace}-empty`;
const registry = createBuiltInTaskSpecSemanticRegistry();
const harness = await options.createRepository(t, { projectId, registry });
t.after(() => harness.close?.());
const snapshot = createProjectToolDefinitionSnapshot({
projectId,
contributions: [],
});
assert.deepEqual(
await harness.repository.listActiveSourcePage({
projectId,
limit: 1,
}),
{ sources: [], truncated: false },
);
assert.equal(
(await pendingProjectIds(harness.repository)).includes(projectId),
true,
);
assert.equal(await harness.repository.findCurrent(projectId), null);
const created = await harness.repository.publish(snapshot);
assert.equal(created.status, 'created');
assert.deepEqual(created.record.snapshot, snapshot);
assert.ok(Number.isSafeInteger(created.record.committedAtMs));
const replay = await harness.repository.publish(snapshot);
assert.equal(replay.status, 'existing');
assert.deepEqual(replay.record, created.record);
assert.deepEqual(
await harness.repository.findCurrent(projectId),
created.record,
);
assert.equal(
(await pendingProjectIds(harness.repository)).includes(projectId),
false,
);
});
test(`${options.name}: binds one active Package to its immutable revision`, async (t) => {
const value = pluginPackageTaskReconciliationFixture(
`${options.namespace}-active`,
{ profile: options.profile },
);
const harness = await options.createRepository(t, value);
t.after(() => harness.close?.());
await activateInstall(harness.installRepository, value);
await harness.materializedRepository.publish(value.revision);
const snapshot = snapshotFor(value);
const sourcePage = await harness.repository.listActiveSourcePage({
projectId: value.projectId,
limit: 1,
});
assert.deepEqual(sourcePage, {
sources: snapshot.sources,
truncated: false,
});
assert.equal(
(await pendingProjectIds(harness.repository)).includes(value.projectId),
true,
);
const created = await harness.repository.publish(snapshot);
assert.equal(created.status, 'created');
assert.deepEqual(
await harness.repository.findCurrent(value.projectId),
created.record,
);
assert.deepEqual(created.record.snapshot.sources, [
{
installationId: value.install.active.installationId,
packageName: value.packageName,
generation: value.generation,
generationDigest: value.revision.generation.generationDigest,
lockDigest: value.lock.lockDigest,
revisionDigest: value.revision.revisionDigest,
},
]);
assert.equal(
(await pendingProjectIds(harness.repository)).includes(value.projectId),
false,
);
await options.assertDurableSource?.(harness, value, created.record);
});
test(`${options.name}: keeps the old snapshot during staging and fails closed after activation`, async (t) => {
const first = pluginPackageTaskReconciliationFixture(
`${options.namespace}-upgrade`,
{ profile: options.profile },
);
const harness = await options.createRepository(t, first);
t.after(() => harness.close?.());
await activateInstall(harness.installRepository, first);
await harness.materializedRepository.publish(first.revision);
const firstSnapshot = snapshotFor(first);
const firstRecord = (await harness.repository.publish(firstSnapshot))
.record;
const second = pluginPackageTaskReconciliationFixture(first.namespace, {
profile: options.profile,
previous: first,
tasks: [['alpha', 'next']],
});
await harness.materializedRepository.publish(second.revision);
await harness.installRepository.create(second.install.create);
assert.deepEqual(
await harness.repository.findCurrent(first.projectId),
firstRecord,
);
await harness.installRepository.commit(second.install.commits[0]);
await harness.installRepository.commit(second.install.commits[1]);
assert.deepEqual(
await harness.repository.findCurrent(first.projectId),
firstRecord,
);
assert.deepEqual(
(
await harness.repository.listActiveSourcePage({
projectId: first.projectId,
limit: 1,
})
).sources,
firstRecord.snapshot.sources,
);
await harness.installRepository.commit(second.install.commits[2]);
assert.equal(await harness.repository.findCurrent(first.projectId), null);
assert.deepEqual(
(
await harness.repository.listActiveSourcePage({
projectId: first.projectId,
limit: 1,
})
).sources,
secondSnapshotSources(second),
);
assert.equal(
(await pendingProjectIds(harness.repository)).includes(first.projectId),
true,
);
await assert.rejects(
harness.repository.publish(firstSnapshot),
ProjectToolDefinitionSnapshotConflictError,
);
const secondSnapshot = snapshotFor(second);
const secondRecord = (await harness.repository.publish(secondSnapshot))
.record;
assert.deepEqual(
await harness.repository.findCurrent(first.projectId),
secondRecord,
);
assert.notEqual(
firstRecord.snapshot.activeVectorDigest,
secondRecord.snapshot.activeVectorDigest,
);
assert.equal(
(await pendingProjectIds(harness.repository)).includes(first.projectId),
false,
);
});
test(`${options.name}: rejects invalid Project identity`, async (t) => {
const projectId = `${options.namespace}-invalid`;
const registry = createBuiltInTaskSpecSemanticRegistry();
const harness = await options.createRepository(t, { projectId, registry });
t.after(() => harness.close?.());
await assert.rejects(
Promise.resolve().then(() =>
harness.repository.findCurrent('project\0invalid'),
),
InvalidProjectToolDefinitionSnapshotError,
);
});
}
function secondSnapshotSources(value) {
return snapshotFor(value).sources;
}
module.exports = {
registerProjectToolDefinitionSnapshotRepositoryContract,
projectToolDefinitionSnapshotForFixture: snapshotFor,
};