@ -38,6 +38,7 @@ func (f handlerFunc) ServeHTTPReturn(w http.ResponseWriter, r *http.Request) err
}
}
func TestStdHandler ( t * testing . T ) {
func TestStdHandler ( t * testing . T ) {
const exampleRequestID = "example-request-id"
var (
var (
handlerCode = func ( code int ) ReturnHandler {
handlerCode = func ( code int ) ReturnHandler {
return handlerFunc ( func ( w http . ResponseWriter , r * http . Request ) error {
return handlerFunc ( func ( w http . ResponseWriter , r * http . Request ) error {
@ -66,16 +67,20 @@ func TestStdHandler(t *testing.T) {
bgCtx = context . Background ( )
bgCtx = context . Background ( )
// canceledCtx, cancel = context.WithCancel(bgCtx)
// canceledCtx, cancel = context.WithCancel(bgCtx)
startTime = time . Unix ( 1687870000 , 1234 )
startTime = time . Unix ( 1687870000 , 1234 )
setExampleRequestID = func ( _ * http . Request ) RequestID { return exampleRequestID }
)
)
// cancel()
// cancel()
tests := [ ] struct {
tests := [ ] struct {
name string
name string
rh ReturnHandler
rh ReturnHandler
r * http . Request
r * http . Request
errHandler ErrorHandlerFunc
errHandler ErrorHandlerFunc
wantCode int
generateRequestID func ( * http . Request ) RequestID
wantLog AccessLogRecord
wantCode int
wantLog AccessLogRecord
wantBody string
} {
} {
{
{
name : "handler returns 200" ,
name : "handler returns 200" ,
@ -94,6 +99,24 @@ func TestStdHandler(t *testing.T) {
} ,
} ,
} ,
} ,
{
name : "handler returns 200 with request ID" ,
rh : handlerCode ( 200 ) ,
r : req ( bgCtx , "http://example.com/" ) ,
generateRequestID : setExampleRequestID ,
wantCode : 200 ,
wantLog : AccessLogRecord {
When : startTime ,
Seconds : 1.0 ,
Proto : "HTTP/1.1" ,
TLS : false ,
Host : "example.com" ,
Method : "GET" ,
Code : 200 ,
RequestURI : "/" ,
} ,
} ,
{
{
name : "handler returns 404" ,
name : "handler returns 404" ,
rh : handlerCode ( 404 ) ,
rh : handlerCode ( 404 ) ,
@ -110,6 +133,23 @@ func TestStdHandler(t *testing.T) {
} ,
} ,
} ,
} ,
{
name : "handler returns 404 with request ID" ,
rh : handlerCode ( 404 ) ,
r : req ( bgCtx , "http://example.com/foo" ) ,
generateRequestID : setExampleRequestID ,
wantCode : 404 ,
wantLog : AccessLogRecord {
When : startTime ,
Seconds : 1.0 ,
Proto : "HTTP/1.1" ,
Host : "example.com" ,
Method : "GET" ,
RequestURI : "/foo" ,
Code : 404 ,
} ,
} ,
{
{
name : "handler returns 404 via HTTPError" ,
name : "handler returns 404 via HTTPError" ,
rh : handlerErr ( 0 , Error ( 404 , "not found" , testErr ) ) ,
rh : handlerErr ( 0 , Error ( 404 , "not found" , testErr ) ) ,
@ -125,6 +165,27 @@ func TestStdHandler(t *testing.T) {
Err : "not found: " + testErr . Error ( ) ,
Err : "not found: " + testErr . Error ( ) ,
Code : 404 ,
Code : 404 ,
} ,
} ,
wantBody : "not found\n" ,
} ,
{
name : "handler returns 404 via HTTPError with request ID" ,
rh : handlerErr ( 0 , Error ( 404 , "not found" , testErr ) ) ,
r : req ( bgCtx , "http://example.com/foo" ) ,
generateRequestID : setExampleRequestID ,
wantCode : 404 ,
wantLog : AccessLogRecord {
When : startTime ,
Seconds : 1.0 ,
Proto : "HTTP/1.1" ,
Host : "example.com" ,
Method : "GET" ,
RequestURI : "/foo" ,
Err : "not found: " + testErr . Error ( ) ,
Code : 404 ,
RequestID : exampleRequestID ,
} ,
wantBody : "not found\n" + exampleRequestID + "\n" ,
} ,
} ,
{
{
@ -142,6 +203,27 @@ func TestStdHandler(t *testing.T) {
Err : "not found" ,
Err : "not found" ,
Code : 404 ,
Code : 404 ,
} ,
} ,
wantBody : "not found\n" ,
} ,
{
name : "handler returns 404 with request ID and nil child error" ,
rh : handlerErr ( 0 , Error ( 404 , "not found" , nil ) ) ,
r : req ( bgCtx , "http://example.com/foo" ) ,
generateRequestID : setExampleRequestID ,
wantCode : 404 ,
wantLog : AccessLogRecord {
When : startTime ,
Seconds : 1.0 ,
Proto : "HTTP/1.1" ,
Host : "example.com" ,
Method : "GET" ,
RequestURI : "/foo" ,
Err : "not found" ,
Code : 404 ,
RequestID : exampleRequestID ,
} ,
wantBody : "not found\n" + exampleRequestID + "\n" ,
} ,
} ,
{
{
@ -159,6 +241,27 @@ func TestStdHandler(t *testing.T) {
Err : "visible error" ,
Err : "visible error" ,
Code : 500 ,
Code : 500 ,
} ,
} ,
wantBody : "visible error\n" ,
} ,
{
name : "handler returns user-visible error with request ID" ,
rh : handlerErr ( 0 , vizerror . New ( "visible error" ) ) ,
r : req ( bgCtx , "http://example.com/foo" ) ,
generateRequestID : setExampleRequestID ,
wantCode : 500 ,
wantLog : AccessLogRecord {
When : startTime ,
Seconds : 1.0 ,
Proto : "HTTP/1.1" ,
Host : "example.com" ,
Method : "GET" ,
RequestURI : "/foo" ,
Err : "visible error" ,
Code : 500 ,
RequestID : exampleRequestID ,
} ,
wantBody : "visible error\n" + exampleRequestID + "\n" ,
} ,
} ,
{
{
@ -176,6 +279,27 @@ func TestStdHandler(t *testing.T) {
Err : "visible error" ,
Err : "visible error" ,
Code : 500 ,
Code : 500 ,
} ,
} ,
wantBody : "visible error\n" ,
} ,
{
name : "handler returns user-visible error wrapped by private error with request ID" ,
rh : handlerErr ( 0 , fmt . Errorf ( "private internal error: %w" , vizerror . New ( "visible error" ) ) ) ,
r : req ( bgCtx , "http://example.com/foo" ) ,
generateRequestID : setExampleRequestID ,
wantCode : 500 ,
wantLog : AccessLogRecord {
When : startTime ,
Seconds : 1.0 ,
Proto : "HTTP/1.1" ,
Host : "example.com" ,
Method : "GET" ,
RequestURI : "/foo" ,
Err : "visible error" ,
Code : 500 ,
RequestID : exampleRequestID ,
} ,
wantBody : "visible error\n" + exampleRequestID + "\n" ,
} ,
} ,
{
{
@ -193,6 +317,27 @@ func TestStdHandler(t *testing.T) {
Err : testErr . Error ( ) ,
Err : testErr . Error ( ) ,
Code : 500 ,
Code : 500 ,
} ,
} ,
wantBody : "internal server error\n" ,
} ,
{
name : "handler returns generic error with request ID" ,
rh : handlerErr ( 0 , testErr ) ,
r : req ( bgCtx , "http://example.com/foo" ) ,
generateRequestID : setExampleRequestID ,
wantCode : 500 ,
wantLog : AccessLogRecord {
When : startTime ,
Seconds : 1.0 ,
Proto : "HTTP/1.1" ,
Host : "example.com" ,
Method : "GET" ,
RequestURI : "/foo" ,
Err : testErr . Error ( ) ,
Code : 500 ,
RequestID : exampleRequestID ,
} ,
wantBody : "internal server error\n" + exampleRequestID + "\n" ,
} ,
} ,
{
{
@ -212,6 +357,25 @@ func TestStdHandler(t *testing.T) {
} ,
} ,
} ,
} ,
{
name : "handler returns error after writing response with request ID" ,
rh : handlerErr ( 200 , testErr ) ,
r : req ( bgCtx , "http://example.com/foo" ) ,
generateRequestID : setExampleRequestID ,
wantCode : 200 ,
wantLog : AccessLogRecord {
When : startTime ,
Seconds : 1.0 ,
Proto : "HTTP/1.1" ,
Host : "example.com" ,
Method : "GET" ,
RequestURI : "/foo" ,
Err : testErr . Error ( ) ,
Code : 200 ,
RequestID : exampleRequestID ,
} ,
} ,
{
{
name : "handler returns HTTPError after writing response" ,
name : "handler returns HTTPError after writing response" ,
rh : handlerErr ( 200 , Error ( 404 , "not found" , testErr ) ) ,
rh : handlerErr ( 200 , Error ( 404 , "not found" , testErr ) ) ,
@ -267,6 +431,7 @@ func TestStdHandler(t *testing.T) {
Code : 101 ,
Code : 101 ,
} ,
} ,
} ,
} ,
{
{
name : "error handler gets run" ,
name : "error handler gets run" ,
rh : handlerErr ( 0 , Error ( 404 , "not found" , nil ) ) , // status code changed in errHandler
rh : handlerErr ( 0 , Error ( 404 , "not found" , nil ) ) , // status code changed in errHandler
@ -286,6 +451,62 @@ func TestStdHandler(t *testing.T) {
Err : "not found" ,
Err : "not found" ,
RequestURI : "/" ,
RequestURI : "/" ,
} ,
} ,
wantBody : "not found\n" ,
} ,
{
name : "error handler gets run with request ID" ,
rh : handlerErr ( 0 , Error ( 404 , "not found" , nil ) ) , // status code changed in errHandler
r : req ( bgCtx , "http://example.com/" ) ,
generateRequestID : setExampleRequestID ,
wantCode : 200 ,
errHandler : func ( w http . ResponseWriter , r * http . Request , e HTTPError ) {
http . Error ( w , fmt . Sprintf ( "%s with request ID %s" , e . Msg , e . RequestID ) , 200 )
} ,
wantLog : AccessLogRecord {
When : startTime ,
Seconds : 1.0 ,
Proto : "HTTP/1.1" ,
TLS : false ,
Host : "example.com" ,
Method : "GET" ,
Code : 404 ,
Err : "not found" ,
RequestURI : "/" ,
RequestID : exampleRequestID ,
} ,
wantBody : "not found with request ID " + exampleRequestID + "\n" ,
} ,
{
name : "request ID can use information from request" ,
rh : handlerErr ( 0 , Error ( 400 , "bad request" , nil ) ) ,
r : func ( ) * http . Request {
r := req ( bgCtx , "http://example.com/" )
r . AddCookie ( & http . Cookie { Name : "want_request_id" , Value : "asdf1234" } )
return r
} ( ) ,
generateRequestID : func ( r * http . Request ) RequestID {
c , _ := r . Cookie ( "want_request_id" )
if c == nil {
return ""
}
return RequestID ( c . Value )
} ,
wantCode : 400 ,
wantLog : AccessLogRecord {
When : startTime ,
Seconds : 1.0 ,
Proto : "HTTP/1.1" ,
TLS : false ,
Host : "example.com" ,
RequestURI : "/" ,
Method : "GET" ,
Code : 400 ,
Err : "bad request" ,
RequestID : "asdf1234" ,
} ,
wantBody : "bad request\nasdf1234\n" ,
} ,
} ,
}
}
@ -305,7 +526,7 @@ func TestStdHandler(t *testing.T) {
} )
} )
rec := noopHijacker { httptest . NewRecorder ( ) , false }
rec := noopHijacker { httptest . NewRecorder ( ) , false }
h := StdHandler ( test . rh , HandlerOptions { Logf : logf , Now : clock . Now , OnError: test . errHandler } )
h := StdHandler ( test . rh , HandlerOptions { Logf : logf , Now : clock . Now , GenerateRequestID: test . generateRequestID , OnError: test . errHandler } )
h . ServeHTTP ( & rec , test . r )
h . ServeHTTP ( & rec , test . r )
res := rec . Result ( )
res := rec . Result ( )
if res . StatusCode != test . wantCode {
if res . StatusCode != test . wantCode {
@ -324,6 +545,9 @@ func TestStdHandler(t *testing.T) {
if diff := cmp . Diff ( logs [ 0 ] , test . wantLog , errTransform ) ; diff != "" {
if diff := cmp . Diff ( logs [ 0 ] , test . wantLog , errTransform ) ; diff != "" {
t . Errorf ( "handler wrote incorrect request log (-got+want):\n%s" , diff )
t . Errorf ( "handler wrote incorrect request log (-got+want):\n%s" , diff )
}
}
if diff := cmp . Diff ( rec . Body . String ( ) , test . wantBody ) ; diff != "" {
t . Errorf ( "handler wrote incorrect body (-got+want):\n%s" , diff )
}
} )
} )
}
}
}
}