68 lines
4.4 KiB
JavaScript
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');
|
|
});
|