Artifact 59c11456bea314f023236ffb1f1d9542003f6b180f95e51dd0d741ea40eacac5:
- File
internal/xfer/fetchusers_test.go
— part of check-in
[b2ef1ce14d]
at
2026-08-22 16:51:19
on branch trunk
— fetch a remote fossil's user table over the wire, admin-credentialed
reqconfig /user in one round trip (sync RFC §6, §9.3). The REMOTE enforces admin — a developer gets no rows, a bad password fails login, both pinned against a real fossil. Row format verified against fossil 2.28: one config card per row, quoted key/value pairs. (user: dmestas, size: 3856) [annotate] [blame] [check-ins using] [more...]
package xfer import ( "fmt" "net" "os/exec" "path/filepath" "strings" "testing" "time" ) // fossilPeer stands up a real fossil server with known users. The // /user row serialisation is outside the protocol profile, so the only // honest way to pin it is against fossil itself. func fossilPeer(t *testing.T) (url, projectCode string) { t.Helper() if _, err := exec.LookPath("fossil"); err != nil { t.Skip("fossil not on PATH") } dir := t.TempDir() repo := filepath.Join(dir, "peer.fossil") run := func(args ...string) string { t.Helper() out, err := exec.Command("fossil", args...).CombinedOutput() if err != nil { t.Fatalf("fossil %v: %v\n%s", args, err, out) } return string(out) } run("init", repo, "--admin-user", "root") run("user", "password", "root", "rootpw", "-R", repo) run("user", "new", "alice", "Alice Dev", "secretpw", "-R", repo) run("user", "capabilities", "alice", "ei", "-R", repo) ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } port := ln.Addr().(*net.TCPAddr).Port ln.Close() cmd := exec.Command("fossil", "server", repo, "--port", fmt.Sprint(port)) if err := cmd.Start(); err != nil { t.Fatal(err) } t.Cleanup(func() { cmd.Process.Kill(); cmd.Wait() }) url = fmt.Sprintf("http://127.0.0.1:%d", port) deadline := time.Now().Add(10 * time.Second) for time.Now().Before(deadline) { if c, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), time.Second); err == nil { c.Close() break } time.Sleep(50 * time.Millisecond) } for _, line := range strings.Split(run("info", repo), "\n") { if f := strings.Fields(line); len(f) == 2 && f[0] == "project-code:" { projectCode = f[1] } } if projectCode == "" { t.Fatal("no project code") } return url, projectCode } // An admin credential fetches the peer's users, caps verbatim. func TestFetchUsersFromFossil(t *testing.T) { url, code := fossilPeer(t) users, err := FetchUsers(nil, url, code, "root", "rootpw") if err != nil { t.Fatalf("fetch: %v", err) } got := map[string]RemoteUser{} for _, u := range users { got[u.Login] = u } if u, ok := got["alice"]; !ok || u.Caps != "ei" { t.Fatalf("alice's caps did not arrive verbatim: %+v", got) } if u, ok := got["root"]; !ok || u.Caps != "s" { t.Fatalf("root's caps did not arrive: %+v", got) } // The credential is fossil's own §6.3 shared secret, so quarry can // verify a login against it directly (RFC 0004 §6). if u := got["alice"]; u.PWHash != SharedSecret(code, "alice", "secretpw") { t.Fatalf("alice's hash is not the §6.3 shared secret: %q", u.PWHash) } if got["alice"].Info != "Alice Dev" { t.Fatalf("info lost: %q", got["alice"].Info) } } // The REMOTE enforces admin — that is what makes this legitimate // rather than a bypass. A developer gets no user rows at all (fossil // strips the group from the mask), and a bad password fails the login. func TestFetchUsersRequiresRemoteAdmin(t *testing.T) { url, code := fossilPeer(t) if _, err := FetchUsers(nil, url, code, "alice", "secretpw"); err == nil { t.Fatal("a non-admin fetched the user table") } if _, err := FetchUsers(nil, url, code, "root", "wrong"); err == nil { t.Fatal("a bad password fetched the user table") } } // The row parser is pinned independently of the network so a format // drift is a readable failure rather than a mysterious empty import. func TestParseUserRow(t *testing.T) { u, err := parseUserRow(`1787417078 'root' pw 'a5df9879' cap 's' info 'it''s mine' jx '{}'`) if err != nil { t.Fatal(err) } if u.Login != "root" || u.Caps != "s" || u.PWHash != "a5df9879" { t.Fatalf("row parsed wrong: %+v", u) } if u.Info != "it's mine" { t.Fatalf("doubled quote not undoubled: %q", u.Info) } if _, err := parseUserRow("garbage"); err == nil { t.Fatal("a malformed row parsed clean") } }