blob: 499f42fc906c039b6e8fcfb1a47396a072eae3d8 [file] [log] [blame]
Edwin Kempinef03eda2020-12-02 13:26:36 +01001// Copyright (C) 2020 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.gerrit.plugins.codeowners.backend;
16
17import static com.google.gerrit.plugins.codeowners.testing.FileCodeOwnerStatusSubject.assertThatStream;
18
Edwin Kempinffe1d0f2021-01-20 14:03:06 +010019import com.google.common.collect.ImmutableMap;
Chris Poucet73b98e92022-10-14 15:26:48 +020020import com.google.common.collect.ImmutableSet;
Edwin Kempinffe1d0f2021-01-20 14:03:06 +010021import com.google.gerrit.acceptance.PushOneCommit;
Edwin Kempinef03eda2020-12-02 13:26:36 +010022import com.google.gerrit.acceptance.TestAccount;
23import com.google.gerrit.acceptance.config.GerritConfig;
24import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
Edwin Kempinef03eda2020-12-02 13:26:36 +010025import com.google.gerrit.entities.Change;
Edwin Kempinffe1d0f2021-01-20 14:03:06 +010026import com.google.gerrit.entities.PatchSet;
Edwin Kempinef03eda2020-12-02 13:26:36 +010027import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest;
28import com.google.gerrit.plugins.codeowners.acceptance.testsuite.CodeOwnerConfigOperations;
Edwin Kempin47d58702021-01-07 16:06:08 +010029import com.google.gerrit.plugins.codeowners.common.CodeOwnerStatus;
Edwin Kempinef03eda2020-12-02 13:26:36 +010030import com.google.gerrit.plugins.codeowners.testing.FileCodeOwnerStatusSubject;
Edwin Kempinb4e832a2021-01-07 15:22:12 +010031import com.google.gerrit.plugins.codeowners.util.JgitPath;
Edwin Kempinef03eda2020-12-02 13:26:36 +010032import com.google.gerrit.server.notedb.ChangeNotes;
Edwin Kempinffe1d0f2021-01-20 14:03:06 +010033import com.google.gerrit.truth.ListSubject;
Edwin Kempinef03eda2020-12-02 13:26:36 +010034import com.google.inject.Inject;
35import java.nio.file.Path;
36import java.nio.file.Paths;
37import java.util.stream.Stream;
38import org.junit.Before;
39import org.junit.Test;
40
Edwin Kempinffe1d0f2021-01-20 14:03:06 +010041/**
Chris Poucet73b98e92022-10-14 15:26:48 +020042 * Tests for {@link CodeOwnerApprovalCheck#getFileStatusesForAccounts(ChangeNotes, PatchSet,
43 * ImmutableSet)}.
Edwin Kempinffe1d0f2021-01-20 14:03:06 +010044 */
Edwin Kempinef03eda2020-12-02 13:26:36 +010045public class CodeOwnerApprovalCheckForAccountTest extends AbstractCodeOwnersTest {
46 @Inject private ChangeNotes.Factory changeNotesFactory;
47 @Inject private RequestScopeOperations requestScopeOperations;
48
49 private CodeOwnerApprovalCheck codeOwnerApprovalCheck;
50 private CodeOwnerConfigOperations codeOwnerConfigOperations;
51
52 @Before
53 public void setUpCodeOwnersPlugin() throws Exception {
54 codeOwnerApprovalCheck = plugin.getSysInjector().getInstance(CodeOwnerApprovalCheck.class);
55 codeOwnerConfigOperations =
56 plugin.getSysInjector().getInstance(CodeOwnerConfigOperations.class);
57 }
58
59 @Test
60 public void notApprovedByUser() throws Exception {
Edwin Kempinef03eda2020-12-02 13:26:36 +010061 Path path = Paths.get("/foo/bar.baz");
62 String changeId =
63 createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
Edwin Kempinffe1d0f2021-01-20 14:03:06 +010064 ChangeNotes changeNotes = getChangeNotes(changeId);
Edwin Kempinef03eda2020-12-02 13:26:36 +010065
66 // Verify that the file would not be approved by the user.
67 Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
Chris Poucet73b98e92022-10-14 15:26:48 +020068 codeOwnerApprovalCheck.getFileStatusesForAccounts(
69 changeNotes, changeNotes.getCurrentPatchSet(), ImmutableSet.of(user.id()));
Edwin Kempinef03eda2020-12-02 13:26:36 +010070 FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
71 assertThatStream(fileCodeOwnerStatuses).onlyElement();
72 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
73 fileCodeOwnerStatusSubject
74 .hasNewPathStatus()
75 .value()
76 .hasStatusThat()
77 .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
Chris Poucet73b98e92022-10-14 15:26:48 +020078 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasOwnersThat().isEmpty();
Edwin Kempinef03eda2020-12-02 13:26:36 +010079 }
80
81 @Test
82 public void approvalFromOtherCodeOwnerHasNoEffect() throws Exception {
83 TestAccount codeOwner =
84 accountCreator.create(
85 "codeOwner", "codeOwner@example.com", "CodeOwner", /* displayName= */ null);
86
87 codeOwnerConfigOperations
88 .newCodeOwnerConfig()
89 .project(project)
90 .branch("master")
91 .folderPath("/foo/")
92 .addCodeOwnerEmail(codeOwner.email())
93 .create();
94
95 Path path = Paths.get("/foo/bar.baz");
96 String changeId =
97 createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
Edwin Kempinffe1d0f2021-01-20 14:03:06 +010098 ChangeNotes changeNotes = getChangeNotes(changeId);
Edwin Kempinef03eda2020-12-02 13:26:36 +010099
100 // Add a Code-Review+1 (= code owner approval) from the code owner.
101 requestScopeOperations.setApiUser(codeOwner.id());
102 recommend(changeId);
103
104 // Verify that the file would not be approved by the user.
105 Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
Chris Poucet73b98e92022-10-14 15:26:48 +0200106 codeOwnerApprovalCheck.getFileStatusesForAccounts(
107 changeNotes, changeNotes.getCurrentPatchSet(), ImmutableSet.of(user.id()));
Edwin Kempinef03eda2020-12-02 13:26:36 +0100108 FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
109 assertThatStream(fileCodeOwnerStatuses).onlyElement();
110 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
111 fileCodeOwnerStatusSubject
112 .hasNewPathStatus()
113 .value()
114 .hasStatusThat()
115 .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
Chris Poucet73b98e92022-10-14 15:26:48 +0200116 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasOwnersThat().isEmpty();
117 }
118
119 @Test
120 public void approvalFromOtherCodeOwnerAsReviewerIsReturned() throws Exception {
121 TestAccount codeOwner =
122 accountCreator.create(
123 "codeOwner", "codeOwner@example.com", "CodeOwner", /* displayName= */ null);
124
125 codeOwnerConfigOperations
126 .newCodeOwnerConfig()
127 .project(project)
128 .branch("master")
129 .folderPath("/foo/")
130 .addCodeOwnerEmail(codeOwner.email())
131 .create();
132
133 Path path = Paths.get("/foo/bar.baz");
134 String changeId =
135 createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
136 ChangeNotes changeNotes = getChangeNotes(changeId);
137
138 // Add a Code-Review+1 (= code owner approval) from the code owner.
139 requestScopeOperations.setApiUser(codeOwner.id());
140 recommend(changeId);
141
142 // Verify that the file would not be approved by the user.
143 Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
144 codeOwnerApprovalCheck.getFileStatusesForAccounts(
145 changeNotes,
146 changeNotes.getCurrentPatchSet(),
147 ImmutableSet.of(codeOwner.id(), user.id()));
148 FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
149 assertThatStream(fileCodeOwnerStatuses).onlyElement();
150 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
151 fileCodeOwnerStatusSubject
152 .hasNewPathStatus()
153 .value()
154 .hasStatusThat()
155 .isEqualTo(CodeOwnerStatus.APPROVED);
156 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasOwners(codeOwner);
157 }
158
159 @Test
160 @GerritConfig(name = "plugin.code-owners.globalCodeOwner", value = "globalCodeOwner@example.com")
161 public void approvalFromGlobalCodeOwnerAsReviewerIsReturned() throws Exception {
162 TestAccount codeOwner =
163 accountCreator.create(
164 "codeOwner", "codeOwner@example.com", "CodeOwner", /* displayName= */ null);
165 TestAccount globalCodeOwner =
166 accountCreator.create(
167 "globalCodeOwner",
168 "globalCodeOwner@example.com",
169 "GlobalCodeOwner",
170 /* displayName= */ null);
171 codeOwnerConfigOperations
172 .newCodeOwnerConfig()
173 .project(project)
174 .branch("master")
175 .folderPath("/foo/")
176 .addCodeOwnerEmail(codeOwner.email())
177 .create();
178
179 Path path = Paths.get("/foo/bar.baz");
180 String changeId =
181 createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
182 ChangeNotes changeNotes = getChangeNotes(changeId);
183
184 // Add a Code-Review+1 (= code owner approval) from the code owner.
185 requestScopeOperations.setApiUser(codeOwner.id());
186 recommend(changeId);
187
188 Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
189 codeOwnerApprovalCheck.getFileStatusesForAccounts(
190 changeNotes,
191 changeNotes.getCurrentPatchSet(),
192 ImmutableSet.of(codeOwner.id(), user.id(), globalCodeOwner.id()));
193 FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
194 assertThatStream(fileCodeOwnerStatuses).onlyElement();
195 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
196 fileCodeOwnerStatusSubject
197 .hasNewPathStatus()
198 .value()
199 .hasStatusThat()
200 .isEqualTo(CodeOwnerStatus.APPROVED);
201 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasOwners(codeOwner, globalCodeOwner);
Edwin Kempinef03eda2020-12-02 13:26:36 +0100202 }
203
204 @Test
205 public void approvedByUser() throws Exception {
206 codeOwnerConfigOperations
207 .newCodeOwnerConfig()
208 .project(project)
209 .branch("master")
210 .folderPath("/foo/")
211 .addCodeOwnerEmail(user.email())
212 .create();
213
214 Path path = Paths.get("/foo/bar.baz");
215 String changeId =
216 createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
Edwin Kempinffe1d0f2021-01-20 14:03:06 +0100217 ChangeNotes changeNotes = getChangeNotes(changeId);
Edwin Kempinef03eda2020-12-02 13:26:36 +0100218
219 // Verify that the file would be approved by the user.
220 Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
Chris Poucet73b98e92022-10-14 15:26:48 +0200221 codeOwnerApprovalCheck.getFileStatusesForAccounts(
222 changeNotes, changeNotes.getCurrentPatchSet(), ImmutableSet.of(user.id()));
Edwin Kempinef03eda2020-12-02 13:26:36 +0100223 FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
224 assertThatStream(fileCodeOwnerStatuses).onlyElement();
225 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
226 fileCodeOwnerStatusSubject
227 .hasNewPathStatus()
228 .value()
229 .hasStatusThat()
230 .isEqualTo(CodeOwnerStatus.APPROVED);
Chris Poucet73b98e92022-10-14 15:26:48 +0200231 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasOwners(user);
Edwin Kempinef03eda2020-12-02 13:26:36 +0100232 }
233
234 @Test
Edwin Kempinffe1d0f2021-01-20 14:03:06 +0100235 public void approvedByUser_forPatchSets() throws Exception {
236 codeOwnerConfigOperations
237 .newCodeOwnerConfig()
238 .project(project)
239 .branch("master")
240 .folderPath("/foo/")
241 .addCodeOwnerEmail(user.email())
242 .create();
243
244 Path path1 = Paths.get("/foo/bar.baz");
245 String changeId =
246 createChange("Change Adding A File", JgitPath.of(path1).get(), "file content")
247 .getChangeId();
248
249 // amend change and add another file
250 Path path2 = Paths.get("/foo/baz.bar");
251 PushOneCommit push =
252 pushFactory.create(
253 admin.newIdent(),
254 testRepo,
255 "subject",
256 ImmutableMap.of(
257 JgitPath.of(path1).get(), "file content", JgitPath.of(path2).get(), "file content"),
258 changeId);
259 push.to("refs/for/master").assertOkStatus();
260
261 ChangeNotes changeNotes = getChangeNotes(changeId);
262
263 // Verify that the file in patch set 1 would be approved by the user.
264 Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
Chris Poucet73b98e92022-10-14 15:26:48 +0200265 codeOwnerApprovalCheck.getFileStatusesForAccounts(
Edwin Kempinffe1d0f2021-01-20 14:03:06 +0100266 changeNotes,
267 changeNotes.getPatchSets().get(PatchSet.id(changeNotes.getChangeId(), 1)),
Chris Poucet73b98e92022-10-14 15:26:48 +0200268 ImmutableSet.of(user.id()));
Edwin Kempinffe1d0f2021-01-20 14:03:06 +0100269 FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
270 assertThatStream(fileCodeOwnerStatuses).onlyElement();
271 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path1);
272 fileCodeOwnerStatusSubject
273 .hasNewPathStatus()
274 .value()
275 .hasStatusThat()
276 .isEqualTo(CodeOwnerStatus.APPROVED);
Chris Poucet73b98e92022-10-14 15:26:48 +0200277 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasOwners(user);
Edwin Kempinffe1d0f2021-01-20 14:03:06 +0100278 // Verify that both files in patch set 2 would be approved by the user.
279 fileCodeOwnerStatuses =
Chris Poucet73b98e92022-10-14 15:26:48 +0200280 codeOwnerApprovalCheck.getFileStatusesForAccounts(
Edwin Kempinffe1d0f2021-01-20 14:03:06 +0100281 changeNotes,
282 changeNotes.getPatchSets().get(PatchSet.id(changeNotes.getChangeId(), 2)),
Chris Poucet73b98e92022-10-14 15:26:48 +0200283 ImmutableSet.of(user.id()));
Edwin Kempinffe1d0f2021-01-20 14:03:06 +0100284 ListSubject<FileCodeOwnerStatusSubject, FileCodeOwnerStatus> fileCodeOwnerStatusListSubject =
285 assertThatStream(fileCodeOwnerStatuses);
286 fileCodeOwnerStatusListSubject.hasSize(2);
287 fileCodeOwnerStatusSubject = fileCodeOwnerStatusListSubject.element(0);
288 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path1);
289 fileCodeOwnerStatusSubject
290 .hasNewPathStatus()
291 .value()
292 .hasStatusThat()
293 .isEqualTo(CodeOwnerStatus.APPROVED);
Chris Poucet73b98e92022-10-14 15:26:48 +0200294 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasOwners(user);
Edwin Kempinffe1d0f2021-01-20 14:03:06 +0100295 fileCodeOwnerStatusSubject = fileCodeOwnerStatusListSubject.element(1);
296 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path2);
297 fileCodeOwnerStatusSubject
298 .hasNewPathStatus()
299 .value()
300 .hasStatusThat()
301 .isEqualTo(CodeOwnerStatus.APPROVED);
Chris Poucet73b98e92022-10-14 15:26:48 +0200302 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasOwners(user);
Edwin Kempinffe1d0f2021-01-20 14:03:06 +0100303 }
304
Edwin Kempinef03eda2020-12-02 13:26:36 +0100305 @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "ALL_USERS")
306 @Test
307 public void approvedByFallbackCodeOwner() throws Exception {
Edwin Kempinef03eda2020-12-02 13:26:36 +0100308 Path path = Paths.get("/foo/bar.baz");
309 String changeId =
310 createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
Edwin Kempinffe1d0f2021-01-20 14:03:06 +0100311 ChangeNotes changeNotes = getChangeNotes(changeId);
Edwin Kempinef03eda2020-12-02 13:26:36 +0100312
313 // Verify that the file would be approved by the user since the user is a fallback code owner.
314 Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
Chris Poucet73b98e92022-10-14 15:26:48 +0200315 codeOwnerApprovalCheck.getFileStatusesForAccounts(
316 changeNotes, changeNotes.getCurrentPatchSet(), ImmutableSet.of(user.id()));
Edwin Kempinef03eda2020-12-02 13:26:36 +0100317 FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
318 assertThatStream(fileCodeOwnerStatuses).onlyElement();
319 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
320 fileCodeOwnerStatusSubject
321 .hasNewPathStatus()
322 .value()
323 .hasStatusThat()
324 .isEqualTo(CodeOwnerStatus.APPROVED);
Chris Poucet73b98e92022-10-14 15:26:48 +0200325 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasOwners(user);
Edwin Kempinef03eda2020-12-02 13:26:36 +0100326 }
327
328 @GerritConfig(name = "plugin.code-owners.fallbackCodeOwners", value = "ALL_USERS")
329 @Test
330 public void notApprovedByFallbackCodeOwnerIfCodeOwerIsDefined() throws Exception {
331 TestAccount codeOwner =
332 accountCreator.create(
333 "codeOwner", "codeOwner@example.com", "CodeOwner", /* displayName= */ null);
334
335 codeOwnerConfigOperations
336 .newCodeOwnerConfig()
337 .project(project)
338 .branch("master")
339 .folderPath("/foo/")
340 .addCodeOwnerEmail(codeOwner.email())
341 .create();
342
343 Path path = Paths.get("/foo/bar.baz");
344 String changeId =
345 createChange("Change Adding A File", JgitPath.of(path).get(), "file content").getChangeId();
Edwin Kempinffe1d0f2021-01-20 14:03:06 +0100346 ChangeNotes changeNotes = getChangeNotes(changeId);
Edwin Kempinef03eda2020-12-02 13:26:36 +0100347
348 // Verify that the file would not be approved by the user since fallback code owners do not
349 // apply.
350 Stream<FileCodeOwnerStatus> fileCodeOwnerStatuses =
Chris Poucet73b98e92022-10-14 15:26:48 +0200351 codeOwnerApprovalCheck.getFileStatusesForAccounts(
352 changeNotes, changeNotes.getCurrentPatchSet(), ImmutableSet.of(user.id()));
Edwin Kempinef03eda2020-12-02 13:26:36 +0100353 FileCodeOwnerStatusSubject fileCodeOwnerStatusSubject =
354 assertThatStream(fileCodeOwnerStatuses).onlyElement();
355 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasPathThat().isEqualTo(path);
356 fileCodeOwnerStatusSubject
357 .hasNewPathStatus()
358 .value()
359 .hasStatusThat()
360 .isEqualTo(CodeOwnerStatus.INSUFFICIENT_REVIEWERS);
Chris Poucet73b98e92022-10-14 15:26:48 +0200361 fileCodeOwnerStatusSubject.hasNewPathStatus().value().hasOwnersThat().isEmpty();
Edwin Kempinef03eda2020-12-02 13:26:36 +0100362 }
363
Edwin Kempinef03eda2020-12-02 13:26:36 +0100364 private ChangeNotes getChangeNotes(String changeId) throws Exception {
365 return changeNotesFactory.create(project, Change.id(gApi.changes().id(changeId).get()._number));
366 }
367}