package executor import ( "bytes" "context" "errors" "net/http" "net/http/httptest" "testing" "time" "github.com/gorilla/websocket" cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/executor" ) func TestInterruptExecutionSessionRequiresActiveRead(t *testing.T) { received := make(chan []byte, 1) upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn, errUpgrade := (&websocket.Upgrader{}).Upgrade(w, r, nil) if errUpgrade != nil { t.Errorf("upgrade: %v", errUpgrade) return } defer func() { _ = conn.Close() }() _ = conn.SetReadDeadline(time.Now().Add(2 * time.Second)) _, payload, errRead := conn.ReadMessage() if errRead != nil { return } received <- payload })) defer upstream.Close() client, _, errDial := websocket.DefaultDialer.Dial("ws"+upstream.URL[len("http"):], nil) if errDial != nil { t.Fatal(errDial) } defer func() { _ = client.Close() }() const sessionID = "interrupt-requires-active-read" executor := NewCodexWebsocketsExecutor(nil) defer executor.CloseExecutionSession(sessionID) sess := executor.getOrCreateSession(sessionID) sess.connMu.Lock() sess.conn = client sess.authID = "auth" sess.wsURL = upstream.URL sess.connMu.Unlock() interrupt := []byte(`{"type":"response.interrupt","response_id":"r1","mode":"discard_partial_items"}`) errIdle := executor.InterruptExecutionSession(context.Background(), sessionID, interrupt) if !errors.Is(errIdle, cliproxyexecutor.ErrNoActiveUpstreamWebsocket) { t.Fatalf("idle socket error = %v, want ErrNoActiveUpstreamWebsocket", errIdle) } select { case payload := <-received: t.Fatalf("idle socket was written: %s", payload) case <-time.After(200 * time.Millisecond): } readCh := sess.activate(client) defer sess.clearActive(client, readCh) if errActive := executor.InterruptExecutionSession(context.Background(), sessionID, interrupt); errActive != nil { t.Fatal(errActive) } select { case payload := <-received: if !bytes.Equal(payload, interrupt) { t.Fatalf("active interrupt = %s", payload) } case <-time.After(2 * time.Second): t.Fatal("active socket did not receive the interrupt") } }