Files
email_agent/internal/session/session_test.go
Sam Rolfe 3c0030c9a3 Phase 1.2: session login + empty HTMX dashboard
- Session login for the single user Sam: server-side session store with an
  HMAC-signed session cookie, POST /login, POST /logout, and double-submit
  CSRF on every form post. Unauthenticated requests to / and /partials/*
  redirect to /login?next=...
- Empty dashboard shell in the Family Home Lab design system: left nav
  (category and folder placeholders), centre list, right detail pane.
- Copy tokens.css and the self-hosted Inter fonts from the family-home-lab
  design system into web/static; vendor htmx 2.0.4. No cross-project imports
  at runtime.
- Dark mode via prefers-color-scheme plus a persisted toggle; phone-first
  layout with 44px touch targets, hairline borders, 8px grid, pill CTAs.
- Config: EMAIL_AGENT_PASSWORD for the login password, falling back to
  EMAIL_AGENT_SESSION_SECRET so one configured secret is enough.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 19:58:25 +11:00

79 lines
1.7 KiB
Go

package session
import (
"testing"
"time"
)
func TestManagerRoundTrip(t *testing.T) {
m, err := NewManager([]byte("test-key"), time.Hour)
if err != nil {
t.Fatalf("NewManager() error = %v", err)
}
sess, cookie, err := m.New("Sam")
if err != nil {
t.Fatalf("New() error = %v", err)
}
if sess.User != "Sam" {
t.Errorf("User = %q, want Sam", sess.User)
}
got, ok := m.Lookup(cookie)
if !ok {
t.Fatal("Lookup() = not ok, want a live session")
}
if got.ID != sess.ID {
t.Errorf("Lookup ID = %q, want %q", got.ID, sess.ID)
}
m.Destroy(cookie)
if _, ok := m.Lookup(cookie); ok {
t.Error("Lookup() after Destroy() = ok, want expired")
}
}
func TestManagerRejectsTamperedCookie(t *testing.T) {
m, err := NewManager([]byte("key-a"), time.Hour)
if err != nil {
t.Fatalf("NewManager() error = %v", err)
}
_, cookie, err := m.New("Sam")
if err != nil {
t.Fatalf("New() error = %v", err)
}
other, err := NewManager([]byte("key-b"), time.Hour)
if err != nil {
t.Fatalf("NewManager() error = %v", err)
}
if _, ok := other.Lookup(cookie); ok {
t.Error("Lookup() with a different key = ok, want rejected")
}
for _, bad := range []string{"", "garbage", "abc.", ".abc", "a.b.c", cookie + "x"} {
if _, ok := m.Lookup(bad); ok {
t.Errorf("Lookup(%q) = ok, want rejected", bad)
}
}
}
func TestStoreExpiry(t *testing.T) {
st := NewStore(time.Minute)
base := time.Now()
st.now = func() time.Time { return base }
sess, err := st.Create("Sam")
if err != nil {
t.Fatalf("Create() error = %v", err)
}
if _, ok := st.Get(sess.ID); !ok {
t.Fatal("Get() = not ok before expiry")
}
st.now = func() time.Time { return base.Add(2 * time.Minute) }
if _, ok := st.Get(sess.ID); ok {
t.Error("Get() = ok after expiry, want expired")
}
}