fix(k8s): read ApiException.code when classifying client-node errors
@kubernetes/client-node v1 raises ApiException, which carries the HTTP status on `.code`. Every check in the orchestrator read only `.statusCode` — the pre-1.0 HttpError field — so `status` came back undefined and each "expected" 404/409 was rethrown instead of handled. Found in production: the first start of a server with a volume read a not-yet-existing PVC, got a 404 that should have meant "create it", and failed the instance instead. The same latent bug sat in removeContainer, where a missing pod failed the delete rather than being treated as already gone. httpStatusOf() checks .code, .statusCode and .response.statusCode, and falls back to parsing the rendered "HTTP-Code: NNN" message for wrappers that keep none of them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017BMXdb2qZbPSh8Q7XpTyjB
This commit is contained in:
@@ -87,7 +87,7 @@ vi.mock('@kubernetes/client-node', () => {
|
||||
});
|
||||
|
||||
// Import after mock
|
||||
import { KubernetesOrchestrator } from '../src/services/k8s/kubernetes-orchestrator.js';
|
||||
import { KubernetesOrchestrator, httpStatusOf } from '../src/services/k8s/kubernetes-orchestrator.js';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const k8sMock = await import('@kubernetes/client-node') as any;
|
||||
const { setHandler, clearHandlers, mockCore } = k8sMock.__testHelpers;
|
||||
@@ -356,3 +356,26 @@ describe('KubernetesOrchestrator', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('httpStatusOf', () => {
|
||||
it('reads .code, which is what client-node v1 ApiException actually sets', () => {
|
||||
// The bug this exists for: reading only .statusCode returned undefined, so
|
||||
// an expected 404 was rethrown and a first-start PVC create never happened.
|
||||
expect(httpStatusOf({ code: 404 })).toBe(404);
|
||||
expect(httpStatusOf({ code: 409 })).toBe(409);
|
||||
});
|
||||
|
||||
it('still reads the pre-1.0 HttpError shapes', () => {
|
||||
expect(httpStatusOf({ statusCode: 404 })).toBe(404);
|
||||
expect(httpStatusOf({ response: { statusCode: 409 } })).toBe(409);
|
||||
});
|
||||
|
||||
it('falls back to parsing the rendered message', () => {
|
||||
expect(httpStatusOf(new Error('HTTP-Code: 403\nMessage: Unknown API Status Code!'))).toBe(403);
|
||||
});
|
||||
|
||||
it('returns undefined when there is no status to find', () => {
|
||||
expect(httpStatusOf(new Error('socket hang up'))).toBeUndefined();
|
||||
expect(httpStatusOf({ code: 'ECONNREFUSED' })).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user