package web
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/ops"
)
// The attachment read routes carry the reader's identity into the op,
// the way every gated page does. Without it a private repository —
// which the op layer keeps invisible to whoever cannot read it — was
// invisible to its own members on exactly these routes.
func TestAttachmentReadRoutesCarryTheReadersIdentity(t *testing.T) {
var saw []string
r := ops.NewRegistry()
type linkOut struct {
External bool `json:"external"`
Download string `json:"download"`
Inline string `json:"inline"`
}
if err := ops.Register(r, ops.Op[viewIn, linkOut]{
Name: "attachment.link", Caps: "jr", Doc: "test stub", Kind: ops.Query, Auth: ops.Anonymous,
Route: "GET /api/v1/r/{repo}/attachment/{hash}/link",
Run: func(ctx context.Context, in viewIn) (linkOut, error) {
saw = append(saw, ops.User(ctx))
return linkOut{External: true, Download: "https://bucket.example/x"}, nil
},
}); err != nil {
t.Fatal(err)
}
verify := Verify(func(repo, user, pass string, need ops.Auth) api.Decision { return api.Allowed })
h := HandlerWith(r, nil, nil, []Option{WithSessions(fakeSessions{user: "dan", roles: map[string]ops.Auth{"": ops.Admin}})}, verify)
srv := httptest.NewServer(h)
t.Cleanup(srv.Close)
client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
req, _ := http.NewRequest("GET", srv.URL+"/r/alpha/attachment/abc/get", nil)
req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusFound {
t.Fatalf("FAIL: signed-in reader got %d, want a redirect to the bucket", resp.StatusCode)
}
anon, _ := http.NewRequest("GET", srv.URL+"/r/alpha/attachment/abc/get", nil)
resp, err = client.Do(anon)
if err != nil {
t.Fatal(err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusFound {
t.Fatalf("FAIL: a stranger got %d, want the same redirect — the op, not a login page, decides what a stranger may read", resp.StatusCode)
}
if len(saw) != 2 || saw[0] != "dan" || saw[1] != "" {
t.Fatalf("FAIL: the op saw %q, want the member first and then nobody", saw)
}
t.Log("ok: the link op learns who is asking, so the read gate can answer per reader")
}