Validiere server_connectivity und stale als Query-Parameter
Bisher wurden ungueltige Werte fuer server_connectivity und stale im Listing-Endpunkt und auf der Statusseite stillschweigend ignoriert bzw. fuehrten zu leeren Ergebnissen ohne Fehlermeldung. Beide Parameter werden jetzt explizit auf erlaubte Werte geprueft und liefern bei ungueltiger Eingabe einen 400-Fehler mit beschreibendem error_code – konsistent mit der bestehenden Validierung fuer updated_since und limit. Neue Tests (playerstatus_test.go): - RejectsInvalidServerConnectivity - RejectsInvalidStale - RejectsInvalidUpdatedSince - RejectsInvalidLimit Neue Tests (router_test.go): - StatusPageRejectsInvalidQueryParams (table-driven, alle 4 Faelle) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0b199f9289
commit
a7889231c0
3 changed files with 93 additions and 2 deletions
|
|
@ -157,8 +157,23 @@ func handleListLatestPlayerStatuses(store playerStatusStore) http.HandlerFunc {
|
||||||
|
|
||||||
func buildScreenStatusOverview(store playerStatusStore, query url.Values) (screenStatusOverview, error) {
|
func buildScreenStatusOverview(store playerStatusStore, query url.Values) (screenStatusOverview, error) {
|
||||||
records := store.List()
|
records := store.List()
|
||||||
|
|
||||||
wantConnectivity := strings.TrimSpace(query.Get("server_connectivity"))
|
wantConnectivity := strings.TrimSpace(query.Get("server_connectivity"))
|
||||||
|
switch wantConnectivity {
|
||||||
|
case "", "online", "degraded", "offline", "unknown":
|
||||||
|
// valid
|
||||||
|
default:
|
||||||
|
return screenStatusOverview{}, errInvalidServerConnectivity
|
||||||
|
}
|
||||||
|
|
||||||
wantStale := strings.TrimSpace(query.Get("stale"))
|
wantStale := strings.TrimSpace(query.Get("stale"))
|
||||||
|
switch wantStale {
|
||||||
|
case "", "true", "false":
|
||||||
|
// valid
|
||||||
|
default:
|
||||||
|
return screenStatusOverview{}, errInvalidStale
|
||||||
|
}
|
||||||
|
|
||||||
updatedSince, err := parseOptionalRFC3339(query.Get("updated_since"))
|
updatedSince, err := parseOptionalRFC3339(query.Get("updated_since"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return screenStatusOverview{}, errInvalidUpdatedSince
|
return screenStatusOverview{}, errInvalidUpdatedSince
|
||||||
|
|
@ -222,8 +237,10 @@ func buildScreenStatusOverview(store playerStatusStore, query url.Values) (scree
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errInvalidUpdatedSince = errors.New("invalid updated_since")
|
errInvalidUpdatedSince = errors.New("invalid updated_since")
|
||||||
errInvalidLimit = errors.New("invalid limit")
|
errInvalidLimit = errors.New("invalid limit")
|
||||||
|
errInvalidServerConnectivity = errors.New("invalid server_connectivity")
|
||||||
|
errInvalidStale = errors.New("invalid stale")
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeOverviewQueryError(w http.ResponseWriter, err error) {
|
func writeOverviewQueryError(w http.ResponseWriter, err error) {
|
||||||
|
|
@ -232,6 +249,10 @@ func writeOverviewQueryError(w http.ResponseWriter, err error) {
|
||||||
writeError(w, http.StatusBadRequest, "invalid_updated_since", "updated_since ist kein gueltiger RFC3339-Zeitstempel", nil)
|
writeError(w, http.StatusBadRequest, "invalid_updated_since", "updated_since ist kein gueltiger RFC3339-Zeitstempel", nil)
|
||||||
case errInvalidLimit:
|
case errInvalidLimit:
|
||||||
writeError(w, http.StatusBadRequest, "invalid_limit", "limit muss eine positive Ganzzahl sein", nil)
|
writeError(w, http.StatusBadRequest, "invalid_limit", "limit muss eine positive Ganzzahl sein", nil)
|
||||||
|
case errInvalidServerConnectivity:
|
||||||
|
writeError(w, http.StatusBadRequest, "invalid_server_connectivity", "server_connectivity muss online, offline, degraded oder unknown sein", nil)
|
||||||
|
case errInvalidStale:
|
||||||
|
writeError(w, http.StatusBadRequest, "invalid_stale", "stale muss true oder false sein", nil)
|
||||||
default:
|
default:
|
||||||
writeError(w, http.StatusBadRequest, "invalid_query", "ungueltige Query-Parameter", nil)
|
writeError(w, http.StatusBadRequest, "invalid_query", "ungueltige Query-Parameter", nil)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -579,6 +579,50 @@ func TestHandleListLatestPlayerStatusesAppliesLimit(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandleListLatestPlayerStatusesRejectsInvalidServerConnectivity(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/screens/status?server_connectivity=garbage", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handleListLatestPlayerStatuses(newInMemoryPlayerStatusStore())(w, req)
|
||||||
|
|
||||||
|
if got, want := w.Code, http.StatusBadRequest; got != want {
|
||||||
|
t.Fatalf("status = %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleListLatestPlayerStatusesRejectsInvalidStale(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/screens/status?stale=maybe", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handleListLatestPlayerStatuses(newInMemoryPlayerStatusStore())(w, req)
|
||||||
|
|
||||||
|
if got, want := w.Code, http.StatusBadRequest; got != want {
|
||||||
|
t.Fatalf("status = %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleListLatestPlayerStatusesRejectsInvalidUpdatedSince(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/screens/status?updated_since=not-a-time", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handleListLatestPlayerStatuses(newInMemoryPlayerStatusStore())(w, req)
|
||||||
|
|
||||||
|
if got, want := w.Code, http.StatusBadRequest; got != want {
|
||||||
|
t.Fatalf("status = %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleListLatestPlayerStatusesRejectsInvalidLimit(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/screens/status?limit=0", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handleListLatestPlayerStatuses(newInMemoryPlayerStatusStore())(w, req)
|
||||||
|
|
||||||
|
if got, want := w.Code, http.StatusBadRequest; got != want {
|
||||||
|
t.Fatalf("status = %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHandleListLatestPlayerStatusesFiltersByUpdatedSince(t *testing.T) {
|
func TestHandleListLatestPlayerStatusesFiltersByUpdatedSince(t *testing.T) {
|
||||||
store := newInMemoryPlayerStatusStore()
|
store := newInMemoryPlayerStatusStore()
|
||||||
store.Save(playerStatusRecord{ScreenID: "screen-old", Timestamp: "2026-03-22T16:00:00Z", Status: "running", ServerConnectivity: "online", ReceivedAt: "2026-03-22T16:00:00Z", HeartbeatEverySeconds: 30})
|
store.Save(playerStatusRecord{ScreenID: "screen-old", Timestamp: "2026-03-22T16:00:00Z", Status: "running", ServerConnectivity: "online", ReceivedAt: "2026-03-22T16:00:00Z", HeartbeatEverySeconds: 30})
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,32 @@ func TestRouterScreenStatusListRoute(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouterStatusPageRejectsInvalidQueryParams(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
query string
|
||||||
|
}{
|
||||||
|
{"invalid server_connectivity", "?server_connectivity=garbage"},
|
||||||
|
{"invalid stale", "?stale=maybe"},
|
||||||
|
{"invalid updated_since", "?updated_since=not-a-time"},
|
||||||
|
{"invalid limit zero", "?limit=0"},
|
||||||
|
{"invalid limit negative", "?limit=-1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/status"+tc.query, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
NewRouter(newInMemoryPlayerStatusStore()).ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if got, want := w.Code, http.StatusBadRequest; got != want {
|
||||||
|
t.Fatalf("status = %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRouterStatusPageRoute(t *testing.T) {
|
func TestRouterStatusPageRoute(t *testing.T) {
|
||||||
store := newInMemoryPlayerStatusStore()
|
store := newInMemoryPlayerStatusStore()
|
||||||
store.now = func() time.Time {
|
store.now = func() time.Time {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue