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:secret@git.example/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'); });