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