Files
qinglong/test/contracts/pluginPackageMaterializedRevisionRepositoryContract.cjs

189 lines
6.0 KiB
JavaScript

const assert = require('node:assert/strict');
const { createHash } = require('node:crypto');
const { test } = require('node:test');
const {
createPluginPackageResourceGenerationFromReferences,
} = require('../../packages/ql3-runtime-core/dist/plugin-package/pluginPackageResourceGeneration');
const {
PLUGIN_PACKAGE_API_VERSION,
PLUGIN_PACKAGE_KIND,
planPluginPackageInstall,
} = require('../../packages/ql3-runtime-core/dist/plugin-package/pluginPackage');
const {
createPluginPackageLock,
pluginPackageInstallActionDigest,
pluginPackageInstallPlanDigest,
serializePluginPackageManifest,
} = require('../../packages/ql3-runtime-core/dist/plugin-package/installation/pluginPackageInstall');
const {
pluginPackageContentTreeDigest,
} = require('../../packages/ql3-runtime-core/dist/plugin-package/pluginPackageBundle');
const {
InvalidPluginPackageResourceMaterializationError,
materializePluginPackageResources,
} = require('../../packages/ql3-runtime-core/dist/plugin-package/pluginPackageResourceMaterialization');
const {
createBuiltInTaskSpecSemanticRegistry,
} = require('../../packages/ql3-runtime-core/dist/task-definition/taskSpecSemantic');
function materializedRevisionFixture(namespace, profile = 'edge') {
const projectId = `project-${namespace}`;
const packageName = `package-${namespace}`;
const resourcePath = 'prompts/report.json';
const resourceBytes = Buffer.from(
JSON.stringify({
schema: 'qinglong/plugin-package-prompt-resource@v1',
id: 'report',
name: 'Report',
template: 'Hello {{name}}\n',
parameters: [{ name: 'name', required: true }],
}),
);
const sourceDigest = createHash('sha256').update(resourceBytes).digest('hex');
const contentDigest = pluginPackageContentTreeDigest([
{ path: resourcePath, bytes: resourceBytes.byteLength, digest: sourceDigest },
]);
const manifest = {
apiVersion: PLUGIN_PACKAGE_API_VERSION,
kind: PLUGIN_PACKAGE_KIND,
metadata: {
name: packageName,
displayName: packageName,
version: '1.0.0',
description: 'One immutable semantic revision fixture',
license: 'Apache-2.0',
},
spec: {
compatibility: {
qinglong: '>=3.0.0-0 <4.0.0',
architectures: ['arm64'],
deploymentProfiles: [profile],
},
runtimes: [],
resources: {
memory: { recommended: '16Mi' },
disk: { install: '4Mi', working: '8Mi' },
},
permissions: {
network: { allowedHosts: [] },
secrets: [],
tools: [],
},
contents: {
tasks: [],
workflows: [],
prompts: [resourcePath],
tools: [],
},
},
};
const environment = {
qinglongVersion: '3.0.0-alpha.0',
architecture: 'arm64',
deploymentProfile: profile,
runtimes: [],
availableMemoryBytes: 128 * 1024 * 1024,
availableDiskBytes: 256 * 1024 * 1024,
};
const plan = planPluginPackageInstall(manifest, environment);
const actionInput = {
lockId: `lock-${namespace}`,
projectId,
manifest,
plan,
environment,
source: {
kind: 'offline',
locator: `offline:sha256:${'a'.repeat(64)}`,
artifactDigest: 'a'.repeat(64),
artifactBytes: 2048,
contentDigest,
},
architecture: 'arm64',
deploymentProfile: profile,
targetGeneration: 1,
};
const lock = createPluginPackageLock({
...actionInput,
approval: {
requestId: `approval-${namespace}`,
requestVersion: 1,
dispatchId: `dispatch-${namespace}`,
actionDigest: pluginPackageInstallActionDigest(actionInput),
previewDigest: pluginPackageInstallPlanDigest(plan),
approvedBy: { type: 'user', id: 'owner-001' },
approvedAtMs: 100,
expiresAtMs: 10_000,
fence: { projectVersion: 1, bindingVersion: 1 },
},
createdAtMs: 200,
});
const generation = createPluginPackageResourceGenerationFromReferences({
installationId: `install-${namespace}`,
projectId,
packageName,
lockDigest: lock.lockDigest,
generation: 1,
previousActiveLockDigest: null,
contentDigest,
resources: lock.resources,
});
const registry = createBuiltInTaskSpecSemanticRegistry();
const revision = materializePluginPackageResources({
generation,
lock,
manifestBytes: Buffer.from(serializePluginPackageManifest(manifest)),
resources: [
{
reference: generation.resources[0],
bytes: resourceBytes,
},
],
taskSpecSemanticRegistry: registry,
});
return { projectId, packageName, registry, revision };
}
function registerPluginPackageMaterializedRevisionRepositoryContract(options) {
test(`${options.name} publishes once and replays the exact revision`, async (t) => {
const fixture = materializedRevisionFixture(
options.namespace ?? 'materialized',
options.profile,
);
const harness = await options.createRepository(t, fixture);
t.after(() => harness.close?.());
const first = await harness.repository.publish(fixture.revision);
assert.equal(first.status, 'created');
assert.deepEqual(first.revision, fixture.revision);
const replay = await harness.repository.publish(fixture.revision);
assert.equal(replay.status, 'existing');
assert.deepEqual(replay.revision, fixture.revision);
assert.deepEqual(
await harness.repository.find(
fixture.revision.generation.generationDigest,
),
fixture.revision,
);
});
test(`${options.name} returns absence and rejects invalid lookup identity`, async (t) => {
const fixture = materializedRevisionFixture(
`${options.namespace ?? 'materialized'}-absence`,
options.profile,
);
const harness = await options.createRepository(t, fixture);
t.after(() => harness.close?.());
assert.equal(await harness.repository.find('f'.repeat(64)), null);
await assert.rejects(
harness.repository.find('../generation'),
InvalidPluginPackageResourceMaterializationError,
);
});
}
module.exports = {
materializedRevisionFixture,
registerPluginPackageMaterializedRevisionRepositoryContract,
};