Files
PaperClipAI/scripts/__tests__/prepare-runner-release-image.test.mjs
T

70 lines
4.3 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { createHash } from 'node:crypto';
import { validateReleaseRun, selectReleaseArtifacts, validateReleaseImageBase, validateImageBindings } from '../prepare-runner-release-image.mjs';
const source = 'a'.repeat(40);
const run = { head_sha: source, conclusion: 'success', path: '.github/workflows/release-smoke.yml', repository: { full_name: 'paperclipai/paperclip' } };
const artifacts = ['runner-provider-packs-123-2-linux-x64', 'runner-release-123-2-linux-x64', 'claude-qualification-public-packages']
.map((name, index) => ({ id: index + 1, name, expired: false, digest: 'sha256:' + 'b'.repeat(64), size_in_bytes: 100 }));
test('release image consumes one successful exact-source producer set', () => {
assert.doesNotThrow(() => validateReleaseRun(run, source));
assert.deepEqual(selectReleaseArtifacts(artifacts, '123', 2), artifacts);
});
for (const [name, patch] of [
['different source', { head_sha: 'c'.repeat(40) }],
['incomplete producer', { conclusion: null }],
['failed producer', { conclusion: 'failure' }],
['unrelated workflow', { path: '.github/workflows/ci.yml' }],
['fork artifact', { repository: { full_name: 'fork/paperclip' } }],
]) test('rejects ' + name, () => assert.throws(() => validateReleaseRun({ ...run, ...patch }, source)));
for (const [name, values] of [
['missing pack', artifacts.slice(1)],
['duplicate authority', [...artifacts, artifacts[0]]],
['expired artifact', artifacts.map((a, i) => i ? a : { ...a, expired: true })],
['unbound digest', artifacts.map((a, i) => i ? a : { ...a, digest: null })],
['oversized archive', artifacts.map((a, i) => i ? a : { ...a, size_in_bytes: 3 * 1024 ** 3 })],
['wrong attempt', artifacts.map((a, i) => i ? a : { ...a, name: 'runner-provider-packs-123-1-linux-x64' })],
]) test('rejects ' + name, () => assert.throws(() => selectReleaseArtifacts(values, '123', 2)));
test('base image must be immutable and repository-owned', () => {
assert.equal(validateReleaseImageBase('ghcr.io/paperclipai/paperclip-daytona-runner@sha256:' + source + 'b'.repeat(24)), 'ghcr.io/paperclipai/paperclip-daytona-runner@sha256:' + source + 'b'.repeat(24));
for (const base of ['ghcr.io/paperclipai/paperclip-daytona-runner:latest', 'ghcr.io/fork/paperclip-daytona-runner@sha256:' + 'a'.repeat(64), 'ubuntu:latest']) {
assert.throws(() => validateReleaseImageBase(base));
}
});
const daemonBytes = Buffer.alloc(64);
daemonBytes.write('7f454c46', 0, 'hex');
daemonBytes[4] = 2;
daemonBytes[5] = 1;
daemonBytes.writeUInt16LE(62, 18);
const daemonDigest = 'sha256:' + createHash('sha256').update(daemonBytes).digest('hex');
const binding = { source, pack: { digest: 'release', payload: { productionLock: 'original', candidateProviders: { pi: 'qualified' } } },
receipt: { schema: 'paperclip.runner.release-artifact.v1', sourceRevision: source, target: 'linux-x64', sha256: daemonDigest },
release: { sourceRevision: source, platforms: { 'linux-x64': { sha256: daemonDigest } } }, daemonBytes };
binding.publicPack = structuredClone(binding.pack);
test('image pack must equal the authority shipped in the public package', () => {
assert.equal(validateImageBindings(binding), daemonDigest.slice(7));
for (const changed of [{ productionLock: 'regenerated' }, { candidateProviders: {} }]) {
const different = structuredClone(binding.pack);
Object.assign(different.payload, changed);
assert.throws(() => validateImageBindings({ ...binding, publicPack: different }), /public package authority/);
}
});
test('image daemon must match source, receipt, public package and actual architecture', () => {
for (const changed of [{ sourceRevision: 'c'.repeat(40) }, { sha256: 'sha256:' + 'c'.repeat(64) }, { target: 'linux-arm64' }]) {
assert.throws(() => validateImageBindings({ ...binding, receipt: { ...binding.receipt, ...changed } }));
}
assert.throws(() => validateImageBindings({ ...binding, release: { ...binding.release, sourceRevision: 'c'.repeat(40) } }));
const wrongArch = Buffer.from(daemonBytes); wrongArch.writeUInt16LE(183, 18);
const digest = 'sha256:' + createHash('sha256').update(wrongArch).digest('hex');
assert.throws(() => validateImageBindings({ ...binding, daemonBytes: wrongArch,
receipt: { ...binding.receipt, sha256: digest }, release: { ...binding.release, platforms: { 'linux-x64': { sha256: digest } } } }));
});