Skip to content
quarry Artifact [ca56fa0ff7]
Login

Artifact [ca56fa0ff7]

Artifact ca56fa0ff79876fe1b9f83db920191ab390983b78d1e76a6afb89ec100793f2f:


// inboxweb_test.go proves RFC 0011 Phase 3b: the HTML plane over the
// same ops Phase 3a registered. A browser subscribes to push and the
// subscription reaches notify's credentials route; a preferences form
// posted by a page lands as notify preferences; Done on an inbox row
// takes it off the next listing.
//
// Everything here goes through the WEB routes with a cookie, because
// that is what a browser and a service worker have — the API plane
// authenticates by HTTP Basic and a worker holds no credential for it.
package harness

import (
	"encoding/json"
	"io"
	"net/http"
	"net/url"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/notify"
)

// webCall performs one cookie-authenticated request against the HTML
// plane. Redirects are NOT followed: a form's 303 is the answer.
func webCall(t *testing.T, f inboxForge, user, method, path, contentType, body string) (*http.Response, string) {
	t.Helper()
	var rd io.Reader
	if body != "" {
		rd = strings.NewReader(body)
	}
	req, err := http.NewRequest(method, f.web.URL+path, rd)
	if err != nil {
		t.Fatal(err)
	}
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	req.AddCookie(&http.Cookie{Name: "quarry_session", Value: user})
	client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error {
		return http.ErrUseLastResponse
	}}
	resp, err := client.Do(req)
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()
	b, _ := io.ReadAll(resp.Body)
	return resp, string(b)
}

// A browser turns push on: the subscription reaches notify's
// credentials route, the device appears on the preferences page, and
// turning it off again drops both.
func TestWebPushSubscribeListsAndRemovesADevice(t *testing.T) {
	f := buildInboxForge(t)

	resp, body := webCall(t, f, "dan", "GET", "/me/push/key", "", "")
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("/me/push/key answered %d: %s", resp.StatusCode, body)
	}
	var key struct {
		PublicKey string `json:"publicKey"`
	}
	if err := json.Unmarshal([]byte(body), &key); err != nil {
		t.Fatalf("the key is not JSON: %v (%s)", err, body)
	}
	if key.PublicKey != "BFakeVapidPublicKey" {
		t.Fatalf("relayed key %q", key.PublicKey)
	}

	sub := `{"endpoint":"https://push.example/dan-1","expirationTime":null,` +
		`"keys":{"p256dh":"BSomeKey","auth":"SomeAuth"}}`
	resp, body = webCall(t, f, "dan", "POST", "/me/push", "application/json", sub)
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("POST /me/push answered %d: %s", resp.StatusCode, body)
	}
	if got := f.fake.find("PUT", "/v1/subscribers/dan/credentials"); len(got) != 1 {
		t.Fatalf("notify received %d credential writes, want 1", len(got))
	}

	_, page := webCall(t, f, "dan", "GET", "/me", "", "")
	if !strings.Contains(page, `data-push="on"`) {
		t.Errorf("the preferences page does not show push enabled:\n%s", page)
	}
	if !strings.Contains(page, `data-device="`) {
		t.Error("the device is not listed")
	}

	resp, body = webCall(t, f, "dan", "POST", "/me/push/delete",
		"application/x-www-form-urlencoded",
		url.Values{"endpoint": {"https://push.example/dan-1"}}.Encode())
	if resp.StatusCode != http.StatusSeeOther {
		t.Fatalf("POST /me/push/delete answered %d: %s", resp.StatusCode, body)
	}
	_, page = webCall(t, f, "dan", "GET", "/me", "", "")
	if strings.Contains(page, `data-device="`) {
		t.Errorf("the device survived its removal:\n%s", page)
	}
	if !strings.Contains(page, `data-push="off"`) {
		t.Error("push still reads as enabled with no devices left")
	}
}

// The preferences form is the page's own: every cell posts an explicit
// state, and what comes back is what notify now holds.
func TestWebPreferencesFormRoundTrips(t *testing.T) {
	f := buildInboxForge(t)
	form := url.Values{}
	for _, family := range []string{"mention", "reply", "landing", "activity"} {
		for _, ch := range []string{"inapp", "push", "email"} {
			form.Add(family+"."+ch, "off")
			if !(family == "mention" && ch == "email") {
				form.Add(family+"."+ch, "on")
			}
		}
	}
	form.Set("toasts", "off")
	form.Set("notify_desktop", "on")
	form.Set("mute", "alpha")
	resp, page := webCall(t, f, "dan", "POST", "/me/notifications",
		"application/x-www-form-urlencoded", form.Encode())
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("saving preferences answered %d: %s", resp.StatusCode, page)
	}
	if !strings.Contains(page, `data-applied-local="1"`) ||
		!strings.Contains(page, `data-applied-notify="1"`) {
		t.Errorf("the banner did not report both halves:\n%s", page)
	}
	if !strings.Contains(page, `data-muted="alpha"`) {
		t.Error("the repository the form muted is not listed as muted")
	}
	if got := f.odb.AccountPref("dan", "toasts"); got != "off" {
		t.Errorf("the toasts switch stored %q, want off", got)
	}
	// The matrix reached notify as one whole write.
	puts := f.fake.find("PUT", "/v1/subscribers/dan/preferences")
	if len(puts) != 1 {
		t.Fatalf("preference PUTs = %d, want 1", len(puts))
	}
	rows, _ := puts[0].body["preferences"].([]any)
	for _, raw := range rows {
		row, _ := raw.(map[string]any)
		if id, _ := row["workflowId"].(string); id != "quarry-mention" {
			continue
		}
		ch, _ := row["channels"].(map[string]any)
		if on, _ := ch["email"].(bool); on {
			t.Errorf("the unticked cell reached notify as on: %+v", ch)
		}
		if on, _ := ch["push"].(bool); !on {
			t.Errorf("a ticked cell did not reach notify: %+v", ch)
		}
	}
	// And the page redrawn from the store agrees: the one cell turned
	// off comes back unticked.
	_, page = webCall(t, f, "dan", "GET", "/me", "", "")
	i := strings.Index(page, `data-cell="mention.email"`)
	if i < 0 {
		t.Fatal("the redrawn page lost the cell")
	}
	if strings.Contains(page[i:i+240], "checked") {
		t.Errorf("a channel saved off came back ticked:\n%s", page[i:i+240])
	}
}

// Done on a notify row archives it, and the next listing does not carry
// it — which is the whole claim of "an inbox you can clear".
func TestWebInboxDoneClearsTheRow(t *testing.T) {
	f := buildInboxForge(t)
	f.fake.seedFor("dan", notify.Message{ID: "m1", Subject: "alice mentioned you",
		CreatedAt: "2026-08-29T12:00:00Z"})
	_, page := webCall(t, f, "dan", "GET", "/inbox", "", "")
	if !strings.Contains(page, `data-id="m1"`) {
		t.Fatalf("the notify message is not on the inbox page:\n%s", page)
	}
	resp, body := webCall(t, f, "dan", "POST", "/inbox/mark",
		"application/x-www-form-urlencoded",
		url.Values{"id": {"m1"}, "as": {"done"}, "f": {"all"}}.Encode())
	if resp.StatusCode != http.StatusSeeOther {
		t.Fatalf("Done answered %d: %s", resp.StatusCode, body)
	}
	_, page = webCall(t, f, "dan", "GET", "/inbox", "", "")
	if strings.Contains(page, `data-id="m1"`) {
		t.Errorf("a row marked done came back on the next listing:\n%s", page)
	}
}