You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by cs...@apache.org on 2026/08/31 13:54:11 UTC
(maven-resolver) branch maven-resolver-1.9.x updated: Compose path segments from repository ids on the 1.9.x line (#2101)
This is an automated email from the ASF dual-hosted git repository.
cstamas pushed a commit to branch maven-resolver-1.9.x
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
The following commit(s) were added to refs/heads/maven-resolver-1.9.x by this push:
new 7df893e1b Compose path segments from repository ids on the 1.9.x line (#2101)
7df893e1b is described below
commit 7df893e1b7a2a88d0f0d9c4c367e41fb2bd0987d
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Mon Aug 31 15:54:05 2026 +0200
Compose path segments from repository ids on the 1.9.x line (#2101)
The 1.9.x line predates the repository key function layer, so the split local repository prefix (`LocalPathPrefixComposerFactorySupport`, four sites) and the trusted checksums sources (`SummaryFileTrustedChecksumsSource`, `SparseDirectoryTrustedChecksumsSource`) splice the raw repository id into paths. This routes those ids through `PathUtils.stringToPathSegment` and validates the result, matching the 2.x behaviour after #2089 and #2099. Also ports `GAECVNameMapperTest` from master; ` [...]
Repository ids that are exactly `.`/`..` or contain path separators now compose to a neutralized segment (e.g. `-DOTDOT-`) instead of being spliced verbatim; well-formed ids are unaffected.
---
.../LocalPathPrefixComposerFactorySupport.java | 19 ++-
.../SparseDirectoryTrustedChecksumsSource.java | 7 +-
.../SummaryFileTrustedChecksumsSource.java | 7 +-
.../DefaultLocalPathPrefixComposerFactoryTest.java | 24 ++++
.../SparseDirectoryTrustedChecksumsSourceTest.java | 45 ++++++
.../SummaryFileTrustedChecksumsSourceTest.java | 46 +++++++
.../synccontext/named/GAECVNameMapperTest.java | 153 +++++++++++++++++++++
7 files changed, 295 insertions(+), 6 deletions(-)
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java
index cf1587bda..89f3c4732 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/LocalPathPrefixComposerFactorySupport.java
@@ -23,6 +23,7 @@ import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.metadata.Metadata;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.util.ConfigUtils;
+import org.eclipse.aether.util.PathUtils;
/**
* Support class for {@link LocalPathPrefixComposerFactory} implementations: it predefines and makes re-usable
@@ -175,13 +176,13 @@ public abstract class LocalPathPrefixComposerFactorySupport implements LocalPath
}
String result = remotePrefix;
if (!splitRemoteRepositoryLast && splitRemoteRepository) {
- result += "/" + repository.getId();
+ result += "/" + repositoryIdSegment(repository);
}
if (splitRemote) {
result += "/" + (artifact.isSnapshot() ? snapshotsPrefix : releasesPrefix);
}
if (splitRemoteRepositoryLast && splitRemoteRepository) {
- result += "/" + repository.getId();
+ result += "/" + repositoryIdSegment(repository);
}
return result;
}
@@ -205,13 +206,13 @@ public abstract class LocalPathPrefixComposerFactorySupport implements LocalPath
}
String result = remotePrefix;
if (!splitRemoteRepositoryLast && splitRemoteRepository) {
- result += "/" + repository.getId();
+ result += "/" + repositoryIdSegment(repository);
}
if (splitRemote) {
result += "/" + (isSnapshot(metadata) ? snapshotsPrefix : releasesPrefix);
}
if (splitRemoteRepositoryLast && splitRemoteRepository) {
- result += "/" + repository.getId();
+ result += "/" + repositoryIdSegment(repository);
}
return result;
}
@@ -219,5 +220,15 @@ public abstract class LocalPathPrefixComposerFactorySupport implements LocalPath
protected boolean isSnapshot(Metadata metadata) {
return !metadata.getVersion().isEmpty() && metadata.getVersion().endsWith("-SNAPSHOT");
}
+
+ /**
+ * Defense in depth: the repository id is spliced into the local repository path prefix, so it
+ * must be a safe path segment.
+ */
+ private String repositoryIdSegment(RemoteRepository repository) {
+ String id = PathUtils.stringToPathSegment(repository.getId());
+ PathUtils.validatePathComponent(id, "repository id");
+ return id;
+ }
}
}
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java
index 039d2d2f2..ec786587a 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSource.java
@@ -36,6 +36,7 @@ import org.eclipse.aether.internal.impl.LocalPathComposer;
import org.eclipse.aether.repository.ArtifactRepository;
import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
import org.eclipse.aether.spi.io.FileProcessor;
+import org.eclipse.aether.util.PathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -128,7 +129,11 @@ public final class SparseDirectoryTrustedChecksumsSource extends FileTrustedChec
String path = localPathComposer.getPathForArtifact(artifact, false) + "."
+ checksumAlgorithmFactory.getFileExtension();
if (originAware) {
- path = artifactRepository.getId() + "/" + path;
+ String safeRepositoryId = PathUtils.stringToPathSegment(artifactRepository.getId());
+ // defense in depth: the repository id is spliced into a path under the checksums basedir,
+ // so it must be a safe path segment
+ PathUtils.validatePathComponent(safeRepositoryId, "repository id");
+ path = safeRepositoryId + "/" + path;
}
return path;
}
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java
index 870fcec6c..be6482018 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSource.java
@@ -44,6 +44,7 @@ import org.eclipse.aether.internal.impl.LocalPathComposer;
import org.eclipse.aether.repository.ArtifactRepository;
import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
import org.eclipse.aether.util.FileUtils;
+import org.eclipse.aether.util.PathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -154,7 +155,11 @@ public final class SummaryFileTrustedChecksumsSource extends FileTrustedChecksum
private Path summaryFile(Path basedir, boolean originAware, String repositoryId, String checksumExtension) {
String fileName = CHECKSUMS_FILE_PREFIX;
if (originAware) {
- fileName += "-" + repositoryId;
+ String safeRepositoryId = PathUtils.stringToPathSegment(repositoryId);
+ // defense in depth: the repository id is spliced into the summary file name,
+ // so it must be a safe path segment
+ PathUtils.validatePathComponent(safeRepositoryId, "repository id");
+ fileName += "-" + safeRepositoryId;
}
return basedir.resolve(fileName + "." + checksumExtension);
}
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactoryTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactoryTest.java
index cee4892bc..149ad636a 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactoryTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultLocalPathPrefixComposerFactoryTest.java
@@ -225,4 +225,28 @@ public class DefaultLocalPathPrefixComposerFactoryTest {
assertNotNull(prefix);
assertEquals("cached/my-repo/releases", prefix);
}
+
+ @Test
+ public void dotDotRepositoryIdIsNeutralizedInSplitPrefix() {
+ DefaultRepositorySystemSession session = TestUtils.newSession();
+ session.setConfigProperty("aether.enhancedLocalRepository.split", Boolean.TRUE.toString());
+ session.setConfigProperty("aether.enhancedLocalRepository.splitRemoteRepository", Boolean.TRUE.toString());
+
+ LocalPathPrefixComposerFactory factory = new DefaultLocalPathPrefixComposerFactory();
+ LocalPathPrefixComposer composer = factory.createComposer(session);
+ assertNotNull(composer);
+
+ // a repository id of ".." must be neutralized into a harmless path segment rather than spliced
+ // into the prefix as-is
+ RemoteRepository dotDotRepository =
+ new RemoteRepository.Builder("..", "default", "https://repo.example.org/").build();
+
+ String prefix = composer.getPathPrefixForRemoteArtifact(releaseArtifact, dotDotRepository);
+ assertNotNull(prefix);
+ assertEquals("cached/-DOTDOT-", prefix);
+
+ prefix = composer.getPathPrefixForRemoteMetadata(releaseMetadata, dotDotRepository);
+ assertNotNull(prefix);
+ assertEquals("cached/-DOTDOT-", prefix);
+ }
}
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java
index 0dd0a0705..b6b017ec1 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SparseDirectoryTrustedChecksumsSourceTest.java
@@ -18,10 +18,25 @@
*/
package org.eclipse.aether.internal.impl.checksum;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.impl.RepositorySystemLifecycle;
import org.eclipse.aether.internal.impl.DefaultFileProcessor;
import org.eclipse.aether.internal.impl.DefaultLocalPathComposer;
+import org.eclipse.aether.internal.test.util.TestUtils;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.spi.checksums.TrustedChecksumsSource;
+import org.junit.Test;
+
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
public class SparseDirectoryTrustedChecksumsSourceTest extends FileTrustedChecksumsSourceTestSupport {
@Override
@@ -33,4 +48,34 @@ public class SparseDirectoryTrustedChecksumsSourceTest extends FileTrustedChecks
protected void enableSource(DefaultRepositorySystemSession session) {
session.setConfigProperty("aether.trustedChecksumsSource.sparseDirectory", Boolean.TRUE.toString());
}
+
+ @Test
+ public void dotDotRepositoryIdIsNeutralized() throws Exception {
+ DefaultRepositorySystemSession session = TestUtils.newSession();
+ enableSource(session);
+
+ SparseDirectoryTrustedChecksumsSource source =
+ new SparseDirectoryTrustedChecksumsSource(new DefaultFileProcessor(), new DefaultLocalPathComposer());
+
+ TrustedChecksumsSource.Writer writer = source.getTrustedArtifactChecksumsWriter(session);
+ assertNotNull(writer);
+
+ // a repository id of ".." must be neutralized into a harmless path segment rather than spliced
+ // into the checksums basedir path as-is
+ RemoteRepository dotDotRepository =
+ new RemoteRepository.Builder("..", "default", "https://repo.example.org/").build();
+ Artifact artifact = new DefaultArtifact("test:test:1.0");
+ Sha1ChecksumAlgorithmFactory checksumAlgorithmFactory = new Sha1ChecksumAlgorithmFactory();
+
+ writer.addTrustedArtifactChecksums(
+ artifact,
+ dotDotRepository,
+ singletonList(checksumAlgorithmFactory),
+ singletonMap(checksumAlgorithmFactory.getName(), "000"));
+
+ Path checksumsBasedir =
+ session.getLocalRepository().getBasedir().toPath().resolve(".checksums");
+ assertTrue(Files.isRegularFile(checksumsBasedir.resolve("-DOTDOT-/test/test/1.0/test-1.0.jar.sha1")));
+ assertFalse(Files.exists(checksumsBasedir.getParent().resolve("test/test/1.0/test-1.0.jar.sha1")));
+ }
}
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java
index 403f1cb9c..2727aea55 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/checksum/SummaryFileTrustedChecksumsSourceTest.java
@@ -18,9 +18,24 @@
*/
package org.eclipse.aether.internal.impl.checksum;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.impl.RepositorySystemLifecycle;
import org.eclipse.aether.internal.impl.DefaultLocalPathComposer;
+import org.eclipse.aether.internal.impl.DefaultRepositorySystemLifecycle;
+import org.eclipse.aether.internal.test.util.TestUtils;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.spi.checksums.TrustedChecksumsSource;
+import org.junit.Test;
+
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
public class SummaryFileTrustedChecksumsSourceTest extends FileTrustedChecksumsSourceTestSupport {
@Override
@@ -32,4 +47,35 @@ public class SummaryFileTrustedChecksumsSourceTest extends FileTrustedChecksumsS
protected void enableSource(DefaultRepositorySystemSession session) {
session.setConfigProperty("aether.trustedChecksumsSource.summaryFile", Boolean.TRUE.toString());
}
+
+ @Test
+ public void dotDotRepositoryIdIsNeutralized() throws Exception {
+ DefaultRepositorySystemSession session = TestUtils.newSession();
+ enableSource(session);
+
+ DefaultRepositorySystemLifecycle lifecycle = new DefaultRepositorySystemLifecycle();
+ SummaryFileTrustedChecksumsSource source =
+ new SummaryFileTrustedChecksumsSource(new DefaultLocalPathComposer(), lifecycle);
+
+ TrustedChecksumsSource.Writer writer = source.getTrustedArtifactChecksumsWriter(session);
+ assertNotNull(writer);
+
+ // a repository id of ".." must be neutralized into a harmless path segment rather than spliced
+ // into the summary file name as-is
+ RemoteRepository dotDotRepository =
+ new RemoteRepository.Builder("..", "default", "https://repo.example.org/").build();
+ Artifact artifact = new DefaultArtifact("test:test:1.0");
+ Sha1ChecksumAlgorithmFactory checksumAlgorithmFactory = new Sha1ChecksumAlgorithmFactory();
+
+ writer.addTrustedArtifactChecksums(
+ artifact,
+ dotDotRepository,
+ singletonList(checksumAlgorithmFactory),
+ singletonMap(checksumAlgorithmFactory.getName(), "000"));
+ lifecycle.systemEnded();
+
+ Path checksumsBasedir =
+ session.getLocalRepository().getBasedir().toPath().resolve(".checksums");
+ assertTrue(Files.isRegularFile(checksumsBasedir.resolve("checksums--DOTDOT-.sha1")));
+ }
}
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/GAECVNameMapperTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/GAECVNameMapperTest.java
new file mode 100644
index 000000000..cb42e41e9
--- /dev/null
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/synccontext/named/GAECVNameMapperTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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.eclipse.aether.internal.impl.synccontext.named;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.metadata.DefaultMetadata;
+import org.eclipse.aether.metadata.Metadata;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+
+public class GAECVNameMapperTest extends NameMapperTestSupport {
+ NameMapper mapper = NameMappers.gaecvNameMapper(true);
+
+ @Test
+ public void nullsAndEmptyInputs() {
+ Collection<String> names;
+
+ names = mapper.nameLocks(session, null, null);
+ assertThat(names, Matchers.empty());
+
+ names = mapper.nameLocks(session, null, emptyList());
+ assertThat(names, Matchers.empty());
+
+ names = mapper.nameLocks(session, emptyList(), null);
+ assertThat(names, Matchers.empty());
+
+ names = mapper.nameLocks(session, emptyList(), emptyList());
+ assertThat(names, Matchers.empty());
+ }
+
+ @Test
+ public void singleArtifactWithoutClassifier() {
+ DefaultArtifact artifact = new DefaultArtifact("group:artifact:1.0");
+ Collection<String> names = mapper.nameLocks(session, singletonList(artifact), null);
+
+ assertThat(names, hasSize(1));
+ assertThat(names.iterator().next(), equalTo("artifact~group~artifact~jar~1.0.lock"));
+ }
+
+ @Test
+ public void singleArtifactWithClassifier() {
+ DefaultArtifact artifact = new DefaultArtifact("group", "artifact", "classifier", "jar", "1.0");
+ Collection<String> names = mapper.nameLocks(session, singletonList(artifact), null);
+
+ assertThat(names, hasSize(1));
+ assertThat(names.iterator().next(), equalTo("artifact~group~artifact~jar~classifier~1.0.lock"));
+ }
+
+ @Test
+ public void singleMetadata() {
+ DefaultMetadata metadata =
+ new DefaultMetadata("group", "artifact", "maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT);
+ Collection<String> names = mapper.nameLocks(session, null, singletonList(metadata));
+
+ assertThat(names, hasSize(1));
+ assertThat(names.iterator().next(), equalTo("metadata~group~artifact.lock"));
+ }
+
+ @Test
+ public void pathSeparatorsInArtifactCoordinatesAreNeutralized() {
+ // wire-supplied coordinate fields must not be able to select a lock path outside the locks directory
+ DefaultArtifact artifact = new DefaultArtifact("group", "artifact", "jar", "1/../../../../home/user/x");
+ Collection<String> names = mapper.nameLocks(session, singletonList(artifact), null);
+
+ assertThat(names, hasSize(1));
+ String name = names.iterator().next();
+ assertThat(
+ name,
+ equalTo(
+ "artifact~group~artifact~jar~1-SLASH-..-SLASH-..-SLASH-..-SLASH-..-SLASH-home-SLASH-user-SLASH-x.lock"));
+ assertThat(name, Matchers.not(Matchers.containsString("/")));
+ assertThat(name, Matchers.not(Matchers.containsString("\\")));
+ }
+
+ @Test
+ public void pathSeparatorsInExtensionAreNeutralized() {
+ // extension is one of the coordinate fields GAECV adds on top of GAV, must be neutralized the same way
+ DefaultArtifact artifact = new DefaultArtifact("group", "artifact", "", "j/a\\r", "1.0");
+ Collection<String> names = mapper.nameLocks(session, singletonList(artifact), null);
+
+ assertThat(names, hasSize(1));
+ String name = names.iterator().next();
+ assertThat(name, equalTo("artifact~group~artifact~j-SLASH-a-BACKSLASH-r~1.0.lock"));
+ assertThat(name, Matchers.not(Matchers.containsString("/")));
+ assertThat(name, Matchers.not(Matchers.containsString("\\")));
+ }
+
+ @Test
+ public void pathSeparatorsInClassifierAreNeutralized() {
+ // classifier is the other coordinate field GAECV adds on top of GAV, must be neutralized the same way
+ DefaultArtifact artifact = new DefaultArtifact("group", "artifact", "../../etc/passwd", "jar", "1.0");
+ Collection<String> names = mapper.nameLocks(session, singletonList(artifact), null);
+
+ assertThat(names, hasSize(1));
+ String name = names.iterator().next();
+ assertThat(name, equalTo("artifact~group~artifact~jar~..-SLASH-..-SLASH-etc-SLASH-passwd~1.0.lock"));
+ assertThat(name, Matchers.not(Matchers.containsString("/")));
+ assertThat(name, Matchers.not(Matchers.containsString("\\")));
+ }
+
+ @Test
+ public void pathSeparatorsInMetadataCoordinatesAreNeutralized() {
+ DefaultMetadata metadata = new DefaultMetadata(
+ "group", "artifact", "1\\..\\..\\x", "maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT);
+ Collection<String> names = mapper.nameLocks(session, null, singletonList(metadata));
+
+ assertThat(names, hasSize(1));
+ String name = names.iterator().next();
+ assertThat(name, equalTo("metadata~group~artifact~1-BACKSLASH-..-BACKSLASH-..-BACKSLASH-x.lock"));
+ assertThat(name, Matchers.not(Matchers.containsString("/")));
+ assertThat(name, Matchers.not(Matchers.containsString("\\")));
+ }
+
+ @Test
+ public void oneAndOne() {
+ DefaultArtifact artifact = new DefaultArtifact("agroup:artifact:1.0");
+ DefaultMetadata metadata =
+ new DefaultMetadata("bgroup", "artifact", "maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT);
+ Collection<String> names = mapper.nameLocks(session, singletonList(artifact), singletonList(metadata));
+
+ assertThat(names, hasSize(2));
+ Iterator<String> namesIterator = names.iterator();
+
+ // they are sorted as well
+ assertThat(namesIterator.next(), equalTo("artifact~agroup~artifact~jar~1.0.lock"));
+ assertThat(namesIterator.next(), equalTo("metadata~bgroup~artifact.lock"));
+ }
+}