towerops/test/towerops_web/controllers/health_controller_test.exs
Graham McIntire 97232117f5 fix: H12 cookie hardening + 5 low/medium bugs (L2, L5, L6, L8, L10, L11)
- H12: session and remember-me cookies get http_only + secure (prod-only).
  Cookie can no longer be read via document.cookie (XSS exfil defense)
  and the Secure flag is set in production via config/prod.exs.
- L2: 404 tracker uses EXPIRE … NX so a sustained probe can't keep
  refreshing the 60s window and dodge the threshold ban.
- L5: vault only reads CLOAK_KEY when :env == :prod — a developer with
  a prod env var set in their shell won't accidentally encrypt local
  data with the production key.
- L6: health endpoint no longer leaks the app version.
- L8: Preseem.dismiss_insight/2 + InsightsLive uses it — dismissing now
  requires the insight to belong to the user's org (closes IDOR).
- L10: SidebarCollapse JS hook stores its click handler and removes it
  in destroyed(); listeners no longer accumulate across LV navigation.
- L11: WebMCP navigate tool rejects anything that isn't a same-origin
  absolute path (blocks javascript:, data:, off-site URLs).
2026-05-12 11:22:47 -05:00

78 lines
2.9 KiB
Elixir

defmodule ToweropsWeb.HealthControllerTest do
use ToweropsWeb.ConnCase, async: false
describe "GET /health" do
test "returns 200 when database is connected", %{conn: conn} do
conn = get(conn, "/health")
body = response(conn, 200)
result = Jason.decode!(body)
assert result["status"] == "ok"
assert result["database"] == "connected"
# `version` deliberately omitted (L6): exposing the app version on a
# public health endpoint helps attackers fingerprint vulnerable builds.
refute Map.has_key?(result, "version")
end
test "reports redis status as not_configured when redis is not configured", %{conn: conn} do
previous = Application.get_env(:towerops, :redis)
Application.put_env(:towerops, :redis, [])
try do
conn = get(conn, "/health")
result = Jason.decode!(response(conn, 200))
assert result["redis"] == "not_configured"
after
if previous do
Application.put_env(:towerops, :redis, previous)
else
Application.delete_env(:towerops, :redis)
end
end
end
test "reports redis as connected when configured to a real instance", %{conn: conn} do
previous = Application.get_env(:towerops, :redis)
Application.put_env(:towerops, :redis, host: "localhost", port: 6379)
try do
conn = get(conn, "/health")
# Either 200 (redis up) or 503 (redis unreachable in CI) — both branches
# exercise the configured-redis code path. We just assert the response
# is well-formed JSON with a redis field.
body = response(conn, conn.status)
result = Jason.decode!(body)
assert result["redis"] in ["connected", "disconnected"]
after
if previous do
Application.put_env(:towerops, :redis, previous)
else
Application.delete_env(:towerops, :redis)
end
end
end
test "returns 503 when redis is configured but unreachable", %{conn: conn} do
previous = Application.get_env(:towerops, :redis)
# Point at a closed port on a routable but unreachable address.
# 198.51.100.0/24 is TEST-NET-2 (RFC 5737) — guaranteed not routable on the public internet.
Application.put_env(:towerops, :redis, host: "198.51.100.1", port: 1, timeout: 50)
try do
conn = get(conn, "/health")
# Expect 503 with redis disconnected
body = response(conn, 503)
result = Jason.decode!(body)
assert result["status"] == "error"
# Database is still connected in tests — exercises db_status_string(:ok)
assert result["database"] == "connected"
assert result["redis"] == "disconnected"
after
if previous do
Application.put_env(:towerops, :redis, previous)
else
Application.delete_env(:towerops, :redis)
end
end
end
end
end