package main import ( "io" "net/http" "net/http/httptest" "strings" "testing" ) func TestGatewayForwardsOnlyCreatorAllowlist(t *testing.T) { upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/creator/tasks/task-1" || r.URL.RawQuery != "session_id=session-1&workspace_id=workspace-1" { t.Fatalf("unexpected upstream target %s?%s", r.URL.Path, r.URL.RawQuery) } if r.Header.Get("Authorization") != "Bearer bounded-session" { t.Fatal("authorization header was not forwarded") } w.Header().Set("Content-Type", "application/json") _, _ = io.WriteString(w, `{"TaskID":"task-1"}`) })) defer upstream.Close() handler, err := newHandler(upstream.URL, upstream.URL, upstream.Client()) if err != nil { t.Fatal(err) } request := httptest.NewRequest(http.MethodGet, "/api/tasks/task-1?session_id=session-1&workspace_id=workspace-1", nil) request.Header.Set("Authorization", "Bearer bounded-session") response := httptest.NewRecorder() handler.ServeHTTP(response, request) if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), "task-1") { t.Fatalf("response = %d %s", response.Code, response.Body.String()) } } func TestGatewayDeniesInternalAndMutationRoutes(t *testing.T) { handler, err := newHandler("http://core-runtime:8080", "http://continuity-runtime:8081", http.DefaultClient) if err != nil { t.Fatal(err) } for _, test := range []struct{ method, path string }{ {http.MethodPost, "/api/tasks/task-1"}, {http.MethodGet, "/api/principal-bindings/actor/principal"}, {http.MethodPost, "/api/focus-task-commits"}, {http.MethodPost, "/api/prospective-items/item-1"}, {http.MethodGet, "/api/tasks/../../healthz"}, } { request := httptest.NewRequest(test.method, test.path, nil) response := httptest.NewRecorder() handler.ServeHTTP(response, request) if response.Code != http.StatusNotFound { t.Fatalf("%s %s returned %d", test.method, test.path, response.Code) } } } func TestGatewayRoutesProspectiveItemToContinuity(t *testing.T) { continuity := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/creator/prospective-items/item-1" { t.Fatalf("unexpected Continuity target %s", r.URL.Path) } if r.Header.Get("Authorization") != "Bearer bounded-session" { t.Fatal("authorization header was not forwarded") } _, _ = io.WriteString(w, `{"ID":"item-1"}`) })) defer continuity.Close() handler, err := newHandler("http://core-runtime.invalid", continuity.URL, continuity.Client()) if err != nil { t.Fatal(err) } request := httptest.NewRequest(http.MethodGet, "/api/prospective-items/item-1", nil) request.Header.Set("Authorization", "Bearer bounded-session") response := httptest.NewRecorder() handler.ServeHTTP(response, request) if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), "item-1") { t.Fatalf("response = %d %s", response.Code, response.Body.String()) } } func TestConsoleAndSecurityHeaders(t *testing.T) { handler, err := newHandler("http://core-runtime:8080", "http://continuity-runtime:8081", http.DefaultClient) if err != nil { t.Fatal(err) } request := httptest.NewRequest(http.MethodGet, "/", nil) response := httptest.NewRecorder() handler.ServeHTTP(response, request) if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), "Домовой") { t.Fatalf("console response = %d", response.Code) } if response.Header().Get("Content-Security-Policy") == "" { t.Fatal("content security policy is missing") } }