Add test API to update code owner configs
For acceptance tests we need an API that allows us to update code owner
configs directly without permission checks.
Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: I86838f587ca37e5ecb611bfc3f267d119dba6aa3
diff --git a/java/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperations.java b/java/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperations.java
index 1b9cf6e..8f5f28e 100644
--- a/java/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperations.java
+++ b/java/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperations.java
@@ -89,5 +89,29 @@
* @return the corresponding {@code CodeOwnerConfig}
*/
CodeOwnerConfig get();
+
+ /**
+ * Starts the fluent chain to update a code owner config. The returned builder can be used to
+ * specify how the attributes of the code owner config should be modified. To update the code
+ * owner config for real, {@link TestCodeOwnerConfigUpdate.Builder#update()} must be called.
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * codeOwnerOperations
+ * .codeOwnerConfig(codeOwnerConfigKey)
+ * .forUpdate()
+ * .addCodeOwnerEmail("jane.roe@example.com")
+ * .removeCodeOwnerEmail("joe.doe@example.com")
+ * .update();
+ * </pre>
+ *
+ * <p><strong>Note:</strong> The update will fail with an {@link IllegalStateException} if the
+ * code owner config to update doesn't exist. If you want to check for the existence of a code
+ * owner config, use {@link #exists()}.
+ *
+ * @return a builder to update the code owner config
+ */
+ TestCodeOwnerConfigUpdate.Builder forUpdate();
}
}
diff --git a/java/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImpl.java b/java/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImpl.java
index aa65587..b9f4e63 100644
--- a/java/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImpl.java
+++ b/java/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImpl.java
@@ -109,8 +109,24 @@
String.format("code owner config %s does not exist", codeOwnerConfigKey)));
}
+ @Override
+ public TestCodeOwnerConfigUpdate.Builder forUpdate() {
+ return TestCodeOwnerConfigUpdate.builder(this::updateNewCodeOwnerConfig);
+ }
+
private Optional<CodeOwnerConfig> getCodeOwnerConfig() {
return codeOwners.get(codeOwnerConfigKey);
}
+
+ private void updateNewCodeOwnerConfig(TestCodeOwnerConfigUpdate codeOwnerConfigUpdate)
+ throws Exception {
+ codeOwnersUpdate
+ .get()
+ .upsertCodeOwnerConfig(
+ codeOwnerConfigKey,
+ CodeOwnerConfigUpdate.builder()
+ .setCodeOwnerModification(codeOwnerConfigUpdate.codeOwnerModification()::apply)
+ .build());
+ }
}
}
diff --git a/java/com/google/gerrit/plugins/codeowners/acceptance/testsuite/TestCodeOwnerConfigUpdate.java b/java/com/google/gerrit/plugins/codeowners/acceptance/testsuite/TestCodeOwnerConfigUpdate.java
new file mode 100644
index 0000000..fb3cd79
--- /dev/null
+++ b/java/com/google/gerrit/plugins/codeowners/acceptance/testsuite/TestCodeOwnerConfigUpdate.java
@@ -0,0 +1,174 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.plugins.codeowners.acceptance.testsuite;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.gerrit.acceptance.testsuite.ThrowingConsumer;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerReference;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Test API to update a code owner config.
+ *
+ * <p>To execute the operations, no Gerrit permissions are necessary.
+ *
+ * <p><strong>Note:</strong> This interface is not implemented using the REST or extension API.
+ * Hence, it cannot be used for testing those APIs.
+ */
+@AutoValue
+public abstract class TestCodeOwnerConfigUpdate {
+ /**
+ * Defines how the code owners of the code owner config should be modified. By default (that is if
+ * nothing is specified), the code owners remain unchanged.
+ *
+ * @return a function which gets the current code owners of the code owner config as input and
+ * outputs the desired resulting code owners
+ */
+ public abstract Function<ImmutableSet<CodeOwnerReference>, Set<CodeOwnerReference>>
+ codeOwnerModification();
+
+ /**
+ * Gets the function that updates the code owner config.
+ *
+ * @return the function that updates the code owner config
+ */
+ abstract ThrowingConsumer<TestCodeOwnerConfigUpdate> codeOwnerConfigUpdater();
+
+ /**
+ * Creates a builder for a {@link TestCodeOwnerConfigUpdate}.
+ *
+ * @param codeOwnerConfigUpdater function that updates the code owner config
+ * @return builder for a {@link TestCodeOwnerConfigUpdate}
+ */
+ public static Builder builder(
+ ThrowingConsumer<TestCodeOwnerConfigUpdate> codeOwnerConfigUpdater) {
+ return new AutoValue_TestCodeOwnerConfigUpdate.Builder()
+ .codeOwnerConfigUpdater(codeOwnerConfigUpdater)
+ .codeOwnerModification(in -> in);
+ }
+
+ /** Builder for a {@link TestCodeOwnerConfigUpdate}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets the code owner modification.
+ *
+ * @return the Builder instance for chaining calls
+ * @see TestCodeOwnerConfigUpdate#codeOwnerModification()
+ */
+ abstract Builder codeOwnerModification(
+ Function<ImmutableSet<CodeOwnerReference>, Set<CodeOwnerReference>> codeOwnerModification);
+
+ /**
+ * Gets the code owner modification.
+ *
+ * @see TestCodeOwnerConfigUpdate#codeOwnerModification()
+ */
+ abstract Function<ImmutableSet<CodeOwnerReference>, Set<CodeOwnerReference>>
+ codeOwnerModification();
+
+ /**
+ * Removes all code owners.
+ *
+ * @return the Builder instance for chaining calls
+ */
+ public Builder clearCodeOwners() {
+ return codeOwnerModification(originalCodeOwners -> ImmutableSet.of());
+ }
+
+ /**
+ * Adds a code owner.
+ *
+ * @param codeOwner code owner that should be added
+ * @return the Builder instance for chaining calls
+ */
+ public Builder addCodeOwner(CodeOwnerReference codeOwner) {
+ Function<ImmutableSet<CodeOwnerReference>, Set<CodeOwnerReference>> previousModification =
+ codeOwnerModification();
+ codeOwnerModification(
+ originalCodeOwners ->
+ Sets.union(
+ previousModification.apply(originalCodeOwners), ImmutableSet.of(codeOwner)));
+ return this;
+ }
+
+ /**
+ * Adds a code owner by email.
+ *
+ * @param codeOwnerEmail email of the code owner that should be added
+ * @return the Builder instance for chaining calls
+ */
+ public Builder addCodeOwnerEmail(String codeOwnerEmail) {
+ return addCodeOwner(CodeOwnerReference.create(codeOwnerEmail));
+ }
+
+ /**
+ * Removes a code owner.
+ *
+ * <p>Removing a non-existing code owner is a no-op.
+ *
+ * @param codeOwner code owner that should be removed
+ * @return the Builder instance for chaining calls
+ */
+ public Builder removeCodeOwner(CodeOwnerReference codeOwner) {
+ Function<ImmutableSet<CodeOwnerReference>, Set<CodeOwnerReference>> previousModification =
+ codeOwnerModification();
+ codeOwnerModification(
+ originalCodeOwners ->
+ Sets.difference(
+ previousModification.apply(originalCodeOwners), ImmutableSet.of(codeOwner)));
+ return this;
+ }
+
+ /**
+ * Removes a code owner by email.
+ *
+ * <p>Removing a non-existing code owner is a no-op.
+ *
+ * @param codeOwnerEmail email of code owner that should be removed
+ * @return the Builder instance for chaining calls
+ */
+ public Builder removeCodeOwnerEmail(String codeOwnerEmail) {
+ return removeCodeOwner(CodeOwnerReference.create(codeOwnerEmail));
+ }
+
+ /**
+ * Sets the function that updates the code owner config.
+ *
+ * @param codeOwnerConfigUpdater the function that updates the code owner config
+ * @return the Builder instance for chaining calls
+ */
+ abstract Builder codeOwnerConfigUpdater(
+ ThrowingConsumer<TestCodeOwnerConfigUpdate> codeOwnerConfigUpdater);
+
+ /**
+ * Builds the {@link TestCodeOwnerConfigUpdate} instance.
+ *
+ * @return the {@link TestCodeOwnerConfigUpdate} instance
+ */
+ abstract TestCodeOwnerConfigUpdate autoBuild();
+
+ /** Executes the code owner config update as specified. */
+ public void update() {
+ TestCodeOwnerConfigUpdate codeOwnerConfigUpdater = autoBuild();
+ codeOwnerConfigUpdater
+ .codeOwnerConfigUpdater()
+ .acceptAndThrowSilently(codeOwnerConfigUpdater);
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImplTest.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImplTest.java
index 289622c..801897b 100644
--- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImplTest.java
+++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/testsuite/CodeOwnerConfigOperationsImplTest.java
@@ -26,6 +26,7 @@
import com.google.gerrit.plugins.codeowners.acceptance.testsuite.CodeOwnerConfigOperations.PerCodeOwnerConfigOperations;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfig;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigUpdate;
+import com.google.gerrit.plugins.codeowners.backend.CodeOwnerConfigUpdate.CodeOwnerModification;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnerReference;
import com.google.gerrit.plugins.codeowners.backend.CodeOwners;
import com.google.gerrit.plugins.codeowners.backend.CodeOwnersUpdate;
@@ -216,6 +217,84 @@
.contains(String.format("code owner config %s already exists", codeOwnerConfigKey));
}
+ @Test
+ public void addCodeOwner() throws Exception {
+ CodeOwnerConfig codeOwnerConfig =
+ createCodeOwnerConfig(
+ codeOwners -> ImmutableSet.of(CodeOwnerReference.create(admin.email())));
+ codeOwnerConfigOperations
+ .codeOwnerConfig(codeOwnerConfig.key())
+ .forUpdate()
+ .addCodeOwnerEmail(user.email())
+ .update();
+ assertThat(getCodeOwnerConfigFromServer(codeOwnerConfig.key()))
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(admin.email(), user.email());
+ }
+
+ @Test
+ public void removeCodeOwner() throws Exception {
+ CodeOwnerConfig codeOwnerConfig =
+ createCodeOwnerConfig(
+ codeOwners ->
+ ImmutableSet.of(
+ CodeOwnerReference.create(admin.email()),
+ CodeOwnerReference.create(user.email())));
+ codeOwnerConfigOperations
+ .codeOwnerConfig(codeOwnerConfig.key())
+ .forUpdate()
+ .removeCodeOwnerEmail(user.email())
+ .update();
+ assertThat(getCodeOwnerConfigFromServer(codeOwnerConfig.key()))
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(admin.email());
+ }
+
+ @Test
+ public void removeNonExistingCodeOwner() throws Exception {
+ CodeOwnerConfig codeOwnerConfig =
+ createCodeOwnerConfig(
+ codeOwners -> ImmutableSet.of(CodeOwnerReference.create(admin.email())));
+ codeOwnerConfigOperations
+ .codeOwnerConfig(codeOwnerConfig.key())
+ .forUpdate()
+ .removeCodeOwnerEmail(user.email())
+ .update();
+ assertThat(getCodeOwnerConfigFromServer(codeOwnerConfig.key()))
+ .hasCodeOwnersEmailsThat()
+ .containsExactly(admin.email());
+ }
+
+ @Test
+ public void clearCodeOwners() throws Exception {
+ CodeOwnerConfig codeOwnerConfig =
+ createCodeOwnerConfig(
+ codeOwners ->
+ ImmutableSet.of(
+ CodeOwnerReference.create(admin.email()),
+ CodeOwnerReference.create(user.email())));
+ codeOwnerConfigOperations
+ .codeOwnerConfig(codeOwnerConfig.key())
+ .forUpdate()
+ .clearCodeOwners()
+ .update();
+
+ // Removing all code owners leads to a deletion of the code owner config file.
+ assertThat(codeOwners.get(codeOwnerConfig.key())).isEmpty();
+ }
+
+ @Test
+ public void cannotUpdateNonExistingCodeOwnerConfig() throws Exception {
+ CodeOwnerConfig.Key codeOwnerConfigKey = CodeOwnerConfig.Key.create(project, "master", "/");
+ IllegalStateException exception =
+ assertThrows(
+ IllegalStateException.class,
+ () -> codeOwnerConfigOperations.codeOwnerConfig(codeOwnerConfigKey).get());
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(String.format("code owner config %s does not exist", codeOwnerConfigKey));
+ }
+
private CodeOwnerConfig getCodeOwnerConfigFromServer(CodeOwnerConfig.Key codeOwnerConfigKey) {
return codeOwners
.get(codeOwnerConfigKey)
@@ -226,12 +305,14 @@
}
private CodeOwnerConfig createArbitraryCodeOwnerConfig() {
+ return createCodeOwnerConfig(
+ codeOwners -> ImmutableSet.of(CodeOwnerReference.create(admin.email())));
+ }
+
+ private CodeOwnerConfig createCodeOwnerConfig(CodeOwnerModification codeOwnerModification) {
CodeOwnerConfig.Key codeOwnerConfigKey = CodeOwnerConfig.Key.create(project, "master", "/");
CodeOwnerConfigUpdate codeOwnerConfigUpdate =
- CodeOwnerConfigUpdate.builder()
- .setCodeOwnerModification(
- codeOwners -> ImmutableSet.of(CodeOwnerReference.create(admin.email())))
- .build();
+ CodeOwnerConfigUpdate.builder().setCodeOwnerModification(codeOwnerModification).build();
return codeOwnersUpdate
.get()
.upsertCodeOwnerConfig(codeOwnerConfigKey, codeOwnerConfigUpdate)