package api import ( "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "visiona-backend/internal/session" ) // newCameraFixture 建立一個掛了 registerCameraRoutes 的 router。 // // 用 fakeSessionStore(List 回空)+ 真實但不 dial 的 Forwarder:這樣每條 proxy 路徑 // 在 pickActiveSessionToken 會回 ErrSessionNotFound → 502 TUNNEL_DISCONNECTED。 // 這足以證明「路徑有被註冊、且走的是 proxy handler(不是 501 stub)」。 // // injectStaticUserContext 模擬 AuthMiddleware 已放行(handler C1 strict mode 要求 // UserContext 非空)。 func newCameraFixture() *gin.Engine { r := gin.New() r.Use(RequestIDMiddleware()) r.Use(injectStaticUserContext("demo-user", "")) g := r.Group("/api") registerCameraRoutes(g, Deps{ SessionStore: &fakeSessionStore{}, // List 回空 → 無 active session Forwarder: session.NewForwarder("http://localhost:0", nil), }) return r } // TestCameraRoutes_RegisteredAsProxy 逐一驗證每條 camera/media 路徑: // - 不再回 501(代表不是 stub、真的掛了 proxy handler) // - 因無 active session → 回 502 TUNNEL_DISCONNECTED(proxy handler 的預期行為) // // 這是「路由宣告正確」的黑箱證據:request 有進到 proxy handler、走到 session lookup。 func TestCameraRoutes_RegisteredAsProxy(t *testing.T) { r := newCameraFixture() cases := []struct { method string path string }{ {http.MethodGet, "/api/camera/list"}, {http.MethodPost, "/api/camera/start"}, {http.MethodPost, "/api/camera/stop"}, {http.MethodGet, "/api/camera/stream"}, {http.MethodPost, "/api/media/upload/image"}, {http.MethodPost, "/api/media/upload/video"}, {http.MethodPost, "/api/media/upload/batch-images"}, {http.MethodGet, "/api/media/batch-images/0"}, {http.MethodPost, "/api/media/seek"}, } for _, tc := range cases { t.Run(tc.method+" "+tc.path, func(t *testing.T) { w := httptest.NewRecorder() r.ServeHTTP(w, httptest.NewRequest(tc.method, tc.path, nil)) assert.NotEqual(t, http.StatusNotImplemented, w.Code, "路徑應走 proxy handler、不再是 501 stub") assert.Equal(t, http.StatusBadGateway, w.Code, "無 active session 時 proxy 應回 502 TUNNEL_DISCONNECTED") assert.Contains(t, w.Body.String(), ErrCodeTunnelDisconnect, "錯誤碼應為 TUNNEL_DISCONNECTED") }) } } // TestCameraRoutes_NoForwarder 驗證沒注入 Forwarder 時每條路徑回 501 // (proxy handler 的依賴缺失分支),確認掛的的確是 newProxyHandler。 func TestCameraRoutes_NoForwarder(t *testing.T) { r := gin.New() r.Use(RequestIDMiddleware()) r.Use(injectStaticUserContext("demo-user", "")) g := r.Group("/api") registerCameraRoutes(g, Deps{}) // 無 Forwarder / SessionStore w := httptest.NewRecorder() r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/api/camera/stream", nil)) assert.Equal(t, http.StatusNotImplemented, w.Code, "缺 Forwarder 時 proxy handler 回 501(證明掛的是 newProxyHandler 而非別的)") }