Skip to content

Commit 8a62b37

Browse files
authored
test(daemon-client): a restart probe that fails outright near the RPC deadline reports the daemon unavailable (#3058)
* test(daemon-client): a restart probe that fails outright near the RPC deadline reports the daemon unavailable * test(daemon-client): document how the refused-probe case proves the probe attempt
1 parent df9f8a4 commit 8a62b37

1 file changed

Lines changed: 59 additions & 0 deletions

File tree

‎src/daemon-client/__tests__/daemon-client-transport.test.ts‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,65 @@ test('a restart health probe cut short by the RPC deadline reports the deadline
256256
}
257257
});
258258

259+
type RestartProbeFailure = 'http-503' | 'closed-connection' | 'refused-connection';
260+
261+
async function assertRestartProbeFailureReportsUnavailable(
262+
t: Parameters<typeof skipWhenLoopbackUnavailable>[0],
263+
failure: RestartProbeFailure,
264+
) {
265+
if (await skipWhenLoopbackUnavailable(t)) return;
266+
let healthProbes = 0;
267+
let rpcCount = 0;
268+
const server = http.createServer((req, res) => {
269+
if (req.url === '/health') {
270+
healthProbes += 1;
271+
if (failure === 'http-503') {
272+
res.statusCode = 503;
273+
res.end();
274+
} else {
275+
req.socket.destroy();
276+
}
277+
return;
278+
}
279+
rpcCount += 1;
280+
res.statusCode = 409;
281+
res.setHeader(DAEMON_HTTP_INSTANCE_MISMATCH_HEADER, 'true');
282+
res.setHeader('connection', 'close');
283+
res.end();
284+
if (failure === 'refused-connection') server.close();
285+
});
286+
try {
287+
const port = await listenOnLoopback(server);
288+
await assert.rejects(sendWithStaleInstance(port, 150), (error: unknown) => {
289+
assert.ok(error instanceof AppError);
290+
assert.equal(error.message, 'Remote daemon is unavailable');
291+
assert.equal(error.details?.daemonBaseUrl, `http://127.0.0.1:${port}`);
292+
assert.notEqual(error.details?.reason, 'daemon_transport_timeout');
293+
return true;
294+
});
295+
assert.equal(rpcCount, 1);
296+
// A closed listener cannot count the probe; its attempt is proven by elimination: a skipped
297+
// probe would exhaust the budget and report daemon_transport_timeout, which the error check rejects.
298+
if (failure !== 'refused-connection') assert.equal(healthProbes, 1);
299+
} finally {
300+
await closeLoopbackServer(server);
301+
}
302+
}
303+
304+
// Catches a mutation that marks every unreachable restart probe timedOut: a probe that fails
305+
// outright inside the capped budget would then surface as an RPC timeout.
306+
test('a restart health probe answering 503 near the RPC deadline reports the daemon unavailable', async (t) => {
307+
await assertRestartProbeFailureReportsUnavailable(t, 'http-503');
308+
});
309+
310+
test('a restart health probe on a closed connection near the RPC deadline reports the daemon unavailable', async (t) => {
311+
await assertRestartProbeFailureReportsUnavailable(t, 'closed-connection');
312+
});
313+
314+
test('a restart health probe refused near the RPC deadline reports the daemon unavailable', async (t) => {
315+
await assertRestartProbeFailureReportsUnavailable(t, 'refused-connection');
316+
});
317+
259318
test('proxy forwards cached upstream identity and rejects a restarted upstream before dispatch', async (t) => {
260319
if (await skipWhenLoopbackUnavailable(t)) return;
261320
let upstreamInstance = 'upstream-one';

0 commit comments

Comments
 (0)