mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 21:11:30 +00:00
de9264baf7
Implements 3 deferred security tickets (TICKET-003, TICKET-007, TICKET-010) and performs comprehensive documentation audit to eliminate drift between code and docs. Code changes: - TICKET-003: Repository integration tests with testcontainers-go (50+ subtests) - TICKET-007: CertificateService decomposition into RevocationSvc + CAOperationsSvc - TICKET-010: Request body size limits via http.MaxBytesReader middleware - Fix missing slog import in certificate.go after service decomposition Documentation updates: - README: Fix endpoint count (97→93), expand env var reference (15→39 vars) - CLAUDE.md: Fix OpenAPI operation count (85→93), update file locations - architecture.md: Add body size limits section, middleware chain ordering - CONTRIBUTING.md: New contributor guide with architecture conventions, test patterns, middleware ordering, CI thresholds - SECURITY_REMEDIATION.md: Removed from repo (moved to cowork, gitignored) - Test files: Add doc comments to all new test files Documentation that should exist but doesn't yet: - Architecture diagrams (C4 model or similar) - Threat model document - Testing philosophy guide - Disaster recovery runbook - Upgrade guide (migration between versions) - API versioning strategy document Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
180 lines
4.9 KiB
Go
180 lines
4.9 KiB
Go
// Tests for the request body size limit middleware (TICKET-010).
|
|
// Covers under/over/exact limit, nil body, default size, GET requests,
|
|
// and custom limits.
|
|
package middleware
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestBodyLimit_UnderLimit(t *testing.T) {
|
|
handler := NewBodyLimit(BodyLimitConfig{MaxBytes: 1024})(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Fatalf("unexpected read error: %v", err)
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(body)
|
|
}),
|
|
)
|
|
|
|
body := bytes.NewReader([]byte("small body"))
|
|
req := httptest.NewRequest(http.MethodPost, "/test", body)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
|
|
}
|
|
}
|
|
|
|
func TestBodyLimit_OverLimit(t *testing.T) {
|
|
handler := NewBodyLimit(BodyLimitConfig{MaxBytes: 10})(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
// MaxBytesReader returns an error when limit exceeded
|
|
http.Error(w, `{"error":"Request body too large"}`, http.StatusRequestEntityTooLarge)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}),
|
|
)
|
|
|
|
body := bytes.NewReader([]byte("this body exceeds ten bytes"))
|
|
req := httptest.NewRequest(http.MethodPost, "/test", body)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusRequestEntityTooLarge {
|
|
t.Errorf("status = %d, want %d", w.Code, http.StatusRequestEntityTooLarge)
|
|
}
|
|
}
|
|
|
|
func TestBodyLimit_ExactLimit(t *testing.T) {
|
|
data := "exactly10!" // 10 bytes
|
|
handler := NewBodyLimit(BodyLimitConfig{MaxBytes: 10})(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"Request body too large"}`, http.StatusRequestEntityTooLarge)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(body)
|
|
}),
|
|
)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/test", strings.NewReader(data))
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
|
|
}
|
|
}
|
|
|
|
func TestBodyLimit_NilBody(t *testing.T) {
|
|
handler := NewBodyLimit(BodyLimitConfig{MaxBytes: 1024})(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}),
|
|
)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
|
|
}
|
|
}
|
|
|
|
func TestBodyLimit_DefaultSize(t *testing.T) {
|
|
// When MaxBytes is 0, should use default (1MB)
|
|
mw := NewBodyLimit(BodyLimitConfig{MaxBytes: 0})
|
|
|
|
called := false
|
|
handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
called = true
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
|
|
body := bytes.NewReader([]byte("test"))
|
|
req := httptest.NewRequest(http.MethodPost, "/test", body)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if !called {
|
|
t.Error("handler was not called")
|
|
}
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
|
|
}
|
|
}
|
|
|
|
func TestBodyLimit_GETRequest_NoBody(t *testing.T) {
|
|
handler := NewBodyLimit(BodyLimitConfig{MaxBytes: 10})(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}),
|
|
)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
|
|
}
|
|
}
|
|
|
|
func TestBodyLimit_ContentLengthZero(t *testing.T) {
|
|
handler := NewBodyLimit(BodyLimitConfig{MaxBytes: 10})(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}),
|
|
)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/test", nil)
|
|
req.ContentLength = 0
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
|
|
}
|
|
}
|
|
|
|
func TestBodyLimit_CustomMaxBytes(t *testing.T) {
|
|
// Test with 512KB limit
|
|
const maxSize = 512 * 1024
|
|
handler := NewBodyLimit(BodyLimitConfig{MaxBytes: maxSize})(
|
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"Request body too large"}`, http.StatusRequestEntityTooLarge)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Length", string(rune(len(body))))
|
|
w.WriteHeader(http.StatusOK)
|
|
}),
|
|
)
|
|
|
|
// Create a body just under the limit
|
|
bodyData := make([]byte, maxSize-1)
|
|
req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(bodyData))
|
|
w := httptest.NewRecorder()
|
|
handler.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want %d for body just under limit", w.Code, http.StatusOK)
|
|
}
|
|
}
|