feat(receiver): storage layer and webhook payload compat

Dual-dialect store (SQLite via modernc.org, MySQL via go-sql-driver,
both pure Go) with order-tolerant start/complete upserts, filtered and
paginated listing, and aggregate queries (per-day, top domains, query
types, statuses, duration percentiles, top clients). Sender gains
optional EXPLOREDNS_WEBHOOK_TOKEN bearer auth; a round-trip test pins
receiver structs byte-compatible with the sender payloads.

Note: go directive moves to 1.25.0, required by modernc.org/sqlite.
CI reads the version from go.mod so GOTOOLCHAIN=local stays satisfied.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Gary Hansen
2026-07-08 02:43:50 +10:00
co-authored by Claude Fable 5
parent d96f25b1d6
commit e8f56a1ef4
11 changed files with 1468 additions and 23 deletions
+31 -1
View File
@@ -235,7 +235,37 @@ func TestWebhook_RetriesOnceOnFailure(t *testing.T) {
func TestWebhook_NilReporterSafe(t *testing.T) {
var wr *webhookReporter
wr.send("start", map[string]string{"event": "start"}) // must not panic
if newWebhookReporter("") != nil {
if newWebhookReporter("", "token") != nil {
t.Fatal("empty URL should disable the webhook reporter")
}
}
// TestWebhook_BearerToken verifies the Authorization header is sent exactly
// when a token is configured (EXPLOREDNS_WEBHOOK_TOKEN on the real path).
func TestWebhook_BearerToken(t *testing.T) {
for _, tc := range []struct {
name, token, wantAuth string
}{
{"token set", "s3cret", "Bearer s3cret"},
{"token unset", "", ""},
} {
t.Run(tc.name, func(t *testing.T) {
done := make(chan string, 1)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
done <- r.Header.Get("Authorization")
}))
defer ts.Close()
newWebhookReporter(ts.URL, tc.token).send("start", map[string]string{"event": "start"})
select {
case got := <-done:
if got != tc.wantAuth {
t.Fatalf("Authorization = %q, want %q", got, tc.wantAuth)
}
case <-time.After(5 * time.Second):
t.Fatal("webhook was not delivered")
}
})
}
}