package httpapi import ( "encoding/json" "net/http" "net/http/httptest" "testing" "time" "juegoland/games2/internal/domain/catalog" "juegoland/games2/internal/server" ) func TestPublishedCatalogIsTheOnlyPublicCatalog(t *testing.T) { registry := catalog.NewRegistry([]catalog.Game{ {ID: "draft", Title: "Borrador", Published: false}, {ID: "conecta4", Version: "1.0.0", Title: "Conecta 4", Summary: "Cuatro fichas en línea", MinPlayers: 2, MaxPlayers: 2, Published: true}, }) handler := NewHandler(server.NewService(registry, time.Now)) recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/api/v2/catalog", nil)) if recorder.Code != http.StatusOK { t.Fatalf("status = %d, want 200", recorder.Code) } if recorder.Header().Get("Cache-Control") != "no-store" { t.Fatal("catalog response must not be cached before audience policies exist") } var body struct { RequestID string `json:"requestId"` Data struct { Items []struct { ID string `json:"id"` } `json:"items"` } `json:"data"` } if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil { t.Fatal(err) } if body.RequestID == "" || len(body.Data.Items) != 1 || body.Data.Items[0].ID != "conecta4" { t.Fatalf("unexpected catalog response: %+v", body) } } func TestBootstrapDoesNotAdvertiseUnavailableFeatures(t *testing.T) { handler := NewHandler(server.NewService(catalog.NewRegistry(nil), func() time.Time { return time.Date(2026, time.October, 5, 12, 0, 0, 0, time.UTC) })) recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/api/v2/bootstrap", nil)) if recorder.Code != http.StatusOK { t.Fatalf("status = %d, want 200", recorder.Code) } var body struct { Data server.Bootstrap `json:"data"` } if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil { t.Fatal(err) } if body.Data.Features.Rooms || body.Data.Features.Chat || !body.Data.Features.Catalog { t.Fatalf("incorrect advertised features: %+v", body.Data.Features) } if len(body.Data.PlatformProtocolVersions) != 0 || len(body.Data.GameProtocolVersions) != 0 { t.Fatal("unimplemented realtime protocols must not be advertised") } if body.Data.DefaultLocale != "es" || len(body.Data.SupportedLocales) != 2 || body.Data.SupportedLocales[1] != "en" { t.Fatalf("wrong locale capabilities: %+v", body.Data) } if body.Data.ServerTime.Format(time.RFC3339) != "2026-10-05T12:00:00Z" { t.Fatalf("wrong server time: %s", body.Data.ServerTime) } } func TestCatalogNegotiatesLanguageAndLabelsFallback(t *testing.T) { registry := catalog.NewRegistry([]catalog.Game{ { ID: "conecta4", Version: "1.0.0", Title: "Conecta 4", Summary: "Cuatro en línea", DefaultLocale: "es", MinPlayers: 2, MaxPlayers: 2, Published: true, Translations: map[string]catalog.Translation{ "en": {Title: "Connect Four", Summary: "Four in a row"}, }, }, }) handler := NewHandler(server.NewService(registry, time.Now)) request := httptest.NewRequest(http.MethodGet, "/api/v2/catalog", nil) request.Header.Set("Accept-Language", "fr, en-GB;q=0.9, es;q=0.5") recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, request) var body struct { Data struct { Items []struct { Title string `json:"title"` Locale string `json:"locale"` } `json:"items"` } `json:"data"` } if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil { t.Fatal(err) } if len(body.Data.Items) != 1 || body.Data.Items[0].Title != "Connect Four" || body.Data.Items[0].Locale != "en" { t.Fatalf("unexpected translated catalog: %+v", body) } if recorder.Header().Get("Content-Language") != "en" || recorder.Header().Get("Vary") != "Accept-Language" { t.Fatalf("missing language headers: %+v", recorder.Header()) } request = httptest.NewRequest(http.MethodGet, "/api/v2/catalog", nil) request.Header.Set("Accept-Language", "de, en;q=0, es;q=0.5") recorder = httptest.NewRecorder() handler.ServeHTTP(recorder, request) if recorder.Header().Get("Content-Language") != "es" { t.Fatalf("expected Spanish fallback, got %q", recorder.Header().Get("Content-Language")) } } func TestCatalogDeclaresGameFallbackLanguage(t *testing.T) { registry := catalog.NewRegistry([]catalog.Game{ {ID: "hundido", Version: "1.0.0", Title: "Hundir la flota", Summary: "Encuentra los barcos", DefaultLocale: "es", MinPlayers: 2, MaxPlayers: 2, Published: true}, }) handler := NewHandler(server.NewService(registry, time.Now)) request := httptest.NewRequest(http.MethodGet, "/api/v2/catalog", nil) request.Header.Set("Accept-Language", "en") recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, request) var body struct { Data struct { Items []struct { Locale string `json:"locale"` } `json:"items"` } `json:"data"` } if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil { t.Fatal(err) } if len(body.Data.Items) != 1 || body.Data.Items[0].Locale != "es" || recorder.Header().Get("Content-Language") != "es" { t.Fatalf("fallback language not declared: %+v, header: %q", body, recorder.Header().Get("Content-Language")) } }