mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
227 lines
7.6 KiB
JavaScript
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,
|
|
};
|