Skip to content

Commit

Permalink
Detects if a plugin repository is archived or not (#572)
Browse files Browse the repository at this point in the history
  • Loading branch information
alecharp authored Dec 4, 2024
1 parent f9ed750 commit 79148a9
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* MIT License
*
* Copyright (c) 2024 Jenkins Infra
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.jenkins.pluginhealth.scoring.probes;

import java.io.IOException;
import java.util.Optional;

import io.jenkins.pluginhealth.scoring.model.Plugin;
import io.jenkins.pluginhealth.scoring.model.ProbeResult;

import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GitHub;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(value = RepositoryArchivedStatusProbe.ORDER)
public class RepositoryArchivedStatusProbe extends Probe {
public static final int ORDER = SCMLinkValidationProbe.ORDER + 100;
public static final String KEY = "repository-archived";

@Override
protected ProbeResult doApply(Plugin plugin, ProbeContext context) {
if (plugin.getScm() == null) {
return this.error("Plugin SCM is unknown, cannot fetch the number of open pull requests.");
}
final GitHub gh = context.getGitHub();
final Optional<String> repositoryName = context.getRepositoryName();
if (repositoryName.isEmpty()) {
return this.error("Cannot find repository for " + plugin.getName());
}

try {
final GHRepository repository = gh.getRepository(repositoryName.get());
return this.success("%b".formatted(repository.isArchived()));
} catch (IOException e) {
return this.error("Cannot access repository " + repositoryName.get());
}
}

@Override
public String key() {
return KEY;
}

@Override
public String getDescription() {
return "Learn if the plugin repository is archived or not.";
}

@Override
public long getVersion() {
return 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package io.jenkins.pluginhealth.scoring.scores;

import java.util.List;
Expand All @@ -32,6 +31,7 @@
import io.jenkins.pluginhealth.scoring.model.Resolution;
import io.jenkins.pluginhealth.scoring.model.ScoringComponentResult;
import io.jenkins.pluginhealth.scoring.probes.DeprecatedPluginProbe;
import io.jenkins.pluginhealth.scoring.probes.RepositoryArchivedStatusProbe;

import org.springframework.stereotype.Component;

Expand All @@ -43,42 +43,73 @@ public class DeprecatedPluginScoring extends Scoring {
@Override
public List<ScoringComponent> getComponents() {
return List.of(
new ScoringComponent() {
@Override
public String getDescription() {
return "The plugin must not be marked as deprecated.";
}
new ScoringComponent() {
@Override
public String getDescription() {
return "The plugin must not be marked as deprecated.";
}

@Override
public ScoringComponentResult getScore(Plugin $, Map<String, ProbeResult> probeResults) {
final ProbeResult probeResult = probeResults.get(DeprecatedPluginProbe.KEY);
if (probeResult == null) {
return new ScoringComponentResult(
0,
getWeight(),
List.of("Cannot determine if the plugin is marked as deprecated or not."));
}

@Override
public ScoringComponentResult getScore(Plugin $, Map<String, ProbeResult> probeResults) {
final ProbeResult probeResult = probeResults.get(DeprecatedPluginProbe.KEY);
if (probeResult == null) {
return new ScoringComponentResult(0, getWeight(), List.of("Cannot determine if the plugin is marked as deprecated or not."));
return switch (probeResult.message()) {
case "This plugin is marked as deprecated." -> new ScoringComponentResult(
0,
getWeight(),
List.of("Plugin is marked as deprecated."),
List.of(
new Resolution(
"See deprecation guidelines",
"https://www.jenkins.io/doc/developer/plugin-governance/deprecating-or-removing-plugin/")));
case "This plugin is NOT deprecated." -> new ScoringComponentResult(
100, 0, List.of("Plugin is not marked as deprecated."));
default -> new ScoringComponentResult(
0,
getWeight(),
List.of(
"Cannot determine if the plugin is marked as deprecated or not.",
probeResult.message()));
};
}

return switch (probeResult.message()) {
case "This plugin is marked as deprecated." ->
new ScoringComponentResult(
0,
getWeight(),
List.of("Plugin is marked as deprecated."),
List.of(
new Resolution("See deprecation guidelines", "https://www.jenkins.io/doc/developer/plugin-governance/deprecating-or-removing-plugin/")
)
);
case "This plugin is NOT deprecated." ->
new ScoringComponentResult(100, getWeight(), List.of("Plugin is not marked as deprecated."));
default ->
new ScoringComponentResult(0, getWeight(), List.of("Cannot determine if the plugin is marked as deprecated or not.", probeResult.message()));
};
}
@Override
public int getWeight() {
return 1;
}
},
new ScoringComponent() {
@Override
public String getDescription() {
return "The plugin's repository must not be archived";
}

@Override
public int getWeight() {
return 1;
}
}
);
@Override
public ScoringComponentResult getScore(Plugin plugin, Map<String, ProbeResult> probeResults) {
final ProbeResult probeResult = probeResults.get(RepositoryArchivedStatusProbe.KEY);
if (probeResult == null || ProbeResult.Status.ERROR.equals(probeResult.status())) {
return new ScoringComponentResult(
-100, 100, List.of("Cannot determine if the repository is archived or not."));
}

final boolean isArchived = Boolean.parseBoolean(probeResult.message());
return isArchived
? new ScoringComponentResult(
0, getWeight(), List.of("The plugin repository is archived."))
: new ScoringComponentResult(100, 0, List.of("The repository is not archived."));
}

@Override
public int getWeight() {
return 1;
}
});
}

@Override
Expand All @@ -98,6 +129,6 @@ public String description() {

@Override
public int version() {
return 3;
return 4;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public Function<Set<ScoringComponentResult>, ScoreResult> finisher() {
.sum();
return new ScoreResult(
key(),
(int) Math.max(0, Math.round(sum / weight)),
weight == 0 ? 100 : (int) Math.max(0, Math.round(sum / weight)),
weight(),
changelogResults,
version());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* MIT License
*
* Copyright (c) 2024 Jenkins Infra
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package io.jenkins.pluginhealth.scoring.probes;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

import java.util.Optional;

import io.jenkins.pluginhealth.scoring.model.Plugin;
import io.jenkins.pluginhealth.scoring.model.ProbeResult;

import org.junit.jupiter.api.Test;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GitHub;

class RepositoryArchivedStatusProbeTest extends AbstractProbeTest<RepositoryArchivedStatusProbe> {

@Override
RepositoryArchivedStatusProbe getSpy() {
return spy(RepositoryArchivedStatusProbe.class);
}

@Test
void shouldNotRequireRelease() {
assertFalse(getSpy().requiresRelease());
}

@Test
void shouldNotBeRelatedToSourceCode() {
assertFalse(getSpy().requiresRelease());
}

@Test
void shouldFailWithNoSCM() {
final Plugin pl = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);

final RepositoryArchivedStatusProbe probe = getSpy();

assertThat(probe.apply(pl, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "status", "message")
.isEqualTo(ProbeResult.error(
RepositoryArchivedStatusProbe.KEY,
"Plugin SCM is unknown, cannot fetch the number of open pull requests.",
1));
}

@Test
void shouldFailWithNoRepositoryName() {
final Plugin pl = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);

when(pl.getName()).thenReturn("_test_");
when(pl.getScm()).thenReturn("valid-url");
when(ctx.getRepositoryName()).thenReturn(Optional.empty());

final RepositoryArchivedStatusProbe probe = getSpy();

assertThat(probe.apply(pl, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "status", "message")
.isEqualTo(
ProbeResult.error(RepositoryArchivedStatusProbe.KEY, "Cannot find repository for _test_", 1));
}

@Test
void shouldSucceedToFindArchivedRepository() throws Exception {
final Plugin pl = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);
final GitHub gh = mock(GitHub.class);
final GHRepository ghRepository = mock(GHRepository.class);

when(pl.getName()).thenReturn("_test_");
when(pl.getScm()).thenReturn("valid-url");
when(ctx.getRepositoryName()).thenReturn(Optional.of("jenkinsci/_test_"));

when(ctx.getGitHub()).thenReturn(gh);
when(gh.getRepository(anyString())).thenReturn(ghRepository);

when(ghRepository.isArchived()).thenReturn(true);

final RepositoryArchivedStatusProbe probe = getSpy();

assertThat(probe.apply(pl, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "status", "message")
.isEqualTo(ProbeResult.success(RepositoryArchivedStatusProbe.KEY, "true", 1));
}

@Test
void shouldSucceedToFindNotArchivedRepository() throws Exception {
final Plugin pl = mock(Plugin.class);
final ProbeContext ctx = mock(ProbeContext.class);
final GitHub gh = mock(GitHub.class);
final GHRepository ghRepository = mock(GHRepository.class);

when(pl.getName()).thenReturn("_test_");
when(pl.getScm()).thenReturn("valid-url");
when(ctx.getRepositoryName()).thenReturn(Optional.of("jenkinsci/_test_"));

when(ctx.getGitHub()).thenReturn(gh);
when(gh.getRepository(anyString())).thenReturn(ghRepository);

when(ghRepository.isArchived()).thenReturn(false);

final RepositoryArchivedStatusProbe probe = getSpy();

assertThat(probe.apply(pl, ctx))
.usingRecursiveComparison()
.comparingOnlyFields("id", "status", "message")
.isEqualTo(ProbeResult.success(RepositoryArchivedStatusProbe.KEY, "false", 1));
}
}
Loading

0 comments on commit 79148a9

Please sign in to comment.