Files
wehub-resource-sync ead81af521
CI / go (push) Waiting to run
CI / build (darwin, macos-14) (push) Waiting to run
CI / build (windows, windows-2025) (push) Waiting to run
Deploy to GitHub Pages / deploy (push) Failing after 0s
chore: import upstream snapshot with attribution
2026-07-13 12:31:13 +08:00

232 lines
6.3 KiB
Go

package ipc
import (
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
)
// TestSendRoundTrip spins up a real server bound to a temp socket and exchanges
// one request/response through the client.
func TestSendRoundTrip(t *testing.T) {
sock := filepath.Join(t.TempDir(), "cliamp.sock")
disp := &captureDispatcher{autoReply: Response{OK: true}}
srv, err := NewServer(sock, disp)
if err != nil {
t.Fatalf("NewServer: %v", err)
}
t.Cleanup(func() { _ = srv.Close() })
resp, err := Send(sock, Request{Cmd: "play"})
if err != nil {
t.Fatalf("Send: %v", err)
}
if !resp.OK {
t.Errorf("resp.OK = false, want true (err=%q)", resp.Error)
}
if _, ok := disp.last.(PlayMsg); !ok {
t.Errorf("server received %T, want PlayMsg", disp.last)
}
}
func TestSendNoServer(t *testing.T) {
sock := filepath.Join(t.TempDir(), "missing.sock")
_, err := Send(sock, Request{Cmd: "status"})
if err == nil {
t.Fatal("Send to missing socket should error")
}
if !strings.Contains(err.Error(), "not running") {
t.Errorf("error = %q, want to mention 'not running'", err.Error())
}
}
func TestSendInvalidRequestReturnsError(t *testing.T) {
// Server responds to an unknown cmd with OK:false, Error:"unknown command:...".
sock := filepath.Join(t.TempDir(), "cliamp.sock")
srv, err := NewServer(sock, &captureDispatcher{})
if err != nil {
t.Fatalf("NewServer: %v", err)
}
t.Cleanup(func() { _ = srv.Close() })
resp, err := Send(sock, Request{Cmd: "doesnotexist"})
if err != nil {
t.Fatalf("Send: %v", err)
}
if resp.OK {
t.Error("unknown cmd should return !OK")
}
if !strings.Contains(resp.Error, "unknown command") {
t.Errorf("error = %q, want to mention 'unknown command'", resp.Error)
}
}
func TestDefaultSocketPath(t *testing.T) {
t.Setenv("HOME", t.TempDir())
p := DefaultSocketPath()
if !strings.HasSuffix(p, filepath.Join("cliamp", "cliamp.sock")) {
t.Errorf("DefaultSocketPath = %q, want to end with cliamp/cliamp.sock", p)
}
}
func TestNewServerRemovesOrphanSocket(t *testing.T) {
dir := t.TempDir()
sock := filepath.Join(dir, "cliamp.sock")
// Create an orphan socket file with no PID file — NewServer should remove it.
if err := os.WriteFile(sock, []byte(""), 0o600); err != nil {
t.Fatalf("WriteFile: %v", err)
}
srv, err := NewServer(sock, &captureDispatcher{})
if err != nil {
t.Fatalf("NewServer: %v", err)
}
t.Cleanup(func() { _ = srv.Close() })
// Server is up and serving.
resp, err := Send(sock, Request{Cmd: "play"})
if err != nil {
t.Fatalf("Send: %v", err)
}
if !resp.OK {
t.Errorf("resp.OK = false, want true")
}
}
func TestNewServerCorruptPIDFile(t *testing.T) {
dir := t.TempDir()
sock := filepath.Join(dir, "cliamp.sock")
// Corrupt PID file is cleaned and NewServer succeeds.
if err := os.WriteFile(sock+".pid", []byte("notanumber"), 0o600); err != nil {
t.Fatalf("WriteFile: %v", err)
}
srv, err := NewServer(sock, &captureDispatcher{})
if err != nil {
t.Fatalf("NewServer with corrupt PID: %v", err)
}
_ = srv.Close()
}
func TestNewServerDeadPIDFile(t *testing.T) {
dir := t.TempDir()
sock := filepath.Join(dir, "cliamp.sock")
// PID 1 is init (alive), but a far-out-of-range PID should be dead on Linux.
// Pick a PID unlikely to exist (>2^30 PIDs don't normally exist on Linux).
deadPID := 0x3FFFFFFF
if err := os.WriteFile(sock+".pid", []byte(strconv.Itoa(deadPID)), 0o600); err != nil {
t.Fatalf("WriteFile: %v", err)
}
srv, err := NewServer(sock, &captureDispatcher{})
if err != nil {
t.Fatalf("NewServer with dead PID: %v", err)
}
_ = srv.Close()
}
func TestNewServerLivePIDReturnsError(t *testing.T) {
dir := t.TempDir()
sock := filepath.Join(dir, "cliamp.sock")
// Our own PID is definitely live → NewServer should refuse to start.
if err := os.WriteFile(sock+".pid", []byte(strconv.Itoa(os.Getpid())), 0o600); err != nil {
t.Fatalf("WriteFile: %v", err)
}
srv, err := NewServer(sock, &captureDispatcher{})
if err == nil {
_ = srv.Close()
t.Fatal("NewServer should error when PID file contains a live process")
}
if !strings.Contains(err.Error(), "already running") {
t.Errorf("error = %q, want to mention 'already running'", err.Error())
}
}
func TestServerCloseRemovesFiles(t *testing.T) {
sock := filepath.Join(t.TempDir(), "cliamp.sock")
srv, err := NewServer(sock, &captureDispatcher{})
if err != nil {
t.Fatalf("NewServer: %v", err)
}
if _, err := os.Stat(sock); err != nil {
t.Fatalf("socket should exist after NewServer: %v", err)
}
if _, err := os.Stat(sock + ".pid"); err != nil {
t.Fatalf("pid file should exist after NewServer: %v", err)
}
if err := srv.Close(); err != nil {
t.Fatalf("Close: %v", err)
}
if _, err := os.Stat(sock); !os.IsNotExist(err) {
t.Errorf("socket still exists after Close: %v", err)
}
if _, err := os.Stat(sock + ".pid"); !os.IsNotExist(err) {
t.Errorf("pid file still exists after Close: %v", err)
}
}
func TestServerMultipleRequestsSameConnection(t *testing.T) {
// Make sure the server can handle multiple requests over a single socket.
// Each Send opens its own connection, so this really verifies the accept
// loop keeps going beyond the first request.
sock := filepath.Join(t.TempDir(), "cliamp.sock")
disp := &captureDispatcher{}
srv, err := NewServer(sock, disp)
if err != nil {
t.Fatalf("NewServer: %v", err)
}
t.Cleanup(func() { _ = srv.Close() })
for _, cmd := range []string{"play", "pause", "next", "prev"} {
resp, err := Send(sock, Request{Cmd: cmd})
if err != nil {
t.Fatalf("Send %s: %v", cmd, err)
}
if !resp.OK {
t.Errorf("cmd %s OK=false, err=%q", cmd, resp.Error)
}
}
}
func TestServerHandlesInvalidJSON(t *testing.T) {
sock := filepath.Join(t.TempDir(), "cliamp.sock")
srv, err := NewServer(sock, &captureDispatcher{})
if err != nil {
t.Fatalf("NewServer: %v", err)
}
t.Cleanup(func() { _ = srv.Close() })
// Connect raw, send garbage, read response line.
conn, err := dialWithTimeout(sock, time.Second)
if err != nil {
t.Fatalf("dial: %v", err)
}
defer conn.Close()
if _, err := conn.Write([]byte("not valid json\n")); err != nil {
t.Fatalf("write: %v", err)
}
buf := make([]byte, 512)
_ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))
n, err := conn.Read(buf)
if err != nil {
t.Fatalf("read: %v", err)
}
if !strings.Contains(string(buf[:n]), "invalid JSON") {
t.Errorf("response = %q, want to mention 'invalid JSON'", string(buf[:n]))
}
}