import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; import test from 'node:test'; import { releaseManifest, rsyncArguments, sshTarget } from './deploy.mjs'; test('site manifest follows the Contabo schema and detects unsafe public output', async () => { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'aia-deploy-manifest-')); try { await fs.mkdir(path.join(root, '_astro')); await fs.writeFile(path.join(root, 'index.html'), 'Synthetic'); await fs.writeFile(path.join(root, '_astro', 'entry.abcdefgh.js'), 'export {}'); const manifest = await releaseManifest(root, 'a'.repeat(40)); assert.deepEqual(Object.keys(manifest).sort(), ['directories','files','immutableAssets','schema','sourceCommit','treeSha256']); assert.equal(manifest.schema, 1); assert.equal(manifest.immutableAssets[0], '_astro/entry.abcdefgh.js'); const expected = createHash('sha256').update(Object.entries(manifest.files).sort(([a], [b]) => a.localeCompare(b)).map(([name, meta]) => `${name}\0${meta.sha256}\n`).join('')).digest('hex'); assert.equal(manifest.treeSha256, expected); const serverTool = path.resolve(process.cwd(), '../../contabo/scripts/website-release.py'); const parity = spawnSync('python3', [serverTool, 'manifest', root, 'a'.repeat(40)], { encoding: 'utf8' }); assert.equal(parity.status, 0, parity.stderr); assert.deepEqual(manifest, JSON.parse(parity.stdout)); await fs.mkdir(path.join(root, 'private')); await assert.rejects(releaseManifest(root, 'a'.repeat(40)), /Unexpected private artifact/); } finally { await fs.rm(root, { recursive: true, force: true }); } }); test('deployment transfer uses the Contabo ownership and mode interface', () => { assert.deepEqual(rsyncArguments('/tmp/source', 'host:/srv/stage', { remove: true }).slice(0, 6), ['-rlt','--delete','--links','--chown=root:root','--chmod=Du=rwx,Dgo=,Fu=rw,Fgo=','/tmp/source/']); assert.deepEqual(sshTarget('root@host.example', '/absolute/key').slice(0, 3), ['ssh','-i','/absolute/key']); assert.throws(() => sshTarget('host;rm -rf /', '/absolute/key'), /invalid/); });