Notifications: staff turn them on per device from a new Notifications page, pick each type (bans, gags, mutes and silences added or lifted, server down and back up) and quiet hours. A watcher polls SimpleAdmin's tables, so in-game penalties are sent too. Web Push with VAPID and RFC 8291 encryption in internal/push, standard library only. Tables in sql/push.sql, created at startup; the VAPID key is made on first start and kept in saw_settings. Penalty notifications link to the public list searching for the player (#bans?q=<steamid64>). The Android app delegates notifications (versionCode 3). Caching: pages, scripts and styles are served with Cache-Control: no-cache and an ETag, so a deploy shows up at once instead of after Cloudflare's default 4-hour browser cache.
78 lines
3.2 KiB
Go
78 lines
3.2 KiB
Go
package web
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"git.zio.sh/cs2/simpleadmin-web/internal/store"
|
|
)
|
|
|
|
func TestPenaltyNotification(t *testing.T) {
|
|
for _, c := range []struct {
|
|
e store.PenaltyEvent
|
|
typ, title, body, url string
|
|
}{
|
|
{store.PenaltyEvent{ID: 56, Kind: "ban", Name: "teabag_tim", SteamID: "76561198012345622", AdminSteamID: "76561198000000001", AdminName: "mossy", Reason: "Toxicity after warnings", Duration: 4320},
|
|
"ban", "Ban added: teabag_tim", "mossy · 3 days · Toxicity after warnings", "/#bans?q=76561198012345622"},
|
|
{store.PenaltyEvent{ID: 7, Kind: "comm", Type: "GAG", Name: "noodlearm", SteamID: "765", AdminName: "Kestrel", Reason: "Spam", Duration: 30},
|
|
"gag", "Gag added: noodlearm", "Kestrel · 30 min · Spam", "/#comms?q=765"},
|
|
{store.PenaltyEvent{ID: 8, Kind: "comm", Type: "SILENCE", SteamID: "765", AdminName: "Console", Duration: 0},
|
|
"silence", "Silence added: 765", "Console · Permanent", "/#comms?q=765"},
|
|
{store.PenaltyEvent{ID: 3, Kind: "unban", Name: "mika", SteamID: "765", AdminName: "astra", Reason: "Appeal accepted"},
|
|
"unban", "Ban lifted: mika", "astra · Appeal accepted", "/#bans?q=765"},
|
|
{store.PenaltyEvent{ID: 4, Kind: "unmute", Type: "MUTE", Name: "pip", SteamID: "765"},
|
|
"unmute", "Mute lifted: pip", "Console", "/#comms?q=765"},
|
|
} {
|
|
n, ok := penaltyNotification(c.e)
|
|
if !ok || n.Type != c.typ || n.Title != c.title || n.Body != c.body || n.URL != c.url {
|
|
t.Errorf("%+v:\n got %v %q %q %q %q", c.e, ok, n.Type, n.Title, n.Body, n.URL)
|
|
}
|
|
if n.Actor != c.e.AdminSteamID || len(n.Actions) != 1 || n.Actions[0].Title != "View player" {
|
|
t.Errorf("%+v: actor %q actions %v", c.e, n.Actor, n.Actions)
|
|
}
|
|
}
|
|
if _, ok := penaltyNotification(store.PenaltyEvent{Kind: "comm", Type: "OTHER"}); ok {
|
|
t.Error("unknown comm type should be skipped")
|
|
}
|
|
}
|
|
|
|
func TestQuietHours(t *testing.T) {
|
|
at := func(h, m int) time.Time { return time.Date(2026, 10, 3, h, m, 0, 0, time.UTC) }
|
|
night := store.PushPrefs{QuietEnabled: true, QuietStart: 23 * 60, QuietEnd: 8 * 60, Timezone: "UTC"}
|
|
day := store.PushPrefs{QuietEnabled: true, QuietStart: 9 * 60, QuietEnd: 17 * 60, Timezone: "UTC"}
|
|
for _, c := range []struct {
|
|
p store.PushPrefs
|
|
t time.Time
|
|
want bool
|
|
}{
|
|
{night, at(23, 0), true},
|
|
{night, at(3, 0), true},
|
|
{night, at(7, 59), true},
|
|
{night, at(8, 0), false},
|
|
{night, at(12, 0), false},
|
|
{day, at(9, 0), true},
|
|
{day, at(17, 0), false},
|
|
{day, at(8, 59), false},
|
|
{store.PushPrefs{QuietEnabled: false, QuietStart: 0, QuietEnd: 1439}, at(12, 0), false},
|
|
{store.PushPrefs{QuietEnabled: true, QuietStart: 600, QuietEnd: 600}, at(10, 0), false},
|
|
// 23:30 UTC is 00:30 in London (BST), inside 00:00-06:00 there.
|
|
{store.PushPrefs{QuietEnabled: true, QuietStart: 0, QuietEnd: 360, Timezone: "Europe/London"}, at(23, 30), true},
|
|
} {
|
|
if got := inQuietHours(c.p, c.t); got != c.want {
|
|
t.Errorf("%+v at %s: got %v", c.p, c.t.Format("15:04"), got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHHMM(t *testing.T) {
|
|
for _, m := range []int{0, 59, 60, 480, 1380, 1439} {
|
|
if got, ok := parseHHMM(hhmm(m)); !ok || got != m {
|
|
t.Errorf("%d -> %s -> %d", m, hhmm(m), got)
|
|
}
|
|
}
|
|
for _, s := range []string{"24:00", "7:00", "12:60", "", "12:00:00"} {
|
|
if _, ok := parseHHMM(s); ok {
|
|
t.Errorf("%q parsed", s)
|
|
}
|
|
}
|
|
}
|