mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-02 14:08:44 +02:00
504 lines
23 KiB
JavaScript
504 lines
23 KiB
JavaScript
/**
|
|
* Contract and lifecycle tests for the opt-in Nasiko CLI lifecycle bridge.
|
|
*/
|
|
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const { spawnSync } = require('child_process');
|
|
|
|
const REPO_ROOT = path.join(__dirname, '..', '..');
|
|
|
|
function read(relativePath) {
|
|
return fs.readFileSync(path.join(REPO_ROOT, relativePath), 'utf8');
|
|
}
|
|
|
|
function readJson(relativePath) {
|
|
return JSON.parse(read(relativePath));
|
|
}
|
|
|
|
async function runTest(name, testFunction) {
|
|
try {
|
|
await testFunction();
|
|
console.log(` ✓ ${name}`);
|
|
return true;
|
|
} catch (error) {
|
|
console.log(` ✗ ${name}`);
|
|
console.error(` ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function sha256Digest(value) {
|
|
const crypto = require('crypto');
|
|
return `sha256:${crypto.createHash('sha256').update(value).digest('hex')}`;
|
|
}
|
|
|
|
function tarGzipFixture({
|
|
name = 'nasiko',
|
|
payload = Buffer.from('x'),
|
|
sizeField = null,
|
|
padding = true,
|
|
terminatorBlocks = 2,
|
|
trailing = Buffer.alloc(0),
|
|
} = {}) {
|
|
const zlib = require('zlib');
|
|
const header = Buffer.alloc(512);
|
|
header.write(name, 0, 100, 'utf8');
|
|
header.write(sizeField || `${payload.length.toString(8).padStart(11, '0')}\0`, 124, 12, 'ascii');
|
|
header[156] = '0'.charCodeAt(0);
|
|
const paddingBytes = padding ? Buffer.alloc((512 - (payload.length % 512)) % 512) : Buffer.alloc(0);
|
|
return zlib.gzipSync(Buffer.concat([
|
|
header,
|
|
payload,
|
|
paddingBytes,
|
|
Buffer.alloc(terminatorBlocks * 512),
|
|
trailing,
|
|
]));
|
|
}
|
|
|
|
async function main() {
|
|
console.log('\n=== Testing Nasiko CLI lifecycle bridge ===\n');
|
|
|
|
const tests = [
|
|
['qualifies only pinned platform releases and rejects latest', () => {
|
|
const {
|
|
getQualifiedRelease,
|
|
normalizePlatform,
|
|
} = require('../../scripts/lib/nasiko-release');
|
|
|
|
assert.deepStrictEqual(normalizePlatform('darwin', 'arm64'), {
|
|
os: 'darwin',
|
|
arch: 'arm64',
|
|
binaryName: 'nasiko',
|
|
});
|
|
assert.deepStrictEqual(normalizePlatform('win32', 'x64'), {
|
|
os: 'windows',
|
|
arch: 'amd64',
|
|
binaryName: 'nasiko.exe',
|
|
});
|
|
assert.match(getQualifiedRelease('v0.1.0', 'linux', 'x64').manifestDigest, /^sha256:[a-f0-9]{64}$/);
|
|
assert.match(getQualifiedRelease('v0.1.0', 'linux', 'x64').binaryDigest, /^sha256:[a-f0-9]{64}$/);
|
|
assert.strictEqual(getQualifiedRelease('v0.1.0', 'linux', 'x64').license, 'Apache-2.0');
|
|
assert.throws(() => getQualifiedRelease('latest', 'darwin', 'arm64'), /pinned version/i);
|
|
assert.throws(() => getQualifiedRelease('v1.0.0', 'darwin', 'arm64'), /not qualified/i);
|
|
assert.throws(() => normalizePlatform('freebsd', 'x64'), /unsupported platform/i);
|
|
assert.throws(() => normalizePlatform('darwin', 'ia32'), /unsupported architecture/i);
|
|
}],
|
|
['requires explicit consent while dry-run remains offline and read-only', async () => {
|
|
const { installNasiko } = require('../../scripts/lib/nasiko-release');
|
|
let fetchCount = 0;
|
|
const dependencies = {
|
|
fetchBytes: async () => {
|
|
fetchCount += 1;
|
|
throw new Error('dry-run fetched the network');
|
|
},
|
|
platform: 'darwin',
|
|
arch: 'arm64',
|
|
};
|
|
|
|
await assert.rejects(
|
|
installNasiko({ version: 'v0.1.0', yes: false }, dependencies),
|
|
/explicit --yes/i
|
|
);
|
|
const plan = await installNasiko({ version: 'v0.1.0', dryRun: true }, dependencies);
|
|
assert.strictEqual(plan.dryRun, true);
|
|
assert.strictEqual(plan.version, 'v0.1.0');
|
|
assert.strictEqual(plan.registryOrigin, 'https://registry.nasiko.dev');
|
|
assert.strictEqual(fetchCount, 0);
|
|
}],
|
|
['cleans an exclusively created lifecycle lock when initialization fails', () => {
|
|
const { acquireLifecycleLock } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-lock-'));
|
|
const lockPath = path.join(installRoot, '.ecc-nasiko-lifecycle.lock');
|
|
try {
|
|
const failingFileSystem = {
|
|
...fs,
|
|
writeFileSync: () => { throw new Error('lock metadata unavailable'); },
|
|
};
|
|
assert.throws(() => acquireLifecycleLock(installRoot, failingFileSystem), /metadata unavailable/i);
|
|
assert.strictEqual(fs.existsSync(lockPath), false);
|
|
const releaseLock = acquireLifecycleLock(installRoot);
|
|
assert.strictEqual(fs.existsSync(lockPath), true);
|
|
releaseLock();
|
|
assert.strictEqual(fs.existsSync(lockPath), false);
|
|
} finally { fs.rmSync(installRoot, { recursive: true, force: true }); }
|
|
}],
|
|
['recovers only locks whose recorded owner is confirmed dead', () => {
|
|
const { acquireLifecycleLock } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-stale-lock-'));
|
|
const lockPath = path.join(installRoot, '.ecc-nasiko-lifecycle.lock');
|
|
try {
|
|
fs.writeFileSync(lockPath, `${JSON.stringify({
|
|
pid: 424242,
|
|
startedAt: '2026-08-25T00:00:00.000Z',
|
|
token: 'stale-owner',
|
|
})}\n`, { mode: 0o600 });
|
|
assert.throws(
|
|
() => acquireLifecycleLock(installRoot, fs, { isProcessAlive: () => true }),
|
|
/already in progress/i
|
|
);
|
|
const releaseLock = acquireLifecycleLock(installRoot, fs, { isProcessAlive: () => false });
|
|
assert.strictEqual(fs.existsSync(lockPath), true);
|
|
releaseLock();
|
|
assert.strictEqual(fs.existsSync(lockPath), false);
|
|
} finally { fs.rmSync(installRoot, { recursive: true, force: true }); }
|
|
}],
|
|
['refuses to recover malformed lifecycle-lock ownership', () => {
|
|
const { acquireLifecycleLock } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-malformed-lock-'));
|
|
const lockPath = path.join(installRoot, '.ecc-nasiko-lifecycle.lock');
|
|
try {
|
|
fs.writeFileSync(lockPath, '{"pid":"unknown"}\n', { mode: 0o600 });
|
|
assert.throws(
|
|
() => acquireLifecycleLock(installRoot, fs, { isProcessAlive: () => false }),
|
|
/already in progress/i
|
|
);
|
|
} finally { fs.rmSync(installRoot, { recursive: true, force: true }); }
|
|
}],
|
|
['recovers a lock abandoned by a finished process', () => {
|
|
const { acquireLifecycleLock } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-dead-process-lock-'));
|
|
const lockPath = path.join(installRoot, '.ecc-nasiko-lifecycle.lock');
|
|
const modulePath = path.join(REPO_ROOT, 'scripts', 'lib', 'nasiko-release.js');
|
|
try {
|
|
const child = spawnSync(process.execPath, ['-e',
|
|
`require(${JSON.stringify(modulePath)}).acquireLifecycleLock(${JSON.stringify(installRoot)});`
|
|
], { encoding: 'utf8' });
|
|
assert.strictEqual(child.status, 0, child.stderr);
|
|
assert.strictEqual(fs.existsSync(lockPath), true);
|
|
const releaseLock = acquireLifecycleLock(installRoot);
|
|
releaseLock();
|
|
assert.strictEqual(fs.existsSync(lockPath), false);
|
|
} finally { fs.rmSync(installRoot, { recursive: true, force: true }); }
|
|
}],
|
|
['a prior release callback never removes a replacement lifecycle lock', () => {
|
|
const { acquireLifecycleLock } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-replaced-lock-'));
|
|
const lockPath = path.join(installRoot, '.ecc-nasiko-lifecycle.lock');
|
|
const displacedPath = `${lockPath}.displaced`;
|
|
try {
|
|
const releaseLock = acquireLifecycleLock(installRoot);
|
|
fs.renameSync(lockPath, displacedPath);
|
|
fs.writeFileSync(lockPath, '{"pid":1,"startedAt":"2026-08-25T00:00:00.000Z","token":"replacement"}\n');
|
|
releaseLock();
|
|
assert.strictEqual(fs.existsSync(lockPath), true);
|
|
} finally { fs.rmSync(installRoot, { recursive: true, force: true }); }
|
|
}],
|
|
['uses descriptor identity when Windows path stats disagree', () => {
|
|
const { acquireLifecycleLock } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-windows-identity-'));
|
|
const lockPath = path.join(installRoot, '.ecc-nasiko-lifecycle.lock');
|
|
const windowsLikeFileSystem = {
|
|
...fs,
|
|
lstatSync: target => {
|
|
const stats = fs.lstatSync(target);
|
|
return {
|
|
...stats,
|
|
dev: Number(stats.dev) + 1,
|
|
isDirectory: () => stats.isDirectory(),
|
|
isFile: () => stats.isFile(),
|
|
isSymbolicLink: () => stats.isSymbolicLink(),
|
|
};
|
|
},
|
|
};
|
|
try {
|
|
const releaseLock = acquireLifecycleLock(installRoot, windowsLikeFileSystem);
|
|
releaseLock();
|
|
assert.strictEqual(fs.existsSync(lockPath), false);
|
|
} finally { fs.rmSync(installRoot, { recursive: true, force: true }); }
|
|
}],
|
|
['verifies manifest and blob digests before an atomic install', async () => {
|
|
const { installNasiko } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-green-'));
|
|
const binary = Buffer.from('#!/bin/sh\necho nasiko 0.1.0\n');
|
|
const manifest = Buffer.from(JSON.stringify({
|
|
schemaVersion: 2,
|
|
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
|
layers: [{
|
|
mediaType: 'application/gzip',
|
|
digest: sha256Digest(Buffer.from('verified archive')),
|
|
size: 16,
|
|
}],
|
|
}));
|
|
const archive = Buffer.from('verified archive');
|
|
|
|
try {
|
|
const result = await installNasiko({
|
|
version: 'v0.1.0',
|
|
yes: true,
|
|
installDir: installRoot,
|
|
}, {
|
|
platform: 'darwin',
|
|
arch: 'arm64',
|
|
releaseOverride: {
|
|
manifestDigest: sha256Digest(manifest),
|
|
binaryDigest: sha256Digest(binary),
|
|
},
|
|
fetchBytes: async (url) => url.includes('/manifests/') ? manifest : archive,
|
|
extractBinary: () => binary,
|
|
});
|
|
assert.strictEqual(result.installed, true);
|
|
assert.strictEqual(result.version, 'v0.1.0');
|
|
assert.strictEqual(fs.existsSync(path.join(installRoot, 'nasiko')), true);
|
|
assert.strictEqual(fs.existsSync(path.join(installRoot, '.ecc-nasiko-install.json')), true);
|
|
const { getQualifiedRelease, inspectInstalledNasiko, uninstallNasiko } = require('../../scripts/lib/nasiko-release');
|
|
const fakeRelease = {
|
|
...getQualifiedRelease('v0.1.0', 'darwin', 'arm64'),
|
|
manifestDigest: sha256Digest(manifest),
|
|
binaryDigest: sha256Digest(binary),
|
|
};
|
|
const preview = await uninstallNasiko({ installDir: installRoot, dryRun: true }, {
|
|
platform: 'darwin', arch: 'arm64',
|
|
});
|
|
assert.strictEqual(preview.dryRun, true);
|
|
assert.strictEqual(preview.version, 'v0.1.0');
|
|
assert.strictEqual(preview.destination, path.join(fs.realpathSync(installRoot), 'nasiko'));
|
|
let renameCount = 0;
|
|
await assert.rejects(async () => uninstallNasiko({ installDir: installRoot, yes: true }, {
|
|
platform: 'darwin', arch: 'arm64',
|
|
inspectInstalled: destination => inspectInstalledNasiko(destination, () => fakeRelease),
|
|
rename: (source, destination) => {
|
|
renameCount += 1;
|
|
if (renameCount === 2) throw new Error('metadata staging unavailable');
|
|
fs.renameSync(source, destination);
|
|
},
|
|
}), /metadata staging unavailable/i);
|
|
assert.strictEqual(fs.existsSync(path.join(installRoot, 'nasiko')), true);
|
|
assert.strictEqual(fs.existsSync(path.join(installRoot, '.ecc-nasiko-install.json')), true);
|
|
await uninstallNasiko({ installDir: installRoot, yes: true }, {
|
|
platform: 'darwin', arch: 'arm64',
|
|
inspectInstalled: destination => inspectInstalledNasiko(destination, () => fakeRelease),
|
|
});
|
|
assert.strictEqual(fs.existsSync(path.join(installRoot, 'nasiko')), false);
|
|
assert.strictEqual(fs.existsSync(path.join(installRoot, '.ecc-nasiko-install.json')), false);
|
|
} finally {
|
|
fs.rmSync(installRoot, { recursive: true, force: true });
|
|
}
|
|
}],
|
|
['rejects digest mismatch and unsafe archive entries without installing', async () => {
|
|
const { extractQualifiedTarGzip, installNasiko } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-reject-'));
|
|
const manifest = Buffer.from('{"schemaVersion":2,"layers":[]}');
|
|
try {
|
|
await assert.rejects(
|
|
installNasiko({ version: 'v0.1.0', yes: true, installDir: installRoot }, {
|
|
platform: 'darwin',
|
|
arch: 'arm64',
|
|
releaseOverride: { manifestDigest: `sha256:${'0'.repeat(64)}` },
|
|
fetchBytes: async () => manifest,
|
|
}),
|
|
/manifest digest mismatch/i
|
|
);
|
|
assert.strictEqual(fs.existsSync(path.join(installRoot, 'nasiko')), false);
|
|
assert.throws(() => extractQualifiedTarGzip(Buffer.from('not gzip'), 'nasiko'), /invalid|size limit/i);
|
|
} finally {
|
|
fs.rmSync(installRoot, { recursive: true, force: true });
|
|
}
|
|
}],
|
|
['accepts one complete tar entry and rejects malformed tar boundaries', () => {
|
|
const { extractQualifiedTarGzip } = require('../../scripts/lib/nasiko-release');
|
|
assert.deepStrictEqual(
|
|
extractQualifiedTarGzip(tarGzipFixture(), 'nasiko'),
|
|
Buffer.from('x')
|
|
);
|
|
assert.throws(
|
|
() => extractQualifiedTarGzip(tarGzipFixture({ padding: false }), 'nasiko'),
|
|
/unsafe|truncated|terminator/i
|
|
);
|
|
assert.throws(
|
|
() => extractQualifiedTarGzip(tarGzipFixture({ trailing: Buffer.from([1]) }), 'nasiko'),
|
|
/unsafe|trailing/i
|
|
);
|
|
assert.throws(
|
|
() => extractQualifiedTarGzip(tarGzipFixture({ sizeField: '00000000001x' }), 'nasiko'),
|
|
/size|octal|unsafe/i
|
|
);
|
|
assert.throws(
|
|
() => extractQualifiedTarGzip(tarGzipFixture({ terminatorBlocks: 1 }), 'nasiko'),
|
|
/terminator|truncated|unsafe/i
|
|
);
|
|
}],
|
|
['read-only status never executes an unqualified explicit executable', () => {
|
|
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-status-'));
|
|
const executable = path.join(fixtureRoot, 'nasiko');
|
|
const marker = path.join(fixtureRoot, 'executed');
|
|
fs.writeFileSync(executable, `#!/bin/sh\ntouch ${JSON.stringify(marker)}\nprintf "nasiko 0.1.0\\n"\n`, { mode: 0o755 });
|
|
try {
|
|
const result = spawnSync(process.execPath, [
|
|
path.join(REPO_ROOT, 'scripts', 'ecc.js'),
|
|
'nasiko',
|
|
'status',
|
|
'--json',
|
|
], {
|
|
encoding: 'utf8',
|
|
env: { ...process.env, ECC_NASIKO_CLI_EXECUTABLE: executable },
|
|
});
|
|
assert.strictEqual(result.status, 0, result.stderr);
|
|
const status = JSON.parse(result.stdout);
|
|
assert.strictEqual(status.installed, true);
|
|
assert.strictEqual(status.qualified, false);
|
|
assert.strictEqual(status.version, null);
|
|
assert.strictEqual(status.executable, executable);
|
|
assert.strictEqual(fs.existsSync(marker), false);
|
|
} finally {
|
|
fs.rmSync(fixtureRoot, { recursive: true, force: true });
|
|
}
|
|
}],
|
|
['read-only status has a stable absent result shape', () => {
|
|
const { readStatus } = require('../../scripts/nasiko');
|
|
const { normalizePlatform } = require('../../scripts/lib/nasiko-release');
|
|
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-absent-'));
|
|
try {
|
|
assert.deepStrictEqual(readStatus({ installDir: fixtureRoot }), {
|
|
installed: false,
|
|
qualified: false,
|
|
version: null,
|
|
executable: path.join(fs.realpathSync(fixtureRoot), normalizePlatform().binaryName),
|
|
});
|
|
} finally { fs.rmSync(fixtureRoot, { recursive: true, force: true }); }
|
|
}],
|
|
['rejects and never executes an unqualified pre-existing binary', async () => {
|
|
const { installNasiko } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-existing-'));
|
|
const executable = path.join(installRoot, 'nasiko');
|
|
const marker = path.join(installRoot, 'executed');
|
|
fs.writeFileSync(executable, `#!/bin/sh\ntouch ${JSON.stringify(marker)}\necho nasiko 0.1.0\n`, { mode: 0o755 });
|
|
try {
|
|
await assert.rejects(
|
|
installNasiko({ version: 'v0.1.0', yes: true, installDir: installRoot }, {
|
|
platform: 'darwin', arch: 'arm64',
|
|
}),
|
|
/unqualified|digest|metadata/i
|
|
);
|
|
assert.strictEqual(fs.existsSync(marker), false);
|
|
} finally {
|
|
fs.rmSync(installRoot, { recursive: true, force: true });
|
|
}
|
|
}],
|
|
['rolls back a published binary when metadata persistence fails', async () => {
|
|
const { installNasiko } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-rollback-'));
|
|
const binary = Buffer.from('qualified binary');
|
|
const archive = Buffer.from('verified archive');
|
|
const manifest = Buffer.from(JSON.stringify({
|
|
schemaVersion: 2,
|
|
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
|
layers: [{ mediaType: 'application/gzip', digest: sha256Digest(archive), size: archive.length }],
|
|
}));
|
|
try {
|
|
await assert.rejects(
|
|
installNasiko({ version: 'v0.1.0', yes: true, installDir: installRoot }, {
|
|
platform: 'darwin',
|
|
arch: 'arm64',
|
|
releaseOverride: {
|
|
manifestDigest: sha256Digest(manifest),
|
|
binaryDigest: sha256Digest(binary),
|
|
},
|
|
fetchBytes: async url => url.includes('/manifests/') ? manifest : archive,
|
|
extractBinary: () => binary,
|
|
writeMetadata: () => { throw new Error('metadata unavailable'); },
|
|
}),
|
|
/metadata unavailable/i
|
|
);
|
|
assert.strictEqual(fs.existsSync(path.join(installRoot, 'nasiko')), false);
|
|
} finally {
|
|
fs.rmSync(installRoot, { recursive: true, force: true });
|
|
}
|
|
}],
|
|
['never overwrites or deletes a destination created during publication', async () => {
|
|
const { installNasiko } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-race-'));
|
|
const binary = Buffer.from('qualified binary');
|
|
const intruder = Buffer.from('concurrent owner');
|
|
const archive = Buffer.from('verified archive');
|
|
const manifest = Buffer.from(JSON.stringify({ schemaVersion: 2, layers: [{ mediaType: 'application/gzip', digest: sha256Digest(archive), size: archive.length }] }));
|
|
try {
|
|
await assert.rejects(installNasiko({ version: 'v0.1.0', yes: true, installDir: installRoot }, {
|
|
platform: 'darwin', arch: 'arm64',
|
|
releaseOverride: { manifestDigest: sha256Digest(manifest), binaryDigest: sha256Digest(binary) },
|
|
fetchBytes: async url => url.includes('/manifests/') ? manifest : archive,
|
|
extractBinary: () => binary,
|
|
beforePublish: destination => fs.writeFileSync(destination, intruder, { flag: 'wx' }),
|
|
}), /exist/i);
|
|
assert.deepStrictEqual(fs.readFileSync(path.join(installRoot, 'nasiko')), intruder);
|
|
} finally { fs.rmSync(installRoot, { recursive: true, force: true }); }
|
|
}],
|
|
['fails uninstall when staged tombstones cannot be removed', () => {
|
|
const { uninstallNasiko } = require('../../scripts/lib/nasiko-release');
|
|
const installRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-nasiko-cleanup-failure-'));
|
|
const executable = path.join(installRoot, 'nasiko');
|
|
const metadataPath = path.join(installRoot, '.ecc-nasiko-install.json');
|
|
fs.writeFileSync(executable, 'qualified binary', { mode: 0o700 });
|
|
fs.writeFileSync(metadataPath, '{}', { mode: 0o600 });
|
|
try {
|
|
assert.throws(() => uninstallNasiko({ installDir: installRoot, yes: true }, {
|
|
platform: 'darwin',
|
|
arch: 'arm64',
|
|
inspectInstalled: () => ({ installed: true, qualified: true, version: 'v0.1.0' }),
|
|
remove: target => { throw new Error(`retained ${target}`); },
|
|
}), /incomplete|retained|cleanup/i);
|
|
assert.ok(fs.readdirSync(installRoot).some(name => name.includes('.remove-')));
|
|
} finally { fs.rmSync(installRoot, { recursive: true, force: true }); }
|
|
}],
|
|
['ships a canonical opt-in skill without silently bundling Nasiko', () => {
|
|
const skill = read('skills/nasiko-control-plane/SKILL.md');
|
|
assert.match(skill, /^name: nasiko-control-plane$/m);
|
|
assert.match(skill, /ecc nasiko status/i);
|
|
assert.match(skill, /explicit.*consent|explicit.*--yes/i);
|
|
assert.match(skill, /pinned.*v0\.1\.0/i);
|
|
assert.match(skill, /telemetry.*opt-in/i);
|
|
assert.match(skill, /never.*secrets|never.*credentials/i);
|
|
assert.match(skill, /install.*does not prove/i);
|
|
assert.match(skill, /ecc nasiko uninstall/i);
|
|
assert.doesNotMatch(skill, /curl[^\n]*\|[^\n]*bash|irm[^\n]*\|[^\n]*iex/i);
|
|
|
|
const modules = readJson('manifests/install-modules.json').modules;
|
|
const module = modules.find(candidate => candidate.id === 'nasiko-control-plane');
|
|
assert.ok(module, 'nasiko-control-plane module is missing');
|
|
assert.deepStrictEqual(module.paths, ['skills/nasiko-control-plane']);
|
|
assert.deepStrictEqual(module.dependencies, ['platform-configs']);
|
|
assert.strictEqual(module.defaultInstall, false);
|
|
assert.strictEqual(module.stability, 'experimental');
|
|
|
|
const components = readJson('manifests/install-components.json').components;
|
|
assert.deepStrictEqual(
|
|
components.find(candidate => candidate.id === 'capability:nasiko-control-plane'),
|
|
{
|
|
id: 'capability:nasiko-control-plane',
|
|
family: 'capability',
|
|
description: 'Experimental Nasiko CLI lifecycle bridge guidance for pinned installation, read-only status, qualified uninstall, and opt-in telemetry boundaries.',
|
|
modules: ['nasiko-control-plane'],
|
|
}
|
|
);
|
|
|
|
const profiles = readJson('manifests/install-profiles.json').profiles;
|
|
assert.ok(profiles.full.modules.includes('nasiko-control-plane'));
|
|
for (const [profileId, profile] of Object.entries(profiles)) {
|
|
if (profileId !== 'full') assert.ok(!profile.modules.includes('nasiko-control-plane'));
|
|
}
|
|
|
|
const packageJson = readJson('package.json');
|
|
assert.ok(packageJson.files.includes('skills/nasiko-control-plane/'));
|
|
assert.ok(packageJson.files.includes('scripts/nasiko.js'));
|
|
assert.ok(packageJson.files.includes('scripts/lib/'));
|
|
assert.ok(!packageJson.dependencies?.nasiko);
|
|
assert.ok(!packageJson.optionalDependencies?.nasiko);
|
|
}],
|
|
];
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
for (const [name, testFunction] of tests) {
|
|
if (await runTest(name, testFunction)) passed += 1;
|
|
else failed += 1;
|
|
}
|
|
|
|
console.log(`\nPassed: ${passed}`);
|
|
console.log(`Failed: ${failed}`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
}
|
|
|
|
main();
|