package api
import (
"errors"
"os"
"strings"
"testing"
"craftdesign.group/quarry/internal/ops"
)
// A repository's uploads do not go to the forge's own bucket.
//
// bucketOf falls back to the forge binding when a repository has none,
// so uploads landed in the operator's storage without anybody being
// told: dmestas attached files to an announcement on quarry and found
// them in his personal R2 (9b8b8fb6). The disclosure half — a repo
// admin handed the endpoint, bucket name and key id — was fixed by
// forgeAnswer. This is the half that decided where the BYTES went.
//
// The refusal names the remedy, because a refusal that does not is the
// other bug this forge keeps finding.
func TestUploadsRefuseTheForgeBucketAndSayWhatToDo(t *testing.T) {
var d Deps
// A forge with no bucket store at all still refuses, and says so
// in its own words rather than falling through to a nil map.
_, _, _, err := d.boundBucket("alpha")
if err == nil {
t.Fatal("FAIL: a forge with no bucket store accepted an upload")
}
if !strings.Contains(err.Error(), "binds no buckets") {
t.Errorf("FAIL: the refusal does not say the forge binds none: %v", err)
}
}
// The refusal for the forge-fallback case is ErrInvalid and names the
// settings page, so the caller learns what to do rather than that
// something is wrong.
func TestTheForgeBucketRefusalIsActionable(t *testing.T) {
// Asserted on the message the code produces, because the whole
// point of this refusal is its words: the previous behaviour was
// silent success into the wrong account.
b, rerr := os.ReadFile("attachwrite.go")
if rerr != nil {
t.Fatal(rerr)
}
src := string(b)
for _, want := range []string{
"has no bucket of its own",
"do not go to the",
"bind a bucket under Settings > Attachments",
"ops.ErrInvalid",
} {
if !strings.Contains(src, want) {
t.Errorf("FAIL: the forge-bucket refusal is missing %q; a refusal that does not "+
"name the remedy is the other half of this class of bug", want)
}
}
if !strings.Contains(src, "if info.Forge {") {
t.Error("FAIL: boundBucket no longer refuses the forge binding, so a repository's " +
"uploads go to the operator's storage again")
}
// And marks are deliberately left alone.
if !strings.Contains(src, "Marks are untouched") {
t.Error("FAIL: the note explaining why avatars still use the forge bucket is gone; " +
"somebody will remove forgeBucketForAvatar next")
}
_ = errors.Is
_ = ops.ErrInvalid
}