Files
smallwebwaf/internal/requestlog/requestlog_test.go
T
clawbot 808e69f442
check / check (push) Waiting to run
Log the rest of the request log's fields (closes #79)
Each request log line now has the fields "Request log" in SPEC.md lists
whose features are built: instance (SWWAF_INSTANCE_NAME), scheme,
request_id (a trusted proxy's X-Request-ID or a new one, sent on to the
app), forwarded_for, client_group, content_type, content_length, the
headers SWWAF_LOG_REQUEST_HEADERS names, has_authorization, has_cookie,
websocket, response_content_type, cache_control, location, counts and
the timings. Authorization, Cookie and Set-Cookie values are never
logged. An entry of SWWAF_LOG_REQUEST_HEADERS that is not a header name,
or is Host or Transfer-Encoding, stops the start.

Deviation: counts has request totals only.
Deviation: SWWAF_INSTANCE_NAME is on request lines only.

Model: opus-5-5
2026-10-06 17:26:21 +02:00

96 lines
2.4 KiB
Go

package requestlog_test
import (
"bytes"
"encoding/json"
"strings"
"testing"
"time"
"sneak.berlin/go/smallwebwaf/internal/requestlog"
)
func TestWriteWritesOneJSONLineMarkedRequest(t *testing.T) {
t.Parallel()
var out bytes.Buffer
err := requestlog.Write(&out, &requestlog.Line{
Time: requestlog.FormatTime(time.Date(2026, 10, 3, 12, 0, 0, 0, time.UTC)),
ClientIP: "203.0.113.9",
Status: 200,
Action: requestlog.ActionForward,
DurationTotal: requestlog.Milliseconds(1500 * time.Microsecond),
})
if err != nil {
t.Fatalf("write: %v", err)
}
text := out.String()
if strings.Count(text, "\n") != 1 || !strings.HasSuffix(text, "\n") {
t.Fatalf("wrote %q, want one line", text)
}
var fields map[string]any
err = json.Unmarshal(out.Bytes(), &fields)
if err != nil {
t.Fatalf("decode %q: %v", text, err)
}
want := map[string]any{
"type": "request", "time": "2026-10-03T12:00:00.000Z",
"client_ip": "203.0.113.9", "status": 200.0, "action": "forward",
"duration_total": 1.5,
}
for name, value := range want {
if fields[name] != value {
t.Errorf("%s is %v, want %v", name, fields[name], value)
}
}
unset := []string{
"forwarded_for", "content_type", "content_length", "request_headers",
"has_authorization", "has_cookie", "websocket", "response_content_type",
"upstream_status", "cache_control", "location", "aborted", "counts",
"limit_hit", "offence", "ban_expires", "duration_checks",
"duration_upstream_connect", "duration_upstream_first_byte",
"duration_upstream_total",
}
for _, name := range unset {
_, present := fields[name]
if present {
t.Errorf("%s is there with no value to give", name)
}
}
}
func TestProcessLinesAreMarkedProcess(t *testing.T) {
t.Parallel()
var out bytes.Buffer
requestlog.NewProcessLogger(&out).Info("starting", "version", "v1")
var fields map[string]any
err := json.Unmarshal(out.Bytes(), &fields)
if err != nil {
t.Fatalf("decode %q: %v", out.String(), err)
}
if fields["type"] != "process" || fields["msg"] != "starting" ||
fields["level"] != "INFO" || fields["version"] != "v1" {
t.Errorf("process line %v", fields)
}
timeText, _ := fields["time"].(string)
logged, err := time.Parse(time.RFC3339, timeText)
if err != nil || !strings.HasSuffix(timeText, "Z") ||
len(timeText) != len("2006-01-02T15:04:05.000Z") ||
time.Since(logged) > time.Minute {
t.Errorf("process line time %q, want now in UTC with milliseconds", timeText)
}
}