package handler_test import ( "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/gorilla/websocket" "github.com/qy/hw-ws-service/internal/audio" "github.com/qy/hw-ws-service/internal/connection" "github.com/qy/hw-ws-service/internal/handler" ) // makeWSPair creates a real WebSocket pair for testing. // svrWS is the server side (used by our Connection), cliWS simulates the hardware. func makeWSPair(t *testing.T) (svrWS *websocket.Conn, cliWS *websocket.Conn, cleanup func()) { t.Helper() ch := make(chan *websocket.Conn, 1) done := make(chan struct{}) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { up := websocket.Upgrader{CheckOrigin: func(*http.Request) bool { return true }} c, err := up.Upgrade(w, r, nil) if err != nil { t.Logf("upgrade error: %v", err) return } ch <- c <-done })) wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") cli, _, err := websocket.DefaultDialer.Dial(wsURL, nil) if err != nil { close(done) srv.Close() t.Fatalf("dial error: %v", err) } svr := <-ch return svr, cli, func() { close(done) svr.Close() cli.Close() srv.Close() } } // makeFrames creates n fake Opus frames of 4 bytes each. func makeFrames(n int) [][]byte { frames := make([][]byte, n) for i := range frames { frames[i] = []byte{byte(i), byte(i >> 8), 0x00, 0xff} } return frames } // TestSendOpusStream_Empty verifies that an empty frame list returns immediately. func TestSendOpusStream_Empty(t *testing.T) { svrWS, _, cleanup := makeWSPair(t) defer cleanup() conn := connection.New(svrWS, "dev1", "cli1") abort := make(chan struct{}) done := make(chan struct{}) go func() { handler.SendOpusStream(conn, nil, abort) close(done) }() select { case <-done: case <-time.After(500 * time.Millisecond): t.Fatal("SendOpusStream did not return immediately for empty frames") } } // TestSendOpusStream_AllFramesSent verifies that all frames reach the client. // Uses PreBufferCount+2 frames so both pre-buffer and timed paths are exercised. func TestSendOpusStream_AllFramesSent(t *testing.T) { svrWS, cliWS, cleanup := makeWSPair(t) defer cleanup() conn := connection.New(svrWS, "dev1", "cli1") totalFrames := audio.PreBufferCount + 2 // 3 pre-buffer + 2 timed frames := makeFrames(totalFrames) abort := make(chan struct{}) senderDone := make(chan struct{}) go func() { handler.SendOpusStream(conn, frames, abort) close(senderDone) }() // Read all frames from the client side (simulates hardware receiving) received := 0 cliWS.SetReadDeadline(time.Now().Add(10 * time.Second)) for received < totalFrames { msgType, _, err := cliWS.ReadMessage() if err != nil { t.Fatalf("client read error after %d frames: %v", received, err) } if msgType == websocket.BinaryMessage { received++ } } select { case <-senderDone: case <-time.After(2 * time.Second): t.Fatal("SendOpusStream did not finish after all frames were sent") } if received != totalFrames { t.Errorf("received %d frames, want %d", received, totalFrames) } } // TestSendOpusStream_Abort verifies that closing abortCh stops streaming early. func TestSendOpusStream_Abort(t *testing.T) { svrWS, _, cleanup := makeWSPair(t) defer cleanup() conn := connection.New(svrWS, "dev1", "cli1") // Many frames so timing control is active (pre-buffer finishes quickly, // then the time.After select can receive the abort signal) frames := makeFrames(100) abort := make(chan struct{}) senderDone := make(chan struct{}) go func() { handler.SendOpusStream(conn, frames, abort) close(senderDone) }() // Close abort after pre-buffer has had time to finish but before timed frames complete time.Sleep(20 * time.Millisecond) close(abort) select { case <-senderDone: // SendOpusStream returned early — correct behaviour case <-time.After(2 * time.Second): t.Fatal("SendOpusStream did not abort within 2s after closing abortCh") } } // TestSendOpusStream_PreBufferOnly verifies frames <= PreBufferCount are all sent // without entering the timed loop (should finish nearly instantly). func TestSendOpusStream_PreBufferOnly(t *testing.T) { svrWS, cliWS, cleanup := makeWSPair(t) defer cleanup() conn := connection.New(svrWS, "dev1", "cli1") frames := makeFrames(audio.PreBufferCount) // exactly the pre-buffer count abort := make(chan struct{}) start := time.Now() senderDone := make(chan struct{}) go func() { handler.SendOpusStream(conn, frames, abort) close(senderDone) }() received := 0 cliWS.SetReadDeadline(time.Now().Add(3 * time.Second)) for received < len(frames) { msgType, _, err := cliWS.ReadMessage() if err != nil { t.Fatalf("read error: %v", err) } if msgType == websocket.BinaryMessage { received++ } } select { case <-senderDone: case <-time.After(time.Second): t.Fatal("sender did not finish") } elapsed := time.Since(start) // Pre-buffer frames should not wait on the timer; allow 200ms for overhead if elapsed > 200*time.Millisecond { t.Errorf("pre-buffer-only send took too long: %v (want < 200ms)", elapsed) } }