Pete's side of the Adventure news feed. Receives structured game-event
facts from gogobee, templates them in Pete's warm-reporter voice, and
publishes to a new /adventure section + live Matrix posts.
- adventure.go: bearer ingest + fact-guard + 13 event templates;
/adventure/{guid} permalink (story.html); per-event SVG emblems at
/adventure/art/{type}.svg (card image + og:image); NoPush suppresses
the live Matrix post (cold-start backfill).
- adventure_digest.go: daily BULLETIN roundup at DigestHour (UTC);
unposted-in-48h = bulletins; marks them digested; per-day ?digest= URL
avoids canonical dedup.
- config AdventureConfig (enabled/ingest_token/channel/digest_hour);
web.New takes the seam + a priority poster; started in main.
- adventure theme colors; thumbURL passes through local emblem paths;
adventure pages are noindex (player-named; gap #5).
Claude-Session: https://claude.ai/code/session_017mEwUmmS7aQTP2NQXj6rUa
60 lines
1.9 KiB
Go
60 lines
1.9 KiB
Go
package web
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"pete/internal/config"
|
|
)
|
|
|
|
func TestStatusTemplateExecutes(t *testing.T) {
|
|
s, err := New(config.WebConfig{SiteTitle: "Pete", ListenAddr: ":0"}, nil, true, config.AdventureConfig{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sources := []sourceStatus{
|
|
{Name: "Broken Feed", Channel: "tech", Failures: 3, LastError: "dial tcp: timeout",
|
|
LastPollAt: time.Now(), NeverRun: false, Healthy: false, Total: 4, Paywalled: 2, PaywallRate: 50},
|
|
{Name: "Good Feed", Channel: "eu", Healthy: true, LastPollAt: time.Now(),
|
|
LastSuccessAt: time.Now(), LastItemCount: 12, Total: 30, Classified: 28,
|
|
LastSeenAt: time.Now(), LastPostedAt: time.Now()},
|
|
{Name: "Idle Feed", Channel: "music", NeverRun: true},
|
|
}
|
|
|
|
render := func(admin bool) string {
|
|
data := statusPage{
|
|
pageData: pageData{SiteTitle: "Pete", Channels: channels},
|
|
DegradedCnt: 1,
|
|
Admin: admin,
|
|
Sources: sources,
|
|
}
|
|
var b strings.Builder
|
|
if err := s.tpls["status"].ExecuteTemplate(&b, "layout", data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// Admin view: every feed plus the operator diagnostics and raw errors.
|
|
admin := render(true)
|
|
for _, want := range []string{"Broken Feed", "Good Feed", "dial tcp: timeout", "1 degraded", "Source health"} {
|
|
if !strings.Contains(admin, want) {
|
|
t.Errorf("admin status page missing %q", want)
|
|
}
|
|
}
|
|
|
|
// Public view: same feeds and states, but no error strings or ops columns.
|
|
pub := render(false)
|
|
for _, want := range []string{"Broken Feed", "Good Feed", "Idle Feed", "delayed", "Source health"} {
|
|
if !strings.Contains(pub, want) {
|
|
t.Errorf("public status page missing %q", want)
|
|
}
|
|
}
|
|
for _, leak := range []string{"dial tcp: timeout", "degraded", "Last posted", "Paywall"} {
|
|
if strings.Contains(pub, leak) {
|
|
t.Errorf("public status page leaked operator detail %q", leak)
|
|
}
|
|
}
|
|
}
|