morz-infoboard/player/agent/internal/playerserver/server_test.go
Jesko Anschütz bbcf0a1228 Baue Ebene 1: Player-UI, Kiosk-Display und vollstaendiges Ansible-Deployment
Player-UI (playerserver):
- Lokale Kiosk-Seite unter /player mit orientierungsgerechtem Splash-Bild
- Splash-PNGs (Portrait/Landscape) eingebettet via go:embed
- Unteres-Drittel-Overlay mit erweiterbaren Sysinfo-Items (Hostname, Uptime)
- /api/now-playing und /api/sysinfo JSON-Endpunkte
- iframe-Overlay fuer spaetere Inhalts-URL

Ansible-Rolle signage_display (neu):
- Pakete: xserver-xorg-core, xinit, openbox, chromium, unclutter
- Kiosk-Skript mit openbox als WM (noetig fuer korrektes --kiosk-Vollbild)
- systemd-Unit mit Conflicts=getty@tty1 (behebt TTY-Blockierung beim Start)
- Chromium Managed Policy: TranslateEnabled=false, Notifications/Geolocation blockiert
- --lang=de Flag gegen Sprachauswahl-Dialog

Ansible-Rolle signage_player (erweitert):
- Legt signage_user an falls nicht vorhanden
- PlayerListenAddr und PlayerContentURL in Konfiguration
- journald volatile Storage (SD-Karten-Schonung)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 22:34:16 +01:00

147 lines
4.1 KiB
Go

package playerserver
import (
"context"
"encoding/json"
"io/fs"
"net"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func newTestServer(np NowPlaying) *Server {
return New("127.0.0.1:0", func() NowPlaying { return np })
}
func TestHandlePlayerReturnsHTML(t *testing.T) {
s := newTestServer(NowPlaying{Status: "running", Connectivity: "online"})
req := httptest.NewRequest(http.MethodGet, "/player", nil)
w := httptest.NewRecorder()
s.handlePlayer(w, req)
if ct := w.Header().Get("Content-Type"); !strings.Contains(ct, "text/html") {
t.Fatalf("Content-Type = %q, want text/html", ct)
}
body := w.Body.String()
for _, want := range []string{"<!DOCTYPE html>", "/api/now-playing", "/api/sysinfo", "splash-portrait.png", "splash-landscape.png"} {
if !strings.Contains(body, want) {
t.Fatalf("HTML missing %q", want)
}
}
}
func TestHandleNowPlayingWithURL(t *testing.T) {
s := newTestServer(NowPlaying{
URL: "https://example.com",
Status: "running",
Connectivity: "online",
})
req := httptest.NewRequest(http.MethodGet, "/api/now-playing", nil)
w := httptest.NewRecorder()
s.handleNowPlaying(w, req)
var got NowPlaying
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
t.Fatalf("Unmarshal() error = %v", err)
}
if got.URL != "https://example.com" {
t.Fatalf("URL = %q, want https://example.com", got.URL)
}
if got.Connectivity != "online" {
t.Fatalf("Connectivity = %q, want online", got.Connectivity)
}
}
func TestHandleNowPlayingWithoutURL(t *testing.T) {
s := newTestServer(NowPlaying{Status: "running", Connectivity: "degraded"})
req := httptest.NewRequest(http.MethodGet, "/api/now-playing", nil)
w := httptest.NewRecorder()
s.handleNowPlaying(w, req)
var got NowPlaying
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
t.Fatalf("Unmarshal() error = %v", err)
}
if got.URL != "" {
t.Fatalf("URL = %q, want empty", got.URL)
}
}
func TestHandleSysInfoReturnsItems(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/sysinfo", nil)
w := httptest.NewRecorder()
handleSysInfo(w, req)
if ct := w.Header().Get("Content-Type"); !strings.Contains(ct, "application/json") {
t.Fatalf("Content-Type = %q, want application/json", ct)
}
var got SysInfo
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
t.Fatalf("Unmarshal() error = %v", err)
}
// At least hostname should always be present.
if len(got.Items) == 0 {
t.Fatal("expected at least one sysinfo item")
}
labels := make(map[string]bool)
for _, item := range got.Items {
labels[item.Label] = true
if item.Value == "" {
t.Errorf("item %q has empty value", item.Label)
}
}
if !labels["Hostname"] {
t.Error("expected Hostname item in sysinfo")
}
}
func TestAssetsServed(t *testing.T) {
s := New("127.0.0.1:0", func() NowPlaying { return NowPlaying{} })
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ready := make(chan string, 1)
go func() {
ln, _ := net.Listen("tcp", "127.0.0.1:0")
ready <- ln.Addr().String()
ln.Close()
}()
// Use httptest recorder to test asset handler directly via the embed FS.
sub, err := fs.Sub(assetsFS, "assets")
if err != nil {
t.Fatalf("fs.Sub error = %v", err)
}
handler := http.StripPrefix("/assets/", http.FileServer(http.FS(sub)))
for _, name := range []string{"splash-landscape.png", "splash-portrait.png"} {
req := httptest.NewRequest(http.MethodGet, "/assets/"+name, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("GET /assets/%s = %d, want 200", name, w.Code)
}
if ct := w.Header().Get("Content-Type"); !strings.Contains(ct, "image/png") {
t.Errorf("GET /assets/%s Content-Type = %q, want image/png", name, ct)
}
}
_ = s
_ = ctx
}
func TestServerRunAndStop(t *testing.T) {
s := New("127.0.0.1:0", func() NowPlaying {
return NowPlaying{Status: "running", Connectivity: "online"}
})
ctx, cancel := context.WithCancel(context.Background())
errCh := make(chan error, 1)
go func() { errCh <- s.Run(ctx) }()
cancel()
if err := <-errCh; err != nil {
t.Fatalf("Run() error = %v", err)
}
}