package xfer
import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"testing"
)
// seen records what the gate tallied, in order.
type seen struct {
mu sync.Mutex
r []Reader
}
func (s *seen) add(_ string, r Reader) {
s.mu.Lock()
defer s.mu.Unlock()
s.r = append(s.r, r)
}
func (s *seen) all() []Reader {
s.mu.Lock()
defer s.mu.Unlock()
return append([]Reader(nil), s.r...)
}
// A real clone through the real gate: the tally has to survive the whole
// path — container decoding, card scanning, the User-Agent — and not
// just the unit tests' hand-built bodies.
func TestARealCloneIsTalliedWithItsClient(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("# counted\n"), 0o644); err != nil {
t.Fatal(err)
}
zrun(t, origin, "add", "README.md")
zrun(t, origin, "commit", "-m", "first")
var got seen
f := New()
defer f.Close()
f.Register("alpha", origin)
f.SetCounted(got.add)
mux := http.NewServeMux()
mux.Handle("POST /r/{repo}", f)
srv := httptest.NewServer(mux)
defer srv.Close()
work := t.TempDir()
zrun(t, work, "clone", srv.URL+"/r/alpha", filepath.Join(work, "clone"))
rs := got.all()
if len(rs) == 0 {
t.Fatal("FAIL: a real clone tallied nothing")
}
clones := 0
for _, r := range rs {
if r.Clone {
clones++
if r.Client != "zit" {
t.Errorf("FAIL: a zit clone was recorded as client %q", r.Client)
}
// The version is self-reported and its value is zit's
// business, but a clone that carried a pragma must not
// record an empty one.
if r.Version == "" {
t.Error("FAIL: a clone carrying a client-version pragma recorded no version")
}
if !r.Anonymous() {
t.Errorf("FAIL: an anonymous clone named %q/%q", r.Account, r.Claimed)
}
}
}
if clones != 1 {
t.Errorf("FAIL: %d clones tallied for one clone, want 1", clones)
}
}
// The one property this whole change must not break: identity resolution
// runs AFTER the response, so a credential that does not check out costs
// a name off a statistic and never a clone. A public repository cloned
// with a stale password works today and has to keep working.
func TestABadCredentialStillServesTheClone(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("# open\n"), 0o644); err != nil {
t.Fatal(err)
}
zrun(t, origin, "add", "README.md")
zrun(t, origin, "commit", "-m", "first")
var got seen
f := New()
defer f.Close()
f.Register("alpha", origin)
f.SetCounted(got.add)
// An Identify that refuses everybody, standing in for a stale
// password, a renamed account, or a database that was busy.
f.SetIdentify(func(*http.Request) string { return "" })
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", strings.Replace(srv.URL, "http://", "http://ghost:wrong@", 1)+"/r/alpha", clone)
if tl := zrun(t, clone, "timeline", "-n", "3"); !strings.Contains(tl, "first") {
t.Fatalf("FAIL: a bad credential cost the clone its history:\n%s", tl)
}
for _, r := range got.all() {
if r.Account != "" {
t.Errorf("FAIL: a credential nothing verified was recorded as account %q", r.Account)
}
}
}