fix(cli): preserve route show health errors
Keep unknown container status when probes return no container state, and surface per-route probe failures in routes show output.
- commit
- cc460f41bef6847915a24ae918ea89c8d9be0d7e
- author
- brice · · 2026-04-15 05:40 UTC
- parent
- 6577047
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +68 -1
M internal/adapters/in/cli/routes.go +6 -1
| @@ -975,8 +975,13 @@ func runRoutesShow(ctx context.Context, cp ControlPlane, out io.Writer, routeDom | |||
| 975 | 975 | containerStatus := "unknown" | |
| 976 | 976 | httpStatus := 0 | |
| 977 | 977 | if routeHealth != nil { | |
| 978 | - | containerStatus = routeHealth.ContainerStatus | |
| 978 | + | if routeHealth.ContainerStatus != "" { | |
| 979 | + | containerStatus = routeHealth.ContainerStatus | |
| 980 | + | } | |
| 979 | 981 | httpStatus = routeHealth.HTTPStatus | |
| 982 | + | if healthErr == "" && routeHealth.Error != "" { | |
| 983 | + | healthErr = routeHealth.Error | |
| 984 | + | } | |
| 980 | 985 | } | |
| 981 | 986 | ||
| 982 | 987 | if jsonOut { | |
| @@ -975,8 +975,13 @@ func runRoutesShow(ctx context.Context, cp ControlPlane, out io.Writer, routeDom | |||||
| 975 | containerStatus := "unknown" | 975 | containerStatus := "unknown" | ||
| 976 | httpStatus := 0 | 976 | httpStatus := 0 | ||
| 977 | if routeHealth != nil { | 977 | if routeHealth != nil { | ||
| 978 | - | containerStatus = routeHealth.ContainerStatus | 978 | + | if routeHealth.ContainerStatus != "" { |
| 979 | + | containerStatus = routeHealth.ContainerStatus | |||
| 980 | + | } | |||
| 979 | httpStatus = routeHealth.HTTPStatus | 981 | httpStatus = routeHealth.HTTPStatus | ||
| 982 | + | if healthErr == "" && routeHealth.Error != "" { | |||
| 983 | + | healthErr = routeHealth.Error | |||
| 984 | + | } | |||
| 980 | } | 985 | } | ||
| 981 | 986 | ||||
| 982 | if jsonOut { | 987 | if jsonOut { | ||
M internal/adapters/in/cli/routes_test.go +62 -0
| @@ -353,6 +353,68 @@ func TestRunRoutesShow_TextIncludesHealthWarning(t *testing.T) { | |||
| 353 | 353 | assert.Contains(t, text, "probe failed") | |
| 354 | 354 | } | |
| 355 | 355 | ||
| 356 | + | func TestRunRoutesShow_JSONIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) { | |
| 357 | + | fake := &routesShowTestControlPlane{ | |
| 358 | + | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | |
| 359 | + | getRoute: func(context.Context, string) (*domain.Route, error) { | |
| 360 | + | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | |
| 361 | + | }, | |
| 362 | + | }, | |
| 363 | + | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | |
| 364 | + | return map[string]*remote.RouteHealth{ | |
| 365 | + | "app.example.com": { | |
| 366 | + | HTTPStatus: 503, | |
| 367 | + | ContainerStatus: "", | |
| 368 | + | Error: "probe failed", | |
| 369 | + | }, | |
| 370 | + | }, nil | |
| 371 | + | }, | |
| 372 | + | } | |
| 373 | + | ||
| 374 | + | var out bytes.Buffer | |
| 375 | + | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", true) | |
| 376 | + | ||
| 377 | + | require.NoError(t, err) | |
| 378 | + | ||
| 379 | + | var got map[string]any | |
| 380 | + | require.NoError(t, json.Unmarshal(out.Bytes(), &got)) | |
| 381 | + | assert.Equal(t, "app.example.com", got["domain"]) | |
| 382 | + | assert.Equal(t, "app:latest", got["image"]) | |
| 383 | + | assert.Equal(t, "unknown", got["container_status"]) | |
| 384 | + | assert.Equal(t, float64(503), got["http_status"]) | |
| 385 | + | assert.Equal(t, "probe failed", got["health_error"]) | |
| 386 | + | } | |
| 387 | + | ||
| 388 | + | func TestRunRoutesShow_TextIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) { | |
| 389 | + | fake := &routesShowTestControlPlane{ | |
| 390 | + | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | |
| 391 | + | getRoute: func(context.Context, string) (*domain.Route, error) { | |
| 392 | + | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | |
| 393 | + | }, | |
| 394 | + | }, | |
| 395 | + | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | |
| 396 | + | return map[string]*remote.RouteHealth{ | |
| 397 | + | "app.example.com": { | |
| 398 | + | HTTPStatus: 503, | |
| 399 | + | ContainerStatus: "", | |
| 400 | + | Error: "probe failed", | |
| 401 | + | }, | |
| 402 | + | }, nil | |
| 403 | + | }, | |
| 404 | + | } | |
| 405 | + | ||
| 406 | + | var out bytes.Buffer | |
| 407 | + | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", false) | |
| 408 | + | ||
| 409 | + | require.NoError(t, err) | |
| 410 | + | ||
| 411 | + | text := stripANSI(out.String()) | |
| 412 | + | assert.Contains(t, text, "Route: app.example.com") | |
| 413 | + | assert.Contains(t, text, "Container:") | |
| 414 | + | assert.Contains(t, text, "unknown") | |
| 415 | + | assert.Contains(t, text, "probe failed") | |
| 416 | + | } | |
| 417 | + | ||
| 356 | 418 | func TestCollectRoutesListSections_DefaultModeIncludesLocalThenSortedRemotes(t *testing.T) { | |
| 357 | 419 | testsDeps := routesListDeps{ | |
| 358 | 420 | explicitRemote: func() (*remote.ResolvedRemote, bool, error) { | |
| @@ -353,6 +353,68 @@ func TestRunRoutesShow_TextIncludesHealthWarning(t *testing.T) { | |||||
| 353 | assert.Contains(t, text, "probe failed") | 353 | assert.Contains(t, text, "probe failed") | ||
| 354 | } | 354 | } | ||
| 355 | 355 | ||||
| 356 | + | func TestRunRoutesShow_JSONIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) { | |||
| 357 | + | fake := &routesShowTestControlPlane{ | |||
| 358 | + | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | |||
| 359 | + | getRoute: func(context.Context, string) (*domain.Route, error) { | |||
| 360 | + | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | |||
| 361 | + | }, | |||
| 362 | + | }, | |||
| 363 | + | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | |||
| 364 | + | return map[string]*remote.RouteHealth{ | |||
| 365 | + | "app.example.com": { | |||
| 366 | + | HTTPStatus: 503, | |||
| 367 | + | ContainerStatus: "", | |||
| 368 | + | Error: "probe failed", | |||
| 369 | + | }, | |||
| 370 | + | }, nil | |||
| 371 | + | }, | |||
| 372 | + | } | |||
| 373 | + | ||||
| 374 | + | var out bytes.Buffer | |||
| 375 | + | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", true) | |||
| 376 | + | ||||
| 377 | + | require.NoError(t, err) | |||
| 378 | + | ||||
| 379 | + | var got map[string]any | |||
| 380 | + | require.NoError(t, json.Unmarshal(out.Bytes(), &got)) | |||
| 381 | + | assert.Equal(t, "app.example.com", got["domain"]) | |||
| 382 | + | assert.Equal(t, "app:latest", got["image"]) | |||
| 383 | + | assert.Equal(t, "unknown", got["container_status"]) | |||
| 384 | + | assert.Equal(t, float64(503), got["http_status"]) | |||
| 385 | + | assert.Equal(t, "probe failed", got["health_error"]) | |||
| 386 | + | } | |||
| 387 | + | ||||
| 388 | + | func TestRunRoutesShow_TextIncludesRouteProbeFailureAndUnknownContainerStatus(t *testing.T) { | |||
| 389 | + | fake := &routesShowTestControlPlane{ | |||
| 390 | + | resolveFromImageTestControlPlane: resolveFromImageTestControlPlane{ | |||
| 391 | + | getRoute: func(context.Context, string) (*domain.Route, error) { | |||
| 392 | + | return &domain.Route{Domain: "app.example.com", Image: "app:latest"}, nil | |||
| 393 | + | }, | |||
| 394 | + | }, | |||
| 395 | + | getHealth: func(context.Context) (map[string]*remote.RouteHealth, error) { | |||
| 396 | + | return map[string]*remote.RouteHealth{ | |||
| 397 | + | "app.example.com": { | |||
| 398 | + | HTTPStatus: 503, | |||
| 399 | + | ContainerStatus: "", | |||
| 400 | + | Error: "probe failed", | |||
| 401 | + | }, | |||
| 402 | + | }, nil | |||
| 403 | + | }, | |||
| 404 | + | } | |||
| 405 | + | ||||
| 406 | + | var out bytes.Buffer | |||
| 407 | + | err := runRoutesShow(context.Background(), fake, &out, "app.example.com", false) | |||
| 408 | + | ||||
| 409 | + | require.NoError(t, err) | |||
| 410 | + | ||||
| 411 | + | text := stripANSI(out.String()) | |||
| 412 | + | assert.Contains(t, text, "Route: app.example.com") | |||
| 413 | + | assert.Contains(t, text, "Container:") | |||
| 414 | + | assert.Contains(t, text, "unknown") | |||
| 415 | + | assert.Contains(t, text, "probe failed") | |||
| 416 | + | } | |||
| 417 | + | ||||
| 356 | func TestCollectRoutesListSections_DefaultModeIncludesLocalThenSortedRemotes(t *testing.T) { | 418 | func TestCollectRoutesListSections_DefaultModeIncludesLocalThenSortedRemotes(t *testing.T) { | ||
| 357 | testsDeps := routesListDeps{ | 419 | testsDeps := routesListDeps{ | ||
| 358 | explicitRemote: func() (*remote.ResolvedRemote, bool, error) { | 420 | explicitRemote: func() (*remote.ResolvedRemote, bool, error) { | ||