Files
tools-legacy/tools/test/fpagent-install.test.js
T

162 lines
5.4 KiB
JavaScript
Raw Normal View History

// The /fpagent installer surface: a public script, and a proxy that reads the
// private Gitea release with GITEA_RELEASE_INSTALL_KEY so the operator's
// laptop never needs credentials.
import test from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createApp, FPAGENT_REPO } from '../server.js';
import { startServer, stopServer, urlOf } from './helpers.js';
const TOKEN = 'release-install-secret';
const TAG = 'v9.9.9';
const ARCHIVE = 'fpagent-deadbee-linux-x86_64.tar.gz';
const UBUNTU_ARCHIVE = 'fpagent-deadbee-ubuntu-jammy-x86_64.tar.gz';
const RELEASE = {
tag_name: TAG,
assets: [
{ name: ARCHIVE },
{ name: `${ARCHIVE}.sha256` },
{ name: UBUNTU_ARCHIVE },
{ name: 'not a safe name' },
{ name: '../escape' },
],
};
// Fake Gitea: refuses without the token, serves the release and its assets.
async function startFakeGitea({ releaseStatus = 200, downloadStatus = 200 } = {}) {
const calls = [];
const server = await startServer((req, res) => {
calls.push({ url: req.url, auth: req.headers.authorization });
if (req.headers.authorization !== `token ${TOKEN}`) {
res.writeHead(404);
res.end();
return;
}
if (req.url === `/api/v1/repos/${FPAGENT_REPO}/releases/latest`) {
res.writeHead(releaseStatus, { 'content-type': 'application/json' });
res.end(JSON.stringify(RELEASE));
return;
}
if (req.url.startsWith(`/${FPAGENT_REPO}/releases/download/`)) {
res.writeHead(downloadStatus, { 'content-type': 'application/gzip' });
res.end(Buffer.from('bundle-bytes'));
return;
}
res.writeHead(404);
res.end();
});
return { server, calls };
}
async function bootFpagentApp(giteaUrl, overrides = {}) {
const app = createApp({
giteaUrl,
fpagentRepo: FPAGENT_REPO,
giteaToken: TOKEN,
cropperClients: null,
...overrides,
});
const server = await new Promise((resolve) => {
const s = app.listen(0, '127.0.0.1', () => resolve(s));
});
return { server, base: urlOf(server) };
}
test('latest release is proxied as tools-relative URLs, without the token', async () => {
const gitea = await startFakeGitea();
const { server, base } = await bootFpagentApp(urlOf(gitea.server));
try {
const r = await fetch(`${base}/fpagent/releases/latest`);
assert.equal(r.status, 200);
const body = await r.json();
assert.equal(body.tag, TAG);
// Unsafe asset names are dropped; the rest point back at this app.
assert.deepEqual(
body.assets.map((a) => a.name),
[ARCHIVE, `${ARCHIVE}.sha256`, UBUNTU_ARCHIVE]
);
for (const a of body.assets) {
assert.equal(a.url, `/fpagent/releases/download/${TAG}/${a.name}`);
}
assert.ok(!JSON.stringify(body).includes(TOKEN));
assert.equal(gitea.calls[0].auth, `token ${TOKEN}`); // server authenticated upstream
} finally {
await stopServer(server);
await stopServer(gitea.server);
}
});
test('bundles stream through this app; the client sends no credentials', async () => {
const gitea = await startFakeGitea();
const { server, base } = await bootFpagentApp(urlOf(gitea.server));
try {
const r = await fetch(`${base}/fpagent/releases/download/${TAG}/${ARCHIVE}`);
assert.equal(r.status, 200);
assert.equal(Buffer.from(await r.arrayBuffer()).toString(), 'bundle-bytes');
const assetCall = gitea.calls.find((c) => c.url.includes('/releases/download/'));
assert.equal(assetCall.auth, `token ${TOKEN}`);
} finally {
await stopServer(server);
await stopServer(gitea.server);
}
});
test('unsafe asset names are rejected before reaching the forge', async () => {
const gitea = await startFakeGitea();
const { server, base } = await bootFpagentApp(urlOf(gitea.server));
try {
const r = await fetch(`${base}/fpagent/releases/download/${TAG}/-bad`);
assert.equal(r.status, 400);
assert.equal(gitea.calls.length, 0);
} finally {
await stopServer(server);
await stopServer(gitea.server);
}
});
test('a missing bundle surfaces as 404', async () => {
const gitea = await startFakeGitea({ downloadStatus: 404 });
const { server, base } = await bootFpagentApp(urlOf(gitea.server));
try {
const r = await fetch(`${base}/fpagent/releases/download/${TAG}/${ARCHIVE}`);
assert.equal(r.status, 404);
} finally {
await stopServer(server);
await stopServer(gitea.server);
}
});
test('without GITEA_RELEASE_INSTALL_KEY the installer reports 503', async () => {
const { server, base } = await bootFpagentApp('https://sources.yrka.app', { giteaToken: '' });
try {
const r = await fetch(`${base}/fpagent/releases/latest`);
assert.equal(r.status, 503);
} finally {
await stopServer(server);
}
});
test('install.sh is served publicly at /fpagent/install.sh', async () => {
const { server, base } = await bootFpagentApp('https://sources.yrka.app', { giteaToken: '' });
try {
const r = await fetch(`${base}/fpagent/install.sh`);
assert.equal(r.status, 200);
assert.match(await r.text(), /FPAGENT_BASE_URL/);
} finally {
await stopServer(server);
}
});
test('install.sh is valid bash', () => {
const script = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'public',
'fpagent',
'install.sh'
);
execFileSync('bash', ['-n', script], { stdio: 'pipe' });
});