8700fbfae1
- backend + gateway multi-stage distroless Dockerfiles; the gateway embeds and
serves the SPA at / and /telegram/ via go:embed (committed dist placeholder,
real build baked in by the image's node stage)
- deploy/docker-compose.yml: backend + gateway + Postgres + Telegram connector
(VPN sidecar) + OTel Collector + Prometheus (15d) + Tempo (72h) + Grafana,
fronted by a caddy owning a single /_gm Basic-Auth (admin console + Grafana
subpath); inter-service on a private network, only caddy on the edge network
- new metrics: backend accounts_created_total{kind} (robots excluded) and an
in-memory gateway active_users{window=24h,7d} gauge
- CI: single .gitea/workflows/ci.yaml (unit/integration/ui + a gated test-contour
deploy) on the new feature/* -> development -> master branch model; the old
go-unit/integration/ui-test workflows are folded in; the connector-scoped
compose is retired (superseded by deploy/)
- docs: ARCHITECTURE §11/§12/§13, root + gateway READMEs, CLAUDE.md branching,
PLAN.md (stage 16 done + refinements + Stage 17 forward-notes)
46 lines
1.1 KiB
Go
46 lines
1.1 KiB
Go
package connectsrv
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestActiveUsersCountsAndPrune(t *testing.T) {
|
|
a := newActiveUsers()
|
|
base := time.Date(2026, 6, 5, 12, 0, 0, 0, time.UTC)
|
|
cur := base
|
|
a.now = func() time.Time { return cur }
|
|
|
|
a.seen("u1") // at base
|
|
cur = base.Add(2 * time.Hour)
|
|
a.seen("u2") // base+2h
|
|
cur = base.Add(50 * time.Hour)
|
|
a.seen("u3") // base+50h
|
|
|
|
windows := []time.Duration{24 * time.Hour, 7 * 24 * time.Hour}
|
|
|
|
// now = base+50h: u3 within 24h; all three within 7d.
|
|
got := a.counts(windows)
|
|
if got[0] != 1 || got[1] != 3 {
|
|
t.Fatalf("counts at +50h = %v, want [1 3]", got)
|
|
}
|
|
|
|
// now = base+169h: u1 (age 169h) prunes past the 7d window; u2/u3 remain in 7d.
|
|
cur = base.Add(169 * time.Hour)
|
|
got = a.counts(windows)
|
|
if got[0] != 0 || got[1] != 2 {
|
|
t.Fatalf("counts at +169h = %v, want [0 2]", got)
|
|
}
|
|
if _, ok := a.lastSeen["u1"]; ok {
|
|
t.Fatalf("u1 should have been pruned from the tracker")
|
|
}
|
|
}
|
|
|
|
func TestActiveUsersIgnoresEmpty(t *testing.T) {
|
|
a := newActiveUsers()
|
|
a.seen("")
|
|
if got := a.counts([]time.Duration{time.Hour}); got[0] != 0 {
|
|
t.Fatalf("empty uid recorded: got %v", got)
|
|
}
|
|
}
|