Aus dem Zettel wird die Notiz: Der Text steht immer formatiert da, roh ist nur
die Zeile mit der Schreibmarke. Dazu eine kleine Werkzeugleiste (Aufgabe,
Aufzaehlung, Nummer, Ueberschrift, fett, Linie), Enter fuehrt Listen fort,
Backspace am Zeilenanfang nimmt erst den Vorsatz. Fuer groessere Umbauten
bleibt der Quelltext-Knopf.
Kein contenteditable: Der Quelltext bleibt die Wahrheit und wird nie aus dem
HTML zurueckgerechnet, das der Browser beim Tippen erzeugt.
Reihenfolge und An/Aus der Widgets liegen jetzt je Nutzer auf dem Server, in
einer Einstellung ("benutzer,notiz,-system,wetter,bild"). Der Schalter fuer das
Wetter auf dem Schreibtisch faellt damit weg — es ist ein Widget wie jedes
andere.
Die Einstellungen sind in Abschnitte sortiert (Darstellung, HoneyBar, Widgets,
Wetter, Programme, Konto), Beschriftung links, Schalter rechts.
180 lines
5.8 KiB
Go
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
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|