Day 86 — Integration Testing the Full Chain
Learning Objectives
- Write integration tests for the complete middleware stack
- Test security scenarios: auth bypass, WAF bypass, rate limit
- Use
httptest.NewTLSServerfor full TLS integration tests
Exercise: Integration Test Suite
// integration_test.go
package integration_test
import (
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
func setupTestGateway(t *testing.T) (*httptest.Server, *TokenService) {
t.Helper()
tokenService, _ := NewTokenService()
handler := buildTestGateway(tokenService)
ts := httptest.NewTLSServer(handler)
t.Cleanup(ts.Close)
return ts, tokenService
}
func TestIntegration_ValidRequest_Passes(t *testing.T) {
ts, svc := setupTestGateway(t)
token, _ := svc.Issue("user:42", []string{"read"})
req, _ := http.NewRequest("GET", ts.URL+"/api/data", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := ts.Client().Do(req)
if err != nil { t.Fatal(err) }
if resp.StatusCode != 200 {
t.Errorf("expected 200, got %d", resp.StatusCode)
}
}
func TestIntegration_NoAuth_Returns401(t *testing.T) {
ts, _ := setupTestGateway(t)
resp, err := ts.Client().Get(ts.URL + "/api/data")
if err != nil { t.Fatal(err) }
if resp.StatusCode != 401 {
t.Errorf("expected 401, got %d", resp.StatusCode)
}
}
func TestIntegration_SQLInjection_Blocked(t *testing.T) {
ts, svc := setupTestGateway(t)
token, _ := svc.Issue("user:42", []string{"read"})
req, _ := http.NewRequest("GET", ts.URL+"/api/search?q=' UNION SELECT * FROM users--", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := ts.Client().Do(req)
if err != nil { t.Fatal(err) }
if resp.StatusCode != 403 {
t.Errorf("expected 403 for SQLi, got %d", resp.StatusCode)
}
// Verify request ID in response (not rule details)
var body map[string]string
json.NewDecoder(resp.Body).Decode(&body)
if _, ok := body["request_id"]; !ok {
t.Error("response should include request_id")
}
if _, ok := body["rule"]; ok {
t.Error("response should NOT include rule details (information disclosure)")
}
}
func TestIntegration_RateLimitEnforced(t *testing.T) {
ts, svc := setupTestGateway(t)
token, _ := svc.Issue("user:42", []string{"read"})
client := ts.Client()
var tooManyRequests int
// Send 100 requests rapidly (limit is 10/s burst 50)
for i := 0; i < 100; i++ {
req, _ := http.NewRequest("GET", ts.URL+"/api/data", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := client.Do(req)
if err != nil { continue }
if resp.StatusCode == 429 {
tooManyRequests++
}
}
if tooManyRequests == 0 {
t.Error("expected some 429 responses, got none")
}
t.Logf("Got %d 429 responses out of 100 requests", tooManyRequests)
}
func TestIntegration_CORS_AllowedOrigin(t *testing.T) {
ts, _ := setupTestGateway(t)
req, _ := http.NewRequest("GET", ts.URL+"/api/data", nil)
req.Header.Set("Origin", "https://app.example.com")
resp, err := ts.Client().Do(req)
if err != nil { t.Fatal(err) }
acao := resp.Header.Get("Access-Control-Allow-Origin")
if acao != "https://app.example.com" {
t.Errorf("expected ACAO=https://app.example.com, got %q", acao)
}
vary := resp.Header.Get("Vary")
if !strings.Contains(vary, "Origin") {
t.Errorf("Vary header should include Origin, got %q", vary)
}
}
func TestIntegration_CORS_DisallowedOrigin_NoHeader(t *testing.T) {
ts, _ := setupTestGateway(t)
req, _ := http.NewRequest("GET", ts.URL+"/api/data", nil)
req.Header.Set("Origin", "https://evil.com")
resp, err := ts.Client().Do(req)
if err != nil { t.Fatal(err) }
if acao := resp.Header.Get("Access-Control-Allow-Origin"); acao != "" {
t.Errorf("evil.com should not get ACAO header, got %q", acao)
}
}
func TestIntegration_OversizedBody_Rejected(t *testing.T) {
ts, svc := setupTestGateway(t)
token, _ := svc.Issue("user:42", []string{"write"})
// 2MB body (above 1MB WAF scan limit)
body := strings.NewReader(strings.Repeat("x", 2*1024*1024))
req, _ := http.NewRequest("POST", ts.URL+"/api/data", body)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := ts.Client().Do(req)
if err != nil { t.Fatal(err) }
if resp.StatusCode != 413 {
t.Errorf("expected 413 for oversized body, got %d", resp.StatusCode)
}
}
Real-World Context
Integration tests catch middleware ordering bugs that unit tests miss. "Auth runs before WAF" is only verifiable if you have a test that sends a malicious request WITHOUT auth — unit tests of individual middleware won't catch this. Run integration tests in CI with -race flag.