go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/web/mock.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package web 9 10 import ( 11 "bytes" 12 "context" 13 "encoding/json" 14 "io" 15 "net/http" 16 "net/http/httptest" 17 "net/url" 18 "time" 19 20 "go.charczuk.com/sdk/r2" 21 ) 22 23 // Mock sends a mock request to an app. 24 // 25 // It will reset the app Server, Listener, and will set the request host to the listener address 26 // for a randomized local listener. 27 func Mock(app *App, req *http.Request, options ...r2.Option) *MockResult { 28 r2req := new(r2.Request) 29 _ = r2.OptRequest(req)(r2req) 30 31 var err error 32 result := &MockResult{ 33 App: app, 34 Request: r2req, 35 } 36 for _, option := range options { 37 if err = option(result.Request); err != nil { 38 _ = r2.OptErr(err)(result.Request) 39 return result 40 } 41 } 42 if err := app.Initialize(); err != nil { 43 _ = r2.OptErr(err)(result.Request) 44 return result 45 } 46 47 if result.URL() == nil { 48 _ = r2.OptURL(new(url.URL))(result.Request) 49 } 50 51 result.Server = httptest.NewUnstartedServer(app) 52 // result.Server.Config.BaseContext = app.BaseContext 53 result.Server.Start() 54 _ = r2.OptCloser(result.Close)(result.Request) 55 56 parsedServerURL := MustParseURL(result.Server.URL) 57 _ = r2.OptScheme(parsedServerURL.Scheme)(result.Request) 58 _ = r2.OptHost(parsedServerURL.Host)(result.Request) 59 return result 60 } 61 62 // MockMethod sends a mock request with a given method to an app. 63 // You should use request options to set the body of the request if it's a post or put etc. 64 func MockMethod(app *App, method, path string, options ...r2.Option) *MockResult { 65 req := &http.Request{ 66 Method: method, 67 URL: &url.URL{ 68 Path: path, 69 }, 70 } 71 return Mock(app, req, options...) 72 } 73 74 // MockGet sends a mock get request to an app. 75 func MockGet(app *App, path string, options ...r2.Option) *MockResult { 76 req := &http.Request{ 77 Method: "GET", 78 URL: &url.URL{ 79 Path: path, 80 }, 81 } 82 return Mock(app, req, options...) 83 } 84 85 // MockPost sends a mock post request to an app. 86 func MockPost(app *App, path string, body io.ReadCloser, options ...r2.Option) *MockResult { 87 req := &http.Request{ 88 Method: "POST", 89 Body: body, 90 URL: &url.URL{ 91 Path: path, 92 }, 93 } 94 return Mock(app, req, options...) 95 } 96 97 // MockPostJSON sends a mock post request with a json body to an app. 98 func MockPostJSON(app *App, path string, body interface{}, options ...r2.Option) *MockResult { 99 contents, _ := json.Marshal(body) 100 req := &http.Request{ 101 Method: "POST", 102 Body: io.NopCloser(bytes.NewReader(contents)), 103 URL: &url.URL{ 104 Path: path, 105 }, 106 } 107 return Mock(app, req, options...) 108 } 109 110 // MockResult is a result of a mocked request. 111 type MockResult struct { 112 *r2.Request 113 App *App 114 Server *httptest.Server 115 } 116 117 // Close stops the app. 118 func (mr *MockResult) Close() error { 119 mr.Server.Close() 120 return nil 121 } 122 123 // MockContext returns a new mock ctx. 124 // It is intended to be used in testing. 125 func MockContext(method, path string) Context { 126 return MockContextWithBuffer(method, path, new(bytes.Buffer)) 127 } 128 129 // MockContextWithBuffer returns a new mock ctx. 130 // It is intended to be used in testing. 131 func MockContextWithBuffer(method, path string, buf io.Writer) Context { 132 return &baseContext{ 133 app: new(App), 134 res: NewMockResponse(buf), 135 req: NewMockRequest(method, path), 136 } 137 } 138 139 var ( 140 _ http.ResponseWriter = (*MockResponseWriter)(nil) 141 _ http.Flusher = (*MockResponseWriter)(nil) 142 ) 143 144 // NewMockResponse returns a mocked response writer. 145 func NewMockResponse(buffer io.Writer) *MockResponseWriter { 146 return &MockResponseWriter{ 147 innerWriter: buffer, 148 contents: new(bytes.Buffer), 149 headers: http.Header{}, 150 } 151 } 152 153 // MockResponseWriter is an object that satisfies response writer but uses an internal buffer. 154 type MockResponseWriter struct { 155 innerWriter io.Writer 156 contents *bytes.Buffer 157 statusCode int 158 contentLength int 159 headers http.Header 160 } 161 162 // Write writes data and adds to ContentLength. 163 func (res *MockResponseWriter) Write(buffer []byte) (int, error) { 164 bytesWritten, err := res.innerWriter.Write(buffer) 165 res.contentLength += bytesWritten 166 defer func() { 167 res.contents.Write(buffer) 168 }() 169 return bytesWritten, err 170 } 171 172 // Header returns the response headers. 173 func (res *MockResponseWriter) Header() http.Header { 174 return res.headers 175 } 176 177 // WriteHeader sets the status code. 178 func (res *MockResponseWriter) WriteHeader(statusCode int) { 179 res.statusCode = statusCode 180 } 181 182 // InnerResponse returns the backing httpresponse writer. 183 func (res *MockResponseWriter) InnerResponse() http.ResponseWriter { 184 return res 185 } 186 187 // StatusCode returns the status code. 188 func (res *MockResponseWriter) StatusCode() int { 189 return res.statusCode 190 } 191 192 // ContentLength returns the content length. 193 func (res *MockResponseWriter) ContentLength() int { 194 return res.contentLength 195 } 196 197 // Bytes returns the raw response. 198 func (res *MockResponseWriter) Bytes() []byte { 199 return res.contents.Bytes() 200 } 201 202 // Flush is a no-op. 203 func (res *MockResponseWriter) Flush() {} 204 205 // Close is a no-op. 206 func (res *MockResponseWriter) Close() error { 207 return nil 208 } 209 210 // NewMockRequest creates a mock request. 211 func NewMockRequest(method, path string) *http.Request { 212 return &http.Request{ 213 Method: method, 214 Proto: "http", 215 ProtoMajor: 1, 216 ProtoMinor: 1, 217 Host: "localhost:8080", 218 RemoteAddr: "127.0.0.1:8080", 219 RequestURI: path, 220 Header: http.Header{ 221 HeaderUserAgent: []string{"go-sdk test"}, 222 }, 223 URL: &url.URL{ 224 Scheme: "http", 225 Host: "localhost", 226 Path: path, 227 RawPath: path, 228 }, 229 } 230 } 231 232 // NewMockRequestWithCookie creates a mock request with a cookie attached to it. 233 func NewMockRequestWithCookie(method, path, cookieName, cookieValue string) *http.Request { 234 req := NewMockRequest(method, path) 235 req.AddCookie(&http.Cookie{ 236 Name: cookieName, 237 Domain: "localhost", 238 Path: "/", 239 Value: cookieValue, 240 }) 241 return req 242 } 243 244 // MockSimulateLogin simulates a user login for a given app as mocked request params (i.e. r2 options). 245 // 246 // This requires an auth manager to be configured to handle and persist sessions. 247 func MockSimulateLogin(ctx context.Context, app *App, userID string, opts ...r2.Option) []r2.Option { 248 sessionID := NewSessionID() 249 session := &Session{ 250 UserID: userID, 251 SessionID: sessionID, 252 CreatedUTC: time.Now().UTC(), 253 } 254 if persistHandler, ok := app.AuthPersister.(PersistSessionHandler); ok { 255 _ = persistHandler.PersistSession(ctx, session) 256 } 257 return append([]r2.Option{ 258 r2.OptCookieValue(app.AuthCookieDefaults.Name, sessionID), 259 }, opts...) 260 }