diff --git a/internal/brief/brief.go b/internal/brief/brief.go index 4ea7cac..ad25bb1 100644 --- a/internal/brief/brief.go +++ b/internal/brief/brief.go @@ -151,6 +151,10 @@ func (s *Service) decoder(ctx context.Context) (func(string) string, error) { // CombinedText returns the single brief for Jo and Sam — both boys in one // message — and the children whose scan did not complete. func (s *Service) CombinedText(ctx context.Context, children []store.Child) (string, []string, error) { + // Finn's block first, then Harry's (FR-9). Enforce the order here, not only in + // the send plan, so every caller — the email, the CLI preview, the dashboard + // and the Hub — renders the combined brief in the same order as the email. + children = OrderChildren(children) parts := make([]dashboard.CombinedBrief, 0, len(children)) var failed []string var notices []string diff --git a/internal/brief/brief_test.go b/internal/brief/brief_test.go index 7548154..3519c0e 100644 --- a/internal/brief/brief_test.go +++ b/internal/brief/brief_test.go @@ -342,3 +342,34 @@ func TestUnconfiguredRecipientFails(t *testing.T) { t.Fatal("want an error for no channels") } } + +// TestCombinedTextKeepsBriefOrder proves FR-9 holds whatever order the caller +// passes: the combined brief is always Finn's block then Harry's, so the CLI +// preview, the dashboard and the Hub match the email that is sent. +func TestCombinedTextKeepsBriefOrder(t *testing.T) { + fake := &fakeApprise{} + srv := fake.server(t) + svc, st := newService(t, srv.URL, seedItems) + + children, err := st.ListChildren(context.Background()) + if err != nil { + t.Fatalf("children: %v", err) + } + if len(children) != 2 { + t.Fatalf("children = %d, want Harry and Finn", len(children)) + } + // Reverse the input on purpose: the email order must not depend on it. + children[0], children[1] = children[1], children[0] + + text, failed, err := svc.CombinedText(context.Background(), children) + if err != nil { + t.Fatalf("combined text: %v", err) + } + if len(failed) != 0 { + t.Fatalf("failed = %#v, want none", failed) + } + i, j := strings.Index(text, "=== FINN ==="), strings.Index(text, "=== HARRY ===") + if i < 0 || j < 0 || i > j { + t.Errorf("combined blocks are not Finn then Harry: %q", text) + } +}