package auth import ( "os" "path/filepath" "testing" "gitweb/internal/store" ) // writeTree writes files (path -> content) under a fresh temp root and returns a // LocalStore over it. func writeTree(t *testing.T, files map[string]string) *store.LocalStore { t.Helper() root := t.TempDir() for p, content := range files { full := filepath.Join(root, filepath.FromSlash(p)) if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(full, []byte(content), 0o644); err != nil { t.Fatal(err) } } return store.NewLocalStore(root) } // checker reduces the boilerplate of asserting CanRead/CanWrite outcomes. type checker struct { t *testing.T a *Authorizer } func (c checker) read(name, user, p string, want bool) { c.t.Helper() got, err := c.a.CanRead(user, p) if err != nil { c.t.Fatalf("%s: CanRead error: %v", name, err) } if got != want { c.t.Errorf("%s: CanRead(%q) = %v, want %v", name, user, got, want) } } func (c checker) write(name, user, p string, want bool) { c.t.Helper() got, err := c.a.CanWrite(user, p) if err != nil { c.t.Fatalf("%s: CanWrite error: %v", name, err) } if got != want { c.t.Errorf("%s: CanWrite(%q) = %v, want %v", name, user, got, want) } } func TestAuthorizerPublicRepo(t *testing.T) { const owner = "u@d.com" repo := owner + "/public.git" st := writeTree(t, map[string]string{ repo + "/HEAD": "ref: refs/heads/main\n", repo + "/Access": "read,list: all\n*: " + owner + "\n", }) c := checker{t, NewAuthorizer(st)} c.read("anon", "", repo, true) c.read("stranger", "x@other.com", repo, true) c.read("owner", owner, repo, true) c.write("anon", "", repo, false) c.write("owner", owner, repo, true) c.write("stranger", "x@other.com", repo, false) } func TestAuthorizerPrivateRepo(t *testing.T) { const owner = "u@d.com" repo := owner + "/private.git" st := writeTree(t, map[string]string{ repo + "/HEAD": "ref: refs/heads/main\n", repo + "/Access": "*: " + owner + "\nread,write: w@d.com\n", }) c := checker{t, NewAuthorizer(st)} c.read("anon", "", repo, false) c.read("collaborator", "w@d.com", repo, true) c.read("stranger", "x@other.com", repo, false) c.write("collaborator", "w@d.com", repo, true) c.write("stranger", "x@other.com", repo, false) c.write("owner", owner, repo, true) } // A private Access file inside the repo overrides a public one on the parent // directory (nearest ancestor wins). func TestAuthorizerNearestAccessWins(t *testing.T) { const owner = "u@d.com" repo := owner + "/proj/secret.git" st := writeTree(t, map[string]string{ owner + "/proj/Access": "read,list: all\n*: " + owner + "\n", repo + "/HEAD": "ref: refs/heads/main\n", repo + "/Access": "*: " + owner + "\n", }) c := checker{t, NewAuthorizer(st)} c.read("anon", "", repo, false) c.read("owner", owner, repo, true) } // With no Access file anywhere, only the owner has any rights. func TestAuthorizerNoAccessOwnerOnly(t *testing.T) { const owner = "u@d.com" repo := owner + "/bare.git" st := writeTree(t, map[string]string{ repo + "/HEAD": "ref: refs/heads/main\n", }) c := checker{t, NewAuthorizer(st)} c.read("owner", owner, repo, true) c.read("anon", "", repo, false) c.read("stranger", "x@other.com", repo, false) c.write("owner", owner, repo, true) }