DC-130 rev2: fail-fast bridge bindings (host), /status gateway-error mapping (401/403/unreachable -> 502, probe-failure stays ok:false) + 3 new tests (13 total)
This commit is contained in:
@@ -140,6 +140,45 @@ describe('routes/deploys — proxied endpoints', () => {
|
|||||||
expect(body.output).toContain('[FAIL]');
|
expect(body.output).toContain('[FAIL]');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('GET /status maps bridge auth failure (401) to 502, not a probe result', async () => {
|
||||||
|
const f = jsonFetcher({
|
||||||
|
'GET /api/status?service=helloworld': { status: 401, body: { ok: false, error: 'invalid token' } },
|
||||||
|
});
|
||||||
|
const app = buildApp(f.fetchT);
|
||||||
|
const server = app.listen(0);
|
||||||
|
const port = server.address().port;
|
||||||
|
const r = await fetch(`http://127.0.0.1:${port}/api/v1/deploys/status?service=helloworld`);
|
||||||
|
const body = await r.json();
|
||||||
|
server.close();
|
||||||
|
expect(r.status).toBe(502);
|
||||||
|
expect(body.success).toBe(false);
|
||||||
|
expect(body.error).toMatch(/bridge/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /status maps unexpected bridge statuses to 502', async () => {
|
||||||
|
const f = jsonFetcher({
|
||||||
|
'GET /api/status?service=helloworld': { status: 404, body: { ok: false, error: 'not found' } },
|
||||||
|
});
|
||||||
|
const app = buildApp(f.fetchT);
|
||||||
|
const server = app.listen(0);
|
||||||
|
const port = server.address().port;
|
||||||
|
const r = await fetch(`http://127.0.0.1:${port}/api/v1/deploys/status?service=helloworld`);
|
||||||
|
server.close();
|
||||||
|
expect(r.status).toBe(502);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /status maps bridge connection failure to 502', async () => {
|
||||||
|
const fetchT = jest.fn(async () => { throw new Error('ECONNREFUSED'); });
|
||||||
|
const app = buildApp(fetchT);
|
||||||
|
const server = app.listen(0);
|
||||||
|
const port = server.address().port;
|
||||||
|
const r = await fetch(`http://127.0.0.1:${port}/api/v1/deploys/status?service=helloworld`);
|
||||||
|
const body = await r.json();
|
||||||
|
server.close();
|
||||||
|
expect(r.status).toBe(502);
|
||||||
|
expect(body.error).toMatch(/unreachable/);
|
||||||
|
});
|
||||||
|
|
||||||
test('POST /deploy proxies dir and audits', async () => {
|
test('POST /deploy proxies dir and audits', async () => {
|
||||||
const f = jsonFetcher({
|
const f = jsonFetcher({
|
||||||
'POST /api/deploy': { status: 200, body: { ok: true, exit: 0, output: 'DEPLOYED helloworld in 30.0s' } },
|
'POST /api/deploy': { status: 200, body: { ok: true, exit: 0, output: 'DEPLOYED helloworld in 30.0s' } },
|
||||||
|
|||||||
@@ -117,9 +117,27 @@ module.exports = function ({ asyncHandler, log, auditLogger, fetchT }) {
|
|||||||
if (!SERVICE_RE.test(service)) {
|
if (!SERVICE_RE.test(service)) {
|
||||||
return errorResponse(res, 400, 'invalid service name');
|
return errorResponse(res, 400, 'invalid service name');
|
||||||
}
|
}
|
||||||
const { status, body } = await bridge('GET', `/api/status?service=${encodeURIComponent(service)}`);
|
let status, body;
|
||||||
// shipdeck status exits non-zero when checks fail — surface that as ok:false
|
try {
|
||||||
// with the probe output so the UI can render the failing checks.
|
({ status, body } = await bridge('GET', `/api/status?service=${encodeURIComponent(service)}`));
|
||||||
|
} catch (e) {
|
||||||
|
log.error('deploys', 'status probe: bridge unreachable', { service, error: e.message });
|
||||||
|
return errorResponse(res, 502, 'shipdeck bridge unreachable: ' + e.message);
|
||||||
|
}
|
||||||
|
if (status === 401 || status === 403) {
|
||||||
|
// bridge auth/protocol failure = infrastructure problem, NOT a probe result
|
||||||
|
log.error('deploys', 'status probe: bridge auth failed', { service, status });
|
||||||
|
return errorResponse(res, 502, 'shipdeck bridge rejected the request (auth/config error)');
|
||||||
|
}
|
||||||
|
if (status >= 500 && body && body.ok === false && body.output) {
|
||||||
|
// shipdeck status exits non-zero when checks fail — that's an EXPECTED
|
||||||
|
// probe result (failing checks), surface as ok:false with the output.
|
||||||
|
return ok(res, { ok: false, output: body.output || '' });
|
||||||
|
}
|
||||||
|
if (status !== 200) {
|
||||||
|
// malformed upstream route or other unexpected bridge failure
|
||||||
|
return errorResponse(res, 502, body && body.error ? body.error : 'unexpected bridge response');
|
||||||
|
}
|
||||||
return ok(res, { ok: body.ok === true, output: body.output || '' });
|
return ok(res, { ok: body.ok === true, output: body.output || '' });
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user