Skip to content

Commit

Permalink
Add tests for ChannelImpl and ChannelSession
Browse files Browse the repository at this point in the history
  • Loading branch information
spyrkob committed Aug 2, 2024
1 parent 6fb3a05 commit 52d404a
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 10 deletions.
14 changes: 7 additions & 7 deletions core/src/main/java/org/wildfly/channel/ChannelImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
class ChannelImpl implements AutoCloseable {

private static final Logger LOG = Logger.getLogger(ChannelImpl.class);
protected static final String SIGNATURE_FILE_SUFFIX = ".asc";

private Channel channelDefinition;

Expand Down Expand Up @@ -203,8 +204,7 @@ static class ResolveLatestVersionResult {
}

private ChannelManifest resolveManifest(ChannelManifestCoordinate manifestCoordinate) throws UnresolvedMavenArtifactException {
final List<URL> manifestUrls = resolveChannelMetadata(List.of(manifestCoordinate), false);
return manifestUrls
return resolveChannelMetadata(List.of(manifestCoordinate), false)
.stream()
.map(ChannelManifestMapper::from)
.findFirst().orElseThrow();
Expand Down Expand Up @@ -238,9 +238,9 @@ public List<URL> resolveChannelMetadata(List<? extends ChannelMetadataCoordinate
channels.add(coord.getUrl());
if (channelDefinition.requiresGpgCheck()) {
try {
validateGpgSignature(coord.getUrl(), new URL(coord.getUrl().toExternalForm()+".asc"));
validateGpgSignature(coord.getUrl(), new URL(coord.getUrl().toExternalForm()+ SIGNATURE_FILE_SUFFIX));
} catch (IOException e) {
throw new InvalidChannelMetadataException("Unable to download a detached signature file from: " + coord.getUrl().toExternalForm()+".asc",
throw new InvalidChannelMetadataException("Unable to download a detached signature file from: " + coord.getUrl().toExternalForm()+ SIGNATURE_FILE_SUFFIX,
List.of(e.getMessage()), e);
}
}
Expand Down Expand Up @@ -410,7 +410,7 @@ private void validateGpgSignature(String groupId, String artifactId, String exte
final ValidationResource mavenArtifact = new ValidationResource.MavenResource(groupId, artifactId, extension,
classifier, version);
try {
final File signature = resolver.resolveArtifact(groupId, artifactId, extension + ".asc",
final File signature = resolver.resolveArtifact(groupId, artifactId, extension + SIGNATURE_FILE_SUFFIX,
classifier, version);
final SignatureResult signatureResult = signatureValidator.validateSignature(
mavenArtifact, new FileInputStream(artifact), new FileInputStream(signature),
Expand All @@ -420,7 +420,7 @@ mavenArtifact, new FileInputStream(artifact), new FileInputStream(signature),
}
} catch (ArtifactTransferException | FileNotFoundException e) {
throw new SignatureValidator.SignatureException("Unable to find required signature for " + mavenArtifact,
SignatureResult.noSignature(mavenArtifact));
e, SignatureResult.noSignature(mavenArtifact));
}
}

Expand All @@ -442,7 +442,7 @@ List<ResolveArtifactResult> resolveArtifacts(List<ArtifactCoordinate> coordinate
if (channelDefinition.requiresGpgCheck()) {
try {
final List<File> signatures = resolver.resolveArtifacts(coordinates.stream()
.map(c->new ArtifactCoordinate(c.getGroupId(), c.getArtifactId(), c.getExtension() + ".asc",
.map(c->new ArtifactCoordinate(c.getGroupId(), c.getArtifactId(), c.getExtension() + SIGNATURE_FILE_SUFFIX,
c.getClassifier(), c.getVersion()))
.collect(Collectors.toList()));
for (int i = 0; i < resolvedArtifacts.size(); i++) {
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/org/wildfly/channel/ChannelSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public ChannelSession(List<Channel> channelDefinitions, MavenVersionsResolver.Fa
int versionResolutionParallelism, SignatureValidator signatureValidator) {
requireNonNull(channelDefinitions);
requireNonNull(factory);
requireNonNull(signatureValidator);

final Set<Repository> repositories = channelDefinitions.stream().flatMap(c -> c.getRepositories().stream()).collect(Collectors.toSet());
this.combinedResolver = factory.create(repositories);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.wildfly.channel.spi.MavenVersionsResolver;
import org.wildfly.channel.spi.SignatureResult;
import org.wildfly.channel.spi.SignatureValidator;
import org.wildfly.channel.spi.ValidationResource;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
Expand All @@ -37,6 +41,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.wildfly.channel.ChannelImpl.SIGNATURE_FILE_SUFFIX;

public class ChannelSessionInitTestCase {
@TempDir
Expand Down Expand Up @@ -360,6 +365,87 @@ public void duplicatedManifestIDsAreDetected() throws Exception {
assertThrows(RuntimeException.class, () -> new ChannelSession(channels, factory));
}

@Test
public void mavenManifestWithoutSignatureCausesError() throws Exception {
MavenVersionsResolver.Factory factory = mock(MavenVersionsResolver.Factory.class);
MavenVersionsResolver resolver = mock(MavenVersionsResolver.class);
when(factory.create(any())).thenReturn(resolver);

final ChannelManifest baseManifest = new ManifestBuilder()
.setId("manifest-one")
.build();
mockManifest(resolver, baseManifest, "test.channels:base-manifest:1.0.0");

// two channels providing base- and required- manifests
List<Channel> channels = List.of(new Channel.Builder()
.setName("channel one")
.addRepository("test", "test")
.setManifestCoordinate("test.channels", "base-manifest", "1.0.0")
.setGpgCheck(true)
.build()
);

when(resolver.resolveArtifact("test.channels", "base-manifest",
ChannelManifest.EXTENSION + SIGNATURE_FILE_SUFFIX, ChannelManifest.CLASSIFIER, "1.0.0"))
.thenThrow(ArtifactTransferException.class);
assertThrows(SignatureValidator.SignatureException.class, () -> new ChannelSession(channels, factory));
}

@Test
public void urlManifestWithoutSignatureCausesError() throws Exception {
MavenVersionsResolver.Factory factory = mock(MavenVersionsResolver.Factory.class);
MavenVersionsResolver resolver = mock(MavenVersionsResolver.class);
when(factory.create(any())).thenReturn(resolver);

final ChannelManifest baseManifest = new ManifestBuilder()
.setId("manifest-one")
.build();
final Path manifestFile = tempDir.resolve("test-manifest.yaml");
Files.writeString(manifestFile, ChannelManifestMapper.toYaml(baseManifest));

// two channels providing base- and required- manifests
List<Channel> channels = List.of(new Channel.Builder()
.setName("channel one")
.addRepository("test", "test")
.setManifestCoordinate(new ChannelManifestCoordinate(manifestFile.toUri().toURL()))
.setGpgCheck(true)
.build()
);

when(resolver.resolveArtifact("test.channels", "base-manifest",
ChannelManifest.EXTENSION + SIGNATURE_FILE_SUFFIX, ChannelManifest.CLASSIFIER, "1.0.0"))
.thenThrow(ArtifactTransferException.class);
assertThrows(InvalidChannelMetadataException.class, () -> new ChannelSession(channels, factory));
}

@Test
public void invalidSignatureCausesError() throws Exception {
MavenVersionsResolver.Factory factory = mock(MavenVersionsResolver.Factory.class);
MavenVersionsResolver resolver = mock(MavenVersionsResolver.class);
final SignatureValidator signatureValidator = mock(SignatureValidator.class);
when(factory.create(any())).thenReturn(resolver);

final ChannelManifest baseManifest = new ManifestBuilder()
.setId("manifest-one")
.build();
mockManifest(resolver, baseManifest, "test.channels:base-manifest:1.0.0");

// two channels providing base- and required- manifests
List<Channel> channels = List.of(new Channel.Builder()
.setName("channel one")
.addRepository("test", "test")
.setManifestCoordinate("test.channels", "base-manifest", "1.0.0")
.setGpgCheck(true)
.build()
);

when(resolver.resolveArtifact("test.channels", "base-manifest",
ChannelManifest.EXTENSION + SIGNATURE_FILE_SUFFIX, ChannelManifest.CLASSIFIER, "1.0.0"))
.thenReturn(tempDir.resolve("test-manifest.yaml.asc").toFile());
when(signatureValidator.validateSignature(any(), any(), any(), any())).thenReturn(SignatureResult.invalid(mock(ValidationResource.class)));
assertThrows(SignatureValidator.SignatureException.class, () -> new ChannelSession(channels, factory));
}

private void mockManifest(MavenVersionsResolver resolver, ChannelManifest manifest, String gav) throws IOException {
mockManifest(resolver, ChannelManifestMapper.toYaml(manifest), gav);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.wildfly.channel.spi.MavenVersionsResolver;
import org.wildfly.channel.spi.SignatureResult;
import org.wildfly.channel.spi.SignatureValidator;
import org.wildfly.channel.spi.ValidationResource;

public class ChannelSessionTestCase {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package org.wildfly.channel;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.wildfly.channel.ChannelImpl.SIGNATURE_FILE_SUFFIX;
import static org.wildfly.channel.ChannelManifestMapper.CURRENT_SCHEMA_VERSION;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.wildfly.channel.spi.MavenVersionsResolver;
import org.wildfly.channel.spi.SignatureResult;
import org.wildfly.channel.spi.SignatureValidator;
import org.wildfly.channel.spi.ValidationResource;

public class ChannelSessionWithSignatureValidationTestCase {

private static final ValidationResource.MavenResource ARTIFACT = new ValidationResource.MavenResource(
"org.wildfly", "wildfly-ee-galleon-pack", "zip", null, "25.0.1.Final");

private static final ValidationResource.MavenResource MANIFEST = new ValidationResource.MavenResource(
"org.channels", "test-manifest", ChannelManifest.EXTENSION, ChannelManifest.CLASSIFIER, "1.0.0");

@TempDir
private Path tempDir;
private MavenVersionsResolver resolver;
private SignatureValidator signatureValidator;
private MavenVersionsResolver.Factory factory;
private File resolvedArtifactFile;
private List<Channel> channels;
private File signatureFile;

@BeforeEach
public void setUp() throws Exception {
factory = mock(MavenVersionsResolver.Factory.class);
resolver = mock(MavenVersionsResolver.class);
signatureValidator = mock(SignatureValidator.class);
when(factory.create(any())).thenReturn(resolver);

// create a manfiest with a versionPattern to test signature od the latest resolved version is downloaded
final String manifest = "schemaVersion: " + CURRENT_SCHEMA_VERSION + "\n" +
"streams:\n" +
" - groupId: org.wildfly\n" +
" artifactId: '*'\n" +
" versionPattern: '25\\.\\d+\\.\\d+.Final'";
// create a channel requiring a gpg check
channels = List.of(new Channel.Builder()
.setName("channel-0")
.setGpgCheck(true)
.setManifestCoordinate(MANIFEST.groupId, MANIFEST.artifactId, MANIFEST.version)
.build());

// the resolved files need to exist otherwise we can't create streams from them
resolvedArtifactFile = tempDir.resolve("test-artifact").toFile();
Files.createFile(resolvedArtifactFile.toPath());
signatureFile = tempDir.resolve("test-signature.asc").toFile();
Files.createFile(signatureFile.toPath());


when(resolver.getAllVersions(ARTIFACT.groupId, ARTIFACT.artifactId, ARTIFACT.extension, ARTIFACT.classifier))
.thenReturn(new HashSet<>(Arrays.asList("25.0.0.Final", ARTIFACT.version)));
when(resolver.resolveArtifact(ARTIFACT.groupId, ARTIFACT.artifactId, ARTIFACT.extension, ARTIFACT.classifier, ARTIFACT.version))
.thenReturn(resolvedArtifactFile);


Path manifestFile = Files.writeString(tempDir.resolve("manifest.yaml"), manifest);
when(resolver.resolveArtifact(MANIFEST.groupId, MANIFEST.artifactId, MANIFEST.extension, MANIFEST.classifier, MANIFEST.version))
.thenReturn(manifestFile.toFile());
when(resolver.resolveArtifact(MANIFEST.groupId, MANIFEST.artifactId,
MANIFEST.extension + SIGNATURE_FILE_SUFFIX, MANIFEST.classifier, MANIFEST.version))
.thenReturn(signatureFile);
}

@Test
public void artifactWithCorrectSignatureIsValidated() throws Exception {
// return signature when resolving it from Maven repository
when(resolver.resolveArtifact(ARTIFACT.groupId, ARTIFACT.artifactId, ARTIFACT.extension + SIGNATURE_FILE_SUFFIX,
ARTIFACT.classifier, ARTIFACT.version))
.thenReturn(signatureFile);
// accept all the validation requests
when(signatureValidator.validateSignature(any(),
any(), any(), any())).thenReturn(SignatureResult.ok());


try (ChannelSession session = new ChannelSession(channels, factory, signatureValidator)) {
MavenArtifact artifact = session.resolveMavenArtifact(
ARTIFACT.groupId, ARTIFACT.artifactId, ARTIFACT.extension, ARTIFACT.classifier, null);
assertNotNull(artifact);

assertEquals(ARTIFACT.groupId, artifact.getGroupId());
assertEquals(ARTIFACT.artifactId, artifact.getArtifactId());
assertEquals(ARTIFACT.extension, artifact.getExtension());
assertNull(artifact.getClassifier());
assertEquals(ARTIFACT.version, artifact.getVersion());
assertEquals(resolvedArtifactFile, artifact.getFile());
assertEquals("channel-0", artifact.getChannelName().get());
}

// validateSignature should have been called for the manifest and the artifact
verify(signatureValidator, times(2)).validateSignature(any(), any(), any(), any());
}

@Test
public void artifactWithoutSignatureIsRejected() throws Exception {
// simulate situation where the signature file does not exist in the repository
when(resolver.resolveArtifact(ARTIFACT.groupId, ARTIFACT.artifactId, ARTIFACT.extension + SIGNATURE_FILE_SUFFIX,
ARTIFACT.classifier, ARTIFACT.version))
.thenThrow(ArtifactTransferException.class);
// accept all the validation requests
when(signatureValidator.validateSignature(any(),
any(), any(), any())).thenReturn(SignatureResult.ok());

try (ChannelSession session = new ChannelSession(channels, factory, signatureValidator)) {
assertThrows(SignatureValidator.SignatureException.class, () -> session.resolveMavenArtifact(
ARTIFACT.groupId, ARTIFACT.artifactId, ARTIFACT.extension, ARTIFACT.classifier, null));
}

// validateSignature should have been called for the manifest only
verify(signatureValidator, times(1)).validateSignature(any(), any(), any(), any());
}

@Test
public void failedSignatureValidationThrowsException() throws Exception {
// return signature when resolving it from Maven repository
when(resolver.resolveArtifact(ARTIFACT.groupId, ARTIFACT.artifactId, ARTIFACT.extension + SIGNATURE_FILE_SUFFIX,
ARTIFACT.classifier, ARTIFACT.version))
.thenReturn(signatureFile);
// simulate a valid signature of the channel manifest, and invalid signature of the artifact
when(signatureValidator.validateSignature(eq(new ValidationResource.MavenResource(
MANIFEST.groupId, MANIFEST.artifactId, MANIFEST.extension, MANIFEST.classifier, MANIFEST.version)),
any(), any(), any())).thenReturn(SignatureResult.ok());
when(signatureValidator.validateSignature(eq(ARTIFACT),
any(), any(), any())).thenReturn(SignatureResult.invalid(ARTIFACT));


try (ChannelSession session = new ChannelSession(channels, factory, signatureValidator)) {
assertThrows(SignatureValidator.SignatureException.class, () -> session.resolveMavenArtifact("org.wildfly",
"wildfly-ee-galleon-pack", "zip", null, "25.0.0.Final"));
}

// validateSignature should have been called for the manifest and the artifact
verify(signatureValidator, times(2)).validateSignature(any(), any(), any(), any());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -325,9 +325,8 @@ public void testResolveLatestMavenArtifactThrowUnresolvedMavenArtifactException(
verify(resolver, times(2)).close();
}

private void mockBlocklist(MavenVersionsResolver resolver, String blocklistFileLocation, String groupId, String artifactId, String version) throws URISyntaxException {
// when(resolver.resolveChannelMetadata(List.of(new BlocklistCoordinate("org.wildfly", "wildfly-blocklist"))))
// .thenReturn(List.of(this.getClass().getClassLoader().getResource("channels/test-blocklist.yaml")));
private void mockBlocklist(MavenVersionsResolver resolver, String blocklistFileLocation,
String groupId, String artifactId, String version) throws URISyntaxException {

if (version == null) {
when(resolver.getAllVersions(groupId, artifactId, BlocklistCoordinate.EXTENSION,
Expand Down

0 comments on commit 52d404a

Please sign in to comment.