package web import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "git.dern.ovh/infrastructure/caddy-opnsense-blocker/internal/model" ) func TestHandlerServesOverviewAndManualActions(t *testing.T) { t.Parallel() app := &stubApp{} handler := NewHandler(app) recorder := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet, "/api/recent-ips?hours=24&limit=10", nil) handler.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("unexpected recent ip status: %d body=%s", recorder.Code, recorder.Body.String()) } var recentIPs []model.RecentIPRow if err := json.Unmarshal(recorder.Body.Bytes(), &recentIPs); err != nil { t.Fatalf("decode recent ips payload: %v", err) } if len(recentIPs) != 1 || recentIPs[0].IP != "203.0.113.10" { t.Fatalf("unexpected recent ips payload: %+v", recentIPs) } recorder = httptest.NewRecorder() request = httptest.NewRequest(http.MethodGet, "/api/overview?limit=10", nil) handler.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("unexpected overview status: %d", recorder.Code) } var overview model.Overview if err := json.Unmarshal(recorder.Body.Bytes(), &overview); err != nil { t.Fatalf("decode overview payload: %v", err) } if overview.TotalEvents != 1 || len(overview.RecentIPs) != 1 { t.Fatalf("unexpected overview payload: %+v", overview) } recorder = httptest.NewRecorder() request = httptest.NewRequest(http.MethodPost, "/api/ips/203.0.113.10/block", strings.NewReader(`{"reason":"test reason","actor":"tester"}`)) request.Header.Set("Content-Type", "application/json") handler.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("unexpected block status: %d body=%s", recorder.Code, recorder.Body.String()) } if app.lastAction != "block:203.0.113.10:tester:test reason" { t.Fatalf("unexpected recorded action: %q", app.lastAction) } recorder = httptest.NewRecorder() request = httptest.NewRequest(http.MethodPost, "/api/ips/203.0.113.10/investigate", strings.NewReader(`{"actor":"tester"}`)) request.Header.Set("Content-Type", "application/json") handler.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("unexpected investigate status: %d body=%s", recorder.Code, recorder.Body.String()) } recorder = httptest.NewRecorder() request = httptest.NewRequest(http.MethodGet, "/", nil) handler.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("unexpected overview page status: %d", recorder.Code) } if !strings.Contains(recorder.Body.String(), "Local-only review and enforcement console") { t.Fatalf("overview page did not render expected content") } if strings.Contains(recorder.Body.String(), "Recent events") { t.Fatalf("overview page should no longer render recent events block") } if !strings.Contains(recorder.Body.String(), "Show known bots") { t.Fatalf("overview page should expose the known bots toggle") } if !strings.Contains(recorder.Body.String(), "Show allowed") { t.Fatalf("overview page should expose the allowed toggle") } if !strings.Contains(recorder.Body.String(), "Review only") { t.Fatalf("overview page should expose the review-only toggle") } if !strings.Contains(recorder.Body.String(), "localStorage") { t.Fatalf("overview page should persist preferences in localStorage") } recorder = httptest.NewRecorder() request = httptest.NewRequest(http.MethodGet, "/ips/203.0.113.10", nil) handler.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("unexpected ip details page status: %d", recorder.Code) } body := recorder.Body.String() if !strings.Contains(body, `data-ip="203.0.113.10"`) { t.Fatalf("ip details page did not expose expected data-ip attribute: %s", body) } if strings.Contains(body, `const ip = "\"203.0.113.10\"";`) { t.Fatalf("ip details page still renders a doubly quoted IP") } if strings.Contains(body, `refresh().then(() => investigate());`) { t.Fatalf("ip details page should not auto-refresh investigation on load") } investigationIndex := strings.Index(body, "