package xfer
import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"sync"
"testing"
)
// THE PUSH LISTENER HEARS A PUSH THAT LANDED, AND NOTHING ELSE.
//
// A tracking store that takes a push has diverged from its upstream,
// and the only place quarry knows a push was ADMITTED — not refused by
// the posture gate, not refused by the store — is the fronter, after
// the child's reply came back clean (5d40f151). A clone and a pull are
// not pushes; a refused push is not a push that landed.
func TestThePushListenerHearsOnlyAPushThatLanded(t *testing.T) {
if _, err := exec.LookPath("zit"); err != nil {
t.Skip("zit not on PATH")
}
origin := t.TempDir()
zrun(t, origin, "init")
if err := os.WriteFile(filepath.Join(origin, "README.md"), []byte("# pushed\n"), 0o644); err != nil {
t.Fatal(err)
}
zrun(t, origin, "add", "README.md")
zrun(t, origin, "commit", "-m", "first")
var mu sync.Mutex
var heard []string
f := New()
defer f.Close()
f.Register("alpha", origin)
f.SetPushed(func(repo string) {
mu.Lock()
defer mu.Unlock()
heard = append(heard, repo)
})
mux := http.NewServeMux()
mux.Handle("POST /r/{repo}", f)
srv := httptest.NewServer(mux)
defer srv.Close()
work := t.TempDir()
clone := filepath.Join(work, "clone")
zrun(t, work, "clone", srv.URL+"/r/alpha", clone)
zrun(t, clone, "pull")
mu.Lock()
n := len(heard)
mu.Unlock()
if n != 0 {
t.Fatalf("FAIL: a clone and a pull were heard as pushes: %v", heard)
}
if err := os.WriteFile(filepath.Join(clone, "NOTES.md"), []byte("more\n"), 0o644); err != nil {
t.Fatal(err)
}
zrun(t, clone, "add", "NOTES.md")
zrun(t, clone, "commit", "-m", "second")
zrun(t, clone, "push")
mu.Lock()
got := append([]string(nil), heard...)
mu.Unlock()
// A push is several rounds and each admitted round carrying a push
// is heard: at least one, all on alpha. The listener counts
// rounds, and its consumer treats them as "pushed at least once".
if len(got) == 0 {
t.Fatalf("FAIL: a push that landed was not heard")
}
for _, r := range got {
if r != "alpha" {
t.Fatalf("FAIL: heard %v, want only alpha", got)
}
}
before := len(got)
// A push the posture gate refuses is not heard.
f.SetWriteGate(func(string) (bool, string) { return false, "https://up.example/r/alpha" })
if err := os.WriteFile(filepath.Join(clone, "NOTES.md"), []byte("more still\n"), 0o644); err != nil {
t.Fatal(err)
}
zrun(t, clone, "commit", "-m", "third")
_ = zrunErr(clone, "push")
mu.Lock()
n = len(heard)
mu.Unlock()
if n != before {
t.Fatalf("FAIL: a refused push was heard: %v", heard)
}
t.Log("ok: one push landed, one push heard")
}
// zrunErr runs zit and returns its error instead of failing the test,
// for the push that is meant to be refused.
func zrunErr(dir string, args ...string) error {
cmd := exec.Command("zit", args...)
cmd.Dir = dir
return cmd.Run()
}