package e2e import ( "fmt" "os/exec" "strings" "testing" ) // Chapter 18 calls itself the complete matrix, so every cell of it is a row here. func TestTheAccessMatrixIsTheWholeMatrix(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git is not installed") } in := newInstance(t) john := in.account("john") lisa := in.account("lisa") mark := in.account("mark") dave := in.account("dave") // john owns it, lisa has push, mark and dave have neither. work := t.TempDir() run(t, work, "git", "init", "-q", "-b", "master", work) write(t, work, "config.go", "package main\n") write(t, work, ".barerepo/config", "[repo]\nvisibility = \"public\"\n\n[access]\npush = [\"lisa\"]\n") run(t, work, "git", "add", "-A") run(t, work, "git", "commit", "-qm", "first") run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master") // mark opens a proposal, which makes him its author for the row that says so. mine := clone(t, in, mark, "/john/johnbot") commit(t, mine, "package main\n\nfunc main() {}\n", "mark proposes") run(t, mine, "git", "push", "-q", in.url(mark, "/john/johnbot"), "HEAD:refs/proposals/new") cases := []struct { row string actor string token string ref string allowed bool }{ {"refs/heads/*", "the owner", john, "refs/heads/topic", true}, {"refs/heads/*", "someone with push", lisa, "refs/heads/lisa-topic", true}, {"refs/heads/*", "a stranger", mark, "refs/heads/mark-topic", false}, {"refs/tags/*", "the owner", john, "refs/tags/v1.0.0", true}, {"refs/tags/*", "someone with push", lisa, "refs/tags/v1.0.1", true}, {"refs/tags/*", "a stranger", mark, "refs/tags/v9.9.9", false}, {"refs/proposals/new", "a stranger", dave, "refs/proposals/new", true}, {"refs/proposals/", "its author", mark, "refs/proposals/1", true}, {"refs/proposals/", "someone with push", lisa, "refs/proposals/1", true}, {"refs/proposals/", "another stranger", dave, "refs/proposals/1", false}, {"refs/notes/runs", "a stranger", mark, "refs/notes/runs", false}, {"refs/notes/runs", "the owner restoring", john, "refs/notes/runs", true}, {"refs/meta/*", "a stranger", mark, "refs/meta/counter", false}, {"refs/meta/*", "the owner restoring", john, "refs/meta/counter", true}, {"everything else", "the owner restoring", john, "refs/wat/anything", true}, {"everything else", "a stranger", mark, "refs/wat/anything", false}, } // Each case pushes its own commit, or git answers "everything up-to-date" and the hook never runs. shas := make([]string, len(cases)) for i := range cases { commit(t, mine, fmt.Sprintf("package main\n\nvar n = %d\n", i), fmt.Sprintf("case %d", i)) shas[i] = strings.TrimSpace(run(t, mine, "git", "rev-parse", "HEAD")) } for i, c := range cases { out, err := try(t, mine, "git", "push", "-f", in.url(c.token, "/john/johnbot"), shas[i]+":"+c.ref) if strings.Contains(out, "up-to-date") { t.Errorf("%s: %s pushed nothing, so the hook never judged it", c.row, c.actor) continue } switch { case c.allowed && err != nil: t.Errorf("%s: %s was refused and chapter 18 allows it:\n%s", c.row, c.actor, out) case !c.allowed && err == nil: t.Errorf("%s: %s wrote it and chapter 18 does not allow that:\n%s", c.row, c.actor, out) } } } // Chapter 18 lets anyone who may read write refs/notes/threads/*, and chapter 13 makes that a reply. func TestAnyoneWhoMayReadMayReplyAndNobodyMayWipeAThread(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git is not installed") } in := newInstance(t) john := in.account("john") dave := in.account("dave") seed(t, in, john, "john", "johnbot") mine := clone(t, in, john, "/john/johnbot") commit(t, mine, "package main\n\nfunc main() {}\n", "john proposes") run(t, mine, "git", "push", "-q", in.url(john, "/john/johnbot"), "HEAD:refs/proposals/new") daves := clone(t, in, dave, "/john/johnbot") ref := "refs/notes/threads/1" run(t, daves, "git", "fetch", "-q", in.url(dave, "/john/johnbot"), ref+":"+ref) run(t, daves, "git", "fetch", "-q", in.url(dave, "/john/johnbot"), "refs/proposals/1:refs/proposals/1") run(t, daves, "git", "notes", "--ref=threads/1", "append", "-m", "dave may read, so dave may reply") if out, err := try(t, daves, "git", "push", in.url(dave, "/john/johnbot"), ref+":"+ref); err != nil { t.Errorf("a reader was refused a reply and chapter 18 allows it:\n%s", out) } // The same ref, the same reader, and a push that keeps nothing. Chapter 13 refuses this one. wipe := strings.TrimSpace(run(t, daves, "git", "rev-parse", "HEAD")) if out, err := try(t, daves, "git", "push", "-f", in.url(dave, "/john/johnbot"), wipe+":"+ref); err == nil { t.Errorf("a reader wiped a thread and chapter 13 forbids that:\n%s", out) } // The meta blob survives this one, so only the guard that counts records can refuse it. run(t, daves, "git", "fetch", "-q", "-f", in.url(dave, "/john/johnbot"), ref+":"+ref) listed := run(t, daves, "git", "notes", "--ref=threads/1", "list") fields := strings.Fields(listed) if len(fields) < 2 { t.Fatalf("thread 1 holds no note to overwrite: %s", listed) } on := fields[1] run(t, daves, "git", "notes", "--ref=threads/1", "add", "-f", "-m", "mine alone now", on) if out, err := try(t, daves, "git", "push", "-f", in.url(dave, "/john/johnbot"), ref+":"+ref); err == nil { t.Errorf("a reader overwrote a comment and chapter 13 forbids that:\n%s", out) } }