Artifact e0597232e5993d65c09d1f72513feecdf94f4ae57647194085c4915aec0a9e8a:
- File
internal/notify/inbox_test.go
— part of check-in
[e33f1c4e8c]
at
2026-08-17 00:19:10
on branch trunk
— notify: authenticate triggers, and embed the inbox
Two things, one of them a live bug. notify's /v1 routes sit behind an API-key middleware and quarry's client sent no Authorization header, so every trigger it fired would have been answered 401. It went unnoticed because --notify was never configured and a trigger failure is logged rather than raised. --notify-key (or QUARRY_NOTIFY_KEY) supplies it, and serve says so at startup when it is missing rather than leaving the answer to be discovered in the log.
The inbox is notify's, embedded (0001 §5): GET /v1/messages?channel=in_app for the signed-in account, rendered with unread emphasised. quarry models no notification state — it displays what notify owns.
The wire field names are notify's Go field names: its domain.Message carries db tags but no json tags, so the encoder falls back to the identifiers. A test pins that, since decoding lowercase would silently produce empty subjects.
An outage is not emptiness: a failed fetch says notifications are unavailable instead of showing an empty list, which would be a lie about whether anyone had written to you. (user: dmestas, size: 2538) [annotate] [blame] [check-ins using]
package notify import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" ) // notify's /v1 routes sit behind an API-key middleware, so a client // without the header is answered 401 on every call. quarry shipped // exactly that for triggers until this was noticed — silent, because a // trigger failure is logged rather than raised. func TestRequestsCarryTheAPIKey(t *testing.T) { seen := make(chan string, 2) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { seen <- r.Header.Get("Authorization") if r.URL.Path == "/v1/messages" { json.NewEncoder(w).Encode([]Message{}) return } w.WriteHeader(http.StatusOK) })) defer srv.Close() c := New(srv.URL, "key-123") if err := c.Trigger(context.Background(), "quarry-activity", Recipient{SubscriberID: "dan"}, map[string]any{}, "txn"); err != nil { t.Fatal(err) } if got := <-seen; got != "ApiKey key-123" { t.Errorf("trigger Authorization = %q", got) } if _, err := c.Inbox(context.Background(), "dan"); err != nil { t.Fatal(err) } if got := <-seen; got != "ApiKey key-123" { t.Errorf("inbox Authorization = %q", got) } } // The wire field names are notify's Go field names: its domain.Message // carries db tags but no json tags, so the encoder falls back to the // identifiers. Decoding lowercase would silently yield empty subjects. func TestInboxDecodesNotifysWireNames(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if got := r.URL.Query().Get("channel"); got != "in_app" { t.Errorf("channel = %q, want in_app", got) } if got := r.URL.Query().Get("subscriberId"); got != "dan" { t.Errorf("subscriberId = %q", got) } w.Write([]byte(`[{"ID":"m1","Subject":"you were mentioned", "Content":"in ticket abc","Read":false,"CreatedAt":"2026-08-16T00:00:00Z"}]`)) })) defer srv.Close() msgs, err := New(srv.URL, "k").Inbox(context.Background(), "dan") if err != nil { t.Fatal(err) } if len(msgs) != 1 { t.Fatalf("got %d messages", len(msgs)) } if msgs[0].Subject != "you were mentioned" || msgs[0].ID != "m1" || msgs[0].Read { t.Fatalf("decoded %+v", msgs[0]) } } // A disabled client (no --notify) answers empty without a call, so the // inbox is simply absent rather than an error surface. func TestDisabledClientIsQuiet(t *testing.T) { msgs, err := New("", "").Inbox(context.Background(), "dan") if err != nil || msgs != nil { t.Fatalf("disabled client returned (%v, %v)", msgs, err) } }