| /* |
| * 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.connector.basic; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.aether.internal.test.util.TestChecksumProcessor; |
| import org.eclipse.aether.internal.test.util.TestFileUtils; |
| import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory; |
| import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy; |
| import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind; |
| import org.eclipse.aether.spi.connector.layout.RepositoryLayout; |
| import org.eclipse.aether.transfer.ChecksumFailureException; |
| import org.junit.jupiter.api.BeforeEach; |
| import org.junit.jupiter.api.Test; |
| |
| import static org.eclipse.aether.connector.basic.TestChecksumAlgorithmSelector.MD5; |
| import static org.eclipse.aether.connector.basic.TestChecksumAlgorithmSelector.SHA1; |
| import static org.junit.jupiter.api.Assertions.*; |
| |
| public class ChecksumValidatorTest { |
| |
| private static class StubChecksumPolicy implements ChecksumPolicy { |
| |
| boolean inspectAll; |
| |
| boolean tolerateFailure; |
| |
| private final ArrayList<String> callbacks = new ArrayList<>(); |
| |
| private Object conclusion; |
| |
| @Override |
| public boolean onChecksumMatch(String algorithm, ChecksumKind kind) { |
| callbacks.add(String.format("match(%s, %s)", algorithm, kind)); |
| if (inspectAll) { |
| if (conclusion == null) { |
| conclusion = true; |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void onChecksumMismatch(String algorithm, ChecksumKind kind, ChecksumFailureException exception) |
| throws ChecksumFailureException { |
| callbacks.add(String.format("mismatch(%s, %s)", algorithm, kind)); |
| if (inspectAll) { |
| conclusion = exception; |
| return; |
| } |
| throw exception; |
| } |
| |
| @Override |
| public void onChecksumError(String algorithm, ChecksumKind kind, ChecksumFailureException exception) { |
| callbacks.add(String.format( |
| "error(%s, %s, %s)", algorithm, kind, exception.getCause().getMessage())); |
| } |
| |
| @Override |
| public void onNoMoreChecksums() throws ChecksumFailureException { |
| callbacks.add(String.format("noMore()")); |
| if (conclusion instanceof ChecksumFailureException) { |
| throw (ChecksumFailureException) conclusion; |
| } else if (!Boolean.TRUE.equals(conclusion)) { |
| throw new ChecksumFailureException("no checksums"); |
| } |
| } |
| |
| @Override |
| public void onTransferRetry() { |
| callbacks.add(String.format("retry()")); |
| } |
| |
| @Override |
| public boolean onTransferChecksumFailure(ChecksumFailureException exception) { |
| callbacks.add(String.format("fail(%s)", exception.getMessage())); |
| return tolerateFailure; |
| } |
| |
| void assertCallbacks(String... callbacks) { |
| assertEquals(Arrays.asList(callbacks), this.callbacks); |
| } |
| } |
| |
| private static class StubChecksumFetcher implements ChecksumValidator.ChecksumFetcher { |
| |
| HashMap<URI, Object> checksums = new HashMap<>(); |
| |
| ArrayList<Path> checksumFiles = new ArrayList<>(); |
| |
| private final ArrayList<URI> fetchedFiles = new ArrayList<>(); |
| |
| @Override |
| public boolean fetchChecksum(URI remote, Path local) throws Exception { |
| fetchedFiles.add(remote); |
| Object checksum = checksums.get(remote); |
| if (checksum == null) { |
| return false; |
| } |
| if (checksum instanceof Exception) { |
| throw (Exception) checksum; |
| } |
| TestFileUtils.writeString(local.toFile(), checksum.toString()); |
| checksumFiles.add(local); |
| return true; |
| } |
| |
| void mock(String algo, Object value) { |
| checksums.put(toUri(algo), value); |
| } |
| |
| void assertFetchedFiles(String... algos) { |
| List<URI> expected = new ArrayList<>(); |
| for (String algo : algos) { |
| expected.add(toUri(algo)); |
| } |
| assertEquals(expected, fetchedFiles); |
| } |
| |
| private static URI toUri(String algo) { |
| return newChecksum(algo).getLocation(); |
| } |
| } |
| |
| private StubChecksumPolicy policy; |
| |
| private StubChecksumFetcher fetcher; |
| |
| private File dataFile; |
| |
| private static final TestChecksumAlgorithmSelector selector = new TestChecksumAlgorithmSelector(); |
| |
| private List<ChecksumAlgorithmFactory> newChecksumAlgorithmFactories(String... factories) { |
| List<ChecksumAlgorithmFactory> checksums = new ArrayList<>(); |
| for (String factory : factories) { |
| checksums.add(selector.select(factory)); |
| } |
| return checksums; |
| } |
| |
| private static RepositoryLayout.ChecksumLocation newChecksum(String factory) { |
| return RepositoryLayout.ChecksumLocation.forLocation(URI.create("file"), selector.select(factory)); |
| } |
| |
| private List<RepositoryLayout.ChecksumLocation> newChecksums( |
| List<ChecksumAlgorithmFactory> checksumAlgorithmFactories) { |
| List<RepositoryLayout.ChecksumLocation> checksums = new ArrayList<>(); |
| for (ChecksumAlgorithmFactory factory : checksumAlgorithmFactories) { |
| checksums.add(RepositoryLayout.ChecksumLocation.forLocation(URI.create("file"), factory)); |
| } |
| return checksums; |
| } |
| |
| private ChecksumValidator newValidator(String... factories) { |
| return newValidator(null, factories); |
| } |
| |
| private ChecksumValidator newValidator(Map<String, String> providedChecksums, String... factories) { |
| List<ChecksumAlgorithmFactory> checksumAlgorithmFactories = newChecksumAlgorithmFactories(factories); |
| return new ChecksumValidator( |
| dataFile.toPath(), |
| checksumAlgorithmFactories, |
| new TestChecksumProcessor(), |
| fetcher, |
| policy, |
| providedChecksums, |
| newChecksums(checksumAlgorithmFactories)); |
| } |
| |
| private Map<String, ?> checksums(String... algoDigestPairs) { |
| Map<String, Object> checksums = new LinkedHashMap<>(); |
| for (int i = 0; i < algoDigestPairs.length; i += 2) { |
| String algo = algoDigestPairs[i]; |
| String digest = algoDigestPairs[i + 1]; |
| if (digest == null) { |
| checksums.put(algo, new IOException("error")); |
| } else { |
| checksums.put(algo, digest); |
| } |
| } |
| return checksums; |
| } |
| |
| @BeforeEach |
| void init() throws Exception { |
| dataFile = TestFileUtils.createTempFile(""); |
| dataFile.delete(); |
| policy = new StubChecksumPolicy(); |
| fetcher = new StubChecksumFetcher(); |
| } |
| |
| @Test |
| void testValidate_NullPolicy() throws Exception { |
| policy = null; |
| ChecksumValidator validator = newValidator(SHA1); |
| validator.validate(checksums(SHA1, "ignored"), null); |
| fetcher.assertFetchedFiles(); |
| } |
| |
| @Test |
| void testValidate_AcceptOnFirstMatch() throws Exception { |
| ChecksumValidator validator = newValidator(SHA1); |
| fetcher.mock(SHA1, "foo"); |
| validator.validate(checksums(SHA1, "foo"), null); |
| fetcher.assertFetchedFiles(SHA1); |
| policy.assertCallbacks("match(SHA-1, REMOTE_EXTERNAL)"); |
| } |
| |
| @Test |
| void testValidate_FailOnFirstMismatch() { |
| ChecksumValidator validator = newValidator(SHA1); |
| fetcher.mock(SHA1, "foo"); |
| try { |
| validator.validate(checksums(SHA1, "not-foo"), null); |
| fail("expected exception"); |
| } catch (ChecksumFailureException e) { |
| assertEquals("foo", e.getExpected()); |
| assertEquals(ChecksumKind.REMOTE_EXTERNAL.name(), e.getExpectedKind()); |
| assertEquals("not-foo", e.getActual()); |
| assertTrue(e.isRetryWorthy()); |
| } |
| fetcher.assertFetchedFiles(SHA1); |
| policy.assertCallbacks("mismatch(SHA-1, REMOTE_EXTERNAL)"); |
| } |
| |
| @Test |
| void testValidate_AcceptOnEnd() throws Exception { |
| policy.inspectAll = true; |
| ChecksumValidator validator = newValidator(SHA1, MD5); |
| fetcher.mock(SHA1, "foo"); |
| fetcher.mock(MD5, "bar"); |
| validator.validate(checksums(SHA1, "foo", MD5, "bar"), null); |
| fetcher.assertFetchedFiles(SHA1, MD5); |
| policy.assertCallbacks("match(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()"); |
| } |
| |
| @Test |
| void testValidate_FailOnEnd() { |
| policy.inspectAll = true; |
| ChecksumValidator validator = newValidator(SHA1, MD5); |
| fetcher.mock(SHA1, "foo"); |
| fetcher.mock(MD5, "bar"); |
| try { |
| validator.validate(checksums(SHA1, "not-foo", MD5, "bar"), null); |
| fail("expected exception"); |
| } catch (ChecksumFailureException e) { |
| assertEquals("foo", e.getExpected()); |
| assertEquals(ChecksumKind.REMOTE_EXTERNAL.name(), e.getExpectedKind()); |
| assertEquals("not-foo", e.getActual()); |
| assertTrue(e.isRetryWorthy()); |
| } |
| fetcher.assertFetchedFiles(SHA1, MD5); |
| policy.assertCallbacks("mismatch(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()"); |
| } |
| |
| @Test |
| void testValidate_IncludedBeforeExternal() throws Exception { |
| policy.inspectAll = true; |
| HashMap<String, String> provided = new HashMap<>(); |
| provided.put(SHA1, "foo"); |
| ChecksumValidator validator = newValidator(provided, SHA1, MD5); |
| fetcher.mock(SHA1, "foo"); |
| fetcher.mock(MD5, "bar"); |
| validator.validate(checksums(SHA1, "foo", MD5, "bar"), checksums(SHA1, "foo", MD5, "bar")); |
| fetcher.assertFetchedFiles(SHA1, MD5); |
| policy.assertCallbacks( |
| "match(SHA-1, PROVIDED)", |
| "match(SHA-1, REMOTE_INCLUDED)", |
| "match(MD5, REMOTE_INCLUDED)", |
| "match(SHA-1, REMOTE_EXTERNAL)", |
| "match(MD5, REMOTE_EXTERNAL)", |
| "noMore()"); |
| } |
| |
| @Test |
| void testValidate_CaseInsensitive() throws Exception { |
| policy.inspectAll = true; |
| ChecksumValidator validator = newValidator(SHA1); |
| fetcher.mock(SHA1, "FOO"); |
| validator.validate(checksums(SHA1, "foo"), checksums(SHA1, "foo")); |
| policy.assertCallbacks("match(SHA-1, REMOTE_INCLUDED)", "match(SHA-1, REMOTE_EXTERNAL)", "noMore()"); |
| } |
| |
| @Test |
| void testValidate_MissingRemoteChecksum() throws Exception { |
| ChecksumValidator validator = newValidator(SHA1, MD5); |
| fetcher.mock(MD5, "bar"); |
| validator.validate(checksums(MD5, "bar"), null); |
| fetcher.assertFetchedFiles(SHA1, MD5); |
| policy.assertCallbacks("match(MD5, REMOTE_EXTERNAL)"); |
| } |
| |
| @Test |
| void testValidate_InaccessibleRemoteChecksum() throws Exception { |
| ChecksumValidator validator = newValidator(SHA1, MD5); |
| fetcher.mock(SHA1, new IOException("inaccessible")); |
| fetcher.mock(MD5, "bar"); |
| validator.validate(checksums(MD5, "bar"), null); |
| fetcher.assertFetchedFiles(SHA1, MD5); |
| policy.assertCallbacks("error(SHA-1, REMOTE_EXTERNAL, inaccessible)", "match(MD5, REMOTE_EXTERNAL)"); |
| } |
| |
| @Test |
| void testValidate_InaccessibleLocalChecksum() throws Exception { |
| ChecksumValidator validator = newValidator(SHA1, MD5); |
| fetcher.mock(SHA1, "foo"); |
| fetcher.mock(MD5, "bar"); |
| validator.validate(checksums(SHA1, null, MD5, "bar"), null); |
| fetcher.assertFetchedFiles(MD5); |
| policy.assertCallbacks("error(SHA-1, REMOTE_EXTERNAL, error)", "match(MD5, REMOTE_EXTERNAL)"); |
| } |
| |
| @Test |
| void testHandle_Accept() { |
| policy.tolerateFailure = true; |
| ChecksumValidator validator = newValidator(SHA1); |
| assertTrue(validator.handle(new ChecksumFailureException("accept"))); |
| policy.assertCallbacks("fail(accept)"); |
| } |
| |
| @Test |
| void testHandle_Reject() { |
| policy.tolerateFailure = false; |
| ChecksumValidator validator = newValidator(SHA1); |
| assertFalse(validator.handle(new ChecksumFailureException("reject"))); |
| policy.assertCallbacks("fail(reject)"); |
| } |
| |
| @Test |
| void testRetry_ResetPolicy() { |
| ChecksumValidator validator = newValidator(SHA1); |
| validator.retry(); |
| policy.assertCallbacks("retry()"); |
| } |
| |
| @Test |
| void testRetry_RemoveTempFiles() throws Exception { |
| ChecksumValidator validator = newValidator(SHA1); |
| fetcher.mock(SHA1, "foo"); |
| validator.validate(checksums(SHA1, "foo"), null); |
| fetcher.assertFetchedFiles(SHA1); |
| assertEquals(1, fetcher.checksumFiles.size()); |
| validator.retry(); |
| for (Path file : fetcher.checksumFiles) { |
| assertFalse(Files.exists(file), file.toAbsolutePath().toString()); |
| } |
| } |
| |
| @Test |
| void testCommit_SaveChecksumFiles() throws Exception { |
| policy.inspectAll = true; |
| ChecksumValidator validator = newValidator(SHA1, MD5); |
| fetcher.mock(MD5, "bar"); |
| validator.validate(checksums(SHA1, "foo", MD5, "bar"), checksums(SHA1, "foo")); |
| assertEquals(1, fetcher.checksumFiles.size()); |
| validator.commit(); |
| File checksumFile = new File(dataFile.getPath() + ".sha1"); |
| assertTrue(checksumFile.isFile(), checksumFile.getAbsolutePath()); |
| assertEquals("foo", TestFileUtils.readString(checksumFile)); |
| checksumFile = new File(dataFile.getPath() + ".md5"); |
| assertTrue(checksumFile.isFile(), checksumFile.getAbsolutePath()); |
| assertEquals("bar", TestFileUtils.readString(checksumFile)); |
| for (Path file : fetcher.checksumFiles) { |
| assertFalse(Files.exists(file), file.toAbsolutePath().toString()); |
| } |
| } |
| |
| @Test |
| void testNoCommit_NoTempFiles() throws Exception { |
| ChecksumValidator validator = newValidator(SHA1); |
| fetcher.mock(SHA1, "foo"); |
| validator.validate(checksums(SHA1, "foo"), null); |
| fetcher.assertFetchedFiles(SHA1); |
| assertEquals(1, fetcher.checksumFiles.size()); |
| for (Path file : fetcher.checksumFiles) { |
| assertFalse(Files.exists(file), file.toAbsolutePath().toString()); |
| } |
| } |
| } |