Files
paliad/internal/services/dashboard_service_test.go
mAi 15bcba5d7c feat(dashboard): t-paliad-219 Slice A3 — widen windows + add InboxSummary
Two changes to DashboardService for the configurable dashboard:

1) Widen upcoming windows from 7d/LIMIT 10 → 60d/LIMIT 40 for both
   loadUpcomingDeadlines and loadUpcomingAppointments. Per design §18
   Note B, the per-widget horizon dropdown (7/14/30/60 days) filters
   client-side from a single payload — server-side widening preserves
   the Q4 "one big payload" pick without forcing per-widget endpoints.
   Existing tests pass: the dashboard CTE bucket math is unchanged and
   the wider rows-list is a superset of what /api/dashboard returned
   before.

2) Add InboxSummary { pending_count, top: []InboxEntry } to DashboardData
   for the new inbox-approvals widget (Q3 expansion). Powered by
   ApprovalService.PendingCountForUser + ListPendingForApprover with
   Limit=InboxTopCap (10). InboxEntry is the minimum needed to render
   a clickable preview line: request id, entity_type/title, project,
   requester, requested_at.

   ApprovalService is wired post-construction via
   DashboardService.SetApprovalService to avoid a circular constructor
   dependency. When unwired (knowledge-platform-only deployments,
   tests), loadInboxSummary is a no-op and the widget renders its
   empty state.

3 new pure-function tests: nil-approvals no-op, SetApprovalService
wiring, InboxTopCap sanity.

go build + go vet + go test ./internal/... -short all clean.
2026-05-20 13:55:56 +02:00

52 lines
1.5 KiB
Go

package services
// Pure-function tests for DashboardService extensions in Slice A3.
import (
"context"
"testing"
"github.com/google/uuid"
"mgit.msbls.de/m/paliad/internal/models"
)
func TestDashboardService_InboxSummary_NilApprovalsIsNoop(t *testing.T) {
s := &DashboardService{} // approvals nil
data := &DashboardData{}
user := &models.User{ID: uuid.New()}
if err := s.loadInboxSummary(context.Background(), data, user); err != nil {
t.Fatalf("loadInboxSummary with nil approvals returned %v; want nil", err)
}
if data.InboxSummary.PendingCount != 0 {
t.Errorf("PendingCount=%d; want 0", data.InboxSummary.PendingCount)
}
if data.InboxSummary.Top == nil {
t.Errorf("Top is nil; want empty slice")
}
if len(data.InboxSummary.Top) != 0 {
t.Errorf("Top has %d entries; want 0", len(data.InboxSummary.Top))
}
}
func TestDashboardService_SetApprovalService_WiringWorks(t *testing.T) {
s := &DashboardService{}
if s.approvals != nil {
t.Fatalf("freshly-constructed DashboardService has non-nil approvals")
}
a := &ApprovalService{} // empty shell; we only check the pointer wiring
s.SetApprovalService(a)
if s.approvals != a {
t.Errorf("SetApprovalService did not wire the pointer")
}
}
func TestInboxTopCap_NonZero(t *testing.T) {
// Sanity guard: if someone zeros this const, the inbox-approvals
// widget falls back to an empty top-N silently. Pin it ≥ the
// largest catalog count option for the inbox widget (10).
if InboxTopCap < 10 {
t.Errorf("InboxTopCap=%d; must be ≥ 10 to satisfy widget catalog max count", InboxTopCap)
}
}