328 lines
9.5 KiB
Go
328 lines
9.5 KiB
Go
package e2e
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"goyco/internal/testutils"
|
|
)
|
|
|
|
func TestE2E_CompressionMiddleware(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("compression_enabled_with_accept_encoding", func(t *testing.T) {
|
|
req, err := http.NewRequest("GET", ctx.baseURL+"/api/posts", nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
req.Header.Set("Accept-Encoding", "gzip")
|
|
testutils.WithStandardHeaders(req)
|
|
|
|
resp, err := ctx.client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
contentEncoding := resp.Header.Get("Content-Encoding")
|
|
if contentEncoding == "gzip" {
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read response body: %v", err)
|
|
}
|
|
|
|
if isGzipCompressed(body) {
|
|
reader, err := gzip.NewReader(bytes.NewReader(body))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create gzip reader: %v", err)
|
|
}
|
|
defer reader.Close()
|
|
|
|
decompressed, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to decompress: %v", err)
|
|
}
|
|
|
|
if len(decompressed) == 0 {
|
|
t.Error("Decompressed body is empty")
|
|
}
|
|
}
|
|
} else {
|
|
t.Logf("Compression not applied (Content-Encoding: %s)", contentEncoding)
|
|
}
|
|
})
|
|
|
|
t.Run("no_compression_without_accept_encoding", func(t *testing.T) {
|
|
req, err := http.NewRequest("GET", ctx.baseURL+"/api/posts", nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
testutils.WithStandardHeaders(req)
|
|
|
|
resp, err := ctx.client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
contentEncoding := resp.Header.Get("Content-Encoding")
|
|
if contentEncoding == "gzip" {
|
|
t.Error("Expected no compression without Accept-Encoding header")
|
|
}
|
|
})
|
|
|
|
t.Run("decompression_handles_gzip_request", func(t *testing.T) {
|
|
testUser := ctx.createUserWithCleanup(t, "compressionuser", "StrongPass123!")
|
|
authClient := ctx.loginUser(t, testUser.Username, "StrongPass123!")
|
|
|
|
var buf bytes.Buffer
|
|
gz := gzip.NewWriter(&buf)
|
|
postData := `{"title":"Compressed Post","url":"https://example.com/compressed","content":"Test content"}`
|
|
gz.Write([]byte(postData))
|
|
gz.Close()
|
|
|
|
req, err := http.NewRequest("POST", ctx.baseURL+"/api/posts", &buf)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Content-Encoding", "gzip")
|
|
testutils.WithStandardHeaders(req)
|
|
req.Header.Set("Authorization", "Bearer "+authClient.Token)
|
|
|
|
resp, err := ctx.client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusBadRequest {
|
|
t.Log("Decompression middleware rejected invalid gzip")
|
|
} else if resp.StatusCode == http.StatusCreated || resp.StatusCode == http.StatusOK {
|
|
t.Log("Decompression middleware handled gzip request successfully")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_CacheMiddleware(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("cache_miss_then_hit", func(t *testing.T) {
|
|
req1, err := http.NewRequest("GET", ctx.baseURL+"/api/posts", nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
testutils.WithStandardHeaders(req1)
|
|
|
|
resp1, err := ctx.client.Do(req1)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
resp1.Body.Close()
|
|
|
|
cacheStatus1 := resp1.Header.Get("X-Cache")
|
|
if cacheStatus1 == "HIT" {
|
|
t.Log("First request was cached (unexpected but acceptable)")
|
|
}
|
|
|
|
req2, err := http.NewRequest("GET", ctx.baseURL+"/api/posts", nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
testutils.WithStandardHeaders(req2)
|
|
|
|
resp2, err := ctx.client.Do(req2)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
defer resp2.Body.Close()
|
|
|
|
cacheStatus2 := resp2.Header.Get("X-Cache")
|
|
if cacheStatus2 == "HIT" {
|
|
t.Log("Second request was served from cache")
|
|
}
|
|
})
|
|
|
|
t.Run("cache_invalidation_on_post", func(t *testing.T) {
|
|
testUser := ctx.createUserWithCleanup(t, "cacheuser", "StrongPass123!")
|
|
authClient := ctx.loginUser(t, testUser.Username, "StrongPass123!")
|
|
|
|
req1, err := http.NewRequest("GET", ctx.baseURL+"/api/posts", nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
testutils.WithStandardHeaders(req1)
|
|
req1.Header.Set("Authorization", "Bearer "+authClient.Token)
|
|
|
|
resp1, err := ctx.client.Do(req1)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
resp1.Body.Close()
|
|
|
|
postData := `{"title":"Cache Invalidation Test","url":"https://example.com/cache","content":"Test"}`
|
|
req2, err := http.NewRequest("POST", ctx.baseURL+"/api/posts", strings.NewReader(postData))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
req2.Header.Set("Content-Type", "application/json")
|
|
testutils.WithStandardHeaders(req2)
|
|
req2.Header.Set("Authorization", "Bearer "+authClient.Token)
|
|
|
|
resp2, err := ctx.client.Do(req2)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
resp2.Body.Close()
|
|
|
|
req3, err := http.NewRequest("GET", ctx.baseURL+"/api/posts", nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
testutils.WithStandardHeaders(req3)
|
|
req3.Header.Set("Authorization", "Bearer "+authClient.Token)
|
|
|
|
resp3, err := ctx.client.Do(req3)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
defer resp3.Body.Close()
|
|
|
|
cacheStatus := resp3.Header.Get("X-Cache")
|
|
if cacheStatus == "HIT" {
|
|
t.Log("Cache was invalidated after POST")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_CSRFProtection(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("csrf_protection_for_non_api_routes", func(t *testing.T) {
|
|
req, err := http.NewRequest("POST", ctx.baseURL+"/auth/login", strings.NewReader(`{"username":"test","password":"test"}`))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
testutils.WithStandardHeaders(req)
|
|
|
|
resp, err := ctx.client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusForbidden {
|
|
t.Log("CSRF protection active for non-API routes")
|
|
} else {
|
|
t.Logf("CSRF check result: status %d", resp.StatusCode)
|
|
}
|
|
})
|
|
|
|
t.Run("csrf_bypass_for_api_routes", func(t *testing.T) {
|
|
testUser := ctx.createUserWithCleanup(t, "csrfuser", "StrongPass123!")
|
|
authClient := ctx.loginUser(t, testUser.Username, "StrongPass123!")
|
|
|
|
postData := `{"title":"CSRF Test","url":"https://example.com/csrf","content":"Test"}`
|
|
req, err := http.NewRequest("POST", ctx.baseURL+"/api/posts", strings.NewReader(postData))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
testutils.WithStandardHeaders(req)
|
|
req.Header.Set("Authorization", "Bearer "+authClient.Token)
|
|
|
|
resp, err := ctx.client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusForbidden {
|
|
t.Error("API routes should bypass CSRF protection")
|
|
}
|
|
})
|
|
|
|
t.Run("csrf_allows_get_requests", func(t *testing.T) {
|
|
req, err := http.NewRequest("GET", ctx.baseURL+"/auth/login", nil)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
testutils.WithStandardHeaders(req)
|
|
|
|
resp, err := ctx.client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusForbidden {
|
|
t.Error("GET requests should not require CSRF token")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestE2E_RequestSizeLimit(t *testing.T) {
|
|
ctx := setupTestContext(t)
|
|
|
|
t.Run("request_within_size_limit", func(t *testing.T) {
|
|
testUser := ctx.createUserWithCleanup(t, "sizelimituser", "StrongPass123!")
|
|
authClient := ctx.loginUser(t, testUser.Username, "StrongPass123!")
|
|
|
|
smallData := strings.Repeat("a", 100)
|
|
postData := `{"title":"` + smallData + `","url":"https://example.com","content":"test"}`
|
|
req, err := http.NewRequest("POST", ctx.baseURL+"/api/posts", strings.NewReader(postData))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
testutils.WithStandardHeaders(req)
|
|
req.Header.Set("Authorization", "Bearer "+authClient.Token)
|
|
|
|
resp, err := ctx.client.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("Request failed: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusRequestEntityTooLarge {
|
|
t.Error("Small request should not exceed size limit")
|
|
}
|
|
})
|
|
|
|
t.Run("request_exceeds_size_limit", func(t *testing.T) {
|
|
testUser := ctx.createUserWithCleanup(t, "sizelimituser2", "StrongPass123!")
|
|
authClient := ctx.loginUser(t, testUser.Username, "StrongPass123!")
|
|
|
|
largeData := strings.Repeat("a", 2*1024*1024)
|
|
postData := `{"title":"test","url":"https://example.com","content":"` + largeData + `"}`
|
|
req, err := http.NewRequest("POST", ctx.baseURL+"/api/posts", strings.NewReader(postData))
|
|
if err != nil {
|
|
t.Fatalf("Failed to create request: %v", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
testutils.WithStandardHeaders(req)
|
|
req.Header.Set("Authorization", "Bearer "+authClient.Token)
|
|
|
|
resp, err := ctx.client.Do(req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusRequestEntityTooLarge {
|
|
t.Log("Request size limit enforced correctly")
|
|
} else {
|
|
t.Logf("Request size limit check result: status %d", resp.StatusCode)
|
|
}
|
|
})
|
|
}
|
|
|
|
func isGzipCompressed(data []byte) bool {
|
|
return len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b
|
|
}
|