View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.connector.basic;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.net.URI;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.HashMap;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Map;
32  
33  import org.eclipse.aether.internal.test.util.TestChecksumProcessor;
34  import org.eclipse.aether.internal.test.util.TestFileUtils;
35  import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
36  import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
37  import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind;
38  import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
39  import org.eclipse.aether.transfer.ChecksumFailureException;
40  import org.junit.jupiter.api.BeforeEach;
41  import org.junit.jupiter.api.Test;
42  
43  import static org.eclipse.aether.connector.basic.TestChecksumAlgorithmSelector.MD5;
44  import static org.eclipse.aether.connector.basic.TestChecksumAlgorithmSelector.SHA1;
45  import static org.junit.jupiter.api.Assertions.*;
46  
47  public class ChecksumValidatorTest {
48  
49      private static class StubChecksumPolicy implements ChecksumPolicy {
50  
51          boolean inspectAll;
52  
53          boolean tolerateFailure;
54  
55          private final ArrayList<String> callbacks = new ArrayList<>();
56  
57          private Object conclusion;
58  
59          @Override
60          public boolean onChecksumMatch(String algorithm, ChecksumKind kind) {
61              callbacks.add(String.format("match(%s, %s)", algorithm, kind));
62              if (inspectAll) {
63                  if (conclusion == null) {
64                      conclusion = true;
65                  }
66                  return false;
67              }
68              return true;
69          }
70  
71          @Override
72          public void onChecksumMismatch(String algorithm, ChecksumKind kind, ChecksumFailureException exception)
73                  throws ChecksumFailureException {
74              callbacks.add(String.format("mismatch(%s, %s)", algorithm, kind));
75              if (inspectAll) {
76                  conclusion = exception;
77                  return;
78              }
79              throw exception;
80          }
81  
82          @Override
83          public void onChecksumError(String algorithm, ChecksumKind kind, ChecksumFailureException exception) {
84              callbacks.add(String.format(
85                      "error(%s, %s, %s)", algorithm, kind, exception.getCause().getMessage()));
86          }
87  
88          @Override
89          public void onNoMoreChecksums() throws ChecksumFailureException {
90              callbacks.add(String.format("noMore()"));
91              if (conclusion instanceof ChecksumFailureException) {
92                  throw (ChecksumFailureException) conclusion;
93              } else if (!Boolean.TRUE.equals(conclusion)) {
94                  throw new ChecksumFailureException("no checksums");
95              }
96          }
97  
98          @Override
99          public void onTransferRetry() {
100             callbacks.add(String.format("retry()"));
101         }
102 
103         @Override
104         public boolean onTransferChecksumFailure(ChecksumFailureException exception) {
105             callbacks.add(String.format("fail(%s)", exception.getMessage()));
106             return tolerateFailure;
107         }
108 
109         void assertCallbacks(String... callbacks) {
110             assertEquals(Arrays.asList(callbacks), this.callbacks);
111         }
112     }
113 
114     private static class StubChecksumFetcher implements ChecksumValidator.ChecksumFetcher {
115 
116         HashMap<URI, Object> checksums = new HashMap<>();
117 
118         ArrayList<Path> checksumFiles = new ArrayList<>();
119 
120         private final ArrayList<URI> fetchedFiles = new ArrayList<>();
121 
122         @Override
123         public boolean fetchChecksum(URI remote, Path local) throws Exception {
124             fetchedFiles.add(remote);
125             Object checksum = checksums.get(remote);
126             if (checksum == null) {
127                 return false;
128             }
129             if (checksum instanceof Exception) {
130                 throw (Exception) checksum;
131             }
132             TestFileUtils.writeString(local.toFile(), checksum.toString());
133             checksumFiles.add(local);
134             return true;
135         }
136 
137         void mock(String algo, Object value) {
138             checksums.put(toUri(algo), value);
139         }
140 
141         void assertFetchedFiles(String... algos) {
142             List<URI> expected = new ArrayList<>();
143             for (String algo : algos) {
144                 expected.add(toUri(algo));
145             }
146             assertEquals(expected, fetchedFiles);
147         }
148 
149         private static URI toUri(String algo) {
150             return newChecksum(algo).getLocation();
151         }
152     }
153 
154     private StubChecksumPolicy policy;
155 
156     private StubChecksumFetcher fetcher;
157 
158     private File dataFile;
159 
160     private static final TestChecksumAlgorithmSelector selector = new TestChecksumAlgorithmSelector();
161 
162     private List<ChecksumAlgorithmFactory> newChecksumAlgorithmFactories(String... factories) {
163         List<ChecksumAlgorithmFactory> checksums = new ArrayList<>();
164         for (String factory : factories) {
165             checksums.add(selector.select(factory));
166         }
167         return checksums;
168     }
169 
170     private static RepositoryLayout.ChecksumLocation newChecksum(String factory) {
171         return RepositoryLayout.ChecksumLocation.forLocation(URI.create("file"), selector.select(factory));
172     }
173 
174     private List<RepositoryLayout.ChecksumLocation> newChecksums(
175             List<ChecksumAlgorithmFactory> checksumAlgorithmFactories) {
176         List<RepositoryLayout.ChecksumLocation> checksums = new ArrayList<>();
177         for (ChecksumAlgorithmFactory factory : checksumAlgorithmFactories) {
178             checksums.add(RepositoryLayout.ChecksumLocation.forLocation(URI.create("file"), factory));
179         }
180         return checksums;
181     }
182 
183     private ChecksumValidator newValidator(String... factories) {
184         return newValidator(null, factories);
185     }
186 
187     private ChecksumValidator newValidator(Map<String, String> providedChecksums, String... factories) {
188         List<ChecksumAlgorithmFactory> checksumAlgorithmFactories = newChecksumAlgorithmFactories(factories);
189         return new ChecksumValidator(
190                 dataFile.toPath(),
191                 checksumAlgorithmFactories,
192                 new TestChecksumProcessor(),
193                 fetcher,
194                 policy,
195                 providedChecksums,
196                 newChecksums(checksumAlgorithmFactories));
197     }
198 
199     private Map<String, ?> checksums(String... algoDigestPairs) {
200         Map<String, Object> checksums = new LinkedHashMap<>();
201         for (int i = 0; i < algoDigestPairs.length; i += 2) {
202             String algo = algoDigestPairs[i];
203             String digest = algoDigestPairs[i + 1];
204             if (digest == null) {
205                 checksums.put(algo, new IOException("error"));
206             } else {
207                 checksums.put(algo, digest);
208             }
209         }
210         return checksums;
211     }
212 
213     @BeforeEach
214     void init() throws Exception {
215         dataFile = TestFileUtils.createTempFile("");
216         dataFile.delete();
217         policy = new StubChecksumPolicy();
218         fetcher = new StubChecksumFetcher();
219     }
220 
221     @Test
222     void testValidate_NullPolicy() throws Exception {
223         policy = null;
224         ChecksumValidator validator = newValidator(SHA1);
225         validator.validate(checksums(SHA1, "ignored"), null);
226         fetcher.assertFetchedFiles();
227     }
228 
229     @Test
230     void testValidate_AcceptOnFirstMatch() throws Exception {
231         ChecksumValidator validator = newValidator(SHA1);
232         fetcher.mock(SHA1, "foo");
233         validator.validate(checksums(SHA1, "foo"), null);
234         fetcher.assertFetchedFiles(SHA1);
235         policy.assertCallbacks("match(SHA-1, REMOTE_EXTERNAL)");
236     }
237 
238     @Test
239     void testValidate_FailOnFirstMismatch() {
240         ChecksumValidator validator = newValidator(SHA1);
241         fetcher.mock(SHA1, "foo");
242         try {
243             validator.validate(checksums(SHA1, "not-foo"), null);
244             fail("expected exception");
245         } catch (ChecksumFailureException e) {
246             assertEquals("foo", e.getExpected());
247             assertEquals(ChecksumKind.REMOTE_EXTERNAL.name(), e.getExpectedKind());
248             assertEquals("not-foo", e.getActual());
249             assertTrue(e.isRetryWorthy());
250         }
251         fetcher.assertFetchedFiles(SHA1);
252         policy.assertCallbacks("mismatch(SHA-1, REMOTE_EXTERNAL)");
253     }
254 
255     @Test
256     void testValidate_AcceptOnEnd() throws Exception {
257         policy.inspectAll = true;
258         ChecksumValidator validator = newValidator(SHA1, MD5);
259         fetcher.mock(SHA1, "foo");
260         fetcher.mock(MD5, "bar");
261         validator.validate(checksums(SHA1, "foo", MD5, "bar"), null);
262         fetcher.assertFetchedFiles(SHA1, MD5);
263         policy.assertCallbacks("match(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()");
264     }
265 
266     @Test
267     void testValidate_FailOnEnd() {
268         policy.inspectAll = true;
269         ChecksumValidator validator = newValidator(SHA1, MD5);
270         fetcher.mock(SHA1, "foo");
271         fetcher.mock(MD5, "bar");
272         try {
273             validator.validate(checksums(SHA1, "not-foo", MD5, "bar"), null);
274             fail("expected exception");
275         } catch (ChecksumFailureException e) {
276             assertEquals("foo", e.getExpected());
277             assertEquals(ChecksumKind.REMOTE_EXTERNAL.name(), e.getExpectedKind());
278             assertEquals("not-foo", e.getActual());
279             assertTrue(e.isRetryWorthy());
280         }
281         fetcher.assertFetchedFiles(SHA1, MD5);
282         policy.assertCallbacks("mismatch(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()");
283     }
284 
285     @Test
286     void testValidate_IncludedBeforeExternal() throws Exception {
287         policy.inspectAll = true;
288         HashMap<String, String> provided = new HashMap<>();
289         provided.put(SHA1, "foo");
290         ChecksumValidator validator = newValidator(provided, SHA1, MD5);
291         fetcher.mock(SHA1, "foo");
292         fetcher.mock(MD5, "bar");
293         validator.validate(checksums(SHA1, "foo", MD5, "bar"), checksums(SHA1, "foo", MD5, "bar"));
294         fetcher.assertFetchedFiles(SHA1, MD5);
295         policy.assertCallbacks(
296                 "match(SHA-1, PROVIDED)",
297                 "match(SHA-1, REMOTE_INCLUDED)",
298                 "match(MD5, REMOTE_INCLUDED)",
299                 "match(SHA-1, REMOTE_EXTERNAL)",
300                 "match(MD5, REMOTE_EXTERNAL)",
301                 "noMore()");
302     }
303 
304     @Test
305     void testValidate_CaseInsensitive() throws Exception {
306         policy.inspectAll = true;
307         ChecksumValidator validator = newValidator(SHA1);
308         fetcher.mock(SHA1, "FOO");
309         validator.validate(checksums(SHA1, "foo"), checksums(SHA1, "foo"));
310         policy.assertCallbacks("match(SHA-1, REMOTE_INCLUDED)", "match(SHA-1, REMOTE_EXTERNAL)", "noMore()");
311     }
312 
313     @Test
314     void testValidate_MissingRemoteChecksum() throws Exception {
315         ChecksumValidator validator = newValidator(SHA1, MD5);
316         fetcher.mock(MD5, "bar");
317         validator.validate(checksums(MD5, "bar"), null);
318         fetcher.assertFetchedFiles(SHA1, MD5);
319         policy.assertCallbacks("match(MD5, REMOTE_EXTERNAL)");
320     }
321 
322     @Test
323     void testValidate_InaccessibleRemoteChecksum() throws Exception {
324         ChecksumValidator validator = newValidator(SHA1, MD5);
325         fetcher.mock(SHA1, new IOException("inaccessible"));
326         fetcher.mock(MD5, "bar");
327         validator.validate(checksums(MD5, "bar"), null);
328         fetcher.assertFetchedFiles(SHA1, MD5);
329         policy.assertCallbacks("error(SHA-1, REMOTE_EXTERNAL, inaccessible)", "match(MD5, REMOTE_EXTERNAL)");
330     }
331 
332     @Test
333     void testValidate_InaccessibleLocalChecksum() throws Exception {
334         ChecksumValidator validator = newValidator(SHA1, MD5);
335         fetcher.mock(SHA1, "foo");
336         fetcher.mock(MD5, "bar");
337         validator.validate(checksums(SHA1, null, MD5, "bar"), null);
338         fetcher.assertFetchedFiles(MD5);
339         policy.assertCallbacks("error(SHA-1, REMOTE_EXTERNAL, error)", "match(MD5, REMOTE_EXTERNAL)");
340     }
341 
342     @Test
343     void testHandle_Accept() {
344         policy.tolerateFailure = true;
345         ChecksumValidator validator = newValidator(SHA1);
346         assertTrue(validator.handle(new ChecksumFailureException("accept")));
347         policy.assertCallbacks("fail(accept)");
348     }
349 
350     @Test
351     void testHandle_Reject() {
352         policy.tolerateFailure = false;
353         ChecksumValidator validator = newValidator(SHA1);
354         assertFalse(validator.handle(new ChecksumFailureException("reject")));
355         policy.assertCallbacks("fail(reject)");
356     }
357 
358     @Test
359     void testRetry_ResetPolicy() {
360         ChecksumValidator validator = newValidator(SHA1);
361         validator.retry();
362         policy.assertCallbacks("retry()");
363     }
364 
365     @Test
366     void testRetry_RemoveTempFiles() throws Exception {
367         ChecksumValidator validator = newValidator(SHA1);
368         fetcher.mock(SHA1, "foo");
369         validator.validate(checksums(SHA1, "foo"), null);
370         fetcher.assertFetchedFiles(SHA1);
371         assertEquals(1, fetcher.checksumFiles.size());
372         validator.retry();
373         for (Path file : fetcher.checksumFiles) {
374             assertFalse(Files.exists(file), file.toAbsolutePath().toString());
375         }
376     }
377 
378     @Test
379     void testCommit_SaveChecksumFiles() throws Exception {
380         policy.inspectAll = true;
381         ChecksumValidator validator = newValidator(SHA1, MD5);
382         fetcher.mock(MD5, "bar");
383         validator.validate(checksums(SHA1, "foo", MD5, "bar"), checksums(SHA1, "foo"));
384         assertEquals(1, fetcher.checksumFiles.size());
385         validator.commit();
386         File checksumFile = new File(dataFile.getPath() + ".sha1");
387         assertTrue(checksumFile.isFile(), checksumFile.getAbsolutePath());
388         assertEquals("foo", TestFileUtils.readString(checksumFile));
389         checksumFile = new File(dataFile.getPath() + ".md5");
390         assertTrue(checksumFile.isFile(), checksumFile.getAbsolutePath());
391         assertEquals("bar", TestFileUtils.readString(checksumFile));
392         for (Path file : fetcher.checksumFiles) {
393             assertFalse(Files.exists(file), file.toAbsolutePath().toString());
394         }
395     }
396 
397     @Test
398     void testNoCommit_NoTempFiles() throws Exception {
399         ChecksumValidator validator = newValidator(SHA1);
400         fetcher.mock(SHA1, "foo");
401         validator.validate(checksums(SHA1, "foo"), null);
402         fetcher.assertFetchedFiles(SHA1);
403         assertEquals(1, fetcher.checksumFiles.size());
404         for (Path file : fetcher.checksumFiles) {
405             assertFalse(Files.exists(file), file.toAbsolutePath().toString());
406         }
407     }
408 }