package main import ( "encoding/json" "net/http" "net/http/httptest" "os" "strings" "testing" "honeyos/internal/auth" "honeyos/internal/store" "honeyos/internal/weather" ) // 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 = weather.FakeFuerTest(t, 0, 20) 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 = weather.FakeFuerTest(t, 3, 8.6) if err := s.store.SetSettings(store.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 weather.Wetter json.Unmarshal(rec.Body.Bytes(), &w) if w.Ort != "Köln" || w.Glyph != "wolke" { t.Fatalf("falsches Wetter: %+v", w) } } // Ist ein Ort eingestellt und der Dienst fällt aus, ist das etwas anderes als // „nichts eingestellt": Es gibt etwas zu zeigen, es kommt nur gerade nicht. Die // Oberfläche unterscheidet das — 204 verschwindet still, 502 sagt „nicht // erreichbar". Beides als 204 zu melden hiess: Ein Ausfall sah aus wie ein // abgeschaltetes Widget. func TestAusfallIstNichtDasselbeWieNichts(t *testing.T) { s := newTestServer(t, "") s.wetter = weather.KaputtFuerTest(t) if err := s.store.SetSettings(store.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.StatusBadGateway { t.Fatalf("bei Ausfall erwartet 502, bekam %d", rec.Code) } } // Der Streifen in der Leiste heisst .apps und blendet seine rechten 18 Pixel // mit einer Maske aus. Bekommt ein zweites Element dieselbe Klasse, erbt es die // Maske — die Schublade sah rechts aus, als loese sie sich auf, und in der // Geometrie war nichts zu sehen. Deshalb hier festgenagelt. func TestNurDerStreifenHeisstApps(t *testing.T) { b, err := os.ReadFile("web/honeybar/honeybar.js") if err != nil { t.Fatal(err) } src := string(b) for _, verboten := range []string{`class="menu apps"`, `class="apps menu"`} { if strings.Contains(src, verboten) { t.Errorf("%s: Das Menue erbt damit die Ausblendmaske des App-Streifens", verboten) } } // Genau ein Element darf die Klasse tragen: der Streifen selbst. if n := strings.Count(src, `class="apps"`); n != 1 { t.Errorf(`class="apps" kommt %dx vor, erwartet genau einmal (der Streifen)`, n) } } // Die HoneyBar bringt ihr CSS und ihr Markup in Vorlagenzeichenketten mit. // Ein Backtick in einem Kommentar beendet die Zeichenkette mittendrin — die // Datei ist dann kaputt, und zwar nur in der HoneyBar, nicht auf dem // Schreibtisch. Genau das ist zweimal passiert; deshalb dieser Wächter. func TestHoneyBarOhneBacktickInKommentaren(t *testing.T) { b, err := os.ReadFile("web/honeybar/honeybar.js") if err != nil { t.Fatal(err) } src := string(b) imBlock := false for nr, zeile := range strings.Split(src, "\n") { rest := zeile for { if imBlock { ende := strings.Index(rest, "*/") if ende < 0 { if strings.Contains(rest, "`") { t.Errorf("Zeile %d: Backtick in einem Kommentar — das beendet die Vorlagenzeichenkette:\n %s", nr+1, strings.TrimSpace(zeile)) } break } if strings.Contains(rest[:ende], "`") { t.Errorf("Zeile %d: Backtick in einem Kommentar — das beendet die Vorlagenzeichenkette:\n %s", nr+1, strings.TrimSpace(zeile)) } rest = rest[ende+2:] imBlock = false continue } start := strings.Index(rest, "/*") if start < 0 { break } rest = rest[start+2:] imBlock = true } } } // Die Notiz ist eine Notiz, kein Dokument: Die Grenze muss der Server ziehen, // nicht das Formular — sonst schreibt sie jeder Aufruf aus, der das Formular // umgeht. func TestNotizHatEineGrenze(t *testing.T) { s := newTestServer(t, "") h := s.routes() put := func(text string) int { body, _ := json.Marshal(map[string]string{"text": text}) req := httptest.NewRequest("PUT", "/api/note", strings.NewReader(string(body))) rec := httptest.NewRecorder() h.ServeHTTP(rec, req) return rec.Code } if code := put("# Einkauf\n- [ ] Milch"); code != http.StatusNoContent { t.Fatalf("kurze Notiz: %d, erwartet 204", code) } if code := put(strings.Repeat("x", 4001)); code != http.StatusBadRequest { t.Errorf("zu lange Notiz: %d, erwartet 400", code) } rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest("GET", "/api/note", nil)) var out map[string]string json.Unmarshal(rec.Body.Bytes(), &out) if out["text"] != "# Einkauf\n- [ ] Milch" { t.Errorf("zurück kam %q — die zu lange Notiz darf die gute nicht überschrieben haben", out["text"]) } } // Fremde Notizen gehen niemanden etwas an: Der Schlüssel für die App-Liste darf // sie nicht öffnen, auch nicht lesend. func TestNotizNichtUeberDenSchluessel(t *testing.T) { idp := newFakeIdP(t, "honeyos") s := newTestServer(t, idp.URL) s.cfg.AppsKey = "geheim123" s.auth = auth.New(s.cfg) req := httptest.NewRequest("GET", "/api/note", nil) req.Header.Set("X-HoneyOS-Key", "geheim123") rec := httptest.NewRecorder() s.routes().ServeHTTP(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("mit Schlüssel: %d, erwartet 401", rec.Code) } } // Das Wappen steht an drei Stellen: als Datei (Favicon), als Modul für den // Schreibtisch und als Zeile in der HoneyBar — die kann keine Datei nachladen, // weil sie in fremden Apps läuft und deren CSP das entscheidet. // // Drei Kopien laufen auseinander, sobald jemand eine davon von Hand anfasst. // Erzeugt werden alle von tools/biene.py; dieser Test hält sie zusammen. func TestWappenIstUeberallDasselbe(t *testing.T) { roh, err := os.ReadFile("web/img/biene.svg") if err != nil { t.Fatalf("web/img/biene.svg fehlt: %v", err) } wappen := strings.TrimSpace(string(roh)) if !strings.HasPrefix(wappen, "