// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package speech import ( "context" "net" "path/filepath" "strconv" "testing" ) func TestResolveServerAddressPrecedence(t *testing.T) { runtimeDirectory := t.TempDir() cacheDirectory := t.TempDir() t.Setenv("XDG_RUNTIME_DIR", runtimeDirectory) t.Setenv("XDG_CACHE_HOME", cacheDirectory) t.Setenv("SPEECHD_SOCKET", "/tmp/legacy-speech.sock") t.Setenv("SPEECHD_ADDRESS", "unix_socket:/tmp/address-speech.sock") for _, test := range []struct { name, override, speechAddress, legacySocket, runtimeDir, want string }{ {"explicit", "/tmp/explicit-speech.sock", "unix_socket:/tmp/address-speech.sock", "/tmp/legacy-speech.sock", runtimeDirectory, "/tmp/explicit-speech.sock"}, {"address", "", "unix_socket:/tmp/address-speech.sock", "/tmp/legacy-speech.sock", runtimeDirectory, "/tmp/address-speech.sock"}, {"legacy", "", "", "/tmp/legacy-speech.sock", runtimeDirectory, "/tmp/legacy-speech.sock"}, {"runtime", "", "", "", runtimeDirectory, filepath.Join(runtimeDirectory, "speech-dispatcher", "speechd.sock")}, {"cache", "", "", "", "", filepath.Join(cacheDirectory, "speech-dispatcher", "speechd.sock")}, } { t.Run(test.name, func(t *testing.T) { t.Setenv("SPEECHD_ADDRESS", test.speechAddress) t.Setenv("SPEECHD_SOCKET", test.legacySocket) t.Setenv("XDG_RUNTIME_DIR", test.runtimeDir) got, err := resolveServerAddress(test.override) if err != nil || got.network != "unix" || got.address != test.want { t.Fatalf("address = %+v, error = %v; want %q", got, err, test.want) } }) } } func TestParseServerAddress(t *testing.T) { for _, test := range []struct { input, network, address string }{ {"unix_socket:/tmp/speech.sock", "unix", "/tmp/speech.sock"}, {"inet_socket", "tcp4", "127.0.0.1:6560"}, {"inet_socket:localhost:6561", "tcp4", "localhost:6561"}, } { got, err := parseServerAddress(test.input) if err != nil || got.network != test.network || got.address != test.address { t.Fatalf("parse %q = %+v, %v", test.input, got, err) } } for _, address := range []string{"invalid:/tmp/speech.sock", "unix_socket:relative.sock", "inet_socket:localhost:0", "inet_socket:localhost:bad"} { if _, err := parseServerAddress(address); err == nil { t.Fatalf("accepted invalid address %q", address) } } } func TestClientAndDiscoveryHonorSpeechdAddress(t *testing.T) { path, _ := newSettingsServer(t, func(command string) string { if command == "LIST OUTPUT_MODULES" { return "200-test\r\n200 OK\r\n" } return "" }) t.Setenv("SPEECHD_ADDRESS", "unix_socket:"+path) t.Setenv("SPEECHD_SOCKET", "/tmp/absent-speech.sock") client := New("") defer client.Close(context.Background()) if err := client.Connect(context.Background()); err != nil { t.Fatal(err) } if _, err := Discover(context.Background(), ""); err != nil { t.Fatal(err) } } func TestTCPAddressConnects(t *testing.T) { listener, err := net.Listen("tcp4", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer listener.Close() server := newSettingsServerListener(t, listener, nil) port := listener.Addr().(*net.TCPAddr).Port t.Setenv("SPEECHD_ADDRESS", "inet_socket:127.0.0.1:"+strconv.Itoa(port)) client := New("") defer client.Close(context.Background()) if err := client.Connect(context.Background()); err != nil { t.Fatal(err) } if !server.saw(1, "HISTORY GET CLIENT_ID") { t.Fatal("speech client did not use the TCP server") } }