Skip to content
quarry Artifact [a5e87c3e54]
Login

Artifact [a5e87c3e54]

Artifact a5e87c3e54244a359bcc04d8e9c5cb964e39f84fcfe8a9fc817d55cd919c1c60:


package web

import (
	"io"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/ops"
)

func inboxOpenForge(t *testing.T) (*attnStub, *memRead, *httptest.Server) {
	t.Helper()
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	attn, read := &attnStub{}, &memRead{at: map[string]uint64{}}
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, api.Deps{Version: "t",
		Repos:     api.NewRepos(map[string]*api.Repo{"alpha": {Index: ix, Code: "c1"}}),
		Attention: attn, AttnRead: read}); err != nil {
		t.Fatal(err)
	}
	srv := httptest.NewServer(HandlerWith(r, nil, nil, []Option{
		WithSessions(fakeSessions{user: "dan"}), WithPrefs(&prefStub{})}))
	t.Cleanup(srv.Close)
	return attn, read, srv
}

// openRow follows no redirect: the point is where it was sent, not what
// is there when you arrive.
func openRow(t *testing.T, srv *httptest.Server, path string) (int, string) {
	t.Helper()
	req, err := http.NewRequest("GET", srv.URL+path, nil)
	if err != nil {
		t.Fatal(err)
	}
	req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
	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()
	return resp.StatusCode, resp.Header.Get("Location")
}

// Opening a row is reading it. An inbox whose count only moves from a
// separate button is an inbox whose badge means nothing.
func TestOpeningARowMarksItRead(t *testing.T) {
	attn, read, srv := inboxOpenForge(t)
	seq, err := attn.Attend("dan", api.AttentionEvent{Kind: "mention", Summary: "look",
		At: "2026-09-08T10:00:00Z", Link: "/r/alpha/forum/abc"})
	if err != nil {
		t.Fatal(err)
	}
	if read.AttentionReadSeq("dan") >= seq {
		t.Fatal("FAIL: the row started out read")
	}
	code, where := openRow(t, srv, "/inbox/go?id=local:1&to=%2Fr%2Falpha%2Fforum%2Fabc")
	if code != 303 || where != "/r/alpha/forum/abc" {
		t.Fatalf("FAIL: opening sent %d to %q, want 303 to the row's own destination", code, where)
	}
	if read.AttentionReadSeq("dan") < seq {
		t.Errorf("FAIL: the row is still unread after being opened")
	}
	t.Log("ok: opening a row marks it read and lands on its destination")
}

// A row's destination comes from a workflow payload, so it is not
// quarry's to trust: a redirect that forwards anywhere is an open
// redirect wearing the forge's own domain.
func TestOpeningRefusesAForeignDestination(t *testing.T) {
	_, read, srv := inboxOpenForge(t)
	for _, bad := range []string{
		"https%3A%2F%2Fevil.test%2Fx",
		"%2F%2Fevil.test%2Fx",
		"javascript%3Aalert(1)",
	} {
		code, where := openRow(t, srv, "/inbox/go?id=local:1&to="+bad)
		if code != 303 || where != "/inbox" {
			t.Errorf("FAIL: %s sent %d to %q, want the inbox", bad, code, where)
		}
	}
	// Refusing the destination must not refuse the reading: they did
	// open it, whatever the link claimed.
	if read.AttentionReadSeq("dan") == 0 {
		t.Error("FAIL: a refused destination also skipped the mark")
	}
	t.Log("ok: only this forge is a destination, and the row is read either way")
}

// The rendered row links through the marking route rather than straight out.
func TestInboxRowsLinkThroughTheMarkingRoute(t *testing.T) {
	attn, _, srv := inboxOpenForge(t)
	if _, err := attn.Attend("dan", api.AttentionEvent{Kind: "mention", Summary: "look",
		At: "2026-09-08T10:00:00Z", Link: "/r/alpha/forum/abc"}); err != nil {
		t.Fatal(err)
	}
	req, err := http.NewRequest("GET", srv.URL+"/inbox", nil)
	if err != nil {
		t.Fatal(err)
	}
	req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
	resp, err := srv.Client().Do(req)
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()
	raw, _ := io.ReadAll(resp.Body)
	body := string(raw)
	if !strings.Contains(body, `href="/inbox/go?id=`) {
		t.Errorf("FAIL: a row still links straight out:\n%s", body)
	}
	t.Log("ok: rows open through the marking route")
}