CI / test (push) Successful in 9m56s
Map Docker deploy failures to actionable how-to-fix messages, keep full detail through RPC sanitization, and stop top-center toasts from doubling the live job tray so operators only see one notification per action.
89 lines
3.4 KiB
JavaScript
89 lines
3.4 KiB
JavaScript
import test from 'brittle'
|
|
import {
|
|
extractDockerMessage,
|
|
extractDockerStatus,
|
|
formatDeployError,
|
|
sanitizeClientError,
|
|
} from '../server/utils/dockerErrors.js'
|
|
|
|
test('extractDockerMessage prefers json.message', (t) => {
|
|
const err = new Error('(HTTP code 409) unexpected - short')
|
|
err.json = { message: 'port is already allocated' }
|
|
err.statusCode = 409
|
|
t.is(extractDockerMessage(err), 'port is already allocated')
|
|
t.is(extractDockerStatus(err), 409)
|
|
})
|
|
|
|
test('formatDeployError port conflict is actionable', (t) => {
|
|
const err = new Error('(HTTP code 500) server error - Bind for 0.0.0.0:8080 failed: port is already allocated')
|
|
err.statusCode = 500
|
|
const out = formatDeployError(err, {
|
|
stage: 'start',
|
|
containerName: 'web',
|
|
image: 'nginx:alpine',
|
|
})
|
|
t.ok(/port conflict/i.test(out.message) || /port/i.test(out.message))
|
|
t.ok(/how to fix/i.test(out.message))
|
|
t.ok(/8080|host port/i.test(out.message))
|
|
})
|
|
|
|
test('formatDeployError name conflict', (t) => {
|
|
const err = new Error(
|
|
'Conflict. The container name "/api" is already in use by container "abc123". You have to remove (or rename) that container to be able to reuse that name.'
|
|
)
|
|
err.statusCode = 409
|
|
const out = formatDeployError(err, { stage: 'create', containerName: 'api', image: 'node:20' })
|
|
t.ok(/already taken|already/i.test(out.message))
|
|
t.ok(/how to fix/i.test(out.message))
|
|
t.ok(/different name|remove/i.test(out.message))
|
|
})
|
|
|
|
test('formatDeployError image not found', (t) => {
|
|
const err = new Error('manifest for totally-fake/image:nope not found: manifest unknown')
|
|
err.statusCode = 404
|
|
const out = formatDeployError(err, {
|
|
stage: 'pull',
|
|
containerName: 'x',
|
|
image: 'totally-fake/image:nope',
|
|
})
|
|
t.ok(/not found/i.test(out.message))
|
|
t.ok(/how to fix/i.test(out.message))
|
|
t.ok(/image name and tag/i.test(out.message))
|
|
})
|
|
|
|
test('formatDeployError bind mount path', (t) => {
|
|
const err = new Error(
|
|
'invalid mount config for type "bind": bind source path does not exist: /does/not/exist'
|
|
)
|
|
const out = formatDeployError(err, { stage: 'create', containerName: 'db', image: 'postgres' })
|
|
t.ok(/volume|bind|mount/i.test(out.message))
|
|
t.ok(/how to fix/i.test(out.message))
|
|
t.ok(/host path|named volume/i.test(out.message))
|
|
})
|
|
|
|
test('sanitizeClientError keeps long deploy messages', (t) => {
|
|
const long =
|
|
'Port conflict while starting "web" — Host port 8080 is already bound by another process or container. — How to fix: Change the host port in Port mappings, stop the other container using that port, or remove the conflicting publish rule. — (Docker HTTP 500)'
|
|
const err = new Error(long)
|
|
err.code = 'DOCKER_CONFLICT'
|
|
const safe = sanitizeClientError(err)
|
|
t.ok(safe.length > 200)
|
|
t.ok(/how to fix/i.test(safe))
|
|
t.absent(/an error occurred\. please try again/i.test(safe))
|
|
})
|
|
|
|
test('sanitizeClientError redacts secrets but keeps detail', (t) => {
|
|
const err = new Error('login failed password=supersecret token=abc123 for registry')
|
|
const safe = sanitizeClientError(err)
|
|
t.ok(/REDACTED/.test(safe))
|
|
t.absent(/supersecret/.test(safe))
|
|
t.ok(/login failed/i.test(safe))
|
|
})
|
|
|
|
test('sanitizeClientError does not nuke long messages', (t) => {
|
|
const msg = 'x'.repeat(250) + ' useful-detail'
|
|
const safe = sanitizeClientError(new Error(msg))
|
|
t.ok(safe.length > 200)
|
|
t.ok(safe.includes('useful') || safe.endsWith('…') || safe.endsWith('...'))
|
|
})
|