Files
HoneyOS/wetter_test.go
T
marc c0abfbb491 Zwei Widgets: Zettel mit kleinem Markdown, Bild je Nutzer
Zettel: ein Textfeld, mehr nicht — InkNote gibt es schon. Gelesen wird
gesetzt (Ueberschriften, Haken, Aufzaehlungen, fett/kursiv/code, Strich),
geschrieben wird roh. Haken lassen sich ohne Bearbeiten setzen und
aendern die Quelle. Gespeichert wird 800 ms nach dem letzten Anschlag,
sofort beim Verlassen und beim Schliessen des Tabs (keepalive statt
sendBeacon: der kann nur POST und traegt keine Kopfzeilen). Grenze 4000
Zeichen, vom Server durchgesetzt.

Bild: genau eines je Nutzer, hochgeladen oder verlinkt. Die erste Datei,
die HoneyOS ablegt — in bilder/ neben honeyos.json, benannt nach einem
Fingerabdruck der Nutzerkennung. Ein neues Bild ersetzt das alte samt
Datei, die Adresse traegt einen Versionsanteil gegen den Zwischen-
speicher. SVG kommt nicht herein (Text, kann Skripte tragen), und der
Typ wird an den Bytes erkannt, nicht an der Angabe des Browsers.

Der Rahmen kann jetzt Widgets mit eigenem DOM: 'stabil' haelt den Inhalt,
'montiert' verdrahtet ihn. Ohne das haette das Anklopfen bei den Apps
alle zwei Minuten den Text unter den Fingern ausgetauscht.

Nebenbei: Der Aufrufhelfer setzte bei jedem Schreibzugriff
Content-Type: application/json — auch fuer Bildbytes.
2026-08-31 00:44:20 +02:00

180 lines
5.8 KiB
Go

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
}
}
}
// Der Zettel ist ein Zettel, kein Dokument: Die Grenze muss der Server ziehen,
// nicht das Formular — sonst schreibt sie jeder Aufruf aus, der das Formular
// umgeht.
func TestZettelHatEineGrenze(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("kurzer Zettel: %d, erwartet 204", code)
}
if code := put(strings.Repeat("x", 4001)); code != http.StatusBadRequest {
t.Errorf("zu langer Zettel: %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 — der zu lange Zettel darf den guten nicht überschrieben haben", out["text"])
}
}
// Fremde Zettel gehen niemanden etwas an: Der Schlüssel für die App-Liste darf
// sie nicht öffnen, auch nicht lesend.
func TestZettelNichtUeberDenSchluessel(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)
}
}