Files
Tobias GesellchenandClaude Opus 4.7 bb71253690 feat(screenshots): add headless-Chrome capture pipeline with fake speaker
Refreshes docs/images/ui-{settings,devices,sync,migration}.png by
driving the web UI in chromedp against a synthetic speaker, so
documentation can be regenerated without real hardware and without
leaking personal data from the local network.

Three independent pieces:

- pkg/service/testing/fakespeaker — embeddable library serving the
  HTTP and telnet surface the migration wizard probes (/info,
  /presets, /recents and a getpdo CurrentSystemConfiguration reply
  that places the device on the unmigrated happy path).
- cmd/dummy-speaker — thin CLI wrapping the library; self-registers
  with a running service via POST /setup/devices.
- scripts/screenshots — chromedp runner driven by a JSON manifest;
  decoupled from speaker/service setup so it can target any backend
  URL. run.sh orchestrates a one-shot end-to-end capture and seeds
  settings.json with a generic hostname plus discovery disabled to
  keep real-network state out of the captures.

Captures are at DPR=2 for retina-sharp text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 20:37:23 +02:00

82 lines
1.5 KiB
Go

package fakespeaker
import (
"bufio"
"context"
"net"
"strings"
"testing"
"time"
)
func TestTelnetServerBannerAndGetpdo(t *testing.T) {
s, err := Start(Config{TelnetListen: "127.0.0.1:0"})
if err != nil {
t.Fatalf("start: %v", err)
}
t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_ = s.Stop(ctx)
})
if s.TelnetAddr() == "" {
t.Fatalf("telnet listener not started")
}
conn, err := net.DialTimeout("tcp", s.TelnetAddr(), 2*time.Second)
if err != nil {
t.Fatalf("dial: %v", err)
}
defer func() { _ = conn.Close() }()
_ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))
reader := bufio.NewReader(conn)
banner, err := reader.ReadString('\n')
if err != nil {
t.Fatalf("read banner: %v", err)
}
if !strings.Contains(banner, "Bose SoundTouch") {
t.Errorf("banner = %q, want substring %q", banner, "Bose SoundTouch")
}
if _, err := conn.Write([]byte("getpdo CurrentSystemConfiguration\r\n")); err != nil {
t.Fatalf("write: %v", err)
}
_ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))
buf := make([]byte, 1024)
var got strings.Builder
for {
n, err := conn.Read(buf)
if n > 0 {
got.Write(buf[:n])
}
if err != nil {
break
}
if strings.Contains(got.String(), "->OK") {
break
}
}
out := got.String()
for _, want := range []string{"margeServerUrl", "streaming.bose.com", "swUpdateUrl"} {
if !strings.Contains(out, want) {
t.Errorf("response missing %q\nfull response:\n%s", want, out)
}
}
}