Added access log to server and modified mock-fts to use it

This commit is contained in:
bwilliams committed 2026-10-16 08:38:41 +01:00
1 parent 2490519c2b
commit 02e3d910a2
10 files changed
+389 -29

No files matched your search

+89
View File
@@ -1,13 +1,16 @@
package server
import (
"bytes"
"context"
"encoding/json"
"log"
"log/slog"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"
@@ -218,3 +221,89 @@ func TestNoLimitIfUnset(t *testing.T) {
}
}
}
// With WithAccessLog, each request, including one refused by middleware, is
// written to the access log in the Combined Log Format.
func TestAccessLog(t *testing.T) {
var buffer bytes.Buffer
ts := newTestServer(t, func(conf *config.JSCServer) {
conf.Headers.AllowedHosts = []string{"example.com"}
}, WithAccessLog(log.New(&buffer, "", 0)))
req, _ := http.NewRequest("GET", ts.URL+"/api/health", nil)
req.Host = "example.com"
req.Header.Set("User-Agent", "test-agent")
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
res.Body.Close()
// A Host that isn't allowed is refused by RequestChecks.
res, err = http.Get(ts.URL + "/api/health")
if err != nil {
t.Fatal(err)
}
res.Body.Close()
lines := strings.Split(strings.TrimSpace(buffer.String()), "\n")
if len(lines) != 2 {
t.Fatalf("access log has %d lines; want 2:\n%s", len(lines), &buffer)
}
if !strings.Contains(lines[0], `"GET /api/health HTTP/1.1" 200`) ||
!strings.Contains(lines[0], `"test-agent"`) {
t.Errorf("first line = %q; want a 200 with the user agent", lines[0])
}
if !strings.Contains(lines[1], `"GET /api/health HTTP/1.1" 421`) {
t.Errorf("second line = %q; want the refused request's 421", lines[1])
}
}
// With WithAccessLog, a WebSocket upgrade still hijacks the connection: the
// client gets its welcome, and the upgrade is logged with 101.
func TestAccessLogWebSocket(t *testing.T) {
var buffer syncBuffer
ts := newTestServer(t, nil, WithAccessLog(log.New(&buffer, "", 0)))
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
wsURL := "ws" + strings.TrimPrefix(ts.URL, "http") + "/ws"
conn, _, err := websocket.Dial(ctx, wsURL, nil)
if err != nil {
t.Fatal(err)
}
var welcome ws.Envelope
err = wsjson.Read(ctx, conn, &welcome)
if err != nil || welcome.Type != ws.TypeWelcome {
t.Fatalf("want welcome, got %+v, %v", welcome, err)
}
conn.Close(websocket.StatusNormalClosure, "")
// The line is written when the connection's handler returns.
deadline := time.Now().Add(time.Second)
for !strings.Contains(buffer.String(), `"GET /ws HTTP/1.1" 101`) {
if time.Now().After(deadline) {
t.Fatalf("upgrade not logged with 101: %q", buffer.String())
}
time.Sleep(5 * time.Millisecond)
}
}
// syncBuffer is a bytes.Buffer safe to write from one goroutine while
// another reads it.
type syncBuffer struct {
mu sync.Mutex
buffer bytes.Buffer
}
func (b *syncBuffer) Write(data []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.buffer.Write(data)
}
func (b *syncBuffer) String() string {
b.mu.Lock()
defer b.mu.Unlock()
return b.buffer.String()
}