From 9ef4e183a74ddf03f760b80a2c417be02491bce3 Mon Sep 17 00:00:00 2001 From: Steve Hill Date: Tue, 22 Aug 2023 22:13:22 -0700 Subject: [PATCH] fix: validate team name for codeowners --- build.gradle.kts | 2 + .../jenkins/github/AddTeamToCodeowners.java | 14 +++-- .../github/InMemoryTeamNameValidator.java | 48 +++++++++++++++ .../jenkins/github/TeamNameValidator.java | 20 +++++++ .../AddTeamToCodeownersScannedTest.java | 59 +++++++++++++++++++ .../github/AddTeamToCodeownersTest.java | 27 +++++++++ .../github/InMemoryTeamNameValidatorTest.java | 45 ++++++++++++++ 7 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/openrewrite/jenkins/github/InMemoryTeamNameValidator.java create mode 100644 src/main/java/org/openrewrite/jenkins/github/TeamNameValidator.java create mode 100644 src/test/java/org/openrewrite/jenkins/github/AddTeamToCodeownersScannedTest.java create mode 100644 src/test/java/org/openrewrite/jenkins/github/InMemoryTeamNameValidatorTest.java diff --git a/build.gradle.kts b/build.gradle.kts index 15d1bf6..5bd640d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,6 +23,8 @@ dependencies { testImplementation(platform("org.junit:junit-bom:latest.release")) testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.junit.jupiter:junit-jupiter-params") + testImplementation(platform("org.mockito:mockito-bom:latest.release")) + testImplementation("org.mockito:mockito-junit-jupiter") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") testImplementation("org.openrewrite:rewrite-test") diff --git a/src/main/java/org/openrewrite/jenkins/github/AddTeamToCodeowners.java b/src/main/java/org/openrewrite/jenkins/github/AddTeamToCodeowners.java index 403ac93..cff1fb6 100644 --- a/src/main/java/org/openrewrite/jenkins/github/AddTeamToCodeowners.java +++ b/src/main/java/org/openrewrite/jenkins/github/AddTeamToCodeowners.java @@ -54,7 +54,7 @@ public String getDescription() { @Override public Scanned getInitialValue(ExecutionContext ctx) { - return new Scanned(new ArtifactIdTeamNameGenerator()); + return new Scanned(new ArtifactIdTeamNameGenerator(), new InMemoryTeamNameValidator()); } @Override @@ -92,7 +92,7 @@ public Collection generate(Scanned acc, ExecutionContext c @Override public TreeVisitor getVisitor(Scanned acc) { - return new PlainTextVisitor() { + return Preconditions.check(acc.hasValidTeamName(), new PlainTextVisitor() { @Override public PlainText visitText(PlainText plainText, ExecutionContext executionContext) { if (!FILE_PATH.equals(plainText.getSourcePath().toString())) { @@ -130,17 +130,19 @@ public PlainText visitText(PlainText plainText, ExecutionContext executionContex return plainText.withText(updated); } } - }; + }); } @Data public static class Scanned { private final TeamNameGenerator generator; + private final TeamNameValidator validator; String artifactId; boolean foundFile; - public Scanned(TeamNameGenerator generator) { + public Scanned(TeamNameGenerator generator, TeamNameValidator validator) { this.generator = generator; + this.validator = validator; } boolean presentIn(String text) { @@ -160,6 +162,10 @@ boolean presentIn(String text) { String teamName() { return generator.generate(new TeamNameInput(artifactId)); } + + boolean hasValidTeamName() { + return validator.isValid(teamName()); + } } private static class ArtifactIdExtractor extends MavenIsoVisitor { diff --git a/src/main/java/org/openrewrite/jenkins/github/InMemoryTeamNameValidator.java b/src/main/java/org/openrewrite/jenkins/github/InMemoryTeamNameValidator.java new file mode 100644 index 0000000..20291b7 --- /dev/null +++ b/src/main/java/org/openrewrite/jenkins/github/InMemoryTeamNameValidator.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://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 org.openrewrite.jenkins.github; + +import org.openrewrite.internal.lang.Nullable; + +import java.util.HashSet; +import java.util.Set; + +/** + * This is a simple stopgap to prevent the same issues from recurring. + * Ideally we'd have a near real-time view of the actual teams in GitHub. + * At the moment there are over 2500, so making an API call for each + * recipe run is likely to run into trouble. + */ +class InMemoryTeamNameValidator implements TeamNameValidator { + private static final Set BANNED = banned(); + + @Override + public boolean isValid(@Nullable String name) { + return name != null && + !name.isEmpty() && + !BANNED.contains(name); + } + + /** + * Known calculated values that do not map to actual teams + */ + private static Set banned() { + Set banned = new HashSet<>(); + banned.add("@jenkinsci/custom-tools-plugin-developers"); + banned.add("@jenkinsci/-plugin-developers"); + return banned; + } +} diff --git a/src/main/java/org/openrewrite/jenkins/github/TeamNameValidator.java b/src/main/java/org/openrewrite/jenkins/github/TeamNameValidator.java new file mode 100644 index 0000000..58de96e --- /dev/null +++ b/src/main/java/org/openrewrite/jenkins/github/TeamNameValidator.java @@ -0,0 +1,20 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://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 org.openrewrite.jenkins.github; + +public interface TeamNameValidator { + boolean isValid(String name); +} diff --git a/src/test/java/org/openrewrite/jenkins/github/AddTeamToCodeownersScannedTest.java b/src/test/java/org/openrewrite/jenkins/github/AddTeamToCodeownersScannedTest.java new file mode 100644 index 0000000..70ba7bb --- /dev/null +++ b/src/test/java/org/openrewrite/jenkins/github/AddTeamToCodeownersScannedTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://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 org.openrewrite.jenkins.github; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openrewrite.jenkins.github.AddTeamToCodeowners.Scanned; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class AddTeamToCodeownersScannedTest { + @Mock + private TeamNameGenerator generator; + @Mock + private TeamNameValidator validator; + + @Test + void shouldBeInvalid() { + String teamName = "abc"; + given(generator.generate(any())).willReturn(teamName); + given(validator.isValid(teamName)).willReturn(false); + Scanned scanned = new Scanned(generator, validator); + + boolean actual = scanned.hasValidTeamName(); + + assertThat(actual).isFalse(); + } + + + @Test + void shouldBeValid() { + String teamName = "abc"; + given(generator.generate(any())).willReturn(teamName); + given(validator.isValid(teamName)).willReturn(true); + Scanned scanned = new Scanned(generator, validator); + + boolean actual = scanned.hasValidTeamName(); + + assertThat(actual).isTrue(); + } +} diff --git a/src/test/java/org/openrewrite/jenkins/github/AddTeamToCodeownersTest.java b/src/test/java/org/openrewrite/jenkins/github/AddTeamToCodeownersTest.java index ca17dac..b6146ca 100644 --- a/src/test/java/org/openrewrite/jenkins/github/AddTeamToCodeownersTest.java +++ b/src/test/java/org/openrewrite/jenkins/github/AddTeamToCodeownersTest.java @@ -193,6 +193,33 @@ void shouldNoOpIfTeamAlreadyDefinedForAll(String content) { ); } + @Test + void shouldNoOpIfInvalidTeamGenerated() { + rewriteRun( + pomXml(""" + + + org.jenkins-ci.plugins + plugin + 4.72 + + custom-tools-plugin + 0.1 + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + + + """), + text( + "* @global-owner1", + s -> s.path(".github/CODEOWNERS").noTrim() + ) + ); + } + @Test void shouldNotModifyNonCodeowners() { rewriteRun( diff --git a/src/test/java/org/openrewrite/jenkins/github/InMemoryTeamNameValidatorTest.java b/src/test/java/org/openrewrite/jenkins/github/InMemoryTeamNameValidatorTest.java new file mode 100644 index 0000000..e6617ac --- /dev/null +++ b/src/test/java/org/openrewrite/jenkins/github/InMemoryTeamNameValidatorTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * 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 + *

+ * https://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 org.openrewrite.jenkins.github; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class InMemoryTeamNameValidatorTest { + private final InMemoryTeamNameValidator validator = new InMemoryTeamNameValidator(); + + @Test + void shouldValidate() { + String input = "@jenkinsci/log-parser-plugin-developers"; + boolean actual = validator.isValid(input); + assertThat(actual).isTrue(); + } + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = { + "@jenkinsci/custom-tools-plugin-developers", // actual: @jenkinsci/customtools-plugin-developers + "@jenkinsci/-plugin-developers", // we didn't get anything for the artifactId + }) + void shouldNotValidate(String input) { + boolean actual = validator.isValid(input); + assertThat(actual).isFalse(); + } +}