Read all required approvals that are configured We want to allow configuring multiple override approvals. As a first step towards this we extend the config backend to read all configured required approvals so that they get available for upper layers (namely CodeOwnersPluginConfiguration). With this change we still only support a single override approval. If multiple override approvals are configured we use the last one. This is the same behavior as before this change, since also JGit uses the last configured value if a single config value is read, but multiple values are configured. Follow-up changes will adapt the upper layers to support multiple override approvals. Change-Id: I2a4387340e0336ea3ebe599b6e0fce913fa3689a Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/java/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfig.java b/java/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfig.java index f1e7acb..260d9d9 100644 --- a/java/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfig.java +++ b/java/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfig.java
@@ -17,13 +17,13 @@ import static com.google.gerrit.plugins.codeowners.config.CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS; import static java.util.Objects.requireNonNull; +import com.google.common.collect.ImmutableList; import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.server.config.PluginConfigFactory; import com.google.gerrit.server.git.validators.CommitValidationMessage; import com.google.gerrit.server.git.validators.ValidationMessage; import com.google.gerrit.server.project.ProjectLevelConfig; import com.google.gerrit.server.project.ProjectState; -import java.util.Optional; import org.eclipse.jgit.lib.Config; /** @@ -51,48 +51,59 @@ protected abstract String getConfigKey(); /** - * Reads the required approval for the specified project from the given plugin config with + * Reads the required approvals for the specified project from the given plugin config with * fallback to {@code gerrit.config}. * - * @param projectState state of the project for which the required approval should be read - * @param pluginConfig the plugin config from which the required approval should be read - * @return the required approval, {@link Optional#empty} if none was configured + * @param projectState state of the project for which the required approvals should be read + * @param pluginConfig the plugin config from which the required approvals should be read + * @return the required approvals, an empty list if none was configured */ - Optional<RequiredApproval> get(ProjectState projectState, Config pluginConfig) { + ImmutableList<RequiredApproval> get(ProjectState projectState, Config pluginConfig) { requireNonNull(projectState, "projectState"); requireNonNull(pluginConfig, "pluginConfig"); - String requiredApproval = - pluginConfig.getString(SECTION_CODE_OWNERS, /* subsection= */ null, getConfigKey()); - if (requiredApproval != null) { - try { - return Optional.of(RequiredApproval.parse(projectState, requiredApproval)); - } catch (IllegalStateException | IllegalArgumentException e) { - throw new InvalidPluginConfigurationException( - pluginName, - String.format( - "Required approval '%s' that is configured in %s.config" - + " (parameter %s.%s) is invalid: %s", - requiredApproval, pluginName, SECTION_CODE_OWNERS, getConfigKey(), e.getMessage())); + ImmutableList.Builder<RequiredApproval> requiredApprovalList = ImmutableList.builder(); + String[] requiredApprovals = + pluginConfig.getStringList(SECTION_CODE_OWNERS, /* subsection= */ null, getConfigKey()); + if (requiredApprovals.length > 0) { + for (String requiredApproval : requiredApprovals) { + try { + requiredApprovalList.add(RequiredApproval.parse(projectState, requiredApproval)); + } catch (IllegalStateException | IllegalArgumentException e) { + throw new InvalidPluginConfigurationException( + pluginName, + String.format( + "Required approval '%s' that is configured in %s.config" + + " (parameter %s.%s) is invalid: %s", + requiredApproval, + pluginName, + SECTION_CODE_OWNERS, + getConfigKey(), + e.getMessage())); + } } + return requiredApprovalList.build(); } - requiredApproval = - pluginConfigFactory.getFromGerritConfig(pluginName).getString(getConfigKey()); - if (requiredApproval != null) { - try { - return Optional.of(RequiredApproval.parse(projectState, requiredApproval)); - } catch (IllegalStateException | IllegalArgumentException e) { - throw new InvalidPluginConfigurationException( - pluginName, - String.format( - "Required approval '%s' that is configured in gerrit.config" - + " (parameter plugin.%s.%s) is invalid: %s", - requiredApproval, pluginName, getConfigKey(), e.getMessage())); + requiredApprovals = + pluginConfigFactory.getFromGerritConfig(pluginName).getStringList(getConfigKey()); + if (requiredApprovals.length > 0) { + for (String requiredApproval : requiredApprovals) { + try { + requiredApprovalList.add(RequiredApproval.parse(projectState, requiredApproval)); + } catch (IllegalStateException | IllegalArgumentException e) { + throw new InvalidPluginConfigurationException( + pluginName, + String.format( + "Required approval '%s' that is configured in gerrit.config" + + " (parameter plugin.%s.%s) is invalid: %s", + requiredApproval, pluginName, getConfigKey(), e.getMessage())); + } } + return requiredApprovalList.build(); } - return Optional.empty(); + return ImmutableList.of(); } /** @@ -103,21 +114,22 @@ * @return list of validation messages for validation errors, empty list if there are no * validation errors */ - Optional<CommitValidationMessage> validateProjectLevelConfig( + ImmutableList<CommitValidationMessage> validateProjectLevelConfig( ProjectState projectState, String fileName, ProjectLevelConfig.Bare projectLevelConfig) { requireNonNull(projectState, "projectState"); requireNonNull(fileName, "fileName"); requireNonNull(projectLevelConfig, "projectLevelConfig"); - String requiredApproval = + String[] requiredApprovals = projectLevelConfig .getConfig() - .getString(SECTION_CODE_OWNERS, /* subsection= */ null, getConfigKey()); - if (requiredApproval != null) { + .getStringList(SECTION_CODE_OWNERS, /* subsection= */ null, getConfigKey()); + ImmutableList.Builder<CommitValidationMessage> validationMessages = ImmutableList.builder(); + for (String requiredApproval : requiredApprovals) { try { RequiredApproval.parse(projectState, requiredApproval); } catch (IllegalArgumentException | IllegalStateException e) { - return Optional.of( + validationMessages.add( new CommitValidationMessage( String.format( "Required approval '%s' that is configured in %s (parameter %s.%s) is invalid: %s", @@ -129,6 +141,6 @@ ValidationMessage.Type.ERROR)); } } - return Optional.empty(); + return validationMessages.build(); } }
diff --git a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java index dba7bb2..2a29766 100644 --- a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java +++ b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfigValidator.java
@@ -158,12 +158,10 @@ validationMessages.addAll(backendConfig.validateProjectLevelConfig(fileName, cfg)); validationMessages.addAll(generalConfig.validateProjectLevelConfig(fileName, cfg)); validationMessages.addAll(statusConfig.validateProjectLevelConfig(fileName, cfg)); - requiredApprovalConfig - .validateProjectLevelConfig(projectState, fileName, cfg) - .ifPresent(validationMessages::add); - overrideApprovalConfig - .validateProjectLevelConfig(projectState, fileName, cfg) - .ifPresent(validationMessages::add); + validationMessages.addAll( + requiredApprovalConfig.validateProjectLevelConfig(projectState, fileName, cfg)); + validationMessages.addAll( + overrideApprovalConfig.validateProjectLevelConfig(projectState, fileName, cfg)); if (!validationMessages.isEmpty()) { throw new CommitValidationException( exceptionMessage(fileName, cfg.getRevision()), validationMessages);
diff --git a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java index 8288c03..9f088a7 100644 --- a/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java +++ b/java/com/google/gerrit/plugins/codeowners/config/CodeOwnersPluginConfiguration.java
@@ -18,7 +18,9 @@ import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; import com.google.common.flogger.FluentLogger; import com.google.gerrit.entities.BranchNameKey; import com.google.gerrit.entities.Project; @@ -322,16 +324,22 @@ * <li>hard-coded default required approval * </ul> * - * <p>The first required code owner approval that exists counts and the evaluation is stopped. + * <p>The first required code owner approval configuration that exists counts and the evaluation + * is stopped. + * + * <p>If the code owner configuration contains multiple required approvals values, the last value + * is used. * * @param project project for which the required approval should be returned * @return the required code owner approval that should be used for the given project */ public RequiredApproval getRequiredApproval(Project.NameKey project) { - Optional<RequiredApproval> configuredRequiredApprovalConfig = + ImmutableList<RequiredApproval> configuredRequiredApprovalConfig = getConfiguredRequiredApproval(requiredApprovalConfig, project); - if (configuredRequiredApprovalConfig.isPresent()) { - return configuredRequiredApprovalConfig.get(); + if (!configuredRequiredApprovalConfig.isEmpty()) { + // There can be only one required approval. If multiple ones are configured just use the last + // one, this is also what Config#getString(String, String, String) does. + return Iterables.getLast(configuredRequiredApprovalConfig); } // fall back to hard-coded default required approval @@ -353,7 +361,9 @@ * <li>globally configured override approval * </ul> * - * <p>The first override approval that exists counts and the evaluation is stopped. + * <p>The first override approval configuration that exists counts and the evaluation is stopped. + * + * <p>If the code owner configuration contains multiple override values, the last value is used. * * @param project project for which the override approval should be returned * @return the override approval that should be used for the given project, {@link @@ -362,10 +372,12 @@ */ public Optional<RequiredApproval> getOverrideApproval(Project.NameKey project) { try { - Optional<RequiredApproval> configuredOverrideApprovalConfig = + ImmutableList<RequiredApproval> configuredOverrideApprovalConfig = getConfiguredRequiredApproval(overrideApprovalConfig, project); - if (configuredOverrideApprovalConfig.isPresent()) { - return configuredOverrideApprovalConfig; + if (!configuredOverrideApprovalConfig.isEmpty()) { + // There can be only one override approval. If multiple ones are configured just use the + // last one, this is also what Config#getString(String, String, String) does. + return Optional.of(Iterables.getLast(configuredOverrideApprovalConfig)); } } catch (InvalidPluginConfigurationException e) { logger.atWarning().withCause(e).log( @@ -378,14 +390,14 @@ } /** - * Gets the required approval that is configured for the given project. + * Gets the required approvals that are configured for the given project. * - * @param requiredApprovalConfig the config from which the required approval should be read - * @param project the project for which the configured required approval should be returned - * @return the required approval that is configured for the given project, {@link - * Optional#empty()} if no required approval is configured + * @param requiredApprovalConfig the config from which the required approvals should be read + * @param project the project for which the configured required approvals should be returned + * @return the required approvals that is configured for the given project, an empty list if no + * required approvals are configured */ - private Optional<RequiredApproval> getConfiguredRequiredApproval( + private ImmutableList<RequiredApproval> getConfiguredRequiredApproval( AbstractRequiredApprovalConfig requiredApprovalConfig, Project.NameKey project) { Config pluginConfig = getPluginConfig(project); ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project));
diff --git a/java/com/google/gerrit/plugins/codeowners/testing/RequiredApprovalSubject.java b/java/com/google/gerrit/plugins/codeowners/testing/RequiredApprovalSubject.java index c0e7563..27d2b20 100644 --- a/java/com/google/gerrit/plugins/codeowners/testing/RequiredApprovalSubject.java +++ b/java/com/google/gerrit/plugins/codeowners/testing/RequiredApprovalSubject.java
@@ -16,11 +16,13 @@ import static com.google.common.truth.Truth.assertAbout; +import com.google.common.collect.ImmutableList; import com.google.common.truth.FailureMetadata; import com.google.common.truth.IntegerSubject; import com.google.common.truth.StringSubject; import com.google.common.truth.Subject; import com.google.gerrit.plugins.codeowners.config.RequiredApproval; +import com.google.gerrit.truth.ListSubject; import com.google.gerrit.truth.OptionalSubject; import java.util.Optional; @@ -48,6 +50,17 @@ } /** + * Starts a fluent chain to do assertions on a list of {@link RequiredApproval}s. + * + * @param requiredApprovals list of required approvals on which assertions should be done + * @return the created {@link ListSubject} + */ + public static ListSubject<RequiredApprovalSubject, RequiredApproval> assertThat( + ImmutableList<RequiredApproval> requiredApprovals) { + return ListSubject.assertThat(requiredApprovals, requiredApprovals()); + } + + /** * Creates a subject factory for mapping {@link RequiredApproval}s to {@link * RequiredApprovalSubject}s. */
diff --git a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java index e81a707..daf08e2 100644 --- a/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java +++ b/javatests/com/google/gerrit/plugins/codeowners/acceptance/api/CodeOwnersPluginConfigValidatorIT.java
@@ -19,6 +19,7 @@ import static com.google.gerrit.acceptance.GitUtil.pushHead; import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat; +import com.google.common.collect.ImmutableList; import com.google.gerrit.entities.BranchNameKey; import com.google.gerrit.entities.RefNames; import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersIT; @@ -268,6 +269,33 @@ } @Test + public void allRequiredApprovalsAreValidated() throws Exception { + fetchRefsMetaConfig(); + + ImmutableList<String> invalidValues = ImmutableList.of("INVALID", "ALSO_INVALID"); + Config cfg = new Config(); + cfg.setStringList( + CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS, + /* subsection= */ null, + RequiredApprovalConfig.KEY_REQUIRED_APPROVAL, + invalidValues); + setCodeOwnersConfig(cfg); + + PushResult r = pushRefsMetaConfig(); + assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()) + .isEqualTo(Status.REJECTED_OTHER_REASON); + for (String invalidValue : invalidValues) { + assertThat(r.getMessages()) + .contains( + String.format( + "Required approval '%s' that is configured in code-owners.config (parameter" + + " codeOwners.%s) is invalid: Invalid format, expected" + + " '<label-name>+<label-value>'.", + invalidValue, RequiredApprovalConfig.KEY_REQUIRED_APPROVAL)); + } + } + + @Test public void configureOverrideApproval() throws Exception { fetchRefsMetaConfig(); @@ -313,6 +341,33 @@ } @Test + public void allOverrideApprovalsAreValidated() throws Exception { + fetchRefsMetaConfig(); + + ImmutableList<String> invalidValues = ImmutableList.of("INVALID", "ALSO_INVALID"); + Config cfg = new Config(); + cfg.setStringList( + CodeOwnersPluginConfiguration.SECTION_CODE_OWNERS, + /* subsection= */ null, + OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL, + invalidValues); + setCodeOwnersConfig(cfg); + + PushResult r = pushRefsMetaConfig(); + assertThat(r.getRemoteUpdate(RefNames.REFS_CONFIG).getStatus()) + .isEqualTo(Status.REJECTED_OTHER_REASON); + for (String invalidValue : invalidValues) { + assertThat(r.getMessages()) + .contains( + String.format( + "Required approval '%s' that is configured in code-owners.config (parameter" + + " codeOwners.%s) is invalid: Invalid format, expected" + + " '<label-name>+<label-value>'.", + invalidValue, OverrideApprovalConfig.KEY_OVERRIDE_APPROVAL)); + } + } + + @Test public void configureMergeCommitStrategy() throws Exception { fetchRefsMetaConfig();
diff --git a/javatests/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfigTest.java index a8b5c7d..42a32dd 100644 --- a/javatests/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfigTest.java +++ b/javatests/com/google/gerrit/plugins/codeowners/config/AbstractRequiredApprovalConfigTest.java
@@ -19,14 +19,13 @@ import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat; import static com.google.gerrit.server.project.ProjectCache.illegalState; import static com.google.gerrit.testing.GerritJUnit.assertThrows; -import static com.google.gerrit.truth.OptionalSubject.assertThat; +import com.google.common.collect.ImmutableList; import com.google.gerrit.plugins.codeowners.acceptance.AbstractCodeOwnersTest; import com.google.gerrit.server.git.validators.CommitValidationMessage; import com.google.gerrit.server.git.validators.ValidationMessage; import com.google.gerrit.server.project.ProjectLevelConfig; import com.google.gerrit.server.project.ProjectState; -import java.util.Optional; import org.eclipse.jgit.lib.Config; import org.junit.Test; @@ -85,11 +84,11 @@ /* subsection= */ null, getRequiredApprovalConfig().getConfigKey(), "Code-Review+2"); - Optional<RequiredApproval> requiredApproval = + ImmutableList<RequiredApproval> requiredApproval = getRequiredApprovalConfig().get(projectState, cfg); - assertThat(requiredApproval).isPresent(); - assertThat(requiredApproval).value().hasLabelNameThat().isEqualTo("Code-Review"); - assertThat(requiredApproval).value().hasValueThat().isEqualTo(2); + assertThat(requiredApproval).hasSize(1); + assertThat(requiredApproval).element(0).hasLabelNameThat().isEqualTo("Code-Review"); + assertThat(requiredApproval).element(0).hasValueThat().isEqualTo(2); } @Test @@ -160,7 +159,7 @@ @Test public void validateEmptyProjectLevelConfig() throws Exception { ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project)); - Optional<CommitValidationMessage> commitValidationMessage = + ImmutableList<CommitValidationMessage> commitValidationMessage = getRequiredApprovalConfig() .validateProjectLevelConfig( projectState, @@ -179,7 +178,7 @@ /* subsection= */ null, getRequiredApprovalConfig().getConfigKey(), "Code-Review+2"); - Optional<CommitValidationMessage> commitValidationMessage = + ImmutableList<CommitValidationMessage> commitValidationMessage = getRequiredApprovalConfig() .validateProjectLevelConfig(projectState, "code-owners.config", cfg); assertThat(commitValidationMessage).isEmpty(); @@ -195,12 +194,12 @@ /* subsection= */ null, getRequiredApprovalConfig().getConfigKey(), "INVALID"); - Optional<CommitValidationMessage> commitValidationMessage = + ImmutableList<CommitValidationMessage> commitValidationMessage = getRequiredApprovalConfig() .validateProjectLevelConfig(projectState, "code-owners.config", cfg); - assertThat(commitValidationMessage).isPresent(); - assertThat(commitValidationMessage.get().getType()).isEqualTo(ValidationMessage.Type.ERROR); - assertThat(commitValidationMessage.get().getMessage()) + assertThat(commitValidationMessage).hasSize(1); + assertThat(commitValidationMessage.get(0).getType()).isEqualTo(ValidationMessage.Type.ERROR); + assertThat(commitValidationMessage.get(0).getMessage()) .isEqualTo( String.format( "Required approval 'INVALID' that is configured in code-owners.config (parameter"
diff --git a/javatests/com/google/gerrit/plugins/codeowners/config/BUILD b/javatests/com/google/gerrit/plugins/codeowners/config/BUILD index e613a56..7f1985a 100644 --- a/javatests/com/google/gerrit/plugins/codeowners/config/BUILD +++ b/javatests/com/google/gerrit/plugins/codeowners/config/BUILD
@@ -26,6 +26,7 @@ "//java/com/google/gerrit/server", "//java/com/google/gerrit/testing:gerrit-test-util", "//java/com/google/gerrit/truth", + "//lib:guava", "//lib:jgit", "//lib/truth", "//plugins/code-owners:code-owners__plugin",
diff --git a/javatests/com/google/gerrit/plugins/codeowners/config/OverrideApprovalConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/config/OverrideApprovalConfigTest.java index c1a04cb..3ea4e18 100644 --- a/javatests/com/google/gerrit/plugins/codeowners/config/OverrideApprovalConfigTest.java +++ b/javatests/com/google/gerrit/plugins/codeowners/config/OverrideApprovalConfigTest.java
@@ -14,14 +14,12 @@ package com.google.gerrit.plugins.codeowners.config; -import static com.google.common.truth.Truth.assertThat; import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat; import static com.google.gerrit.server.project.ProjectCache.illegalState; -import static com.google.gerrit.truth.OptionalSubject.assertThat; +import com.google.common.collect.ImmutableList; import com.google.gerrit.acceptance.config.GerritConfig; import com.google.gerrit.server.project.ProjectState; -import java.util.Optional; import org.eclipse.jgit.lib.Config; import org.junit.Before; import org.junit.Test; @@ -46,11 +44,11 @@ createOwnersOverrideLabel(); ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project)); - Optional<RequiredApproval> requiredApproval = + ImmutableList<RequiredApproval> requiredApproval = getRequiredApprovalConfig().get(projectState, new Config()); - assertThat(requiredApproval).isPresent(); - assertThat(requiredApproval).value().hasLabelNameThat().isEqualTo("Owners-Override"); - assertThat(requiredApproval).value().hasValueThat().isEqualTo(1); + assertThat(requiredApproval).hasSize(1); + assertThat(requiredApproval).element(0).hasLabelNameThat().isEqualTo("Owners-Override"); + assertThat(requiredApproval).element(0).hasValueThat().isEqualTo(1); } @Test
diff --git a/javatests/com/google/gerrit/plugins/codeowners/config/RequiredApprovalConfigTest.java b/javatests/com/google/gerrit/plugins/codeowners/config/RequiredApprovalConfigTest.java index e45b823..37da12d 100644 --- a/javatests/com/google/gerrit/plugins/codeowners/config/RequiredApprovalConfigTest.java +++ b/javatests/com/google/gerrit/plugins/codeowners/config/RequiredApprovalConfigTest.java
@@ -18,11 +18,10 @@ import static com.google.gerrit.plugins.codeowners.testing.RequiredApprovalSubject.assertThat; import static com.google.gerrit.server.project.ProjectCache.illegalState; import static com.google.gerrit.testing.GerritJUnit.assertThrows; -import static com.google.gerrit.truth.OptionalSubject.assertThat; +import com.google.common.collect.ImmutableList; import com.google.gerrit.acceptance.config.GerritConfig; import com.google.gerrit.server.project.ProjectState; -import java.util.Optional; import org.eclipse.jgit.lib.Config; import org.junit.Before; import org.junit.Test; @@ -45,11 +44,11 @@ @GerritConfig(name = "plugin.code-owners.requiredApproval", value = "Code-Review+2") public void getFromGlobalPluginConfig() throws Exception { ProjectState projectState = projectCache.get(project).orElseThrow(illegalState(project)); - Optional<RequiredApproval> requiredApproval = + ImmutableList<RequiredApproval> requiredApproval = getRequiredApprovalConfig().get(projectState, new Config()); - assertThat(requiredApproval).isPresent(); - assertThat(requiredApproval).value().hasLabelNameThat().isEqualTo("Code-Review"); - assertThat(requiredApproval).value().hasValueThat().isEqualTo(2); + assertThat(requiredApproval).hasSize(1); + assertThat(requiredApproval).element(0).hasLabelNameThat().isEqualTo("Code-Review"); + assertThat(requiredApproval).element(0).hasValueThat().isEqualTo(2); } @Test