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.
52 lines
1.5 KiB
Go
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)
|
|
}
|
|
}
|