diff --git a/src/main/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerBuilder.java b/src/main/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerBuilder.java index 7883b1b..049f71d 100644 --- a/src/main/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerBuilder.java +++ b/src/main/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerBuilder.java @@ -271,7 +271,10 @@ public FormValidation doCheckExcludes(@AncestorInPath AbstractProject project, @ } @POST - public FormValidation doCheckRemote(@QueryParameter String value) throws IOException { + public FormValidation doCheckRemote(@AncestorInPath AbstractProject project, @QueryParameter String value) throws IOException { + if (project != null) { + project.checkPermission(Item.CONFIGURE); + } if (value == null || value.trim().length() == 0) { throw FormValidation.error("Remote directory is mandatory."); } diff --git a/src/main/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerPublisher.java b/src/main/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerPublisher.java index 99af4f6..e3e2be8 100644 --- a/src/main/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerPublisher.java +++ b/src/main/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerPublisher.java @@ -364,7 +364,10 @@ public FormValidation doCheckExcludes(@AncestorInPath AbstractProject project, @ } @POST - public FormValidation doCheckRemote(@QueryParameter String value) throws IOException { + public FormValidation doCheckRemote(@AncestorInPath AbstractProject project, @QueryParameter String value) throws IOException { + if (project != null) { + project.checkPermission(Item.CONFIGURE); + } if (value == null || value.trim().length() == 0) { return FormValidation.error("Remote directory is mandatory."); } diff --git a/src/main/java/org/jenkinsci/plugins/artifactdeployer/DeployedArtifactsResult.java b/src/main/java/org/jenkinsci/plugins/artifactdeployer/DeployedArtifactsResult.java index f85e366..939253b 100644 --- a/src/main/java/org/jenkinsci/plugins/artifactdeployer/DeployedArtifactsResult.java +++ b/src/main/java/org/jenkinsci/plugins/artifactdeployer/DeployedArtifactsResult.java @@ -26,6 +26,7 @@ import hudson.model.Item; import org.kohsuke.stapler.StaplerRequest2; import org.kohsuke.stapler.StaplerResponse2; +import org.kohsuke.stapler.verb.POST; import jakarta.servlet.ServletException; import java.io.File; @@ -77,6 +78,7 @@ public int compare(ArtifactDeployerVO artifactDeployer1, ArtifactDeployerVO arti } @SuppressWarnings("unused") + @POST public void doDownload(final StaplerRequest2 request, final StaplerResponse2 response) throws IOException, ServletException { getOwner().checkPermission(Item.READ); diff --git a/src/test/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerBuildActionTest.java b/src/test/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerBuildActionTest.java index ddaad2f..80e4ad8 100644 --- a/src/test/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerBuildActionTest.java +++ b/src/test/java/org/jenkinsci/plugins/artifactdeployer/ArtifactDeployerBuildActionTest.java @@ -38,6 +38,13 @@ void setArtifactsInfoAndCount_shouldExposeStoredData() { assertEquals(3, action.getDeployedArtifactsCount()); } + @Test + void getDeployedArtifactsCount_withEmptyMap_shouldReturnZero() { + ArtifactDeployerBuildAction action = new ArtifactDeployerBuildAction(); + action.setArtifactsInfo(null, new java.util.HashMap<>()); + assertEquals(0, action.getDeployedArtifactsCount()); + } + @Test void getTarget_shouldCreateDeployedArtifactsResult() { ArtifactDeployerBuildAction action = new ArtifactDeployerBuildAction(); diff --git a/src/test/java/org/jenkinsci/plugins/artifactdeployer/DeployedArtifactsResultTest.java b/src/test/java/org/jenkinsci/plugins/artifactdeployer/DeployedArtifactsResultTest.java new file mode 100644 index 0000000..e2a2468 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/artifactdeployer/DeployedArtifactsResultTest.java @@ -0,0 +1,84 @@ +package org.jenkinsci.plugins.artifactdeployer; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DeployedArtifactsResultTest { + + private DeployedArtifactsResult resultFor(Map> data) { + ArtifactDeployerBuildAction action = new ArtifactDeployerBuildAction(); + action.setArtifactsInfo(null, data); + return new DeployedArtifactsResult(action); + } + + private ArtifactDeployerVO vo(int id, String remotePath) { + ArtifactDeployerVO vo = new ArtifactDeployerVO(); + vo.setId(id); + vo.setRemotePath(remotePath); + vo.setFileName(remotePath.substring(remotePath.lastIndexOf('/') + 1)); + return vo; + } + + // getAllArtifacts with empty map should return empty collection + @Test + void getAllArtifacts_emptyData_returnsEmpty() { + DeployedArtifactsResult result = resultFor(new HashMap<>()); + assertTrue(result.getAllArtifacts().isEmpty()); + } + + // getAllArtifacts should return artifacts sorted by remotePath + @Test + void getAllArtifacts_sortsByRemotePath() { + ArtifactDeployerVO z = vo(1, "/z/file.jar"); + ArtifactDeployerVO a = vo(2, "/a/file.jar"); + ArtifactDeployerVO m = vo(3, "/m/file.jar"); + + Map> data = new HashMap<>(); + data.put(1, Arrays.asList(z, a, m)); + + Collection artifacts = resultFor(data).getAllArtifacts(); + + assertEquals(3, artifacts.size()); + Iterator iter = artifacts.iterator(); + assertEquals("/a/file.jar", iter.next().getRemotePath()); + assertEquals("/m/file.jar", iter.next().getRemotePath()); + assertEquals("/z/file.jar", iter.next().getRemotePath()); + } + + // getAllArtifacts should aggregate across multiple entries in the map + @Test + void getAllArtifacts_aggregatesAcrossMultipleEntries() { + Map> data = new HashMap<>(); + data.put(1, Collections.singletonList(vo(1, "/b/b.jar"))); + data.put(2, Collections.singletonList(vo(2, "/a/a.jar"))); + + Collection artifacts = resultFor(data).getAllArtifacts(); + + assertEquals(2, artifacts.size()); + assertEquals("/a/a.jar", artifacts.iterator().next().getRemotePath()); + } + + // getAllArtifacts comparator should handle null remotePath without throwing + @Test + void getAllArtifacts_nullRemotePath_doesNotThrow() { + ArtifactDeployerVO noPath = new ArtifactDeployerVO(); + noPath.setId(1); + + Map> data = new HashMap<>(); + data.put(1, Collections.singletonList(noPath)); + + // Should not throw + Collection result = resultFor(data).getAllArtifacts(); + assertEquals(1, result.size()); + } +} diff --git a/src/test/java/org/jenkinsci/plugins/artifactdeployer/service/LocalCopyTest.java b/src/test/java/org/jenkinsci/plugins/artifactdeployer/service/LocalCopyTest.java index 4565e19..096434f 100644 --- a/src/test/java/org/jenkinsci/plugins/artifactdeployer/service/LocalCopyTest.java +++ b/src/test/java/org/jenkinsci/plugins/artifactdeployer/service/LocalCopyTest.java @@ -53,4 +53,20 @@ void copyAndGetNumbers_whenTargetIsFile_shouldWrapBuildException() throws Except assertEquals("Error on copying file.", exception.getMessage()); } + + @Test + void copyAndGetNumbers_withFlatten_shouldCopyFilesIntoTargetFlatly() throws Exception { + Path sourceDir = Files.createDirectories(tempDir.resolve("src3")); + Path subDir = Files.createDirectories(sourceDir.resolve("sub")); + Path targetDir = Files.createDirectories(tempDir.resolve("target3")); + Files.writeString(subDir.resolve("nested.txt"), "nested content"); + + FileSet fileSet = Util.createFileSet(sourceDir.toFile(), "**/*.txt", null); + LocalCopy localCopy = new LocalCopy(); + List copied = localCopy.copyAndGetNumbers(fileSet, true, targetDir.toFile()); + + assertEquals(1, copied.size()); + assertTrue(Files.exists(targetDir.resolve("nested.txt"))); + assertEquals("nested.txt", copied.get(0).getName()); + } } \ No newline at end of file