mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:25:29 +08:00
647 lines
20 KiB
JavaScript
647 lines
20 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const { createHash, generateKeyPairSync, sign } = require('node:crypto');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { DatabaseSync } = require('node:sqlite');
|
|
const { test } = require('node:test');
|
|
|
|
const {
|
|
LocalPluginPackageActivationPublisher,
|
|
} = require('@qinglong/local-admin/package-activation');
|
|
const {
|
|
analyzeLocalPluginPackageRecoveryCatalogPublisherKey,
|
|
collectLocalPluginPackageRecoveryCatalog,
|
|
createLocalPluginPackagePublisherTrustRegistry,
|
|
inspectLocalPluginPackageRecoveryCatalog,
|
|
publishLocalPluginPackageRecoveryCatalogEntry,
|
|
} = require('@qinglong/local-admin/package-recovery-catalog');
|
|
const {
|
|
localPluginPackagePublisherKeyRevocationImpactDigest,
|
|
publishLocalPluginPackagePublisherTrust,
|
|
proposeLocalPluginPackagePublisherKeyRevocation,
|
|
} = require('@qinglong/local-admin/package-publisher-trust');
|
|
const {
|
|
migrateLocalSqliteDatabase,
|
|
} = require('@qinglong/local-sqlite/migration');
|
|
const {
|
|
LocalSqlitePluginPackageInstallRepository,
|
|
} = require('@qinglong/local-sqlite/plugin-package-install');
|
|
const {
|
|
PLUGIN_PACKAGE_API_VERSION,
|
|
PLUGIN_PACKAGE_KIND,
|
|
planPluginPackageInstall,
|
|
} = require('@qinglong/runtime-core/plugin-package');
|
|
const {
|
|
PLUGIN_PACKAGE_SIGNATURE_SCHEMA,
|
|
pluginPackageContentTreeDigest,
|
|
pluginPackagePublisherSignaturePayload,
|
|
} = require('@qinglong/runtime-core/plugin-package-bundle');
|
|
const {
|
|
createPluginPackageInstall,
|
|
createPluginPackageLock,
|
|
pluginPackageInstallActionDigest,
|
|
pluginPackageInstallCreate,
|
|
pluginPackageInstallPlanDigest,
|
|
serializePluginPackageManifest,
|
|
} = require('@qinglong/runtime-core/plugin-package-install');
|
|
const {
|
|
PluginPackageRecoveryCoordinator,
|
|
} = require('@qinglong/runtime-core/plugin-package-recovery');
|
|
const {
|
|
LOCAL_PLUGIN_PACKAGE_PUBLISHER_TRUST_SCHEMA,
|
|
LOCAL_PLUGIN_PACKAGE_RECOVERY_SOURCE_SCHEMA,
|
|
MAX_LOCAL_PLUGIN_PACKAGE_RECOVERY_CATALOG_ENTRIES,
|
|
LocalPluginPackageRecoveryCatalogError,
|
|
createLocalPluginPackageRecoveryCatalogStageProvider,
|
|
} = require('../dist/production-process/pluginPackageRecoveryCatalog.js');
|
|
|
|
const PUBLISHER = 'packages.example.com';
|
|
const KEY_ID = 'release-2026';
|
|
|
|
function digest(value) {
|
|
return createHash('sha256').update(value).digest('hex');
|
|
}
|
|
|
|
function octal(value, bytes) {
|
|
return Buffer.from(`${value.toString(8).padStart(bytes - 1, '0')}\0`);
|
|
}
|
|
|
|
function tarHeader(entryPath, bytes) {
|
|
const header = Buffer.alloc(512);
|
|
Buffer.from(entryPath).copy(header, 0);
|
|
Buffer.from('0000644\0').copy(header, 100);
|
|
Buffer.from('0000000\0').copy(header, 108);
|
|
Buffer.from('0000000\0').copy(header, 116);
|
|
octal(bytes, 12).copy(header, 124);
|
|
Buffer.from('00000000000\0').copy(header, 136);
|
|
header.fill(0x20, 148, 156);
|
|
Buffer.from('0').copy(header, 156);
|
|
Buffer.from('ustar\0').copy(header, 257);
|
|
Buffer.from('00').copy(header, 263);
|
|
const checksum = header.reduce((total, byte) => total + byte, 0);
|
|
Buffer.from(`${checksum.toString(8).padStart(6, '0')}\0 `).copy(header, 148);
|
|
return header;
|
|
}
|
|
|
|
function tar(entries) {
|
|
const parts = [];
|
|
for (const entry of entries) {
|
|
parts.push(tarHeader(entry.path, entry.body.byteLength), entry.body);
|
|
const padding = (512 - (entry.body.byteLength % 512)) % 512;
|
|
if (padding > 0) parts.push(Buffer.alloc(padding));
|
|
}
|
|
parts.push(Buffer.alloc(1024));
|
|
return Buffer.concat(parts);
|
|
}
|
|
|
|
function manifest() {
|
|
return {
|
|
apiVersion: PLUGIN_PACKAGE_API_VERSION,
|
|
kind: PLUGIN_PACKAGE_KIND,
|
|
metadata: {
|
|
name: 'example-monitor',
|
|
displayName: 'Example Monitor',
|
|
version: '1.2.0',
|
|
description: 'One bounded package',
|
|
license: 'Apache-2.0',
|
|
},
|
|
spec: {
|
|
compatibility: {
|
|
qinglong: '>=3.0.0-0 <4.0.0',
|
|
architectures: ['arm64'],
|
|
deploymentProfiles: ['edge'],
|
|
},
|
|
runtimes: [],
|
|
resources: {
|
|
memory: { recommended: '16Mi' },
|
|
disk: { install: '4Mi', working: '16Mi' },
|
|
},
|
|
permissions: {
|
|
network: { allowedHosts: [] },
|
|
secrets: [],
|
|
tools: [],
|
|
},
|
|
contents: {
|
|
tasks: ['tasks/collect.yaml'],
|
|
workflows: [],
|
|
prompts: [],
|
|
tools: [],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
function packageFixture(kind) {
|
|
const packageManifest = manifest();
|
|
const manifestBody = Buffer.from(
|
|
serializePluginPackageManifest(packageManifest),
|
|
);
|
|
const taskBody = Buffer.from(
|
|
'apiVersion: qinglong.io/v1\nkind: Task\nmetadata:\n name: collect\n',
|
|
);
|
|
const artifact = tar([
|
|
{ path: 'package.json', body: manifestBody },
|
|
{ path: 'tasks/collect.yaml', body: taskBody },
|
|
]);
|
|
const artifactDigest = digest(artifact);
|
|
const environment = {
|
|
qinglongVersion: '3.0.0-alpha.0',
|
|
architecture: 'arm64',
|
|
deploymentProfile: 'edge',
|
|
runtimes: [],
|
|
availableMemoryBytes: 128 * 1024 * 1024,
|
|
availableDiskBytes: 256 * 1024 * 1024,
|
|
};
|
|
const plan = planPluginPackageInstall(packageManifest, environment);
|
|
const source = {
|
|
kind,
|
|
locator:
|
|
kind === 'offline'
|
|
? `offline:sha256:${artifactDigest}`
|
|
: `oci://registry.example.com/qinglong/example-monitor@sha256:${'f'.repeat(
|
|
64,
|
|
)}`,
|
|
artifactDigest,
|
|
artifactBytes: artifact.byteLength,
|
|
contentDigest: pluginPackageContentTreeDigest([
|
|
{
|
|
path: 'tasks/collect.yaml',
|
|
bytes: taskBody.byteLength,
|
|
digest: digest(taskBody),
|
|
},
|
|
]),
|
|
};
|
|
const action = {
|
|
lockId: `lock-${kind}-001`,
|
|
projectId: 'default',
|
|
manifest: packageManifest,
|
|
plan,
|
|
environment,
|
|
source,
|
|
architecture: 'arm64',
|
|
deploymentProfile: 'edge',
|
|
targetGeneration: 1,
|
|
};
|
|
const lock = createPluginPackageLock({
|
|
...action,
|
|
approval: {
|
|
requestId: `approval-${kind}-001`,
|
|
requestVersion: 1,
|
|
dispatchId: `dispatch-${kind}-001`,
|
|
actionDigest: pluginPackageInstallActionDigest(action),
|
|
previewDigest: pluginPackageInstallPlanDigest(plan),
|
|
approvedBy: { type: 'user', id: 'owner-001' },
|
|
approvedAtMs: 100,
|
|
expiresAtMs: 10_000,
|
|
fence: { projectVersion: 1, bindingVersion: 1 },
|
|
},
|
|
createdAtMs: 200,
|
|
});
|
|
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
|
|
const key = {
|
|
publisher: PUBLISHER,
|
|
keyId: KEY_ID,
|
|
publicKeyPem: publicKey.export({ format: 'pem', type: 'spki' }),
|
|
notBeforeMs: 100,
|
|
notAfterMs: 10_000,
|
|
};
|
|
const signature = {
|
|
schema: PLUGIN_PACKAGE_SIGNATURE_SCHEMA,
|
|
publisher: PUBLISHER,
|
|
keyId: KEY_ID,
|
|
signature: sign(
|
|
null,
|
|
pluginPackagePublisherSignaturePayload(lock, PUBLISHER, KEY_ID),
|
|
privateKey,
|
|
).toString('base64url'),
|
|
};
|
|
return { artifact, key, lock, packageManifest, signature };
|
|
}
|
|
|
|
function directories(t) {
|
|
const unresolved = fs.mkdtempSync(
|
|
path.join(os.tmpdir(), 'ql3-package-catalog-'),
|
|
);
|
|
const root = fs.realpathSync(unresolved);
|
|
const catalogRoot = path.join(root, 'catalog');
|
|
const bundleRoot = path.join(root, 'bundles');
|
|
const stagingRoot = path.join(root, 'staging');
|
|
const trustRoot = path.join(root, 'publisher-trust');
|
|
const publisherTrustFilePath = path.join(trustRoot, 'current.json');
|
|
fs.mkdirSync(catalogRoot, { mode: 0o700 });
|
|
fs.mkdirSync(bundleRoot, { mode: 0o700 });
|
|
fs.mkdirSync(stagingRoot, { mode: 0o700 });
|
|
fs.mkdirSync(trustRoot, { mode: 0o700 });
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
return {
|
|
root,
|
|
catalogRoot,
|
|
bundleRoot,
|
|
stagingRoot,
|
|
trustRoot,
|
|
publisherTrustFilePath,
|
|
};
|
|
}
|
|
|
|
function writePrivateJson(filePath, value) {
|
|
fs.writeFileSync(filePath, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
|
fs.chmodSync(filePath, 0o600);
|
|
}
|
|
|
|
async function ensureTrust(filesystem, value) {
|
|
if (fs.existsSync(filesystem.publisherTrustFilePath)) return;
|
|
await publishLocalPluginPackagePublisherTrust({
|
|
trustRoot: filesystem.trustRoot,
|
|
mode: 'provision',
|
|
expectedGeneration: 0,
|
|
mutationId: 'application-test-trust-v1',
|
|
occurredAtMs: value.lock.createdAtMs,
|
|
trust: {
|
|
schema: LOCAL_PLUGIN_PACKAGE_PUBLISHER_TRUST_SCHEMA,
|
|
keys: [value.key],
|
|
},
|
|
});
|
|
}
|
|
|
|
async function publish(filesystem, value) {
|
|
const bundlePath = path.join(
|
|
filesystem.bundleRoot,
|
|
`${value.lock.source.artifactDigest}.bundle`,
|
|
);
|
|
fs.writeFileSync(bundlePath, value.artifact, { mode: 0o600 });
|
|
fs.chmodSync(bundlePath, 0o600);
|
|
await ensureTrust(filesystem, value);
|
|
const sourcePath = path.join(
|
|
filesystem.catalogRoot,
|
|
`${value.lock.lockDigest}.json`,
|
|
);
|
|
writePrivateJson(sourcePath, {
|
|
schema: LOCAL_PLUGIN_PACKAGE_RECOVERY_SOURCE_SCHEMA,
|
|
lockDigest: value.lock.lockDigest,
|
|
source: value.lock.source,
|
|
bundlePath,
|
|
manifest: value.packageManifest,
|
|
signature: value.signature,
|
|
});
|
|
return { bundlePath, sourcePath };
|
|
}
|
|
|
|
function provider(filesystem) {
|
|
return createLocalPluginPackageRecoveryCatalogStageProvider({
|
|
catalogRoot: filesystem.catalogRoot,
|
|
bundleRoot: filesystem.bundleRoot,
|
|
publisherTrustFilePath: filesystem.publisherTrustFilePath,
|
|
stagingRoot: filesystem.stagingRoot,
|
|
});
|
|
}
|
|
|
|
test('consumes an entry published by the authenticated catalog boundary', async (t) => {
|
|
const filesystem = directories(t);
|
|
const value = packageFixture('offline');
|
|
const sourceBundlePath = path.join(filesystem.root, 'incoming.bundle');
|
|
fs.writeFileSync(sourceBundlePath, value.artifact, { mode: 0o600 });
|
|
fs.chmodSync(sourceBundlePath, 0o600);
|
|
await ensureTrust(filesystem, value);
|
|
|
|
let publicationGuards = 0;
|
|
const published = await publishLocalPluginPackageRecoveryCatalogEntry({
|
|
catalogRoot: filesystem.catalogRoot,
|
|
bundleRoot: filesystem.bundleRoot,
|
|
sourceBundlePath,
|
|
lock: value.lock,
|
|
manifest: value.packageManifest,
|
|
signature: value.signature,
|
|
trust: createLocalPluginPackagePublisherTrustRegistry({
|
|
schema: LOCAL_PLUGIN_PACKAGE_PUBLISHER_TRUST_SCHEMA,
|
|
keys: [value.key],
|
|
}),
|
|
confirmPublicationAllowed() {
|
|
publicationGuards += 1;
|
|
assert.equal(
|
|
fs
|
|
.readdirSync(filesystem.catalogRoot)
|
|
.filter((entry) => /^\.qlpkg-catalog-[0-9a-f]{32}\.tmp$/.test(entry))
|
|
.length,
|
|
1,
|
|
);
|
|
},
|
|
});
|
|
|
|
assert.equal(published.status, 'published');
|
|
assert.equal(publicationGuards, 2);
|
|
assert.equal(published.lockDigest, value.lock.lockDigest);
|
|
assert.equal(
|
|
fs.statSync(
|
|
path.join(
|
|
filesystem.bundleRoot,
|
|
`${value.lock.source.artifactDigest}.bundle`,
|
|
),
|
|
).mode & 0o777,
|
|
0o600,
|
|
);
|
|
const staged = await provider(filesystem).stage(value.lock);
|
|
assert.equal(staged.artifactDigest, value.lock.source.artifactDigest);
|
|
assert.equal(staged.manifestDigest, value.lock.manifestDigest);
|
|
|
|
assert.equal(
|
|
(
|
|
await publishLocalPluginPackageRecoveryCatalogEntry({
|
|
catalogRoot: filesystem.catalogRoot,
|
|
bundleRoot: filesystem.bundleRoot,
|
|
sourceBundlePath,
|
|
lock: value.lock,
|
|
manifest: value.packageManifest,
|
|
signature: value.signature,
|
|
trust: createLocalPluginPackagePublisherTrustRegistry({
|
|
schema: LOCAL_PLUGIN_PACKAGE_PUBLISHER_TRUST_SCHEMA,
|
|
keys: [value.key],
|
|
}),
|
|
})
|
|
).status,
|
|
'existing',
|
|
);
|
|
assert.deepEqual(
|
|
inspectLocalPluginPackageRecoveryCatalog({
|
|
catalogRoot: filesystem.catalogRoot,
|
|
bundleRoot: filesystem.bundleRoot,
|
|
}),
|
|
{
|
|
lockDigests: [value.lock.lockDigest],
|
|
entryCount: 1,
|
|
bundleCount: 1,
|
|
unresolvedTransactions: 0,
|
|
},
|
|
);
|
|
assert.deepEqual(
|
|
analyzeLocalPluginPackageRecoveryCatalogPublisherKey({
|
|
catalogRoot: filesystem.catalogRoot,
|
|
bundleRoot: filesystem.bundleRoot,
|
|
publisher: PUBLISHER,
|
|
keyId: KEY_ID,
|
|
}),
|
|
{
|
|
catalogEntryCount: 1,
|
|
bundleCount: 1,
|
|
matchingEntryCount: 1,
|
|
unresolvedTransactions: 0,
|
|
},
|
|
);
|
|
await assert.rejects(
|
|
publishLocalPluginPackageRecoveryCatalogEntry({
|
|
catalogRoot: filesystem.catalogRoot,
|
|
bundleRoot: filesystem.bundleRoot,
|
|
sourceBundlePath,
|
|
lock: value.lock,
|
|
manifest: value.packageManifest,
|
|
signature: value.signature,
|
|
trust: createLocalPluginPackagePublisherTrustRegistry({
|
|
schema: LOCAL_PLUGIN_PACKAGE_PUBLISHER_TRUST_SCHEMA,
|
|
keys: [value.key],
|
|
}),
|
|
confirmPublicationAllowed() {
|
|
throw new Error('retirement intent won');
|
|
},
|
|
}),
|
|
/catalog publication is unavailable/,
|
|
);
|
|
assert.equal(
|
|
fs
|
|
.readdirSync(filesystem.catalogRoot)
|
|
.some((entry) => entry.endsWith('.tmp')),
|
|
false,
|
|
);
|
|
|
|
const catalogTransaction = path.join(
|
|
filesystem.catalogRoot,
|
|
`.qlpkg-catalog-${'a'.repeat(32)}.tmp`,
|
|
);
|
|
const bundleTransaction = path.join(
|
|
filesystem.bundleRoot,
|
|
`.qlpkg-bundle-${'b'.repeat(32)}.tmp`,
|
|
);
|
|
fs.writeFileSync(catalogTransaction, '', { mode: 0o600 });
|
|
fs.writeFileSync(bundleTransaction, '', { mode: 0o600 });
|
|
let deleteFences = 0;
|
|
const collected = await collectLocalPluginPackageRecoveryCatalog({
|
|
catalogRoot: filesystem.catalogRoot,
|
|
bundleRoot: filesystem.bundleRoot,
|
|
candidateLockDigests: [value.lock.lockDigest],
|
|
maxDeletes: 4,
|
|
beforeDelete() {
|
|
deleteFences += 1;
|
|
},
|
|
});
|
|
assert.deepEqual(collected, {
|
|
removedEntries: 1,
|
|
removedBundles: 1,
|
|
removedTransactions: 2,
|
|
remaining: false,
|
|
});
|
|
assert.equal(deleteFences, 1);
|
|
});
|
|
|
|
test('verifies a historical lock at its immutable creation time', async (t) => {
|
|
const filesystem = directories(t);
|
|
const value = packageFixture('offline');
|
|
await publish(filesystem, {
|
|
...value,
|
|
key: {
|
|
...value.key,
|
|
notAfterMs: value.lock.createdAtMs + 1,
|
|
},
|
|
});
|
|
|
|
const staged = await provider(filesystem).stage(value.lock);
|
|
assert.equal(staged.artifactDigest, value.lock.source.artifactDigest);
|
|
assert.equal(staged.manifestDigest, value.lock.manifestDigest);
|
|
});
|
|
|
|
test('blocks queued staging as soon as a compromise proposal is durable', async (t) => {
|
|
const filesystem = directories(t);
|
|
const value = packageFixture('offline');
|
|
await publish(filesystem, value);
|
|
const impactedLockDigests = [value.lock.lockDigest];
|
|
const impact = {
|
|
catalogEntryCount: 1,
|
|
bundleCount: 1,
|
|
matchingEntryCount: 1,
|
|
unresolvedTransactions: 0,
|
|
impactedLockDigests,
|
|
impactDigest: localPluginPackagePublisherKeyRevocationImpactDigest({
|
|
publisher: PUBLISHER,
|
|
keyId: KEY_ID,
|
|
catalogEntryCount: 1,
|
|
bundleCount: 1,
|
|
matchingEntryCount: 1,
|
|
unresolvedTransactions: 0,
|
|
impactedLockDigests,
|
|
}),
|
|
};
|
|
await proposeLocalPluginPackagePublisherKeyRevocation({
|
|
trustRoot: filesystem.trustRoot,
|
|
expectedGeneration: 1,
|
|
mutationId: 'application-test-revoke-v2',
|
|
occurredAtMs: 300,
|
|
publisher: PUBLISHER,
|
|
keyId: KEY_ID,
|
|
proposerSubjectId: 'owner-a',
|
|
impact,
|
|
});
|
|
await assert.rejects(
|
|
provider(filesystem).stage(value.lock),
|
|
/blocked by a durable lifecycle mutation/,
|
|
);
|
|
assert.deepEqual(fs.readdirSync(filesystem.stagingRoot), []);
|
|
});
|
|
|
|
for (const kind of ['offline', 'oci']) {
|
|
test(`stages one exact ${kind} lock from the materialized catalog`, async (t) => {
|
|
const filesystem = directories(t);
|
|
const value = packageFixture(kind);
|
|
const files = await publish(filesystem, value);
|
|
const stageProvider = provider(filesystem);
|
|
const staged = await stageProvider.stage(value.lock);
|
|
assert.equal(staged.stageRef, `local-stage:${value.lock.lockDigest}`);
|
|
assert.equal(staged.artifactDigest, value.lock.source.artifactDigest);
|
|
assert.equal(staged.manifestDigest, value.lock.manifestDigest);
|
|
assert.equal(staged.contentDigest, value.lock.source.contentDigest);
|
|
|
|
fs.unlinkSync(files.bundlePath);
|
|
assert.deepEqual(await stageProvider.stage(value.lock), staged);
|
|
});
|
|
}
|
|
|
|
test('recovers one durable queued install to active through the catalog', async (t) => {
|
|
const filesystem = directories(t);
|
|
const activationRoot = path.join(filesystem.root, 'activation');
|
|
fs.mkdirSync(activationRoot, { mode: 0o700 });
|
|
const value = packageFixture('offline');
|
|
await publish(filesystem, value);
|
|
const database = new DatabaseSync(':memory:');
|
|
database.exec('PRAGMA foreign_keys = ON');
|
|
await migrateLocalSqliteDatabase(database);
|
|
t.after(() => database.close());
|
|
const repository = new LocalSqlitePluginPackageInstallRepository(database);
|
|
const queued = createPluginPackageInstall(value.lock, {
|
|
installationId: 'catalog-recovery-install-001',
|
|
mutationId: 'catalog-recovery-create-001',
|
|
occurredAtMs: 300,
|
|
});
|
|
await repository.create(pluginPackageInstallCreate(value.lock, queued, null));
|
|
const recovery = new PluginPackageRecoveryCoordinator({
|
|
repository,
|
|
stageProvider: provider(filesystem),
|
|
publisher: new LocalPluginPackageActivationPublisher({
|
|
stagingRoot: filesystem.stagingRoot,
|
|
activationRoot,
|
|
now: () => 700,
|
|
}),
|
|
now: () => 600,
|
|
});
|
|
|
|
const result = await recovery.recover({ pageSize: 1, maxPages: 2 });
|
|
|
|
assert.equal(result.safeToAdmit, true);
|
|
assert.equal(result.settled, 1);
|
|
const active = await repository.find(
|
|
value.lock.projectId,
|
|
value.lock.packageName,
|
|
);
|
|
assert.equal(active.state, 'active');
|
|
assert.equal(active.activeLockDigest, value.lock.lockDigest);
|
|
});
|
|
|
|
test('construction is lazy and a missing locked entry fails closed', async (t) => {
|
|
const root = path.join(directoryName(t), 'not-created');
|
|
const stageProvider = createLocalPluginPackageRecoveryCatalogStageProvider({
|
|
catalogRoot: root,
|
|
bundleRoot: path.join(root, 'bundles'),
|
|
publisherTrustFilePath: path.join(root, 'trust', 'current.json'),
|
|
stagingRoot: path.join(root, 'staging'),
|
|
});
|
|
await assert.rejects(
|
|
stageProvider.stage(packageFixture('offline').lock),
|
|
LocalPluginPackageRecoveryCatalogError,
|
|
);
|
|
});
|
|
|
|
function directoryName(t) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-catalog-lazy-'));
|
|
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
return fs.realpathSync(root);
|
|
}
|
|
|
|
test('rejects source drift, widened trust and unknown catalog entries', async (t) => {
|
|
const filesystem = directories(t);
|
|
const value = packageFixture('offline');
|
|
const files = await publish(filesystem, value);
|
|
const entry = JSON.parse(fs.readFileSync(files.sourcePath, 'utf8'));
|
|
entry.source.artifactDigest = 'f'.repeat(64);
|
|
writePrivateJson(files.sourcePath, entry);
|
|
await assert.rejects(
|
|
provider(filesystem).stage(value.lock),
|
|
/does not match its durable PackageLock/,
|
|
);
|
|
|
|
await publish(filesystem, value);
|
|
fs.chmodSync(filesystem.publisherTrustFilePath, 0o644);
|
|
await assert.rejects(
|
|
provider(filesystem).stage(value.lock),
|
|
/trust file must be a bounded owner-only regular file/,
|
|
);
|
|
|
|
fs.chmodSync(filesystem.publisherTrustFilePath, 0o600);
|
|
fs.writeFileSync(path.join(filesystem.catalogRoot, 'unexpected'), '', {
|
|
mode: 0o600,
|
|
});
|
|
await assert.rejects(
|
|
provider(filesystem).stage(value.lock),
|
|
/unbounded or unknown entries/,
|
|
);
|
|
});
|
|
|
|
test('hard-caps catalog cardinality before reading a source', async (t) => {
|
|
const filesystem = directories(t);
|
|
const value = packageFixture('offline');
|
|
await publish(filesystem, value);
|
|
for (
|
|
let index = 0;
|
|
index < MAX_LOCAL_PLUGIN_PACKAGE_RECOVERY_CATALOG_ENTRIES;
|
|
index += 1
|
|
) {
|
|
const name = index.toString(16).padStart(64, '0');
|
|
if (name === value.lock.lockDigest) continue;
|
|
writePrivateJson(path.join(filesystem.catalogRoot, `${name}.json`), {});
|
|
}
|
|
const existing = fs.readdirSync(filesystem.catalogRoot).length;
|
|
if (existing <= MAX_LOCAL_PLUGIN_PACKAGE_RECOVERY_CATALOG_ENTRIES) {
|
|
writePrivateJson(
|
|
path.join(filesystem.catalogRoot, `${'e'.repeat(64)}.json`),
|
|
{},
|
|
);
|
|
}
|
|
assert.equal(
|
|
fs.readdirSync(filesystem.catalogRoot).length >
|
|
MAX_LOCAL_PLUGIN_PACKAGE_RECOVERY_CATALOG_ENTRIES,
|
|
true,
|
|
);
|
|
await assert.rejects(
|
|
provider(filesystem).stage(value.lock),
|
|
/unbounded or unknown entries/,
|
|
);
|
|
});
|
|
|
|
test('publishes catalog authority only through its explicit subpath', () => {
|
|
const root = require('../dist');
|
|
const subpath = require('@qinglong/local-application/plugin-package-recovery-catalog');
|
|
assert.equal(
|
|
root.createLocalPluginPackageRecoveryCatalogStageProvider,
|
|
undefined,
|
|
);
|
|
assert.equal(
|
|
subpath.createLocalPluginPackageRecoveryCatalogStageProvider,
|
|
createLocalPluginPackageRecoveryCatalogStageProvider,
|
|
);
|
|
});
|