Frontend Labs
⋯

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.NewTLSServer for 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.


Generated from week12.md · 8 Oct 2026 16:36