Files
mesh-catalog/modules/gitea/cmd/repositories/check_test.go
T

264 lines
11 KiB
Go

package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
// The tests of ADR 0534 §1, §2 (the operator, 2026-10-23): the forge requires no status to merge. Main's rule keeps no
// push, no push whitelist, the tools' account alone in its merge whitelist and no administrator merging past it, and
// requires no status: the program writes it so on every repository it lists, and takes every required status off a
// rule covering main in every repository of the organisation it does not list. Nothing else of an unlisted
// repository's rule is sent.
func narrowedRule(contexts ...any) map[string]any {
return map[string]any{
"enable_push": false, "enable_push_whitelist": false, "push_whitelist_usernames": []any{},
"enable_merge_whitelist": true, "merge_whitelist_usernames": []any{tools},
"enable_status_check": len(contexts) > 0, "status_check_contexts": contexts,
"block_admin_merge_override": true,
}
}
// requiresNone is whether a rule as the fake forge keeps it requires no status.
func requiresNone(rule map[string]any) bool {
return rule["enable_status_check"] != true && len(strs(rule["status_check_contexts"])) == 0
}
// 0534-1: a listed repository whose main requires statuses has them all taken off, the rest of the rule kept; a
// second pass changes nothing.
func TestListedRepositoryRequiresNoStatus(t *testing.T) {
f := newFake()
repo := f.add("novox/mesh-delivery", map[string]map[string]any{mainRule: narrowedRule("mesh/merge-gate", "mesh/repo-check", "other/x")}, "main")
checked := f.add("novox/checked", map[string]map[string]any{mainRule: narrowedRule("mesh/check")}, "main")
s, _ := syncer(t, f)
o := run(t, s, Value{"mesh-delivery": {}, "checked": {}})
for name, r := range map[string]*fRepo{"mesh-delivery": repo, "checked": checked} {
rule := r.Rules[mainRule]
if !requiresNone(rule) {
t.Errorf("%s's main still requires %v", name, rule["status_check_contexts"])
}
if rule["enable_push"] != false || rule["enable_merge_whitelist"] != true ||
strings.Join(strs(rule["merge_whitelist_usernames"]), ",") != tools || rule["block_admin_merge_override"] != true {
t.Errorf("%s's main lost the rest of its rule: %v", name, rule)
}
if r := result(o, name); r.Did != DidPutRight {
t.Errorf("%s outcome %+v, want put right", name, r)
}
}
before := len(f.writes())
o = run(t, s, Value{"mesh-delivery": {}, "checked": {}})
if w := f.writes(); len(w) != before {
t.Errorf("a second pass wrote %v", w[before:])
}
if result(o, "checked").Did != DidUnchanged {
t.Errorf("second outcome %+v, want unchanged", result(o, "checked"))
}
}
// 0534-1: a repository the program makes requires no status from its first rule in the organisation on.
func TestNewRepositoryRequiresNoStatus(t *testing.T) {
f := newFake()
s, _ := syncer(t, f)
run(t, s, Value{"fresh": {Description: "d"}})
rule := f.repos["novox/fresh"].Rules[mainRule]
if !requiresNone(rule) {
t.Errorf("a new repository's main requires %v, want none", rule["status_check_contexts"])
}
if readme := f.repos["novox/fresh"].Files["README.md"]; strings.Contains(readme, "mesh/merge-gate") || strings.Contains(readme, "mesh/repo-check") {
t.Errorf("the README names the statuses of before: %q", readme)
}
}
// 0534-1: a listed repository whose main requires nothing stays so, and is not written when the rest is right.
func TestListedRuleRequiringNothingIsRight(t *testing.T) {
f := newFake()
none := f.add("novox/none", map[string]map[string]any{mainRule: narrowedRule()}, "main")
s, _ := syncer(t, f)
o := run(t, s, Value{"none": {}})
if !requiresNone(none.Rules[mainRule]) {
t.Errorf("none requires %v", none.Rules[mainRule]["status_check_contexts"])
}
if result(o, "none").Did != DidUnchanged {
t.Errorf("outcome %+v, want unchanged", result(o, "none"))
}
for _, w := range f.writes() {
t.Errorf("a right rule was written: %s", w)
}
}
// 0534-1: hq, whose main required mesh/repo-check alone, requires no status either, listed or not.
func TestOwnCheckAloneGoesToo(t *testing.T) {
f := newFake()
hq := f.add("novox/hq", map[string]map[string]any{mainRule: narrowedRule("mesh/repo-check")}, "main")
lab := f.add("novox/mesh-lab", map[string]map[string]any{mainRule: narrowedRule("mesh/repo-check")}, "main")
s, _ := syncer(t, f)
o := run(t, s, Value{"hq": {}})
for name, repo := range map[string]*fRepo{"hq": hq, "mesh-lab": lab} {
if !requiresNone(repo.Rules[mainRule]) {
t.Errorf("%s requires %v, want none", name, repo.Rules[mainRule]["status_check_contexts"])
}
if r := result(o, name); r.Did != DidPutRight {
t.Errorf("%s outcome %+v, want put right", name, r)
}
}
}
// 0534-1: a repository the value does not list whose rule covering main requires any status has them taken off, and
// only its statuses: nothing else of its rule is sent. One that requires none is not written to.
func TestUnlistedRepositoryRequiringAStatusHasItTakenOff(t *testing.T) {
f := newFake()
catalog := f.add("novox/mesh-catalog", map[string]map[string]any{mainRule: {
"enable_push": true, "enable_status_check": true, "status_check_contexts": []any{"mesh/check"},
}}, "main")
toolsGo := f.add("novox/mesh-tools-go", map[string]map[string]any{mainRule: narrowedRule("mesh/merge-gate")}, "main")
f.add("novox/plain", map[string]map[string]any{mainRule: {"enable_push": false}}, "main")
f.add("novox/unticked", map[string]map[string]any{mainRule: {"enable_push": false, "enable_status_check": false,
"status_check_contexts": []any{"mesh/check"}}}, "main")
f.add("novox/norule", nil, "main")
f.add("mesh-admin/elsewhere", map[string]map[string]any{mainRule: narrowedRule("mesh/merge-gate")}, "main")
s, _ := syncer(t, f)
o := run(t, s, Value{})
if !requiresNone(catalog.Rules[mainRule]) {
t.Errorf("mesh-catalog requires %v, want none", catalog.Rules[mainRule]["status_check_contexts"])
}
if catalog.Rules[mainRule]["enable_push"] != true {
t.Errorf("an unlisted repository's rule was changed beyond its statuses: %v", catalog.Rules[mainRule])
}
if !requiresNone(toolsGo.Rules[mainRule]) {
t.Errorf("mesh-tools-go requires %v, want none", toolsGo.Rules[mainRule]["status_check_contexts"])
}
want := []string{
"PATCH /api/v1/repos/novox/mesh-catalog/branch_protections/main",
"PATCH /api/v1/repos/novox/mesh-tools-go/branch_protections/main",
}
if got := f.writes(); strings.Join(got, "\n") != strings.Join(want, "\n") {
t.Errorf("writes:\n%s\nwant:\n%s", strings.Join(got, "\n"), strings.Join(want, "\n"))
}
for _, n := range []string{"mesh-catalog", "mesh-tools-go"} {
if r := result(o, n); r.Did != DidPutRight {
t.Errorf("%s outcome %+v, want put right", n, r)
}
}
for _, n := range []string{"plain", "unticked", "norule", "elsewhere"} {
if r := result(o, n); r.Name != "" {
t.Errorf("%s is in the outcome though nothing was done: %+v", n, r)
}
}
before := len(f.writes())
run(t, s, Value{})
if w := f.writes(); len(w) != before {
t.Errorf("a second pass wrote %v", w[before:])
}
}
// 0534-1: the organisation's repositories are read page by page, so one past the first page is put right too.
func TestUnlistedRepositoriesAreReadPastTheFirstPage(t *testing.T) {
f := newFake()
f.pageSize = 2
for _, n := range []string{"a", "b", "c", "d", "e"} {
f.add("novox/"+n, map[string]map[string]any{mainRule: narrowedRule("mesh/check")}, "main")
}
s, _ := syncer(t, f)
run(t, s, Value{})
for _, n := range []string{"a", "b", "c", "d", "e"} {
if rule := f.repos["novox/"+n].Rules[mainRule]; !requiresNone(rule) {
t.Errorf("%s requires %v", n, rule["status_check_contexts"])
}
}
}
// 0534-1: a refused removal is said with the repository and its step, and the pass goes on to the next.
func TestRefusedRemovalIsSaid(t *testing.T) {
f := newFake()
f.add("novox/a", map[string]map[string]any{mainRule: narrowedRule("mesh/check")}, "main")
f.add("novox/b", map[string]map[string]any{mainRule: narrowedRule("mesh/check")}, "main")
f.refuse = func(method, path string) (int, string) {
if method == "PATCH" && strings.Contains(path, "/novox/a/") {
return 403, "no"
}
return 0, ""
}
s, _ := syncer(t, f)
o := run(t, s, Value{})
if r := result(o, "a"); r.Did != DidRefused || r.Step != StepNoStatus {
t.Errorf("a: %+v, want refused at %s", r, StepNoStatus)
}
if r := result(o, "b"); r.Did != DidPutRight {
t.Errorf("b: %+v, want put right", r)
}
}
// 1301-S3: the organisation's list refused is said, and the listed repositories are still done.
func TestOrganisationListRefusedIsSaid(t *testing.T) {
f := newFake()
f.refuse = func(method, path string) (int, string) {
if strings.HasPrefix(path, "/api/v1/orgs/") {
return 500, "down"
}
return 0, ""
}
s, _ := syncer(t, f)
o := run(t, s, Value{"x": {}})
if result(o, "x").Did != DidMade {
t.Errorf("x: %+v", result(o, "x"))
}
refusedList := false
for _, r := range o.Refused() {
refusedList = refusedList || r.Step == StepList
}
if !refusedList {
t.Errorf("the refused list is not in the outcome: %+v", o)
}
}
// 0534-2 (the operator, 2026-10-23 ~12:05): the operator's own forge login stays on main's merge whitelist beside the
// tools' account, so the operator can always merge by hand at the forge; a rule naming the tools' account alone is put
// right, and a second pass changes nothing.
func TestTheOperatorsLoginStaysBesideTheTools(t *testing.T) {
f := newFake()
repo := f.add("novox/mesh-delivery", map[string]map[string]any{mainRule: narrowedRule()}, "main")
s, _ := syncer(t, f)
s.Mergers = []string{"jschoubben", tools, "jschoubben"}
o := run(t, s, Value{"mesh-delivery": {}, "fresh": {}})
for name, rule := range map[string]map[string]any{"mesh-delivery": repo.Rules[mainRule], "fresh": f.repos["novox/fresh"].Rules[mainRule]} {
if got := strings.Join(strs(rule["merge_whitelist_usernames"]), ","); got != tools+",jschoubben" {
t.Errorf("%s's merge whitelist is %s, want %s and jschoubben", name, got, tools)
}
if !requiresNone(rule) || rule["enable_push"] != false {
t.Errorf("%s's rule: %v", name, rule)
}
}
if r := result(o, "mesh-delivery"); r.Did != DidPutRight {
t.Errorf("outcome %+v, want put right", r)
}
before := len(f.writes())
run(t, s, Value{"mesh-delivery": {}, "fresh": {}})
if w := f.writes(); len(w) != before {
t.Errorf("a second pass wrote %v", w[before:])
}
}
// 0534-2: the mergers are read from the module's runtime config file; a missing key is none; a name that is no forge login
// is refused.
func TestReadMergers(t *testing.T) {
p := filepath.Join(t.TempDir(), "r.json")
_ = os.WriteFile(p, []byte(`{"url":"","main-mergers":["jschoubben"]}`), 0o600)
if m, err := ReadMergers(p); err != nil || strings.Join(m, ",") != "jschoubben" {
t.Fatalf("%v %v", m, err)
}
_ = os.WriteFile(p, []byte(`{"url":""}`), 0o600)
if m, err := ReadMergers(p); err != nil || len(m) != 0 {
t.Fatalf("%v %v", m, err)
}
for _, bad := range []string{`["a b"]`, `["../x"]`, `[""]`, `"jschoubben"`} {
_ = os.WriteFile(p, []byte(`{"main-mergers":`+bad+`}`), 0o600)
if _, err := ReadMergers(p); err == nil {
t.Errorf("%s was accepted", bad)
}
}
}