Files
peardock/test/errors.test.js
T
Raven Scott bbf0607aaf
Release rolling / release (push) Successful in 9m13s
Fix container remove timeouts and make force-remove reliable.
Lifecycle RPCs now use a 120s operation timeout, remove cleans up
stats/terminal/log streams then SIGKILLs before force-remove, and the
UI awaits the request instead of a fragile 30s wait race.
2026-07-15 14:12:13 -04:00

126 lines
4.4 KiB
JavaScript

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))
})