Files
marc 0a5cd94ba0 Systemmenue im Logo, in beiden Leisten
Das ■ HONEYOS ist jetzt ein Menue statt eines Links: Schreibtisch,
Einstellungen, HoneyBar einklappen, Abmelden. In fremden Apps sind es
Links auf den Schreibtisch (?einstellungen=1 / ?abmelden=1), weil die
HoneyBar dort nicht schreiben darf — Sitzung und CSRF-Pruefwort ueber-
leben die Site-Grenze nicht, und ein Abmelden per Aufruf koennte jede
Seite ausloesen.

Der Abmeldeknopf in der Menueleiste faellt weg (stand doppelt), und
Abmelden erscheint nur, wenn HoneyOS ueberhaupt anmeldet — dafuer gibt
/api/apps jetzt oidc mit heraus. Nebenbei: restliche 'Leiste' im Text
zu 'HoneyBar' vereinheitlicht.
2026-08-29 21:23:11 +02:00

117 lines
3.4 KiB
Go

package weather
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) *Dienst {
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 := Neu()
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 HoneyBar 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)
}
}