package main import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "sync/atomic" "testing" ) // Ein erfundener Wetterdienst. Kein Test in diesem Projekt fragt je wirklich // bei Open-Meteo an: Das wäre langsam, unzuverlässig und würde einen fremden // Dienst für unsere Testläufe benutzen. func fakeWetter(t *testing.T, code int, grad float64, aufrufe *int32) *wetterDienst { t.Helper() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if aufrufe != nil { atomic.AddInt32(aufrufe, 1) } if strings.Contains(r.URL.Path, "search") { json.NewEncoder(w).Encode(map[string]any{"results": []map[string]any{ {"name": "Köln", "country": "Deutschland", "admin1": "Nordrhein-Westfalen", "latitude": 50.938, "longitude": 6.96}, }}) return } json.NewEncoder(w).Encode(map[string]any{"current": map[string]any{ "temperature_2m": grad, "weather_code": code, }}) })) t.Cleanup(srv.Close) d := neuerWetterdienst() d.wetterURL = srv.URL + "/forecast" d.sucheURL = srv.URL + "/search" return d } func TestWetterKommtAn(t *testing.T) { d := fakeWetter(t, 61, 12.4, nil) w, err := d.Jetzt(context.Background(), "Köln", 50.9, 6.9) if err != nil { t.Fatalf("unerwartet: %v", err) } if w.Ort != "Köln" || w.Grad != 12.4 { t.Fatalf("falsche Werte: %+v", w) } if w.Glyph != "regen" || w.Text != "regen" { t.Fatalf("Code 61 ist Regen, nicht %q/%q", w.Glyph, w.Text) } } // Der Zwischenspeicher ist der Grund, warum die Leiste auf jeder Seite jeder App // stehen darf: Ohne ihn ginge bei jedem Seitenaufruf eine Anfrage nach draußen. func TestZweiteFrageGehtNichtNachDraussen(t *testing.T) { var aufrufe int32 d := fakeWetter(t, 0, 21, &aufrufe) for i := 0; i < 5; i++ { if _, err := d.Jetzt(context.Background(), "Köln", 50.9, 6.9); err != nil { t.Fatalf("unerwartet: %v", err) } } if aufrufe != 1 { t.Fatalf("%d Anfragen nach draußen, erwartet 1", aufrufe) } } func TestAndererOrtWirdGetrenntGehalten(t *testing.T) { var aufrufe int32 d := fakeWetter(t, 0, 21, &aufrufe) d.Jetzt(context.Background(), "Köln", 50.9, 6.9) d.Jetzt(context.Background(), "Wien", 48.2, 16.4) if aufrufe != 2 { t.Fatalf("zwei Orte, %d Anfragen", aufrufe) } } func TestWettercodesWerdenZuSymbolen(t *testing.T) { fälle := map[int]string{ 0: "sonne", 2: "heiter", 3: "wolke", 45: "nebel", 61: "regen", 80: "regen", 73: "schnee", 86: "schnee", 95: "gewitter", } for code, will := range fälle { if g, _ := wmo(code); g != will { t.Errorf("Code %d → %q, erwartet %q", code, g, will) } } } func TestOrtssucheGibtKoordinatenZurueck(t *testing.T) { d := fakeWetter(t, 0, 20, nil) orte, err := d.Suche(context.Background(), "Köln") if err != nil { t.Fatalf("unerwartet: %v", err) } if len(orte) != 1 || orte[0].Lat == 0 || orte[0].Lon == 0 { t.Fatalf("ohne Koordinaten müsste jede Abfrage neu suchen: %+v", orte) } // Bundesland statt Land: „Köln, Deutschland" gibt es mehrfach. if !strings.Contains(orte[0].Land, "Nordrhein-Westfalen") { t.Fatalf("Land zu grob: %q", orte[0].Land) } } func TestSucheOhneBegriffFragtNichtAn(t *testing.T) { var aufrufe int32 d := fakeWetter(t, 0, 20, &aufrufe) if _, err := d.Suche(context.Background(), " "); err == nil { t.Fatal("leerer Begriff müsste abgelehnt werden") } if aufrufe != 0 { t.Fatalf("leerer Begriff ging trotzdem nach draußen (%d)", aufrufe) } } // Ohne eingestellten Ort ist 204 die richtige Antwort: kein Fehler, nur nichts // zu zeigen. Die Leiste lässt das Symbol dann weg, statt etwas zu melden. func TestOhneOrtGibtEsKeinWetter(t *testing.T) { s := newTestServer(t, "") // ohne Anbieter: offener Betrieb, kein Anmelden nötig s.wetter = fakeWetter(t, 0, 20, nil) rec := httptest.NewRecorder() s.routes().ServeHTTP(rec, httptest.NewRequest("GET", "/api/weather", nil)) if rec.Code != http.StatusNoContent { t.Fatalf("ohne Ort erwartet 204, bekam %d", rec.Code) } } func TestMitOrtKommtDasWetter(t *testing.T) { s := newTestServer(t, "") s.wetter = fakeWetter(t, 3, 8.6, nil) if err := s.store.SetSettings(localSub, map[string]string{ "weatherCity": "Köln", "weatherLat": "50.938", "weatherLon": "6.96", }); err != nil { t.Fatal(err) } rec := httptest.NewRecorder() s.routes().ServeHTTP(rec, httptest.NewRequest("GET", "/api/weather", nil)) if rec.Code != http.StatusOK { t.Fatalf("erwartet 200, bekam %d: %s", rec.Code, rec.Body) } var w Wetter json.Unmarshal(rec.Body.Bytes(), &w) if w.Ort != "Köln" || w.Glyph != "wolke" { t.Fatalf("falsches Wetter: %+v", w) } }