package web import ( "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "antidrift/internal/settings" ) // fakeApplier records the last settings it was asked to apply and can be told to // reject with ErrInvalidBackend. type fakeApplier struct { called int last settings.Settings reject bool } func (f *fakeApplier) apply(s settings.Settings) error { if f.reject { return settings.ErrInvalidBackend } f.called++ f.last = s return nil } func TestGetSettingsReturnsCurrent(t *testing.T) { s := newTestServer(t) cur := settings.Settings{AIBackend: "claude", MarvinCmd: "am", KnowledgePath: "/tmp/k.md"} fa := &fakeApplier{} s.SetSettings(filepath.Join(t.TempDir(), "settings.json"), cur, fa.apply) r := s.Router() req := httptest.NewRequest(http.MethodGet, "/settings", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("status = %d, want 200", w.Code) } var got settings.Settings if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil { t.Fatalf("decode: %v", err) } if got != cur { t.Errorf("got %+v, want %+v", got, cur) } } func TestPostSettingsValidAppliesAndSaves(t *testing.T) { s := newTestServer(t) path := filepath.Join(t.TempDir(), "settings.json") fa := &fakeApplier{} s.SetSettings(path, settings.Settings{}, fa.apply) r := s.Router() body := `{"ai_backend":"codex","marvin_cmd":"uv run am","knowledge_path":"/tmp/k.md"}` w := post(t, r, "/settings", body) if w.Code != http.StatusOK { t.Fatalf("status = %d, want 200 (body %s)", w.Code, w.Body.String()) } if fa.called != 1 { t.Fatalf("applier called %d times, want 1", fa.called) } if fa.last.AIBackend != "codex" { t.Errorf("applied backend = %q, want codex", fa.last.AIBackend) } saved, err := settings.Load(path) if err != nil { t.Fatalf("settings file not loadable: %v", err) } if saved.AIBackend != "codex" || saved.MarvinCmd != "uv run am" || saved.KnowledgePath != "/tmp/k.md" { t.Errorf("saved settings = %+v, want codex/uv run am//tmp/k.md", saved) } } func TestPostSettingsInvalidBackendIs400AndNoSave(t *testing.T) { s := newTestServer(t) path := filepath.Join(t.TempDir(), "settings.json") fa := &fakeApplier{reject: true} s.SetSettings(path, settings.Settings{}, fa.apply) r := s.Router() w := post(t, r, "/settings", `{"ai_backend":"bogus"}`) if w.Code != http.StatusBadRequest { t.Fatalf("status = %d, want 400", w.Code) } if _, err := os.Stat(path); !os.IsNotExist(err) { t.Errorf("settings file should not exist after rejected save, stat err = %v", err) } if fa.called != 0 { t.Errorf("applier recorded %d successful applies, want 0", fa.called) } } func TestPostSettingsInvalidJSONIs400(t *testing.T) { s := newTestServer(t) fa := &fakeApplier{} s.SetSettings(filepath.Join(t.TempDir(), "settings.json"), settings.Settings{}, fa.apply) r := s.Router() w := post(t, r, "/settings", `{not json`) if w.Code != http.StatusBadRequest { t.Fatalf("status = %d, want 400", w.Code) } if fa.called != 0 { t.Errorf("applier called on invalid json; want 0, got %d", fa.called) } }