This is an automated email from the ASF dual-hosted git repository. royteeuwen pushed a commit to branch feature/sling-cli-release-automation in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git
commit e165246d7b8d011d4a86441ed5b83510a3f95ae1 Author: Roy Teeuwen <[email protected]> AuthorDate: Sat May 30 08:09:57 2026 +0200 release: add tests for create/release-jira-version and verify commands Add unit tests for CreateJiraVersionCommand, ReleaseJiraVersionCommand and VerifyReleasesCommand, covering their DRY_RUN/INTERACTIVE/AUTO behaviour. --- .../impl/release/CreateJiraVersionCommandTest.java | 133 +++++++++++++++++++++ .../release/ReleaseJiraVersionCommandTest.java | 127 ++++++++++++++++++++ .../impl/release/VerifyReleasesCommandTest.java | 127 ++++++++++++++++++++ 3 files changed, 387 insertions(+) diff --git a/src/test/java/org/apache/sling/cli/impl/release/CreateJiraVersionCommandTest.java b/src/test/java/org/apache/sling/cli/impl/release/CreateJiraVersionCommandTest.java new file mode 100644 index 0000000..06c57e2 --- /dev/null +++ b/src/test/java/org/apache/sling/cli/impl/release/CreateJiraVersionCommandTest.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.apache.sling.cli.impl.release; + +import java.util.List; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.sling.cli.impl.Command; +import org.apache.sling.cli.impl.ExecutionMode; +import org.apache.sling.cli.impl.jira.Issue; +import org.apache.sling.cli.impl.jira.Version; +import org.apache.sling.cli.impl.jira.VersionClient; +import org.apache.sling.cli.impl.junit.LogCapture; +import org.apache.sling.cli.impl.nexus.RepositoryService; +import org.apache.sling.testing.mock.osgi.junit.OsgiContext; +import org.junit.Rule; +import org.junit.Test; +import picocli.CommandLine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class CreateJiraVersionCommandTest { + + private static final String VERSION_NAME = "Apache Sling CLI Test 1.0.0"; + + @Rule + public final OsgiContext osgiContext = new OsgiContext(); + + @Rule + public final LogCapture logCapture = new LogCapture(CreateJiraVersionCommand.class); + + private VersionClient versionClient; + + private void prepare() { + versionClient = mock(VersionClient.class); + osgiContext.registerService(VersionClient.class, versionClient); + osgiContext.registerService(RepositoryService.class, mock(RepositoryService.class)); + } + + @Test + public void testDryRunCreatesNothing() throws Exception { + prepare(); + Version version = mock(Version.class); + when(version.getName()).thenReturn("1.0.0"); + when(versionClient.find(any())).thenReturn(version); + when(versionClient.findSuccessorVersion(any())).thenReturn(null); + when(versionClient.findUnresolvedIssues(any())).thenReturn(List.of()); + + Command command = createCommand(ExecutionMode.DRY_RUN, VERSION_NAME); + assertEquals(CommandLine.ExitCode.OK, (int) command.call()); + + verify(versionClient, never()).create(anyString()); + assertTrue(logCapture.containsMessage("would be created")); + } + + @Test + public void testAutoCreatesSuccessor() throws Exception { + prepare(); + Version version = mock(Version.class); + when(version.getName()).thenReturn("1.0.0"); + Version successor = mock(Version.class); + when(successor.getName()).thenReturn("1.0.2"); + when(versionClient.find(any())).thenReturn(version); + when(versionClient.findSuccessorVersion(any())).thenReturn(null, successor); + when(versionClient.findUnresolvedIssues(any())).thenReturn(List.of()); + + Command command = createCommand(ExecutionMode.AUTO, VERSION_NAME); + assertEquals(CommandLine.ExitCode.OK, (int) command.call()); + + verify(versionClient, times(1)).create(anyString()); + } + + @Test + public void testAutoMovesUnresolvedIssues() throws Exception { + prepare(); + Version version = mock(Version.class); + when(version.getName()).thenReturn("1.0.0"); + Version successor = mock(Version.class); + when(successor.getName()).thenReturn("1.0.2"); + Issue issue = mock(Issue.class); + when(issue.getKey()).thenReturn("SLING-123"); + when(issue.getSummary()).thenReturn("Some bug"); + when(versionClient.find(any())).thenReturn(version); + when(versionClient.findSuccessorVersion(any())).thenReturn(successor); + when(versionClient.findUnresolvedIssues(any())).thenReturn(List.of(issue)); + + Command command = createCommand(ExecutionMode.AUTO, VERSION_NAME); + assertEquals(CommandLine.ExitCode.OK, (int) command.call()); + + verify(versionClient, never()).create(anyString()); + verify(versionClient, times(1)).moveIssuesToNewVersion(any(), any(), any()); + } + + private Command createCommand(ExecutionMode executionMode, String jiraVersionName) throws IllegalAccessException { + CreateJiraVersionCommand createJiraVersionCommand = spy(new CreateJiraVersionCommand()); + FieldUtils.writeField(createJiraVersionCommand, "repositoryId", 123, true); + FieldUtils.writeField(createJiraVersionCommand, "jiraVersionName", jiraVersionName, true); + ReusableCLIOptions reusableCLIOptions = mock(ReusableCLIOptions.class); + FieldUtils.writeField(reusableCLIOptions, "executionMode", executionMode, true); + FieldUtils.writeField(createJiraVersionCommand, "reusableCLIOptions", reusableCLIOptions, true); + osgiContext.registerInjectActivateService(createJiraVersionCommand); + Command result = osgiContext.getService(Command.class); + assertTrue( + "Expected to retrieve the CreateJiraVersionCommand from the mocked OSGi environment.", + result instanceof CreateJiraVersionCommand); + return result; + } +} diff --git a/src/test/java/org/apache/sling/cli/impl/release/ReleaseJiraVersionCommandTest.java b/src/test/java/org/apache/sling/cli/impl/release/ReleaseJiraVersionCommandTest.java new file mode 100644 index 0000000..e80f289 --- /dev/null +++ b/src/test/java/org/apache/sling/cli/impl/release/ReleaseJiraVersionCommandTest.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.apache.sling.cli.impl.release; + +import java.util.List; +import java.util.Set; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.sling.cli.impl.Command; +import org.apache.sling.cli.impl.ExecutionMode; +import org.apache.sling.cli.impl.InputOption; +import org.apache.sling.cli.impl.UserInput; +import org.apache.sling.cli.impl.jira.Issue; +import org.apache.sling.cli.impl.jira.VersionClient; +import org.apache.sling.cli.impl.junit.LogCapture; +import org.apache.sling.cli.impl.nexus.RepositoryService; +import org.apache.sling.cli.impl.nexus.StagingRepository; +import org.apache.sling.testing.mock.osgi.junit.OsgiContext; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.MockedStatic; +import picocli.CommandLine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ReleaseJiraVersionCommandTest { + + @Rule + public final OsgiContext osgiContext = new OsgiContext(); + + @Rule + public final LogCapture logCapture = new LogCapture(ReleaseJiraVersionCommand.class); + + private RepositoryService repositoryService; + private VersionClient versionClient; + + private void prepare() throws Exception { + StagingRepository stagingRepository = mock(StagingRepository.class); + repositoryService = mock(RepositoryService.class); + when(repositoryService.find(123)).thenReturn(stagingRepository); + Set<Release> releases = + Set.of(Release.fromString("Apache Sling CLI Test 1.0.0").get(0)); + when(repositoryService.getReleases(stagingRepository)).thenReturn(releases); + + Issue issue = mock(Issue.class); + when(issue.getKey()).thenReturn("SLING-1"); + when(issue.getSummary()).thenReturn("A fixed issue"); + when(issue.getStatus()).thenReturn("Closed"); + when(issue.getResolution()).thenReturn("Fixed"); + + versionClient = mock(VersionClient.class); + when(versionClient.findFixedIssues(any())).thenReturn(List.of(issue)); + + osgiContext.registerService(RepositoryService.class, repositoryService); + osgiContext.registerService(VersionClient.class, versionClient); + } + + @Test + public void testDryRun() throws Exception { + prepare(); + Command command = createCommand(123, ExecutionMode.DRY_RUN); + assertEquals(CommandLine.ExitCode.OK, (int) command.call()); + verify(versionClient, never()).release(any()); + assertTrue(logCapture.containsMessage("The following Jira versions would be released:")); + } + + @Test + public void testInteractiveYes() throws Exception { + prepare(); + try (MockedStatic<UserInput> userInputMock = mockStatic(UserInput.class)) { + userInputMock + .when(() -> UserInput.yesNo(anyString(), any(InputOption.class))) + .thenReturn(InputOption.YES); + Command command = createCommand(123, ExecutionMode.INTERACTIVE); + assertEquals(CommandLine.ExitCode.OK, (int) command.call()); + verify(versionClient, times(1)).release(any()); + } + } + + @Test + public void testAuto() throws Exception { + prepare(); + Command command = createCommand(123, ExecutionMode.AUTO); + assertEquals(CommandLine.ExitCode.OK, (int) command.call()); + verify(versionClient, times(1)).release(any()); + } + + private Command createCommand(int repositoryId, ExecutionMode executionMode) throws Exception { + ReleaseJiraVersionCommand releaseJiraVersionCommand = spy(new ReleaseJiraVersionCommand()); + FieldUtils.writeField(releaseJiraVersionCommand, "repositoryId", repositoryId, true); + ReusableCLIOptions reusableCLIOptions = mock(ReusableCLIOptions.class); + FieldUtils.writeField(reusableCLIOptions, "executionMode", executionMode, true); + FieldUtils.writeField(releaseJiraVersionCommand, "reusableCLIOptions", reusableCLIOptions, true); + osgiContext.registerInjectActivateService(releaseJiraVersionCommand); + Command result = osgiContext.getService(Command.class); + assertTrue( + "Expected to retrieve the ReleaseJiraVersionCommand from the mocked OSGi environment.", + result instanceof ReleaseJiraVersionCommand); + return result; + } +} diff --git a/src/test/java/org/apache/sling/cli/impl/release/VerifyReleasesCommandTest.java b/src/test/java/org/apache/sling/cli/impl/release/VerifyReleasesCommandTest.java new file mode 100644 index 0000000..e7f606d --- /dev/null +++ b/src/test/java/org/apache/sling/cli/impl/release/VerifyReleasesCommandTest.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.apache.sling.cli.impl.release; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.sling.cli.impl.Command; +import org.apache.sling.cli.impl.ExecutionMode; +import org.apache.sling.cli.impl.ci.CIStatusValidator; +import org.apache.sling.cli.impl.junit.LogCapture; +import org.apache.sling.cli.impl.nexus.Artifact; +import org.apache.sling.cli.impl.nexus.LocalRepository; +import org.apache.sling.cli.impl.nexus.RepositoryService; +import org.apache.sling.cli.impl.nexus.StagingRepository; +import org.apache.sling.cli.impl.pgp.HashValidator; +import org.apache.sling.cli.impl.pgp.PGPSignatureValidator; +import org.apache.sling.testing.mock.osgi.junit.OsgiContext; +import org.junit.Rule; +import org.junit.Test; +import picocli.CommandLine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +public class VerifyReleasesCommandTest { + + @Rule + public final OsgiContext osgiContext = new OsgiContext(); + + @Rule + public final LogCapture logCapture = new LogCapture(VerifyReleasesCommand.class); + + @Test + public void testEmptyRepositoryIsValid() throws Exception { + // A downloaded repository with no artifacts runs no checks and is reported valid. + LocalRepository localRepository = mock(LocalRepository.class); + when(localRepository.getArtifacts()).thenReturn(Set.<Artifact>of()); + when(localRepository.getRootFolder()).thenReturn(Paths.get("/tmp")); + + RepositoryService repositoryService = mock(RepositoryService.class); + StagingRepository stagingRepository = mock(StagingRepository.class); + when(repositoryService.find(123)).thenReturn(stagingRepository); + when(repositoryService.download(stagingRepository)).thenReturn(localRepository); + + osgiContext.registerService(RepositoryService.class, repositoryService); + osgiContext.registerService(PGPSignatureValidator.class, mock(PGPSignatureValidator.class)); + osgiContext.registerService(CIStatusValidator.class, mock(CIStatusValidator.class)); + osgiContext.registerService(HashValidator.class, mock(HashValidator.class)); + + Command command = createCommand(123, ExecutionMode.DRY_RUN); + assertEquals(CommandLine.ExitCode.OK, (int) command.call()); + assertTrue(logCapture.containsMessage("VALID (0 checks executed)")); + } + + @Test + public void testInvalidSignatureFailsVerification() throws Exception { + // One artifact whose signature, SHA-1 and MD5 all fail -> command reports INVALID. + StagingRepository stagingRepository = mock(StagingRepository.class); + Artifact jar = + new Artifact(stagingRepository, "org.apache.sling", "org.apache.sling.cli.test", "1.0.0", null, "jar"); + LocalRepository localRepository = mock(LocalRepository.class); + when(localRepository.getArtifacts()).thenReturn(Set.of(jar)); + when(localRepository.getRootFolder()).thenReturn(Paths.get("/tmp")); + + RepositoryService repositoryService = mock(RepositoryService.class); + when(repositoryService.find(123)).thenReturn(stagingRepository); + when(repositoryService.download(stagingRepository)).thenReturn(localRepository); + + PGPSignatureValidator pgp = mock(PGPSignatureValidator.class); + PGPSignatureValidator.ValidationResult pgpResult = mock(PGPSignatureValidator.ValidationResult.class); + when(pgpResult.isValid()).thenReturn(false); + when(pgp.verify(any(Path.class), any(Path.class))).thenReturn(pgpResult); + + HashValidator hashValidator = mock(HashValidator.class); + HashValidator.ValidationResult hashResult = mock(HashValidator.ValidationResult.class); + when(hashResult.isValid()).thenReturn(false); + when(hashResult.getExpectedHash()).thenReturn("expected"); + when(hashResult.getActualHash()).thenReturn("actual"); + when(hashValidator.validate(any(Path.class), any(Path.class), any())).thenReturn(hashResult); + + osgiContext.registerService(RepositoryService.class, repositoryService); + osgiContext.registerService(PGPSignatureValidator.class, pgp); + osgiContext.registerService(CIStatusValidator.class, mock(CIStatusValidator.class)); + osgiContext.registerService(HashValidator.class, hashValidator); + + Command command = createCommand(123, ExecutionMode.DRY_RUN); + assertEquals(CommandLine.ExitCode.USAGE, (int) command.call()); + assertTrue(logCapture.containsMessage("INVALID")); + } + + private Command createCommand(int repositoryId, ExecutionMode executionMode) throws IllegalAccessException { + VerifyReleasesCommand verifyReleasesCommand = spy(new VerifyReleasesCommand()); + FieldUtils.writeField(verifyReleasesCommand, "repositoryId", repositoryId, true); + ReusableCLIOptions reusableCLIOptions = mock(ReusableCLIOptions.class); + FieldUtils.writeField(reusableCLIOptions, "executionMode", executionMode, true); + FieldUtils.writeField(verifyReleasesCommand, "reusableCLIOptions", reusableCLIOptions, true); + osgiContext.registerInjectActivateService(verifyReleasesCommand); + Command result = osgiContext.getService(Command.class); + assertTrue( + "Expected to retrieve the VerifyReleasesCommand from the mocked OSGi environment.", + result instanceof VerifyReleasesCommand); + return result; + } +}
