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