1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package reviewer
16
17 import (
18 "context"
19 "errors"
20 "math/rand"
21 "testing"
22
23 "github.com/stretchr/testify/assert"
24 "github.com/stretchr/testify/require"
25
26 "edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/pull"
27 "edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/pull/pulltest"
28
29 "edge-infra.dev/pkg/f8n/devinfra/repo/owners/policybot/policy/common"
30 )
31
32 func TestFindLeafResults(t *testing.T) {
33 result := &common.Result{
34 Name: "Parent",
35 Status: common.StatusPending,
36 Children: []*common.Result{
37 {
38 Name: "One",
39 Status: common.StatusPending,
40 ReviewRequestRule: &common.ReviewRequestRule{},
41 },
42 {
43 Name: "Skipped",
44 Status: common.StatusSkipped,
45 ReviewRequestRule: &common.ReviewRequestRule{},
46 },
47 {
48 Name: "Error",
49 Status: common.StatusPending,
50 Error: errors.New("failed"),
51 ReviewRequestRule: &common.ReviewRequestRule{},
52 },
53 {
54 Name: "Disapproved",
55 Status: common.StatusDisapproved,
56 ReviewRequestRule: &common.ReviewRequestRule{},
57 },
58 {
59 Name: "Two",
60 Status: common.StatusPending,
61 Children: []*common.Result{
62 {
63 Name: "Three",
64 Status: common.StatusPending,
65 ReviewRequestRule: &common.ReviewRequestRule{},
66 },
67 },
68 },
69 },
70 }
71 actualResults := FindRequests(result)
72 require.Len(t, actualResults, 2, "incorrect number of leaf results")
73 }
74
75 func TestSelectionDifference(t *testing.T) {
76 tests := map[string]struct {
77 Input Selection
78 Reviewers []*pull.Reviewer
79 Output Selection
80 }{
81 "users": {
82 Input: Selection{
83 Users: []string{"a", "b", "c"},
84 },
85 Reviewers: []*pull.Reviewer{
86 {
87 Type: pull.ReviewerUser,
88 Name: "a",
89 },
90 {
91 Type: pull.ReviewerUser,
92 Name: "c",
93 },
94 {
95 Type: pull.ReviewerTeam,
96 Name: "team-a",
97 },
98 },
99 Output: Selection{
100 Users: []string{"b"},
101 },
102 },
103 "teams": {
104 Input: Selection{
105 Teams: []string{"team-a", "team-b", "team-c"},
106 },
107 Reviewers: []*pull.Reviewer{
108 {
109 Type: pull.ReviewerUser,
110 Name: "a",
111 },
112 {
113 Type: pull.ReviewerUser,
114 Name: "c",
115 },
116 {
117 Type: pull.ReviewerTeam,
118 Name: "team-a",
119 },
120 },
121 Output: Selection{
122 Teams: []string{"team-b", "team-c"},
123 },
124 },
125 "dismissedUsers": {
126 Input: Selection{
127 Users: []string{"a", "b", "c"},
128 },
129 Reviewers: []*pull.Reviewer{
130 {
131 Type: pull.ReviewerUser,
132 Name: "a",
133 Removed: true,
134 },
135 {
136 Type: pull.ReviewerUser,
137 Name: "c",
138 },
139 },
140 Output: Selection{
141 Users: []string{"b"},
142 },
143 },
144 "dismissedTeams": {
145 Input: Selection{
146 Teams: []string{"team-a", "team-b", "team-c"},
147 },
148 Reviewers: []*pull.Reviewer{
149 {
150 Type: pull.ReviewerTeam,
151 Name: "team-a",
152 },
153 {
154 Type: pull.ReviewerTeam,
155 Name: "team-c",
156 Removed: true,
157 },
158 },
159 Output: Selection{
160 Teams: []string{"team-b"},
161 },
162 },
163 }
164
165 for name, test := range tests {
166 t.Run(name, func(t *testing.T) {
167 out := test.Input.Difference(test.Reviewers)
168 assert.Equal(t, test.Output.Users, out.Users, "incorrect users in difference")
169 assert.Equal(t, test.Output.Teams, out.Teams, "incorrect users in difference")
170 })
171 }
172 }
173
174 func TestSelectRandomUsers(t *testing.T) {
175 r := rand.New(rand.NewSource(42))
176
177 require.Len(t, selectRandomUsers(0, []string{"a"}, r), 0, "0 selection should return nothing")
178 require.Len(t, selectRandomUsers(1, []string{}, r), 0, "empty list should return nothing")
179
180 assert.Equal(t, []string{"a"}, selectRandomUsers(1, []string{"a"}, r))
181 assert.Equal(t, []string{"a", "b"}, selectRandomUsers(3, []string{"a", "b"}, r))
182
183 pseudoRandom := selectRandomUsers(1, []string{"a", "b", "c"}, r)
184 assert.Equal(t, []string{"c"}, pseudoRandom)
185
186 multiplePseudoRandom := selectRandomUsers(4, []string{"a", "b", "c", "d", "e", "f", "g"}, r)
187 assert.Equal(t, []string{"c", "e", "b", "f"}, multiplePseudoRandom)
188 }
189
190 func TestSelectReviewers(t *testing.T) {
191 r := rand.New(rand.NewSource(42))
192 results := []*common.Result{
193 {
194 Name: "users",
195 Status: common.StatusPending,
196 ReviewRequestRule: &common.ReviewRequestRule{
197 Users: []string{"mhaypenny", "review-approver", "contributor-committer"},
198 RequiredCount: 2,
199 Mode: common.RequestModeRandomUsers,
200 },
201 },
202 {
203 Name: "admin-users",
204 Status: common.StatusPending,
205 ReviewRequestRule: &common.ReviewRequestRule{
206 Permissions: []pull.Permission{pull.PermissionAdmin},
207 RequiredCount: 1,
208 Mode: common.RequestModeRandomUsers,
209 },
210 },
211 }
212
213 prctx := makeContext()
214
215 selection, err := SelectReviewers(context.Background(), prctx, results, r)
216 require.NoError(t, err)
217 require.Len(t, selection.Users, 3, "policy should request three people")
218 require.Contains(t, selection.Users, "review-approver", "at least review-approver must be selected")
219 require.NotContains(t, selection.Users, "mhaypenny", "the author cannot be requested")
220 require.NotContains(t, selection.Users, "not-a-collaborator", "a non collaborator cannot be requested")
221 require.NotContains(t, selection.Users, "org-owner", "org-owner should not be requested")
222 }
223
224 func TestSelectReviewers_UserPermission(t *testing.T) {
225 r := rand.New(rand.NewSource(42))
226 results := []*common.Result{
227 {
228 Name: "user-permissions",
229 Status: common.StatusPending,
230 ReviewRequestRule: &common.ReviewRequestRule{
231 Permissions: []pull.Permission{pull.PermissionTriage, pull.PermissionMaintain},
232 RequiredCount: 2,
233 Mode: common.RequestModeAllUsers,
234 },
235 },
236 }
237
238 prctx := makeContext()
239
240 selection, err := SelectReviewers(context.Background(), prctx, results, r)
241 require.NoError(t, err)
242 require.Len(t, selection.Teams, 0, "policy should request no teams")
243
244 require.Len(t, selection.Users, 4, "policy should request two users")
245 require.Contains(t, selection.Users, "maintainer", "maintainer selected")
246 require.Contains(t, selection.Users, "triager", "triager selected")
247 require.Contains(t, selection.Users, "org-owner-team-maintainer", "triager selected")
248 require.Contains(t, selection.Users, "direct-write-team-maintainer", "triager selected")
249 }
250
251 func TestSelectReviewers_TeamPermission(t *testing.T) {
252 r := rand.New(rand.NewSource(42))
253 results := []*common.Result{
254 {
255 Name: "team-permissions",
256 Status: common.StatusPending,
257 ReviewRequestRule: &common.ReviewRequestRule{
258 Permissions: []pull.Permission{pull.PermissionAdmin, pull.PermissionMaintain},
259 RequiredCount: 1,
260 Mode: common.RequestModeTeams,
261 },
262 },
263 }
264
265 prctx := makeContext()
266
267 selection, err := SelectReviewers(context.Background(), prctx, results, r)
268 require.NoError(t, err)
269 require.Len(t, selection.Teams, 2, "policy should request two teams")
270 require.Contains(t, selection.Teams, "team-admin", "admin team seleted")
271 require.Contains(t, selection.Teams, "team-maintain", "maintainer team seleted")
272
273 require.Len(t, selection.Users, 0, "policy should request no people")
274 }
275
276 func TestSelectReviewers_TeamMembers(t *testing.T) {
277 r := rand.New(rand.NewSource(42))
278 results := []*common.Result{
279 {
280 Name: "team-users",
281 Status: common.StatusPending,
282 ReviewRequestRule: &common.ReviewRequestRule{
283 Teams: []string{"everyone/team-write"},
284 RequiredCount: 1,
285 Mode: common.RequestModeRandomUsers,
286 },
287 },
288 }
289
290 prctx := makeContext()
291 selection, err := SelectReviewers(context.Background(), prctx, results, r)
292 require.NoError(t, err)
293 require.Empty(t, selection.Teams, "no teams should be returned")
294 require.Len(t, selection.Users, 1, "policy should request one reviewer")
295 require.Contains(t, selection.Users, "user-team-write", "user-team-write must be selected")
296 }
297
298 func TestSelectReviewers_Team(t *testing.T) {
299 r := rand.New(rand.NewSource(42))
300 results := []*common.Result{
301 {
302 Name: "Team",
303 Status: common.StatusPending,
304 ReviewRequestRule: &common.ReviewRequestRule{
305 Teams: []string{"everyone/team-write", "everyone/team-not-collaborators"},
306 Users: []string{"user-team-write"},
307 RequiredCount: 1,
308 Mode: common.RequestModeTeams,
309 },
310 },
311 }
312
313 prctx := makeContext()
314 selection, err := SelectReviewers(context.Background(), prctx, results, r)
315 require.NoError(t, err)
316 require.Len(t, selection.Teams, 1, "one team should be returned")
317 require.Contains(t, selection.Teams, "team-write", "team-write should be selected")
318 require.Len(t, selection.Users, 0, "policy should request 0 users")
319 }
320
321 func TestSelectReviewers_TeamNotCollaborator(t *testing.T) {
322 r := rand.New(rand.NewSource(42))
323 results := []*common.Result{
324 {
325 Name: "not-collaborators",
326 Status: common.StatusPending,
327 ReviewRequestRule: &common.ReviewRequestRule{
328 Teams: []string{"everyone/team-not-collaborators"},
329 Users: []string{"user-team-write"},
330 RequiredCount: 1,
331 Mode: common.RequestModeTeams,
332 },
333 },
334 }
335
336 prctx := makeContext()
337 selection, err := SelectReviewers(context.Background(), prctx, results, r)
338 require.NoError(t, err)
339 require.Empty(t, selection.Teams, "no team should be returned")
340 require.Len(t, selection.Users, 0, "policy should request no people")
341 }
342
343 func TestSelectReviewers_Org(t *testing.T) {
344 r := rand.New(rand.NewSource(42))
345 results := []*common.Result{
346 {
347 Name: "org",
348 Status: common.StatusPending,
349 ReviewRequestRule: &common.ReviewRequestRule{
350 Organizations: []string{"everyone"},
351 RequiredCount: 1,
352 Mode: common.RequestModeRandomUsers,
353 },
354 },
355 }
356
357 prctx := makeContext()
358 selection, err := SelectReviewers(context.Background(), prctx, results, r)
359 require.NoError(t, err)
360 require.Len(t, selection.Users, 1, "policy should request one person")
361 require.Contains(t, selection.Users, "review-approver", "review-approver must be selected")
362 }
363
364 func makeContext() pull.Context {
365 return &pulltest.Context{
366 OwnerValue: "everyone",
367
368 AuthorValue: "mhaypenny",
369 CommentsValue: []*pull.Comment{},
370 ReviewsValue: []*pull.Review{},
371
372 OrgMemberships: map[string][]string{
373 "mhaypenny": {"everyone"},
374 "contributor-author": {"everyone"},
375 "contributor-committer": {"everyone"},
376 "comment-approver": {"everyone", "cool-org"},
377 "review-approver": {"everyone", "even-cooler-org"},
378 },
379 CollaboratorsValue: []*pull.Collaborator{
380 {
381 Name: "mhaypenny",
382 Permissions: []pull.CollaboratorPermission{
383 {Permission: pull.PermissionAdmin},
384 },
385 },
386 {
387 Name: "org-owner",
388 Permissions: []pull.CollaboratorPermission{
389 {Permission: pull.PermissionAdmin},
390 },
391 },
392 {
393 Name: "user-team-admin",
394 Permissions: []pull.CollaboratorPermission{
395 {Permission: pull.PermissionAdmin, ViaRepo: true},
396 },
397 },
398 {
399 Name: "user-direct-admin",
400 Permissions: []pull.CollaboratorPermission{
401 {Permission: pull.PermissionAdmin, ViaRepo: true},
402 },
403 },
404 {
405 Name: "user-team-write",
406 Permissions: []pull.CollaboratorPermission{
407 {Permission: pull.PermissionWrite, ViaRepo: true},
408 },
409 },
410 {
411 Name: "contributor-committer",
412 Permissions: []pull.CollaboratorPermission{
413 {Permission: pull.PermissionWrite},
414 },
415 },
416 {
417 Name: "contributor-author",
418 Permissions: []pull.CollaboratorPermission{
419 {Permission: pull.PermissionWrite},
420 },
421 },
422 {
423 Name: "review-approver",
424 Permissions: []pull.CollaboratorPermission{
425 {Permission: pull.PermissionWrite},
426 },
427 },
428 {
429 Name: "maintainer",
430 Permissions: []pull.CollaboratorPermission{
431 {Permission: pull.PermissionMaintain, ViaRepo: true},
432 },
433 },
434 {
435
436 Name: "indirect-maintainer",
437 Permissions: []pull.CollaboratorPermission{
438 {Permission: pull.PermissionMaintain},
439 },
440 },
441 {
442 Name: "triager",
443 Permissions: []pull.CollaboratorPermission{
444 {Permission: pull.PermissionTriage, ViaRepo: true},
445 },
446 },
447 {
448
449 Name: "indirect-triager",
450 Permissions: []pull.CollaboratorPermission{
451 {Permission: pull.PermissionTriage},
452 },
453 },
454 {
455 Name: "org-owner-team-maintainer",
456 Permissions: []pull.CollaboratorPermission{
457 {Permission: pull.PermissionAdmin, ViaRepo: false},
458 {Permission: pull.PermissionMaintain, ViaRepo: true},
459 },
460 },
461 {
462 Name: "direct-write-team-maintainer",
463 Permissions: []pull.CollaboratorPermission{
464 {Permission: pull.PermissionMaintain, ViaRepo: true},
465 {Permission: pull.PermissionWrite, ViaRepo: true},
466 },
467 },
468 },
469 TeamsValue: map[string]pull.Permission{
470 "team-write": pull.PermissionWrite,
471 "team-admin": pull.PermissionAdmin,
472 "team-maintain": pull.PermissionMaintain,
473 },
474 TeamMemberships: map[string][]string{
475 "user-team-admin": {"everyone/team-admin"},
476 "user-team-write": {"everyone/team-write"},
477 "maintainer": {"everyone/team-maintain"},
478 "not-a-collaborator": {"everyone/team-not-collaborators"},
479 },
480 }
481 }
482
View as plain text