Skip to content
quarry Artifact [a237425ece]
Login

Artifact [a237425ece]

Artifact a237425ece12447533e5f320d576c2da399d6140ce9e690c0b312e453c08879d:


package api

import (
	"os"
	"reflect"
	"regexp"
	"strings"
	"testing"
)

// repo.update is a PATCH: a field the body did not mention is left
// alone.
//
// Every field was a plain string or bool, so "absent" and "the zero
// value" were indistinguishable and every call wrote every field. The
// expensive one was mirrorable: false makes the sync endpoint answer
// 404, so editing a description silently stopped the repository
// syncing. jva hit exactly that at 22:37:21Z, one minute after the caps
// gate stopped refusing repository admins — the partial-update footgun
// had been reachable only by site admins until then, and the gate fix
// unmasked it (dc748de1).
//
// Asserted on the type rather than through a live registry because the
// property is about what the DECODER can distinguish: a plain bool
// cannot say "not sent", and no amount of Run-side care recovers that.
func TestRepoUpdateFieldsAreATriState(t *testing.T) {
	for _, field := range []string{"Description", "Private", "Homepage", "Topics", "Chat", "Mirrorable"} {
		f, ok := fieldOf(repoUpdateIn{}, field)
		if !ok {
			t.Errorf("repoUpdateIn has no %s — if it was renamed, move this guard with it", field)
			continue
		}
		if !strings.HasPrefix(f, "*") {
			t.Errorf("repoUpdateIn.%s is %s, not a pointer: absent and the zero value are the "+
				"same value, so a partial update overwrites this field with nothing", field, f)
		}
	}
	// Name is the one field a body must always carry.
	if f, _ := fieldOf(repoUpdateIn{}, "Name"); f != "string" {
		t.Errorf("repoUpdateIn.Name is %s, want a plain string: it identifies the repository "+
			"and is never optional", f)
	}
}

// The lengths must still be enforced. ops.Validate's applyRules falls
// through to `default: return nil` for a pointer, so the `max=` tags
// these fields used to carry became decoration the moment they became
// pointers. Moving a field to a tri-state and keeping its tag would
// silently drop its validation, which is exactly the class of mistake
// this ticket is about.
func TestRepoUpdateStillBoundsItsStrings(t *testing.T) {
	body := opBody(t, "repo.update", "repo.delete")
	for _, want := range []string{
		"description is at most 300",
		"homepage is at most 300",
		"topics are at most 600",
	} {
		if !strings.Contains(body, want) {
			t.Errorf("repo.update no longer checks %q by hand, and a validate tag on a pointer "+
				"field does nothing: the limit is unenforced", want)
		}
	}
}

// The merge reads what the repository holds now. Without this the
// pointers change nothing: an unmentioned field would still be written
// back as the zero value, just more deliberately.
func TestRepoUpdateWritesBackWhatItWasNotAsked(t *testing.T) {
	src := readSource(t, "register.go")
	if !strings.Contains(src, "func (d Deps) repoSettings(repo string) repoSettingsNow") {
		t.Fatal("repoSettings is gone: repo.update has nothing to merge an absent field against")
	}
	body := opBody(t, "repo.update", "repo.delete")
	if !strings.Contains(body, "cur := d.repoSettings(in.Name)") {
		t.Error("repo.update does not read the repository's current settings, so an absent " +
			"field is still written as the zero value")
	}
	// The one that broke sync: mirrorable must come from the merge.
	if !strings.Contains(body, "cur.mirrorable") {
		t.Error("repo.update no longer writes mirrorable from the merged settings — " +
			"a description-only update can turn sync off again")
	}
	if strings.Contains(body, "in.Mirrorable)") {
		t.Error("repo.update passes in.Mirrorable straight through: nil would write false")
	}
}

// fieldOf reports a struct field's type as written, for a guard that is
// about the TYPE rather than about any particular value.
func fieldOf(v any, name string) (string, bool) {
	t := reflect.TypeOf(v)
	f, ok := t.FieldByName(name)
	if !ok {
		return "", false
	}
	return f.Type.String(), true
}

func readSource(t *testing.T, name string) string {
	t.Helper()
	b, err := os.ReadFile(name)
	if err != nil {
		t.Fatal(err)
	}
	return string(b)
}

// opBody returns the source of one op's registration, from its Name up
// to the next op's. Matched with a regexp rather than an exact string
// because gofmt realigns `Name:` when a neighbouring field changes
// width — which broke these guards the first time the Doc string grew,
// and a guard that fails on formatting is a guard nobody trusts.
func opBody(t *testing.T, name, next string) string {
	t.Helper()
	src := readSource(t, "register.go")
	at := func(n string) int {
		loc := regexp.MustCompile(`Name:\s*"` + regexp.QuoteMeta(n) + `"`).FindStringIndex(src)
		if loc == nil {
			return -1
		}
		return loc[0]
	}
	i := at(name)
	if i < 0 {
		t.Fatalf("%s is gone from register.go", name)
	}
	body := src[i:]
	if j := at(next); j > i {
		body = src[i:j]
	}
	return body
}

// A private detour must not cost a repository its mirrorable flag.
//
// Deps.Mirrorable answers the EFFECTIVE question — MayMirror, false for
// every private repository whatever the stored flag says — and the
// partial-update merge read it. So taking a repository private and then
// restoring it to public wrote "false because private" back as "false
// because asked": the repository returned reading Public everywhere,
// with /xfer answering 404 and no one able to clone it. Found by jva on
// dantest-alias within an hour of the merge landing (G-115).
//
// The merge must read the STORED flag, which is what RepoMeta now
// carries.
func TestAPrivateDetourKeepsTheMirrorableFlag(t *testing.T) {
	src := readSource(t, "register.go")
	i := strings.Index(src, "func (d Deps) repoSettings(repo string) repoSettingsNow")
	if i < 0 {
		t.Fatal("repoSettings is gone")
	}
	body := src[i:]
	if end := strings.Index(body, "\ntype repoUpdateOut"); end > 0 {
		body = body[:end]
	}
	if strings.Contains(body, "d.Mirrorable(repo)") {
		t.Error("repoSettings reads the EFFECTIVE mirrorable and writes it back: " +
			"a repository taken private and restored to public comes out unclonable")
	}
	if !strings.Contains(body, "meta.Mirrorable") {
		t.Error("repoSettings does not read the stored mirrorable flag from RepoMeta")
	}
}