Files
job-search/internal/middleware/logging_test.go
T

97 lines
2.4 KiB
Go

package middleware
import (
"bytes"
"encoding/json"
"log/slog"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
)
// lockedBuffer is a bytes.Buffer safe to write from the server's goroutine
// while the test reads it.
type lockedBuffer struct {
mu sync.Mutex
buffer bytes.Buffer
}
func (b *lockedBuffer) Write(data []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.buffer.Write(data)
}
func (b *lockedBuffer) String() string {
b.mu.Lock()
defer b.mu.Unlock()
return b.buffer.String()
}
// A request is logged at debug level, a hijacked connection at info, a 4xx
// response at warn and a 5xx at error.
func TestLoggerLevels(t *testing.T) {
tests := []struct {
name string
handler http.HandlerFunc
want string
}{
{"ok", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}, "DEBUG"},
{"not found", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}, "WARN"},
{"server error", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}, "ERROR"},
{"hijacked", func(w http.ResponseWriter, r *http.Request) {
conn, readWriter, err := http.NewResponseController(w).Hijack()
if err != nil {
t.Errorf("hijack: %v", err)
return
}
defer conn.Close()
readWriter.WriteString("HTTP/1.1 204 No Content\r\n\r\n")
readWriter.Flush()
}, "INFO"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var buffer lockedBuffer
logger := slog.New(slog.NewJSONHandler(&buffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
server := httptest.NewServer(
Logger(WithLogger(logger))(tt.handler),
)
defer server.Close()
res, err := http.Get(server.URL)
if err != nil {
t.Fatal(err)
}
res.Body.Close()
// The line is written when the handler returns, which may be after
// the client has its response.
deadline := time.Now().Add(time.Second)
for buffer.String() == "" && time.Now().Before(deadline) {
time.Sleep(5 * time.Millisecond)
}
var entry struct {
Level string `json:"level"`
Msg string `json:"msg"`
}
if err := json.Unmarshal([]byte(buffer.String()), &entry); err != nil {
t.Fatalf("log line %q: %v", buffer.String(), err)
}
if entry.Msg != "request" || entry.Level != tt.want {
t.Errorf("logged %+v; want a request at %s", entry, tt.want)
}
})
}
}