Every time in UTC, and every day as YYYY-MM-DD (closes #313)
check / check (push) Failing after 1m25s
check / check (push) Failing after 1m25s
AbuseIPDB's daily count keeps its day as a civil.Date, taken from the UTC clock and written to reputation.json as "YYYY-MM-DD"; it was a time.Time at midnight. Every time read from a state file is converted to UTC, so a time an admin writes with an offset is written back in UTC. TestMainRunsWAF runs sww waf with TZ 14 hours ahead of UTC and checks that the process line, the request line and clients.json are in UTC. SPEC.md states the rule near its start; AGENTS.md makes it an iron rule. Model: opus-5-5
This commit is contained in:
1 parent
f7e3190654
commit
42b99007c8
11 files changed
+256
-22
No files matched your search
@@ -14,6 +14,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/civil"
|
||||
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
||||
"sneak.berlin/go/smallwebwaf/internal/anomaly"
|
||||
"sneak.berlin/go/smallwebwaf/internal/bans"
|
||||
@@ -258,7 +260,7 @@ const filledReputationJSON = `{
|
||||
}
|
||||
],
|
||||
"abuseipdb": {
|
||||
"day": "2026-10-06T00:00:00Z",
|
||||
"day": "2026-10-06",
|
||||
"spent": 3,
|
||||
"scores": [
|
||||
{
|
||||
@@ -1306,7 +1308,7 @@ func TestEditOfEachFileTakenIn(t *testing.T) {
|
||||
`"tried": "2026-10-06T00:00:00Z", "fetched": "2026-10-06T00:00:00Z", `+
|
||||
`"lines": ["198.51.100.7"]}], "verdicts": [{"zone": "`+dnsblZone+`", `+
|
||||
`"client": "198.51.100.7", "listed": true, "fetched": "2026-10-06T00:00:00Z"}], `+
|
||||
`"abuseipdb": {"day": "2026-10-06T00:00:00Z", "spent": 9, "scores": [`+
|
||||
`"abuseipdb": {"day": "2026-10-06", "spent": 9, "scores": [`+
|
||||
`{"client": "198.51.100.7/32", "score": 80, "fetched": "2026-10-06T00:00:00Z"}]}}`)
|
||||
wantTakenIn(t, lines, dir, reputationJSON)
|
||||
|
||||
@@ -1321,7 +1323,7 @@ func TestEditOfEachFileTakenIn(t *testing.T) {
|
||||
}})
|
||||
|
||||
checks := reputation.Checks{
|
||||
Day: midnight(), Spent: 9,
|
||||
Day: civil.DateOf(midnight()), Spent: 9,
|
||||
Scores: []reputation.Score{{Client: client, Score: 80, Fetched: midnight()}},
|
||||
}
|
||||
if got := params.AbuseIPDB.Snapshot(); !reflect.DeepEqual(got, checks) {
|
||||
@@ -1350,6 +1352,62 @@ func TestEditOfEachFileTakenIn(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimesEditedWithAnOffsetWrittenBackInUTC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir := t.TempDir()
|
||||
params := newParams(dir)
|
||||
fill(params)
|
||||
// A lifted ban, so that bans.json holds every time a ban has.
|
||||
params.Ledger.Lift(netip.MustParseAddr("192.0.2.1"), midnight())
|
||||
|
||||
files := load(t, params)
|
||||
|
||||
err := files.WriteAll()
|
||||
if err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
|
||||
// An admin saves each file with every time in it two hours ahead of
|
||||
// UTC, midnight as "2026-10-06T02:00:00+02:00".
|
||||
stamp := regexp.MustCompile(`"\d{4}-\d\d-\d\dT[^"]*Z"`)
|
||||
ahead := time.FixedZone("UTC+2", 2*60*60)
|
||||
written := map[string]string{}
|
||||
|
||||
for _, name := range []string{
|
||||
bansJSON, clientsJSON, lookupsJSON, reputationJSON, alertsJSON,
|
||||
} {
|
||||
written[name] = readFile(t, filepath.Join(dir, name))
|
||||
edited := stamp.ReplaceAllStringFunc(written[name], func(quoted string) string {
|
||||
at, err := time.Parse(time.RFC3339Nano, strings.Trim(quoted, `"`))
|
||||
if err != nil {
|
||||
t.Fatalf("%s holds %s: %v", name, quoted, err)
|
||||
}
|
||||
|
||||
return `"` + at.In(ahead).Format(time.RFC3339Nano) + `"`
|
||||
})
|
||||
|
||||
if !strings.Contains(edited, "+02:00") {
|
||||
t.Fatalf("%s holds no time:\n%s", name, edited)
|
||||
}
|
||||
|
||||
edit(t, dir, name, edited)
|
||||
}
|
||||
|
||||
// Each write takes its edit in first, and writes the same times back in
|
||||
// UTC.
|
||||
err = files.WriteAll()
|
||||
if err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
|
||||
for name, want := range written {
|
||||
if got := readFile(t, filepath.Join(dir, name)); got != want {
|
||||
t.Errorf("%s written back as\n%s\nwant\n%s", name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOwnWritesAreNotTakenIn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1837,10 +1895,12 @@ func fill(params state.Params) {
|
||||
},
|
||||
{Zone: dnsblZone, Client: client.Addr(), Listed: true, Fetched: now.Add(-time.Hour)},
|
||||
})
|
||||
params.AbuseIPDB.Load(reputation.Checks{Day: now, Spent: 3, Scores: []reputation.Score{
|
||||
{Client: netip.MustParsePrefix("2001:db8::/64"), Fetched: now.Add(-2 * time.Hour)},
|
||||
{Client: client, Score: 100, Fetched: now.Add(-time.Hour)},
|
||||
}})
|
||||
params.AbuseIPDB.Load(reputation.Checks{
|
||||
Day: civil.DateOf(now), Spent: 3, Scores: []reputation.Score{
|
||||
{Client: netip.MustParsePrefix("2001:db8::/64"), Fetched: now.Add(-2 * time.Hour)},
|
||||
{Client: client, Score: 100, Fetched: now.Add(-time.Hour)},
|
||||
},
|
||||
})
|
||||
|
||||
// An alert waiting, a repeat of it the cooldown holds back, another
|
||||
// alert waiting, and one past the two an hour, for the hour's summary.
|
||||
|
||||
Reference in new issue
Block a user