Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

395 commit(s) on main

update readme Keenan Nemetz · 28m ago 32290114
history blame raw 5.0 KiB
1package web
2
3import (
4 "testing"
5
6 "rodent.software/vole/forge/access"
7)
8
9// The capability rules are the whole authorization story for the browser, so they
10// are tested as rules rather than only through the handlers that call them. A
11// change here is a change to what every page offers and every write accepts.
12
13func TestCapabilitiesByRole(t *testing.T) {
14 acl := access.Empty("owner")
15 for _, entry := range []struct{ name, role string }{
16 {"outsider", "none"},
17 {"reader", "read"},
18 {"contributor", "contribute"},
19 {"maintainer", "maintain"},
20 {"owner", "admin"},
21 } {
22 if err := acl.SetEntry(entry.name, entry.role); err != nil {
23 t.Fatalf("set %s to %s: %v", entry.name, entry.role, err)
24 }
25 }
26
27 cases := []struct {
28 name string
29 account string
30 discuss bool
31 openRequest bool
32 maintain bool
33 admin bool
34 }{
35 {"anonymous", "", false, false, false, false},
36 {"outsider", "outsider", false, false, false, false},
37 {"reader", "reader", false, false, false, false},
38 {"contributor", "contributor", true, true, false, false},
39 {"maintainer", "maintainer", true, true, true, false},
40 {"owner", "owner", true, true, true, true},
41 }
42 for _, c := range cases {
43 t.Run(c.name, func(t *testing.T) {
44 if got := canDiscuss(acl, c.account); got != c.discuss {
45 t.Errorf("canDiscuss = %v, want %v", got, c.discuss)
46 }
47 if got := canOpenChangeRequest(acl, c.account); got != c.openRequest {
48 t.Errorf("canOpenChangeRequest = %v, want %v", got, c.openRequest)
49 }
50 if got := canMaintain(acl, c.account); got != c.maintain {
51 t.Errorf("canMaintain = %v, want %v", got, c.maintain)
52 }
53 if got := canAdminister(acl, c.account); got != c.admin {
54 t.Errorf("canAdminister = %v, want %v", got, c.admin)
55 }
56 })
57 }
58}
59
60// Triaging somebody else's issue is a maintainer's job; managing your own is not
61// a privilege. That is the rule the labels, assignees and state forms rely on.
62func TestCanTriageAllowsTheAuthorWithoutMaintainer(t *testing.T) {
63 acl := access.Empty("owner")
64 if err := acl.SetEntry("author", "contribute"); err != nil {
65 t.Fatal(err)
66 }
67 if !canTriage(acl, "author", "author") {
68 t.Error("an author cannot manage their own issue")
69 }
70 if canTriage(acl, "author", "somebody-else") {
71 t.Error("a contributor can triage somebody else's issue")
72 }
73 if !canTriage(acl, "owner", "author") {
74 t.Error("a maintainer cannot triage another account's issue")
75 }
76 // An empty author is not a wildcard: a record with no author must not become
77 // manageable by anyone who can triage.
78 if canTriage(acl, "", "") {
79 t.Error("an empty account was treated as the author")
80 }
81}
82
83// Approving your own change request is refused as its own rule, so a maintainer
84// is told why rather than being called unauthorized.
85func TestCanApproveRequestRefusesSelfApproval(t *testing.T) {
86 acl := access.Empty("owner")
87 if !canApproveRequest(acl, "owner", "someone") {
88 t.Error("a maintainer cannot approve another account's request")
89 }
90 if !canApprove(acl, "owner") {
91 t.Error("canApprove dropped the capability for a maintainer")
92 }
93 if canApproveRequest(acl, "owner", "owner") {
94 t.Error("a maintainer was allowed to approve their own request")
95 }
96 if canApproveRequest(acl, "", "someone") {
97 t.Error("an anonymous viewer was allowed to approve")
98 }
99}
100
101// A contributor's branches live in their own namespace; everybody at maintainer
102// or above may name any branch.
103func TestContributorOnly(t *testing.T) {
104 acl := access.Empty("owner")
105 for name, role := range map[string]string{
106 "contributor": "contribute",
107 "maintainer": "maintain",
108 "reader": "read",
109 } {
110 if err := acl.SetEntry(name, role); err != nil {
111 t.Fatal(err)
112 }
113 }
114 if !contributorOnly(acl, "contributor") {
115 t.Error("a contribute-only account is not limited to its namespace")
116 }
117 if contributorOnly(acl, "maintainer") || contributorOnly(acl, "owner") {
118 t.Error("a maintainer was confined to a contrib namespace")
119 }
120 if contributorOnly(acl, "reader") || contributorOnly(acl, "") {
121 t.Error("an account that cannot open a request was treated as a contributor")
122 }
123}
124
125// Deleting somebody else's comment needs the admin role; editing your own does
126// not.
127func TestCanModerateComment(t *testing.T) {
128 acl := access.Empty("owner")
129 if err := acl.SetEntry("maintainer", "maintain"); err != nil {
130 t.Fatal(err)
131 }
132 cases := []struct {
133 name, account, author string
134 want bool
135 }{
136 {"own comment", "maintainer", "maintainer", true},
137 {"another account's comment", "maintainer", "owner", false},
138 {"admin repairing a thread", "owner", "maintainer", true},
139 {"anonymous", "", "maintainer", false},
140 {"no rules", "maintainer", "maintainer", true},
141 }
142 for _, c := range cases {
143 t.Run(c.name, func(t *testing.T) {
144 got := canModerateComment(acl, c.account, c.author)
145 if got != c.want {
146 t.Errorf("canModerateComment(%q, %q) = %v, want %v", c.account, c.author, got, c.want)
147 }
148 })
149 }
150 // A nil ACL is a repo whose rules could not be read: deny, do not panic.
151 if canModerateComment(nil, "owner", "owner") {
152 t.Error("a nil ACL granted moderation")
153 }
154}
155