Aus dem Quadrat links in beiden Leisten wird eine Pixelbiene von der Seite: braune Streifen, schwarze Beine, Fluegel mit Kerbe. Sie steht in genau einem Raster (tools/biene.py, 16x16 Zeichen); daraus entstehen SVG, PNG und eine Fassung aus Schriftzeichen. SVG statt PNG in den Leisten: Die HoneyBar laeuft in fremden Apps, und ob ein <img> dort laedt, entscheidet deren CSP. Markup kann niemand blockieren. Im System-Fenster und ueber dem Startlauf steht die Biene aus Schriftzeichen — beides sind Textflaechen, ein Bild darin waere ein Fremdkoerper. Das Wappen liegt an drei Stellen (Datei, Modul, HoneyBar-Zeile), weil die Leiste nichts nachladen kann. TestWappenIstUeberallDasselbe haelt sie gleich.
207 lines
6.9 KiB
Go
207 lines
6.9 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
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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, "<svg") || len(wappen) < 500 {
|
|
t.Fatalf("web/img/biene.svg sieht nicht nach dem Wappen aus (%d Zeichen)", len(wappen))
|
|
}
|
|
|
|
for _, datei := range []string{"web/honeybar/honeybar.js", "web/js/biene.js"} {
|
|
b, err := os.ReadFile(datei)
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", datei, err)
|
|
}
|
|
if !strings.Contains(string(b), wappen) {
|
|
t.Errorf("%s trägt ein anderes Wappen als web/img/biene.svg — tools/biene.py laufen lassen", datei)
|
|
}
|
|
}
|
|
}
|