package web
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
// A forge-only binding draws no composer.
//
// bucket.get used to answer bound:true for the forge fallback, so this
// composer re-derived "bound and not forge" itself, and a source-text
// test pinned that workaround. The API now answers the uploader's
// question (bucket.get is bound:false for the fallback, with the reason
// in note), so the composer reads Bound and nothing else — and what
// this test pins is the BEHAVIOUR the workaround protected: a
// repository with only the forge's binding offers no upload control,
// and tells an admin what to do; a repository with its own bucket
// offers one.
func TestAForgeOnlyBindingDrawsNoComposer(t *testing.T) {
buckets := webBuckets{
oltp.ForgeBucket: {Endpoint: "https://s3.example", Bucket: "forge", Region: "auto", AccessKey: "k", Secret: "s", LimitBytes: 1 << 20},
"own": {Endpoint: "https://s3.example", Bucket: "mine", Region: "auto", AccessKey: "k2", Secret: "s2", LimitBytes: 2 << 20, CorsOK: true},
}
r := ops.NewRegistry()
if err := api.RegisterAll(r, api.Deps{Version: "v",
Repos: api.NewRepos(map[string]*api.Repo{"own": {}, "inherit": {}}),
Buckets: buckets}); err != nil {
t.Fatal(err)
}
// An admin who may attach: letters b (upload) and a (bind).
caps, sess := stubCaps{letters: "ba"}, fakeSessions{user: "dan"}
for repo, want := range map[string]bool{"own": true, "inherit": false} {
req := httptest.NewRequest("GET", "/r/"+repo+"/ticket/t1", nil)
req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
w := attachWriteFor(r, caps, nil, sess, repo, "ticket", req)
if w.CanAttach != want {
t.Errorf("FAIL %s: composer drawn = %v, want %v — %s", repo, w.CanAttach, want,
map[bool]string{true: "a bucket of its own must offer an upload", false: "the forge's binding is not this repository's to upload into"}[want])
} else {
t.Logf("ok %s: composer drawn = %v", repo, want)
}
if !want && !w.Unbound {
t.Errorf("FAIL %s: an admin on a repository with no bucket of its own is not told to bind one", repo)
}
}
}
// And the words a reader gets are the actionable ones.
func TestTheUnboundNoteNamesTheSettingsPage(t *testing.T) {
var out strings.Builder
c := attachCtx{Repo: "alpha", Target: "t", Write: &attachWrite{Unbound: true}}
if err := attachFragPage.ExecuteTemplate(&out, "attachments", c); err != nil {
t.Fatal(err)
}
body := out.String()
if !strings.Contains(body, "settings#attachments") {
t.Error("FAIL: the unbound note does not link the settings page")
}
if !strings.Contains(body, "No bucket is bound") {
t.Error("FAIL: the unbound note does not say what is wrong")
}
// A reader who cannot bind one is told nothing they cannot act on.
var reader strings.Builder
rc := attachCtx{Repo: "alpha", Target: "t", Write: &attachWrite{}}
if err := attachFragPage.ExecuteTemplate(&reader, "attachments", rc); err != nil {
t.Fatal(err)
}
if strings.Contains(reader.String(), "No bucket is bound") {
t.Error("FAIL: a reader who cannot bind a bucket is told to bind one")
}
}