Files
projax/web/admin_test.go
mAi c486a8b028 feat(phase 3o admin-index): /admin landing + system panel + nav consolidation
The three admin pages (classify, caldav, bulk) had no shared entry point —
m navigated around and couldn't find them. /admin is now their index:

- 3 cards, each linking to the underlying tool, with live counts
  (orphan count via projax.items_unified predicate; calendar count via
  ListCalendars; item count via projax.items where deleted_at IS NULL
  AND archived = false)
- CalDAV card auto-disables when DAV_URL isn't configured
- System panel: version (build-time ldflags hook), last migration
  (projax.schema_migrations top row), MCP status (token present
  yes/no — token itself never displayed), upstream health (DAV +
  Gitea + Supabase, parallel-probed with 1s HTTP timeout each,
  cached 30s)

web/admin.go houses the handler + cache + probeURL helper + count
queries. Templates/admin.tmpl renders the cards + system grid.
admin_test.go covers /admin render + nav-link presence on every
chrome-bearing route.

Nav consolidation: the three separate admin links in layout.tmpl
collapse to one /admin entry. Pre-existing TestTreeRenders updated
to assert the new shape.

Probe-URL caveat: probeURL counts any HTTP response as "alive" (incl.
4xx) — the admin panel measures reachability, not authorisation. CalDAV
returns 401 on bare GET; Gitea returns 200 at the root; Supabase same.
All show green when alive.
2026-05-16 02:26:07 +02:00

72 lines
2.2 KiB
Go

package web_test
import (
"strings"
"testing"
)
// TestAdminIndexRenders proves /admin returns 200 with the three card links
// visible. Counts come from the live DB (orphan + total item counts run
// against projax.items_unified, so they reflect whatever m has seeded).
func TestAdminIndexRenders(t *testing.T) {
srv, pool := mustServer(t)
defer pool.Close()
h := srv.Routes()
code, body := get(t, h, "/admin")
if code != 200 {
t.Fatalf("GET /admin → %d body=%s", code, body)
}
for _, want := range []string{
`Classify orphans`,
`href="/admin/classify"`,
`Bulk edit`,
`href="/admin/bulk"`,
// CalDAV card title renders regardless of config; the link is only
// emitted when DAV_URL is configured. mustServer leaves CalDAV nil,
// so we only assert the card text exists (with a "DAV_URL not
// configured" stub instead of a link).
`CalDAV calendars`,
`admin-cards`,
`admin-system`,
// system panel scaffold
`Last migration`,
`MCP`,
} {
if !strings.Contains(body, want) {
t.Errorf("/admin missing %q", want)
}
}
}
// TestLayoutHasAdminNavLink proves the header nav across every chrome-bearing
// route exposes /admin. Without the link the index page is invisible — m
// reported that as the headline bug.
func TestLayoutHasAdminNavLink(t *testing.T) {
srv, pool := mustServer(t)
defer pool.Close()
h := srv.Routes()
for _, path := range []string{"/", "/dashboard", "/graph", "/admin/bulk", "/admin/classify"} {
_, body := get(t, h, path)
if !strings.Contains(body, `href="/admin"`) {
t.Errorf("GET %s: nav missing /admin link", path)
}
}
}
// TestAdminCountsReflectStore: seed an item, render /admin, item count
// goes up by 1. Stale-cache regression guard.
func TestAdminCountsReflectStore(t *testing.T) {
srv, pool := mustServer(t)
defer pool.Close()
h := srv.Routes()
// Baseline.
_, before := get(t, h, "/admin")
// Count occurrences of "items in scope" — the bulk card's label. The card
// renders Count + CountLabel; assert the label is present and that we can
// parse the number on either side of the strong tag.
if !strings.Contains(before, "items in scope") {
t.Fatalf("baseline /admin missing 'items in scope' label")
}
}