package imgw import ( "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" ) func serve(t *testing.T, fixture string, gotQuery *string) *httptest.Server { t.Helper() body, err := os.ReadFile(filepath.Join("testdata", fixture)) if err != nil { t.Fatal(err) } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if gotQuery != nil { *gotQuery = r.URL.RawQuery } w.Write(body) })) t.Cleanup(srv.Close) return srv } func serveText(t *testing.T, body string) *httptest.Server { t.Helper() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(body)) })) t.Cleanup(srv.Close) return srv } func pinClock(t *testing.T, at time.Time) { t.Helper() old := now now = func() time.Time { return at } t.Cleanup(func() { now = old }) } // A point inside Poland resolves to the first four digits of its TERYT code. func TestPowiatTruncatesTerytToFourDigits(t *testing.T) { srv := serve(t, "gugik_krakow.json", nil) gugikURL = srv.URL code, status, err := Powiat(50.0617, 19.9373) if err != nil { t.Fatal(err) } if status != StatusOK { t.Fatalf("status = %v, want StatusOK", status) } if code != "1261" { t.Fatalf("code = %q, want 1261 (first four digits of 126101)", code) } } // GUGiK answers a foreign point with HTTP 200 and no results. That is a real // answer -- "not in Poland" -- and must never be confused with a failure, which // is the difference between "no warnings apply" and "I do not know". func TestPowiatOutsidePolandIsAnAnswerNotAnError(t *testing.T) { srv := serve(t, "gugik_abroad.json", nil) gugikURL = srv.URL code, status, err := Powiat(52.52, 13.40) if err != nil { t.Fatalf("a valid 'no results' response must not be an error: %v", err) } if status != StatusOutside { t.Fatalf("status = %v, want StatusOutside", status) } if code != "" { t.Fatalf("code = %q, want empty", code) } } func TestPowiatUnreachableIsStatusError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "boom", http.StatusBadGateway) })) srv.Close() // closed: the request cannot connect at all gugikURL = srv.URL _, status, err := Powiat(50, 21) if status != StatusError { t.Fatalf("status = %v, want StatusError", status) } if err == nil { t.Fatal("expected an error describing the failure") } } // The default 100 m radius finds nothing in the mountains, which is // indistinguishable from being abroad and would suppress real warnings. func TestPowiatAsksForAWideRadius(t *testing.T) { var query string srv := serve(t, "gugik_krakow.json", &query) gugikURL = srv.URL if _, _, err := Powiat(50, 21); err != nil { t.Fatal(err) } if !strings.Contains(query, "radius=10000") { t.Fatalf("query %q must ask for a wide radius", query) } } func TestWarningsKeepOnlyThisPowiat(t *testing.T) { pinClock(t, time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local)) // nothing expired body := `[ {"nazwa_zdarzenia":"Upal","stopien":"1","obowiazuje_do":"2030-01-01 00:00:00","teryt":["1261","1234"]}, {"nazwa_zdarzenia":"Burze","stopien":"2","obowiazuje_do":"2030-01-01 00:00:00","teryt":["2207"]} ]` srv := serveText(t, body) warningsURL = srv.URL live, err := Warnings("1261") if err != nil { t.Fatal(err) } if len(live) != 1 || live[0].Event != "Upal" { t.Fatalf("got %+v, want only the warning covering 1261", live) } } func TestWarningsDropExpiredButKeepUnparseableDates(t *testing.T) { pinClock(t, time.Date(2026, 8, 10, 12, 0, 0, 0, time.Local)) body := `[ {"nazwa_zdarzenia":"Wczorajsze","stopien":"1","obowiazuje_do":"2026-08-09 23:00:00","teryt":["1261"]}, {"nazwa_zdarzenia":"Trwajace","stopien":"1","obowiazuje_do":"2026-08-10 20:00:00","teryt":["1261"]}, {"nazwa_zdarzenia":"Bezdaty","stopien":"1","obowiazuje_do":"","teryt":["1261"]} ]` srv := serveText(t, body) warningsURL = srv.URL live, err := Warnings("1261") if err != nil { t.Fatal(err) } var names []string for _, w := range live { names = append(names, w.Event) } got := strings.Join(names, ",") // Unparseable is kept: showing a stale warning beats hiding a live one. if got != "Trwajace,Bezdaty" { t.Fatalf("got %q, want \"Trwajace,Bezdaty\"", got) } } // IMGW has been seen to encode TERYT codes as numbers as well as strings. func TestWarningsMatchNumericTerytCodes(t *testing.T) { pinClock(t, time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local)) srv := serveText(t, `[{"nazwa_zdarzenia":"X","obowiazuje_do":"2030-01-01 00:00:00","teryt":[1261]}]`) warningsURL = srv.URL live, err := Warnings("1261") if err != nil { t.Fatal(err) } if len(live) != 1 { t.Fatalf("a numeric teryt entry must still match, got %d warnings", len(live)) } } func TestWarningsUnreachableIsAnError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) srv.Close() warningsURL = srv.URL if _, err := Warnings("1261"); err == nil { t.Fatal("expected an error: the caller must be able to say 'could not check'") } } // The recorded national feed must parse, and must not match a made-up powiat. func TestWarningsParseTheRecordedFeed(t *testing.T) { pinClock(t, time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local)) srv := serve(t, "warnings.json", nil) warningsURL = srv.URL if _, err := Warnings("1261"); err != nil { t.Fatalf("the recorded feed must parse: %v", err) } none, err := Warnings("9999") if err != nil { t.Fatal(err) } if len(none) != 0 { t.Fatalf("powiat 9999 does not exist but matched %d warnings", len(none)) } }