Files
stack/packages/webui/tests/project-remote-http.test.mjs

68 lines
4.4 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { projectRemoteHTTP } from '../src/project-remote-http.mjs';
const origin = 'https://console.test', revision = 'a'.repeat(64);
const subject = { provider: 'oidc', iss: 'https://id.test', sub: 'owner' };
function setup() {
const session = { ...subject, name: 'owner' }, calls = [];
let current = session;
const transport = async (verb, args, context) => {
calls.push({ verb, args, context });
return { project: args.project, remote: verb.includes('.set.') ? args.remote : null, revision, firstSaveAllowed: !verb.includes('.set.') };
};
async function run({ method = 'GET', body = {}, headers = {}, read = transport, write = transport, live = () => current } = {}) {
const h = { origin: [origin], 'sec-fetch-site': ['same-origin'], 'content-type': ['application/json'], ...headers };
for (const k of Object.keys(h)) if (h[k] === null) delete h[k];
const req = { method, headersDistinct: h, async *[Symbol.asyncIterator]() { yield Buffer.from(JSON.stringify(body)); } };
const res = { writeHead(status, headers) { this.status = status; this.headers = headers; }, end(value) { this.value = JSON.parse(value); } };
await projectRemoteHTTP(req, res, { path: '/api/project-remotes/stack', read, write, session, live, origin });
return res;
}
return { calls, run, logout: () => { current = null; } };
}
test('only the trusted session subject and exact route fields reach the Console transport', async () => {
const s = setup();
assert.equal((await s.run()).status, 200);
const result = await s.run({ method: 'POST', body: { revision, remote: 'https://git.example/owner/repo' } });
assert.equal(result.status, 200);
assert.deepEqual(s.calls, [
{ verb: 'project.remote.get.console', args: { project: 'stack' }, context: { subject } },
{ verb: 'project.remote.set.console', args: { project: 'stack', revision, remote: 'https://git.example/owner/repo' }, context: { subject } },
]);
});
test('method, origin, payload and expired session fail before transport', async () => {
const s = setup(), body = { revision, remote: 'https://git.example/owner/repo' };
assert.equal((await s.run({ method: 'PUT' })).status, 405);
for (const headers of [{ origin: ['https://other.test'] }, { origin: null }, { 'sec-fetch-site': ['same-site'] }, { 'sec-fetch-site': null }, { 'content-type': ['text/plain'] }])
assert.notEqual((await s.run({ method: 'POST', body, headers })).status, 200);
for (const bad of [{ revision }, { remote: body.remote }, { ...body, business: 'other' }, { ...body, projects: {} }, { ...body, credentials: 'secret' }, { ...body, publish: true }, { ...body, revision: 'bad' }])
assert.equal((await s.run({ method: 'POST', body: bad })).status, 400);
s.logout(); assert.equal((await s.run({ method: 'POST', body })).status, 401);
assert.deepEqual(s.calls, []);
});
test('refusals and malformed replies never echo credential-bearing URLs', async () => {
const s = setup(), secret = 'https://name:[email protected]/owner/repo';
const write = async () => { const e = new Error(secret); e.code = 'project-remote-invalid'; throw e; };
const refused = await s.run({ method: 'POST', body: { revision, remote: secret }, write });
assert.equal(refused.status, 400); assert.ok(!JSON.stringify(refused.value).includes('secret'));
const read = async () => ({ project: 'stack', revision, remote: secret, firstSaveAllowed: false });
const invalid = await s.run({ read });
assert.equal(invalid.status, 503); assert.ok(!JSON.stringify(invalid.value).includes('secret'));
});
test('an expiry during the body read prevents the write from starting', async () => {
const s = setup(); let checks = 0;
const out = await s.run({ method: 'POST', body: { revision, remote: 'https://git.example/owner/repo' }, live: () => { checks++; return null; } });
assert.equal(out.status, 401); assert.equal(checks, 1); assert.deepEqual(s.calls, []);
});
test('a write admitted before sign-out returns its receipt if the session ends during the call', async () => {
const s = setup();
const write = async (verb, args) => {
s.logout();
return { project: args.project, revision, remote: args.remote, firstSaveAllowed: false };
};
const out = await s.run({ method: 'POST', body: { revision, remote: 'https://git.example/owner/repo' }, write });
assert.equal(out.status, 200); assert.equal(out.value.remote, 'https://git.example/owner/repo');
});