Files
email_agent/internal/summary/summary_test.go
Sam Rolfe a349b42b1f Phase 7: daily summary and read-only Hub API (AUD-73)
- internal/hub: project side of the frozen ask-contract. GET /capabilities,
  read-only POST /ask answerer (no LLM, <3s), and the outbound POST /notify
  client.
- internal/apprise: daily-summary delivery through the house Apprise API.
- internal/summary: deterministic plain-text daily summary builder.
- internal/store: CountByCategory, MessagesByCategory, and summary CRUD.
- internal/server: /capabilities and /ask (bearer-token protected), plus the
  Reports screen (/reports, build, send, delete).
- main.go: -summary, -summary-send, and -notify CLI entry points for n8n.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 22:38:49 +11:00

107 lines
2.9 KiB
Go

package summary
import (
"context"
"path/filepath"
"strings"
"testing"
"time"
"gitea.lab.audasmedia.com.au/sam/email_agent/internal/store"
)
func testStore(t *testing.T) (*store.Store, context.Context) {
t.Helper()
ctx := context.Background()
st, err := store.Open(ctx, filepath.Join(t.TempDir(), "email_agent.db"))
if err != nil {
t.Fatalf("store.Open() error = %v", err)
}
t.Cleanup(func() { _ = st.Close() })
return st, ctx
}
func seed(t *testing.T, st *store.Store, ctx context.Context, gmailID, from, subject, category, importance string) {
t.Helper()
id, err := st.UpsertMessage(ctx, &store.Message{
GmailID: gmailID,
FromAddr: from,
Subject: subject,
DateSent: time.Now().UTC(),
})
if err != nil {
t.Fatalf("seed message: %v", err)
}
if _, err := st.SaveClassification(ctx, store.Classification{
MessageID: id, Category: category, Importance: importance,
}); err != nil {
t.Fatalf("classify: %v", err)
}
}
func TestBuildAndText(t *testing.T) {
st, ctx := testStore(t)
seed(t, st, ctx, "1", "Google <no-reply@google.com>", "Security alert", "Action", "high")
seed(t, st, ctx, "2", "Store <shop@example.com>", "Spring sale", "Newsletter", "low")
seed(t, st, ctx, "3", "Store <shop@example.com>", "More sale", "Newsletter", "low")
seed(t, st, ctx, "4", "Spammer <x@example.com>", "Cheap pills", "Spam", "low")
b := New(st)
b.Now = func() time.Time { return time.Date(2026, 10, 11, 8, 0, 0, 0, time.UTC) }
data, err := b.Build(ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if data.Date != "2026-10-11" {
t.Errorf("date = %q", data.Date)
}
if data.InboxTotal != 4 {
t.Errorf("InboxTotal = %d, want 4", data.InboxTotal)
}
if data.NeedsAttention() != 1 {
t.Errorf("NeedsAttention = %d, want 1", data.NeedsAttention())
}
if data.Newsletters != 2 {
t.Errorf("Newsletters = %d, want 2", data.Newsletters)
}
if data.Spam != 1 {
t.Errorf("Spam = %d, want 1", data.Spam)
}
if len(data.Important) != 1 || data.Important[0].Subject != "Security alert" {
t.Errorf("Important = %+v", data.Important)
}
text := data.Text()
for _, want := range []string{
"daily summary for 2026-10-11",
"Inbox: 4 message(s)",
"Security alert",
"Google",
"Newsletters: 2",
"Spam (recommend, never auto-delete): 1",
} {
if !strings.Contains(text, want) {
t.Errorf("Text() missing %q:\n%s", want, text)
}
}
if sub := data.Subject(); !strings.Contains(sub, "2026-10-11") {
t.Errorf("Subject() = %q", sub)
}
}
func TestBuildEmptyInbox(t *testing.T) {
st, ctx := testStore(t)
b := New(st)
b.Now = func() time.Time { return time.Date(2026, 10, 11, 8, 0, 0, 0, time.UTC) }
data, err := b.Build(ctx)
if err != nil {
t.Fatalf("Build() error = %v", err)
}
if data.InboxTotal != 0 || data.NeedsAttention() != 0 {
t.Errorf("data = %+v, want empty", data)
}
if !strings.Contains(data.Text(), "nothing waiting") {
t.Errorf("Text() = %s", data.Text())
}
}