package httpapi import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" ) func TestHandlePlayerStatusAccepted(t *testing.T) { store := newInMemoryPlayerStatusStore() body := []byte(`{ "screen_id": "info01-dev", "ts": "2026-03-22T16:00:00Z", "status": "running", "server_url": "http://127.0.0.1:8080", "mqtt_broker": "tcp://127.0.0.1:1883", "heartbeat_every_seconds": 30, "started_at": "2026-03-22T15:59:30Z", "last_heartbeat_at": "2026-03-22T16:00:00Z" }`) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) w := httptest.NewRecorder() handlePlayerStatus(store)(w, req) if got, want := w.Code, http.StatusOK; got != want { t.Fatalf("status = %d, want %d", got, want) } var response struct { Status string `json:"status"` } if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil { t.Fatalf("Unmarshal() error = %v", err) } if got, want := response.Status, "accepted"; got != want { t.Fatalf("response status = %q, want %q", got, want) } stored, ok := store.Get("info01-dev") if !ok { t.Fatal("store.Get() ok = false, want true") } if got, want := stored.ScreenID, "info01-dev"; got != want { t.Fatalf("stored.ScreenID = %q, want %q", got, want) } } func TestHandlePlayerStatusRejectsInvalidJSON(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewBufferString("{")) w := httptest.NewRecorder() handlePlayerStatus(newInMemoryPlayerStatusStore())(w, req) if got, want := w.Code, http.StatusBadRequest; got != want { t.Fatalf("status = %d, want %d", got, want) } } func TestHandlePlayerStatusRejectsMissingScreenID(t *testing.T) { body := []byte(`{ "screen_id": " ", "ts": "2026-03-22T16:00:00Z", "status": "running" }`) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) w := httptest.NewRecorder() handlePlayerStatus(newInMemoryPlayerStatusStore())(w, req) if got, want := w.Code, http.StatusBadRequest; got != want { t.Fatalf("status = %d, want %d", got, want) } } func TestHandlePlayerStatusRejectsMissingTimestamp(t *testing.T) { body := []byte(`{ "screen_id": "info01-dev", "status": "running" }`) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) w := httptest.NewRecorder() handlePlayerStatus(newInMemoryPlayerStatusStore())(w, req) if got, want := w.Code, http.StatusBadRequest; got != want { t.Fatalf("status = %d, want %d", got, want) } } func TestHandlePlayerStatusRejectsMissingStatus(t *testing.T) { body := []byte(`{ "screen_id": "info01-dev", "ts": "2026-03-22T16:00:00Z" }`) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) w := httptest.NewRecorder() handlePlayerStatus(newInMemoryPlayerStatusStore())(w, req) if got, want := w.Code, http.StatusBadRequest; got != want { t.Fatalf("status = %d, want %d", got, want) } } func TestHandlePlayerStatusRejectsMalformedTimestamps(t *testing.T) { body := []byte(`{ "screen_id": "info01-dev", "ts": "not-a-time", "status": "running" }`) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) w := httptest.NewRecorder() handlePlayerStatus(newInMemoryPlayerStatusStore())(w, req) if got, want := w.Code, http.StatusBadRequest; got != want { t.Fatalf("status = %d, want %d", got, want) } } func TestHandlePlayerStatusRejectsMalformedStartedAt(t *testing.T) { body := []byte(`{ "screen_id": "info01-dev", "ts": "2026-03-22T16:00:00Z", "status": "running", "started_at": "not-a-time" }`) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) w := httptest.NewRecorder() handlePlayerStatus(newInMemoryPlayerStatusStore())(w, req) if got, want := w.Code, http.StatusBadRequest; got != want { t.Fatalf("status = %d, want %d", got, want) } } func TestHandlePlayerStatusRejectsMalformedLastHeartbeatAt(t *testing.T) { body := []byte(`{ "screen_id": "info01-dev", "ts": "2026-03-22T16:00:00Z", "status": "running", "last_heartbeat_at": "not-a-time" }`) req := httptest.NewRequest(http.MethodPost, "/api/v1/player/status", bytes.NewReader(body)) w := httptest.NewRecorder() handlePlayerStatus(newInMemoryPlayerStatusStore())(w, req) if got, want := w.Code, http.StatusBadRequest; got != want { t.Fatalf("status = %d, want %d", got, want) } } func TestHandleGetLatestPlayerStatus(t *testing.T) { store := newInMemoryPlayerStatusStore() store.Save(playerStatusRecord{ ScreenID: "info01-dev", Timestamp: "2026-03-22T16:00:00Z", Status: "running", ServerURL: "http://127.0.0.1:8080", MQTTBroker: "tcp://127.0.0.1:1883", HeartbeatEverySeconds: 30, StartedAt: "2026-03-22T15:59:30Z", LastHeartbeatAt: "2026-03-22T16:00:00Z", }) req := httptest.NewRequest(http.MethodGet, "/api/v1/screens/info01-dev/status", nil) req.SetPathValue("screenId", "info01-dev") w := httptest.NewRecorder() handleGetLatestPlayerStatus(store)(w, req) if got, want := w.Code, http.StatusOK; got != want { t.Fatalf("status = %d, want %d", got, want) } var response playerStatusRecord if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil { t.Fatalf("Unmarshal() error = %v", err) } if got, want := response.ScreenID, "info01-dev"; got != want { t.Fatalf("response.ScreenID = %q, want %q", got, want) } } func TestHandleGetLatestPlayerStatusNotFound(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/v1/screens/missing/status", nil) req.SetPathValue("screenId", "missing") w := httptest.NewRecorder() handleGetLatestPlayerStatus(newInMemoryPlayerStatusStore())(w, req) if got, want := w.Code, http.StatusNotFound; got != want { t.Fatalf("status = %d, want %d", got, want) } }