import test from 'brittle' import { unwrapError, explainError, presentError, formatResponseError, normalizeRpcError, isBackgroundMethod, } from '../client/errors.js' test('unwrapError peels REQUEST_ERROR wrapper to cause', (t) => { const cause = new Error('Docker socket not found') cause.code = 'ENGINE_DOWN' const outer = new Error('REQUEST_ERROR: Request failed') outer.code = 'REQUEST_ERROR' outer.cause = cause const root = unwrapError(outer) t.is(root.message, 'Docker socket not found') t.is(root.code, 'ENGINE_DOWN') }) test('explainError uses unwrapped message not Request failed', (t) => { const cause = new Error('permission denied while trying to connect to Docker') const outer = new Error('REQUEST_ERROR: Request failed') outer.code = 'REQUEST_ERROR' outer.cause = cause const info = explainError(outer, 'listContainers') t.ok(info.message.toLowerCase().includes('docker') || info.message.toLowerCase().includes('permission')) t.absent(/request failed/i.test(info.message)) }) test('generic Request failed without cause is quiet for background methods', (t) => { const err = new Error('REQUEST_ERROR: Request failed') err.code = 'REQUEST_ERROR' const info = explainError(err, 'listContainers') t.ok(info.silent || isBackgroundMethod('listContainers')) }) test('formatResponseError suppresses bare Request failed spam', (t) => { const formatted = formatResponseError('REQUEST_ERROR: Request failed', 'listVolumes') t.ok(formatted) t.ok(formatted.silent === true || !/REQUEST_ERROR/i.test(formatted.message)) }) test('normalizeRpcError sets method and readable message', (t) => { const cause = new Error('Invalid arguments: name required') cause.code = 'INVALID_ARGS' const outer = new Error('Request failed') outer.code = 'REQUEST_ERROR' outer.cause = cause const n = normalizeRpcError(outer, 'createVolume') t.is(n.method, 'createVolume') t.ok(n.message.includes('name') || n.message.includes('Invalid')) }) test('presentError silent skips showAlert', (t) => { let called = 0 const showAlert = () => { called += 1 } const err = new Error('REQUEST_ERROR: Request failed') err.code = 'REQUEST_ERROR' presentError(err, 'ping', { showAlert, silent: true }) t.is(called, 0) }) test('presentError dedupes identical messages', (t) => { let called = 0 const showAlert = () => { called += 1 } const err = new Error('Something unique for dedupe test xyz') presentError(err, 'deployContainer', { showAlert, force: true }) presentError(err, 'deployContainer', { showAlert }) t.is(called, 1) }) test('PEER_NOT_FOUND is not misclassified as image not found', (t) => { const err = new Error('PEER_NOT_FOUND: Peer not found') err.code = 'PEER_NOT_FOUND' const info = explainError(err, 'connect') t.is(info.code, 'PEER_NOT_FOUND') t.ok(/server not found|peer not found/i.test(info.title + info.message)) t.absent(/image not found/i.test(info.title)) }) test('explainError surfaces How to fix from deploy messages', (t) => { const err = new Error( 'Port conflict while starting "web" — Host port 8080 is already bound. — How to fix: Change the host port mapping.' ) err.code = 'DOCKER_CONFLICT' const info = explainError(err, 'deployContainer') t.ok(/port/i.test(info.title + info.message)) t.ok(/host port|change/i.test(info.recovery)) t.is(info.silent, false) }) test('presentError can suppress toast when job drawer owns UI', (t) => { let opts = null const showAlert = (_type, _text, o) => { opts = o } const err = new Error('deploy boom unique suppress toast') presentError(err, 'deployContainer', { showAlert, force: true, toast: false, tray: false }) t.ok(opts) t.is(opts.toast, false) t.is(opts.tray, false) }) test('RPC timeout is operation timeout not connection problem', (t) => { const err = new Error('timeout of 30000ms exceeded') err.code = 'TIMEOUT_EXCEEDED' const info = explainError(err, 'removeContainer') t.is(info.code, 'TIMEOUT_EXCEEDED') t.ok(/timed out|timeout/i.test(info.title + info.message)) t.absent(/connection problem/i.test(info.title)) t.ok(/refresh|retry|docker/i.test(info.recovery)) }) test('timeout message without code is not connection problem', (t) => { const err = new Error('timeout of 120000ms exceeded') const info = explainError(err, 'removeContainer') t.ok(/timed out|timeout/i.test(info.title)) t.absent(/connection problem/i.test(info.title)) })