package api import ( "net/http" "strconv" "testing" ) // TestAgentDisabledWithoutAKey — an instance with no API key must start, serve // the app, and not offer the assistant. // // The contract CHANGED with #79: the chat route is now always registered (so a // settings change can turn the assistant on without a restart), so "off" is a // runtime 503 rather than a missing route. capabilities reports agent:false, and // the frontend keys the chat tab off that — so a user never reaches the 503. func TestAgentDisabledWithoutAKey(t *testing.T) { r := authEngine(t, localCfg()) // localCfg has no agent configuration cookie := registerAndCookie(t, r, "noagent@example.com") gid := createGardenAPI(t, r, cookie, "G") // Chat is refused, plainly, because there is no Runner to run. w := doJSON(t, r, http.MethodPost, "/api/v1/agent/chat", map[string]any{"gardenId": gid, "message": "plant garlic"}, cookie) if w.Code != http.StatusServiceUnavailable { t.Errorf("chat without a key: status %d, want 503", w.Code) } // Capabilities advertises the assistant as unavailable, which is what the UI // actually consults. w = doJSON(t, r, http.MethodGet, "/api/v1/capabilities", nil, cookie) if w.Code != http.StatusOK { t.Fatalf("capabilities: status %d", w.Code) } if agent, _ := decodeMap(t, w.Body.Bytes())["agent"].(bool); agent { t.Error("capabilities reported agent:true with no key") } // History is just stored data behind the ordinary garden-role check, so it // reads fine (empty) whether or not a Runner exists — it isn't gated on one. path := "/api/v1/gardens/" + strconv.FormatInt(gid, 10) + "/agent/history" if w := doJSON(t, r, http.MethodGet, path, nil, cookie); w.Code != http.StatusOK { t.Errorf("history without a key: status %d, want 200 (it's data, not the model)", w.Code) } // And the rest of the app is entirely unaffected. if w := doJSON(t, r, http.MethodGet, fullPath(gid), nil, cookie); w.Code != http.StatusOK { t.Errorf("editor load: status %d, want 200 — an unconfigured agent must not break the app", w.Code) } }