// +build integration package serviceadapter import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "strconv" "strings" "testing" "time" ) // TestRealIntegration tests that the gateway correctly routes requests to upstreams. // Services may return 404/errors if paths don't match their actual API. func TestRealIntegration(t *testing.T) { gatewayURL := os.Getenv("GATEWAY_URL") if gatewayURL == "" { gatewayURL = "http://localhost:8080" } authentikURL := os.Getenv("AUTHENTIK_URL") if authentikURL == "" { authentikURL = "https://authentik.riotpiao.com" } clientID := os.Getenv("AUTHENTIK_CLIENT_ID") clientSecret := os.Getenv("AUTHENTIK_CLIENT_SECRET") skipAuthTests := clientID == "" || clientSecret == "" timeoutStr := os.Getenv("TEST_TIMEOUT") timeout := 30 if t, err := strconv.Atoi(timeoutStr); err == nil { timeout = t } client := &http.Client{Timeout: time.Duration(timeout) * time.Second} var jwtToken string if !skipAuthTests { t.Run("get JWT from Authentik", func(t *testing.T) { data := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s&scope=openid", clientID, clientSecret) resp, err := http.Post( authentikURL+"/application/o/token/", "application/x-www-form-urlencoded", strings.NewReader(data), ) if err != nil { t.Fatalf("failed to get token: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) t.Fatalf("token request failed (%d): %s", resp.StatusCode, string(body)) } var tokenResp struct { AccessToken string `json:"access_token"` } if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { t.Fatalf("failed to parse token response: %v", err) } jwtToken = tokenResp.AccessToken t.Logf("✅ Got JWT token") }) } // Test that gateway routes and passes through Authorization header // Services may return 404 if paths don't exist, but that's OK // We're testing that the request reached the service, not that it succeeded t.Run("SQS routing", func(t *testing.T) { payload := map[string]interface{}{"queue": "test"} body, _ := json.Marshal(payload) req, err := http.NewRequest("POST", gatewayURL+"/", bytes.NewReader(body)) if err != nil { t.Fatalf("failed to create request: %v", err) } req.Header.Set("X-Service", "sqs") req.Header.Set("X-Resource", "send-message") req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { t.Logf("SQS unreachable: %v", err) t.Skip() } defer resp.Body.Close() // Any response (even 404) means gateway routed it // 502/503 means service unreachable if resp.StatusCode >= 500 { t.Logf("SQS backend unreachable (%d)", resp.StatusCode) t.Skip() } t.Logf("✅ SQS routed: %d", resp.StatusCode) }) t.Run("S3 routing", func(t *testing.T) { req, err := http.NewRequest("GET", gatewayURL+"/", nil) if err != nil { t.Fatalf("failed to create request: %v", err) } req.Header.Set("X-Service", "s3") req.Header.Set("X-Resource", "list-objects") if !skipAuthTests && jwtToken != "" { req.Header.Set("Authorization", "Bearer "+jwtToken) t.Logf("Testing with JWT") } resp, err := client.Do(req) if err != nil { t.Logf("MinIO unreachable: %v", err) t.Skip() } defer resp.Body.Close() if resp.StatusCode >= 500 { t.Logf("MinIO backend unreachable (%d)", resp.StatusCode) t.Skip() } t.Logf("✅ S3 routed: %d", resp.StatusCode) }) t.Run("Memory routing", func(t *testing.T) { payload := map[string]interface{}{"query": "test"} body, _ := json.Marshal(payload) req, err := http.NewRequest("POST", gatewayURL+"/", bytes.NewReader(body)) if err != nil { t.Fatalf("failed to create request: %v", err) } req.Header.Set("X-Service", "memory") req.Header.Set("X-Resource", "query") req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { t.Logf("Memory unreachable: %v", err) t.Skip() } defer resp.Body.Close() if resp.StatusCode >= 500 { t.Logf("Memory backend unreachable (%d)", resp.StatusCode) t.Skip() } t.Logf("✅ Memory routed: %d", resp.StatusCode) }) t.Run("IAM routing with JWT", func(t *testing.T) { if skipAuthTests { t.Skip("No JWT token") } req, err := http.NewRequest("GET", gatewayURL+"/", nil) if err != nil { t.Fatalf("failed to create request: %v", err) } req.Header.Set("X-Service", "iam") req.Header.Set("X-Resource", "list-roles") req.Header.Set("Authorization", "Bearer "+jwtToken) resp, err := client.Do(req) if err != nil { t.Logf("Authentik unreachable: %v", err) t.Skip() } defer resp.Body.Close() if resp.StatusCode >= 500 { t.Logf("Authentik backend unreachable (%d)", resp.StatusCode) t.Skip() } t.Logf("✅ IAM routed: %d", resp.StatusCode) }) t.Run("Authorization header pass-through", func(t *testing.T) { testToken := "Bearer test-token-xyz" req, err := http.NewRequest("GET", gatewayURL+"/", nil) if err != nil { t.Fatalf("failed to create request: %v", err) } req.Header.Set("X-Service", "s3") req.Header.Set("X-Resource", "list-objects") req.Header.Set("Authorization", testToken) resp, err := client.Do(req) if err != nil { t.Logf("S3 unreachable: %v", err) t.Skip() } defer resp.Body.Close() // Gateway passed the request through // MinIO responded (even with error) t.Logf("✅ Authorization header passed through: %d", resp.StatusCode) }) }