package session import ( "context" "errors" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" ) // TestForwarder_OpenStream_NoProxyHost 驗證 baseURL 為空時直接拒絕。 func TestForwarder_OpenStream_NoProxyHost(t *testing.T) { f := NewForwarder("", nil) _, err := f.OpenStream(context.Background(), "vAc_x") assert.Error(t, err) } // TestForwarder_OpenStream_EmptyToken 驗證空 token 拒絕。 func TestForwarder_OpenStream_EmptyToken(t *testing.T) { f := NewForwarder("http://localhost:9999", nil) _, err := f.OpenStream(context.Background(), "") assert.Error(t, err) } // TestForwarder_ForwardWebSocket_NotImplemented 驗證 ForwardWebSocket 仍是 stub。 func TestForwarder_ForwardWebSocket_NotImplemented(t *testing.T) { f := NewForwarder("http://localhost:9999", nil) req, _ := http.NewRequest(http.MethodGet, "/ws", nil) _, err := f.ForwardWebSocket(context.Background(), "vAc_x", req) assert.Error(t, err) } // TestForwarder_OpenStream_502_TreatedAsNotFound 驗證當 remote-proxy 回 502 // (session 不存在時的雛形行為)→ 包裝成 ErrSessionNotFound。 // // 用 httptest 起一個假的 internal endpoint,回 502 JSON。 func TestForwarder_OpenStream_502_TreatedAsNotFound(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadGateway) _, _ = w.Write([]byte(`{"error":{"code":"TUNNEL_DISCONNECTED","message":"session not connected"}}`)) })) defer ts.Close() f := NewForwarder(ts.URL, nil) _, err := f.OpenStream(context.Background(), "vAc_dead") if !errors.Is(err, ErrSessionNotFound) { t.Fatalf("expected ErrSessionNotFound, got %v", err) } } // TestForwarder_OpenStream_HandshakeRead 驗證能正確讀「HTTP/1.1 200 Connected\r\n\r\n」 // 握手;用一個假 server 回正確握手後立刻 close — 期望我們的 OpenStream 成功, // 後續 Read 拿 EOF(這對 forwarder 而言是合法情境,由 caller 處理)。 // // 此 case 直接驗證 happy-path 握手解析;真正的端對端轉發由 integration test 涵蓋。 func TestForwarder_OpenStream_HandshakeRead(t *testing.T) { // 為了保證 server 端在 200 Connected 後不再寫 body(讓 forwarder 結束 header 讀 // 不被預讀干擾),用一個 raw TCP listener 而非 httptest.NewServer。 // 但 raw listener 會增加測試複雜度;在 unit test 用 httptest 已足以驗證 // 「能 parse 200 Connected + 兩個 \r\n」的路徑——讀 body 結束會回 EOF, // 後續 caller 用該 conn 才會發現問題,這裡僅驗證 OpenStream 不 error。 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 不能直接寫 raw "HTTP/1.1 200 Connected\r\n\r\n" — httptest 會額外加 // content-length 等 header。改用 hijack 模擬真實 raw forward 行為。 hj, ok := w.(http.Hijacker) if !ok { http.Error(w, "no hijacker", 500) return } conn, _, err := hj.Hijack() if err != nil { return } defer conn.Close() _, _ = conn.Write([]byte("HTTP/1.1 200 Connected\r\n\r\n")) // 不再寫;讓 forwarder 拿到 conn 後若 read 會 EOF })) defer ts.Close() f := NewForwarder(ts.URL, nil) conn, err := f.OpenStream(context.Background(), "vAc_x") if err != nil { t.Fatalf("OpenStream should succeed: %v", err) } defer conn.Close() // 不再做 read 驗證(行為由 integration test 涵蓋) }