Skip to content

Commit

Permalink
fix: validate team name for codeowners
Browse files Browse the repository at this point in the history
  • Loading branch information
sghill committed Aug 23, 2023
1 parent d89dfcb commit 9ef4e18
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 4 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -92,7 +92,7 @@ public Collection<? extends SourceFile> generate(Scanned acc, ExecutionContext c

@Override
public TreeVisitor<?, ExecutionContext> getVisitor(Scanned acc) {
return new PlainTextVisitor<ExecutionContext>() {
return Preconditions.check(acc.hasValidTeamName(), new PlainTextVisitor<ExecutionContext>() {
@Override
public PlainText visitText(PlainText plainText, ExecutionContext executionContext) {
if (!FILE_PATH.equals(plainText.getSourcePath().toString())) {
Expand Down Expand Up @@ -130,17 +130,19 @@ public PlainText visitText(PlainText plainText, ExecutionContext executionContex
return plainText.withText(updated);
}
}
};
});
}

@Data
public static class Scanned {
private final TeamNameGenerator<TeamNameInput> generator;
private final TeamNameValidator validator;
String artifactId;
boolean foundFile;

public Scanned(TeamNameGenerator<TeamNameInput> generator) {
public Scanned(TeamNameGenerator<TeamNameInput> generator, TeamNameValidator validator) {
this.generator = generator;
this.validator = validator;
}

boolean presentIn(String text) {
Expand All @@ -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<ExecutionContext> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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<String> 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<String> banned() {
Set<String> banned = new HashSet<>();
banned.add("@jenkinsci/custom-tools-plugin-developers");
banned.add("@jenkinsci/-plugin-developers");
return banned;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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<TeamNameInput> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,33 @@ void shouldNoOpIfTeamAlreadyDefinedForAll(String content) {
);
}

@Test
void shouldNoOpIfInvalidTeamGenerated() {
rewriteRun(
pomXml("""
<project>
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>4.72</version>
</parent>
<artifactId>custom-tools-plugin</artifactId>
<version>0.1</version>
<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
</project>
"""),
text(
"* @global-owner1",
s -> s.path(".github/CODEOWNERS").noTrim()
)
);
}

@Test
void shouldNotModifyNonCodeowners() {
rewriteRun(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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();
}
}

0 comments on commit 9ef4e18

Please sign in to comment.