-
-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Plugin description migration probe (#475)
- Loading branch information
Showing
4 changed files
with
363 additions
and
14 deletions.
There are no files selected for viewing
92 changes: 92 additions & 0 deletions
92
...src/main/java/io/jenkins/pluginhealth/scoring/probes/PluginDescriptionMigrationProbe.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/* | ||
* 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.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.Optional; | ||
import java.util.stream.Stream; | ||
|
||
import io.jenkins.pluginhealth.scoring.model.Plugin; | ||
import io.jenkins.pluginhealth.scoring.model.ProbeResult; | ||
|
||
import org.springframework.core.annotation.Order; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
@Order(PluginDescriptionMigrationProbe.ORDER) | ||
public class PluginDescriptionMigrationProbe extends Probe { | ||
public static final String KEY = "description-migration"; | ||
public static final int ORDER = DocumentationMigrationProbe.ORDER + 100; | ||
|
||
@Override | ||
protected ProbeResult doApply(Plugin plugin, ProbeContext context) { | ||
final Optional<Path> scmRepositoryOpt = context.getScmRepository(); | ||
if (scmRepositoryOpt.isEmpty()) { | ||
return error("Cannot access plugin repository."); | ||
} | ||
|
||
final Path repository = scmRepositoryOpt.get(); | ||
final Path pluginFolder = context.getScmFolderPath().map(repository::resolve).orElse(repository); | ||
|
||
try (Stream<Path> files = Files.find( | ||
pluginFolder.resolve("src").resolve("main").resolve("resources"), | ||
1, | ||
(path, attributes) -> "index.jelly".equals(path.getFileName().toString()) | ||
)) { | ||
final Optional<Path> jellyFileOpt = files.findFirst(); | ||
if (jellyFileOpt.isEmpty()) { | ||
return success("There is no `index.jelly` file in `src/main/resources`."); | ||
} | ||
final Path jellyFile = jellyFileOpt.get(); | ||
return Files.readAllLines(jellyFile).stream().map(String::trim).anyMatch(s -> s.contains("TODO")) ? | ||
success("Plugin is using description from the plugin archetype.") : | ||
success("Plugin seems to have a correct description."); | ||
} catch (IOException e) { | ||
return error("Cannot browse plugin source code folder."); | ||
} | ||
} | ||
|
||
@Override | ||
public String key() { | ||
return KEY; | ||
} | ||
|
||
@Override | ||
public String getDescription() { | ||
return "Checks if the plugin description is located in the `src/main/resources/index.jelly` file."; | ||
} | ||
|
||
@Override | ||
public long getVersion() { | ||
return 1; | ||
} | ||
|
||
@Override | ||
protected boolean requiresRelease() { | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
200 changes: 200 additions & 0 deletions
200
...test/java/io/jenkins/pluginhealth/scoring/probes/PluginDescriptionMigrationProbeTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
/* | ||
* 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.Assertions.assertThat; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.spy; | ||
import static org.mockito.Mockito.when; | ||
|
||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.Optional; | ||
|
||
import io.jenkins.pluginhealth.scoring.model.Plugin; | ||
import io.jenkins.pluginhealth.scoring.model.ProbeResult; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
public class PluginDescriptionMigrationProbeTest extends AbstractProbeTest<PluginDescriptionMigrationProbe> { | ||
@Override | ||
PluginDescriptionMigrationProbe getSpy() { | ||
return spy(PluginDescriptionMigrationProbe.class); | ||
} | ||
|
||
@Test | ||
void shouldRequireRelease() { | ||
assertThat(getSpy().requiresRelease()).isTrue(); | ||
} | ||
|
||
@Test | ||
void shouldRequiresPluginRepository() { | ||
final Plugin plugin = mock(Plugin.class); | ||
final ProbeContext ctx = mock(ProbeContext.class); | ||
|
||
final PluginDescriptionMigrationProbe probe = getSpy(); | ||
final ProbeResult result = probe.apply(plugin, ctx); | ||
|
||
assertThat(result) | ||
.usingRecursiveComparison() | ||
.comparingOnlyFields("id", "status", "message") | ||
.isEqualTo(ProbeResult.error(PluginDescriptionMigrationProbe.KEY, "Cannot access plugin repository.", 0)); | ||
} | ||
|
||
@Test | ||
void shouldFailWhenPluginRepositoryIsEmpty() throws Exception { | ||
final Plugin plugin = mock(Plugin.class); | ||
final ProbeContext ctx = mock(ProbeContext.class); | ||
|
||
final Path repository = Files.createTempDirectory(getClass().getSimpleName()); | ||
when(ctx.getScmRepository()).thenReturn(Optional.of(repository)); | ||
|
||
final PluginDescriptionMigrationProbe probe = getSpy(); | ||
final ProbeResult result = probe.apply(plugin, ctx); | ||
|
||
assertThat(result) | ||
.usingRecursiveComparison() | ||
.comparingOnlyFields("id", "message", "status") | ||
.isEqualTo(ProbeResult.error(PluginDescriptionMigrationProbe.KEY, "Cannot browse plugin source code folder.", 0)); | ||
} | ||
|
||
@Test | ||
void shouldDetectMissingJellyFile() throws Exception { | ||
final Plugin plugin = mock(Plugin.class); | ||
final ProbeContext ctx = mock(ProbeContext.class); | ||
|
||
final Path repository = Files.createTempDirectory(getClass().getSimpleName()); | ||
Files.createDirectories(repository.resolve("src").resolve("main").resolve("resources")); | ||
when(ctx.getScmRepository()).thenReturn(Optional.of(repository)); | ||
|
||
final PluginDescriptionMigrationProbe probe = getSpy(); | ||
final ProbeResult result = probe.apply(plugin, ctx); | ||
|
||
assertThat(result) | ||
.usingRecursiveComparison() | ||
.comparingOnlyFields("id", "message", "status") | ||
.isEqualTo(ProbeResult.success(PluginDescriptionMigrationProbe.KEY, "There is no `index.jelly` file in `src/main/resources`.", 0)); | ||
} | ||
|
||
@Test | ||
void shouldDetectExampleJellyFile() throws Exception { | ||
final Plugin plugin = mock(Plugin.class); | ||
final ProbeContext ctx = mock(ProbeContext.class); | ||
|
||
final Path repository = Files.createTempDirectory(getClass().getSimpleName()); | ||
final Path resources = Files.createDirectories(repository.resolve("src").resolve("main").resolve("resources")); | ||
final Path jelly = Files.createFile(resources.resolve("index.jelly")); | ||
Files.writeString(jelly, """ | ||
<div> | ||
TODO | ||
</div> | ||
"""); | ||
when(ctx.getScmRepository()).thenReturn(Optional.of(repository)); | ||
|
||
final PluginDescriptionMigrationProbe probe = getSpy(); | ||
final ProbeResult result = probe.apply(plugin, ctx); | ||
|
||
assertThat(result) | ||
.usingRecursiveComparison() | ||
.comparingOnlyFields("id", "message", "status") | ||
.isEqualTo(ProbeResult.success(PluginDescriptionMigrationProbe.KEY, "Plugin is using description from the plugin archetype.", 0)); | ||
} | ||
|
||
@Test | ||
void shouldDetectExampleJellyFileWithModule() throws Exception { | ||
final Plugin plugin = mock(Plugin.class); | ||
final ProbeContext ctx = mock(ProbeContext.class); | ||
|
||
final Path repository = Files.createTempDirectory(getClass().getSimpleName()); | ||
final Path module = Files.createDirectory(repository.resolve("plugin")); | ||
final Path resources = Files.createDirectories(module.resolve("src").resolve("main").resolve("resources")); | ||
final Path jelly = Files.createFile(resources.resolve("index.jelly")); | ||
Files.writeString(jelly, """ | ||
<div> | ||
TODO | ||
</div> | ||
"""); | ||
when(ctx.getScmRepository()).thenReturn(Optional.of(repository)); | ||
when(ctx.getScmFolderPath()).thenReturn(Optional.of(module)); | ||
|
||
final PluginDescriptionMigrationProbe probe = getSpy(); | ||
final ProbeResult result = probe.apply(plugin, ctx); | ||
|
||
assertThat(result) | ||
.usingRecursiveComparison() | ||
.comparingOnlyFields("id", "message", "status") | ||
.isEqualTo(ProbeResult.success(PluginDescriptionMigrationProbe.KEY, "Plugin is using description from the plugin archetype.", 0)); | ||
} | ||
|
||
@Test | ||
void shouldDetectCorrectJellyFile() throws Exception { | ||
final Plugin plugin = mock(Plugin.class); | ||
final ProbeContext ctx = mock(ProbeContext.class); | ||
|
||
final Path repository = Files.createTempDirectory(getClass().getSimpleName()); | ||
final Path resources = Files.createDirectories(repository.resolve("src").resolve("main").resolve("resources")); | ||
final Path jelly = Files.createFile(resources.resolve("index.jelly")); | ||
Files.writeString(jelly, """ | ||
<div> | ||
This is a plugin doing something. | ||
</div> | ||
"""); | ||
when(ctx.getScmRepository()).thenReturn(Optional.of(repository)); | ||
|
||
final PluginDescriptionMigrationProbe probe = getSpy(); | ||
final ProbeResult result = probe.apply(plugin, ctx); | ||
|
||
assertThat(result) | ||
.usingRecursiveComparison() | ||
.comparingOnlyFields("id", "message", "status") | ||
.isEqualTo(ProbeResult.success(PluginDescriptionMigrationProbe.KEY, "Plugin seems to have a correct description.", 0)); | ||
} | ||
|
||
@Test | ||
void shouldDetectCorrectJellyFileWithModule() throws Exception { | ||
final Plugin plugin = mock(Plugin.class); | ||
final ProbeContext ctx = mock(ProbeContext.class); | ||
|
||
final Path repository = Files.createTempDirectory(getClass().getSimpleName()); | ||
final Path module = Files.createDirectory(repository.resolve("plugin")); | ||
final Path resources = Files.createDirectories(module.resolve("src").resolve("main").resolve("resources")); | ||
final Path jelly = Files.createFile(resources.resolve("index.jelly")); | ||
Files.writeString(jelly, """ | ||
<div> | ||
This is a plugin doing something. | ||
</div> | ||
"""); | ||
when(ctx.getScmRepository()).thenReturn(Optional.of(repository)); | ||
when(ctx.getScmFolderPath()).thenReturn(Optional.of(module)); | ||
|
||
final PluginDescriptionMigrationProbe probe = getSpy(); | ||
final ProbeResult result = probe.apply(plugin, ctx); | ||
|
||
assertThat(result) | ||
.usingRecursiveComparison() | ||
.comparingOnlyFields("id", "message", "status") | ||
.isEqualTo(ProbeResult.success(PluginDescriptionMigrationProbe.KEY, "Plugin seems to have a correct description.", 0)); | ||
} | ||
} |
Oops, something went wrong.