Files
smallwebwaf/internal/requestlog/requestlog_test.go
T
clawbot 73ca94f850
check / check (push) Successful in 3m48s
Ban the netblock of a client that breaks a rate limit, in memory (closes #18)
A request over a rate limit is refused with SWWAF_BAN_RESPONSE and bans
the client's netblock: an hour at first, three times the last ban when
broken again within a day of its end, permanent past seven days. The
ban ledger in internal/bans is checked after the static lists and
before the lookup, and the requests it refuses are not counted. A ban
resets the client's counters and carries notes holding the request
that broke the limit, as SPEC.md now says. At most SWWAF_MAX_BANS are
held. SWWAF_BAN_RESPONSE also answers SWWAF_DENY_NETS and the country
lists.

Judgement call: the six ban settings cannot be off.
Judgement call: a permanent ban's ban_expires is "permanent".

Model: opus-5-5
2026-10-06 05:29:03 +02:00

92 lines
2.2 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{
"upstream_status", "limit_hit", "offence", "ban_expires", "aborted",
"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)
}
}