Files
paliad/internal/handlers/audit_test.go
m 2422603abf feat(admin): /admin/audit-log global timeline (t-paliad-071)
Replaces the "Geplant: Audit-Log" placeholder on /admin with a working
viewer that unions paliad.project_events + caldav_sync_log + reminder_log
into a single keyset-paginated timeline.

- AuditService.ListEntries (internal/services/audit_service.go) does one
  UNION ALL across the three sources, projecting each into a unified
  AuditEntry shape and ordering by (timestamp, id) DESC. Cursor is
  (BeforeTS, BeforeID) — matches the project-event Verlauf pattern. ILIKE
  search escapes %/_ so "100%" doesn't act as a wildcard.

- GET /api/audit-log (internal/handlers/audit.go) accepts
  source/from/to/q/before_ts/before_id/limit, validates the cursor halves
  are paired, and returns { entries, next_cursor }. Both API and the
  GET /admin/audit-log SPA shell are wrapped in auth.RequireAdminFunc, so
  non-admins get 403 (API) / 302 (browser) via the same gate /admin/team
  uses.

- Frontend (admin-audit-log.tsx + client/admin-audit-log.ts) renders the
  table with source dropdown, range presets (24h / 7d / 30d / custom /
  all), free-text search (debounced 250ms), and "Weitere laden" cursor
  pagination. project_events rows reuse translateEvent (t-paliad-067 PR-1)
  for DE/EN narrative parity with the dashboard activity feed; caldav and
  reminder rows have their own per-event-type i18n keys.

- /admin landing card moved from PLANNED to AVAILABLE; sidebar admin
  group gains a third entry.
2026-04-29 19:12:11 +02:00

35 lines
798 B
Go

package handlers
import (
"testing"
"time"
)
func TestParseAuditTimestamp_RFC3339(t *testing.T) {
got, err := parseAuditTimestamp("2026-04-29T14:35:06Z")
if err != nil {
t.Fatalf("parse RFC3339: %v", err)
}
want := time.Date(2026, 4, 29, 14, 35, 6, 0, time.UTC)
if !got.Equal(want) {
t.Errorf("got %v, want %v", got, want)
}
}
func TestParseAuditTimestamp_DateOnly(t *testing.T) {
got, err := parseAuditTimestamp("2026-04-29")
if err != nil {
t.Fatalf("parse date-only: %v", err)
}
want := time.Date(2026, 4, 29, 0, 0, 0, 0, time.UTC)
if !got.Equal(want) {
t.Errorf("got %v, want %v", got, want)
}
}
func TestParseAuditTimestamp_Invalid(t *testing.T) {
if _, err := parseAuditTimestamp("not-a-time"); err == nil {
t.Error("expected error for malformed timestamp")
}
}