DC-130 rev3: log.error on unexpected bridge status in /status + test asserting the log call (13 tests green)
CI / Test & Lint (push) Waiting to run
CI / Security audit (push) Waiting to run

This commit is contained in:
DashCaddy Polish Loop
2026-09-14 04:19:55 -07:00
parent 121caef488
commit 65a447e27e
2 changed files with 29 additions and 5 deletions
@@ -19,6 +19,10 @@ const FIXTURE_ROWS = {
};
function buildApp(fetchT, env = {}) {
return buildAppWithLogCapture(fetchT, env, { info: jest.fn(), warn: jest.fn(), error: jest.fn() });
}
function buildAppWithLogCapture(fetchT, env = {}, log) {
process.env.SHIPDECK_BRIDGE_URL = env.url !== undefined ? env.url : 'http://172.17.0.1:8977';
process.env.SHIPDECK_BRIDGE_TOKEN_FILE = env.tokenFile !== undefined ? env.tokenFile : '';
jest.resetModules();
@@ -27,7 +31,7 @@ function buildApp(fetchT, env = {}) {
asyncHandler: (fn) => async (req, res, next) => {
try { await fn(req, res, next); } catch (e) { next(e); }
},
log: { info: jest.fn(), warn: jest.fn(), error: jest.fn() },
log,
auditLogger: { log: jest.fn(async () => {}) },
fetchT,
});
@@ -41,11 +45,20 @@ function buildApp(fetchT, env = {}) {
return app;
}
// expose log on the fetcher wrapper for log-capture assertions
function jsonFetcherWithLog(responses) {
const calls = [];
const log = { info: jest.fn(), warn: jest.fn(), error: jest.fn() };
return { calls, log, fetchT: null };
}
function jsonFetcher(responses) {
// responses: map of "METHOD path" -> {status, body}
const calls = [];
return {
const log = { info: jest.fn(), warn: jest.fn(), error: jest.fn() };
const wrapper = {
calls,
log,
fetchT: jest.fn(async (url, opts) => {
const key = `${(opts && opts.method) || 'GET'} ${url.replace(/^https?:\/\/[^/]+/, '')}`;
calls.push({ key, opts });
@@ -56,6 +69,7 @@ function jsonFetcher(responses) {
};
}),
};
return wrapper;
}
describe('routes/deploys — feature gate', () => {
@@ -155,16 +169,21 @@ describe('routes/deploys — proxied endpoints', () => {
expect(body.error).toMatch(/bridge/i);
});
test('GET /status maps unexpected bridge statuses to 502', async () => {
test('GET /status maps unexpected bridge statuses to 502 with error logging', async () => {
const f = jsonFetcher({
'GET /api/status?service=helloworld': { status: 404, body: { ok: false, error: 'not found' } },
});
const app = buildApp(f.fetchT);
const app = buildAppWithLogCapture(f.fetchT, {}, f.log);
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);
expect(f.log.error).toHaveBeenCalledWith(
'deploys',
'status probe: unexpected bridge response',
expect.objectContaining({ service: 'helloworld', status: 404 }),
);
});
test('GET /status maps bridge connection failure to 502', async () => {
+6 -1
View File
@@ -136,7 +136,12 @@ module.exports = function ({ asyncHandler, log, auditLogger, fetchT }) {
}
if (status !== 200) {
// malformed upstream route or other unexpected bridge failure
return errorResponse(res, 502, body && body.error ? body.error : 'unexpected bridge response');
log.error('deploys', 'status probe: unexpected bridge response', {
service,
status,
bridgeError: body && body.error ? String(body.error).slice(0, 200) : 'none',
});
return errorResponse(res, 502, 'shipdeck bridge protocol error (unexpected response, status ' + status + ')');
}
return ok(res, { ok: body.ok === true, output: body.output || '' });
}));