-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhttp_utils.go
130 lines (105 loc) · 2.87 KB
/
http_utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package testutils
import (
"bytes"
"io"
"net/http"
"net/http/httptest"
"sync"
"testing"
)
// MockHTTPServer creates a test HTTP server with the given handler.
// Returns the server URL and a function to close it.
func MockHTTPServer(t *testing.T, handler http.Handler) (serverURL string, cleanup func()) {
t.Helper()
server := httptest.NewServer(handler)
cleanup = func() {
server.Close()
}
// register cleanup with t.Cleanup to ensure the server is closed
// even if the test fails
t.Cleanup(cleanup)
return server.URL, cleanup
}
// RequestRecord holds information about a captured HTTP request
type RequestRecord struct {
Method string
Path string
Headers http.Header
Body []byte
}
// RequestCaptor captures HTTP requests for inspection in tests
type RequestCaptor struct {
mu sync.Mutex
requests []RequestRecord
}
// Len returns the number of captured requests
func (c *RequestCaptor) Len() int {
c.mu.Lock()
defer c.mu.Unlock()
return len(c.requests)
}
// GetRequest returns the request at the specified index
func (c *RequestCaptor) GetRequest(idx int) (RequestRecord, bool) {
c.mu.Lock()
defer c.mu.Unlock()
if idx < 0 || idx >= len(c.requests) {
return RequestRecord{}, false
}
return c.requests[idx], true
}
// GetRequests returns all captured requests
func (c *RequestCaptor) GetRequests() []RequestRecord {
c.mu.Lock()
defer c.mu.Unlock()
// return a copy to avoid race conditions
result := make([]RequestRecord, len(c.requests))
copy(result, c.requests)
return result
}
// Reset clears all captured requests
func (c *RequestCaptor) Reset() {
c.mu.Lock()
defer c.mu.Unlock()
c.requests = nil
}
// add records a new request
func (c *RequestCaptor) add(rec RequestRecord) {
c.mu.Lock()
defer c.mu.Unlock()
c.requests = append(c.requests, rec)
}
// HTTPRequestCaptor returns a request captor and HTTP handler that captures requests
// The returned handler will forward requests to the provided next handler if not nil
func HTTPRequestCaptor(t *testing.T, next http.Handler) (*RequestCaptor, http.Handler) {
t.Helper()
captor := &RequestCaptor{
requests: []RequestRecord{},
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// create a record from the request
record := RequestRecord{
Method: r.Method,
Path: r.URL.Path,
Headers: r.Header.Clone(),
}
// read and store the body if present
if r.Body != nil {
// read body
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
t.Logf("failed to read request body: %v", err)
}
// store the body in the record
record.Body = bodyBytes
// replace the body for downstream handlers
r.Body = io.NopCloser(bytes.NewReader(bodyBytes))
}
// add the record to the captor
captor.add(record)
// forward the request if a next handler is provided
if next != nil {
next.ServeHTTP(w, r)
}
})
return captor, handler
}