package controllers import ( "context" "encoding/json" "io" "net/http/httptest" "strings" "testing" "nearle/services" "nearle/services/tools" "github.com/gofiber/fiber/v2" ) /* A server that can say what it is. This exists because of a day spent unable to answer two questions about a running deployment: which build is it, and does the assistant have a model. Both were knowable inside the container and neither was reachable from outside — `/assistant/status` sits behind the session guard, and a 401 from `/v1/web` proves nothing, because the middleware answers before routing and a route that does not exist returns the same 401 as one that does. So the tests that matter here are about what it answers WITHOUT a session, and about what it refuses to include. */ func healthApp(t *testing.T, assistantReady bool, hasDatabase bool) *fiber.App { t.Helper() app := fiber.New() controller := NewHealthController(stubAssistant{ready: assistantReady}, hasDatabase) app.Get("/live/api/v1/health", controller.Health) return app } func readHealth(t *testing.T, app *fiber.App) (int, map[string]any, string) { t.Helper() resp, err := app.Test(httptest.NewRequest("GET", "/live/api/v1/health", nil), -1) if err != nil { t.Fatalf("health: %v", err) } raw, _ := io.ReadAll(resp.Body) var envelope struct { Details map[string]any `json:"details"` } if err := json.Unmarshal(raw, &envelope); err != nil { t.Fatalf("not the envelope the console unwraps: %s", raw) } return resp.StatusCode, envelope.Details, string(raw) } func TestHealthAnswersWithoutASession(t *testing.T) { // The point. A health check that needs a credential cannot be used by the // person working out why credentials are not working — which is exactly // when somebody reaches for it. status, details, body := readHealth(t, healthApp(t, true, true)) if status != fiber.StatusOK { t.Fatalf("HTTP %d without a session: %s", status, body) } if details["version"] == nil { t.Fatalf("no build id: %s", body) } } func TestHealthSaysWhetherTheAssistantHasAModel(t *testing.T) { // "I set the key and redeployed — did it take?" took a day to answer. This // is that answer, in one unauthenticated request. _, ready, _ := readHealth(t, healthApp(t, true, true)) if ready["assistant"] != true { t.Fatalf("a configured assistant reported as %v", ready["assistant"]) } _, off, body := readHealth(t, healthApp(t, false, true)) if off["assistant"] != false { t.Fatalf("an unconfigured assistant reported as %v: %s", off["assistant"], body) } } func TestHealthNeverLeaksTheConfiguration(t *testing.T) { // Booleans, never values. WHICH model, at which endpoint, under which key is // not operational information, and the `reason` string on /assistant/status // names environment variables — that stays behind the guard. _, _, body := readHealth(t, healthApp(t, false, true)) for _, secret := range []string{ "ASSISTANT_", "api.groq.com", "gsk_", "openai/gpt-oss", "POS_TOKEN", "password", } { if strings.Contains(strings.ToLower(body), strings.ToLower(secret)) { t.Fatalf("%q is exposed on an unauthenticated endpoint: %s", secret, body) } } } func TestHealthSurvivesAServerWithNothingWiredUp(t *testing.T) { // A deployment with no database and no model must still ANSWER. This is the // state in which somebody is most likely to ask, and a 500 here would leave // them exactly where they started. app := fiber.New() app.Get("/live/api/v1/health", NewHealthController(nil, false).Health) resp, err := app.Test(httptest.NewRequest("GET", "/live/api/v1/health", nil), -1) if err != nil { t.Fatalf("health: %v", err) } if resp.StatusCode != fiber.StatusOK { t.Fatalf("a bare server could not report its own health: HTTP %d", resp.StatusCode) } raw, _ := io.ReadAll(resp.Body) var envelope struct { Details map[string]any `json:"details"` } _ = json.Unmarshal(raw, &envelope) if envelope.Details["assistant"] != false || envelope.Details["database"] != false { t.Fatalf("a bare server claimed to be wired up: %s", raw) } } func TestAnUnstampedBuildSaysSoRatherThanGuessing(t *testing.T) { // "unknown" is informative: it means nothing stamped the image, so the // version cannot be trusted to date it. Inventing one would be worse than // admitting it. original := Version Version = "unknown" defer func() { Version = original }() got := buildVersion() // Either the toolchain recorded a revision, or it says unknown. What it must // not do is return an empty string, which renders as a blank field and reads // like the endpoint is broken. if strings.TrimSpace(got) == "" { t.Fatal("the build id is blank") } } func TestAStampedBuildIsReported(t *testing.T) { original := Version Version = "abc1234" defer func() { Version = original }() _, details, body := readHealth(t, healthApp(t, true, true)) if details["version"] != "abc1234" { t.Fatalf("the stamped build id was not reported: %s", body) } } // stubAssistant is only ever asked one question. type stubAssistant struct{ ready bool } func (s stubAssistant) Available() bool { return s.ready } func (s stubAssistant) Unavailable() string { return "" } func (s stubAssistant) Ask(_ context.Context, _, _ string, _ tools.Caller) (services.AssistantAnswer, error) { return services.AssistantAnswer{}, nil } func (s stubAssistant) Approve(_ context.Context, _, _ string, _ tools.Caller) (services.AssistantAnswer, error) { return services.AssistantAnswer{}, nil } func TestHealthSaysWhetherSessionsCanBeIssued(t *testing.T) { // The failure this exists for: `attachWebSession` logs a minting failure and // lets the login succeed anyway, so a server with no signing secret issues // sessions that cannot authenticate. The console renders, every request // after it 401s with no `authorization` header, and nothing says why. t.Setenv("POS_TOKEN_SECRET", "") t.Setenv("JWT_SECRET_KEY", "") _, broken, body := readHealth(t, healthApp(t, true, true)) if broken["sessions"] != false { t.Fatalf("a server that cannot sign a session claimed it could: %s", body) } t.Setenv("POS_TOKEN_SECRET", "a-secret-of-quite-sufficient-length") _, working, _ := readHealth(t, healthApp(t, true, true)) if working["sessions"] != true { t.Fatal("a server with a signing secret reported it could not issue sessions") } } func TestHealthDoesNotLeakTheSigningSecret(t *testing.T) { // A boolean about the secret, never the secret. t.Setenv("POS_TOKEN_SECRET", "correct-horse-battery-staple") _, _, body := readHealth(t, healthApp(t, true, true)) if strings.Contains(body, "correct-horse") { t.Fatalf("the signing secret is on an unauthenticated endpoint: %s", body) } }