package httpapi import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" ) // TestPlayerStatusLifecycle covers the full lifecycle of a screen status entry: // ingest → list → HTML detail → JSON detail → delete → verify gone. func TestPlayerStatusLifecycle(t *testing.T) { store := newInMemoryPlayerStatusStore() store.now = func() time.Time { return time.Date(2026, 3, 22, 16, 10, 0, 0, time.UTC) } router := NewRouter(RouterDeps{StatusStore: store}) // 1. POST /api/v1/player/status – ingest a status report body := `{ "screen_id": "lifecycle-screen", "ts": "2026-03-22T16:09:30Z", "status": "running", "server_connectivity": "online", "server_url": "http://127.0.0.1:8080", "mqtt_broker": "tcp://127.0.0.1:1883", "heartbeat_every_seconds": 30, "started_at": "2026-03-22T16:00:00Z", "last_heartbeat_at": "2026-03-22T16:09:30Z" }` req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewBufferString(body)) w := httptest.NewRecorder() router.ServeHTTP(w, req) if got, want := w.Code, http.StatusOK; got != want { t.Fatalf("POST status: got %d, want %d – body: %s", got, want, w.Body.String()) } // 2. GET /api/v1/screens/status – appears in list with derived_state=online req = httptest.NewRequest(http.MethodGet, "/api/v1/screens/status", nil) w = httptest.NewRecorder() router.ServeHTTP(w, req) if got, want := w.Code, http.StatusOK; got != want { t.Fatalf("GET list: got %d, want %d", got, want) } var list struct { Summary struct{ Total int } `json:"summary"` Screens []playerStatusRecord `json:"screens"` } if err := json.Unmarshal(w.Body.Bytes(), &list); err != nil { t.Fatalf("GET list Unmarshal: %v", err) } if got, want := list.Summary.Total, 1; got != want { t.Fatalf("summary.total = %d, want %d", got, want) } if got, want := list.Screens[0].ScreenID, "lifecycle-screen"; got != want { t.Fatalf("Screens[0].ScreenID = %q, want %q", got, want) } if got, want := list.Screens[0].DerivedState, "online"; got != want { t.Fatalf("Screens[0].DerivedState = %q, want %q", got, want) } // 3. GET /status/lifecycle-screen – HTML detail page contains key fields req = httptest.NewRequest(http.MethodGet, "/status/lifecycle-screen", nil) w = httptest.NewRecorder() router.ServeHTTP(w, req) if got, want := w.Code, http.StatusOK; got != want { t.Fatalf("GET HTML detail: got %d, want %d", got, want) } if ct := w.Header().Get("Content-Type"); !strings.Contains(ct, "text/html") { t.Fatalf("Content-Type = %q, want text/html", ct) } htmlBody := w.Body.String() for _, want := range []string{"lifecycle-screen", "online", "running"} { if !strings.Contains(htmlBody, want) { t.Fatalf("HTML detail page missing %q", want) } } // 4. GET /api/v1/screens/lifecycle-screen/status – JSON detail req = httptest.NewRequest(http.MethodGet, "/api/v1/screens/lifecycle-screen/status", nil) w = httptest.NewRecorder() router.ServeHTTP(w, req) if got, want := w.Code, http.StatusOK; got != want { t.Fatalf("GET JSON detail: got %d, want %d", got, want) } var detail playerStatusRecord if err := json.Unmarshal(w.Body.Bytes(), &detail); err != nil { t.Fatalf("GET JSON detail Unmarshal: %v", err) } if got, want := detail.ScreenID, "lifecycle-screen"; got != want { t.Fatalf("detail.ScreenID = %q, want %q", got, want) } if got, want := detail.ServerURL, "http://127.0.0.1:8080"; got != want { t.Fatalf("detail.ServerURL = %q, want %q", got, want) } // 5. DELETE /api/v1/screens/lifecycle-screen/status – remove the record req = httptest.NewRequest(http.MethodDelete, "/api/v1/screens/lifecycle-screen/status", nil) w = httptest.NewRecorder() router.ServeHTTP(w, req) if got, want := w.Code, http.StatusOK; got != want { t.Fatalf("DELETE: got %d, want %d – body: %s", got, want, w.Body.String()) } // 6. GET /api/v1/screens/lifecycle-screen/status – must be 404 now req = httptest.NewRequest(http.MethodGet, "/api/v1/screens/lifecycle-screen/status", nil) w = httptest.NewRecorder() router.ServeHTTP(w, req) if got, want := w.Code, http.StatusNotFound; got != want { t.Fatalf("GET after delete: got %d, want %d", got, want) } // 7. GET /api/v1/screens/status – list must be empty again req = httptest.NewRequest(http.MethodGet, "/api/v1/screens/status", nil) w = httptest.NewRecorder() router.ServeHTTP(w, req) if err := json.Unmarshal(w.Body.Bytes(), &list); err != nil { t.Fatalf("GET list after delete Unmarshal: %v", err) } if got, want := list.Summary.Total, 0; got != want { t.Fatalf("summary.total after delete = %d, want %d", got, want) } }