1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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_ProvidedAllComparableAlgorithmsMustMatch() {
259
260
261
262 Map<String, String> provided = new LinkedHashMap<>();
263 provided.put(MD5, "bar");
264 provided.put(SHA1, "foo");
265 ChecksumValidator validator = newValidator(provided, SHA1, MD5);
266 try {
267 validator.validate(checksums(SHA1, "not-foo", MD5, "bar"), null);
268 fail("expected exception");
269 } catch (ChecksumFailureException e) {
270 assertEquals("foo", e.getExpected());
271 assertEquals(ChecksumKind.PROVIDED.name(), e.getExpectedKind());
272 assertEquals("not-foo", e.getActual());
273 assertTrue(e.isRetryWorthy());
274 }
275 fetcher.assertFetchedFiles();
276 policy.assertCallbacks("match(MD5, PROVIDED)", "mismatch(SHA-1, PROVIDED)");
277 }
278
279 @Test
280 void testValidate_ProvidedAllMatchingAccepts() throws Exception {
281 Map<String, String> provided = new LinkedHashMap<>();
282 provided.put(SHA1, "foo");
283 provided.put(MD5, "bar");
284 ChecksumValidator validator = newValidator(provided, SHA1, MD5);
285 validator.validate(checksums(SHA1, "foo", MD5, "bar"), null);
286 fetcher.assertFetchedFiles();
287 policy.assertCallbacks("match(SHA-1, PROVIDED)", "match(MD5, PROVIDED)");
288 }
289
290 @Test
291 void testValidate_IncludedWeakMatchDoesNotMaskStrongMismatch() {
292 ChecksumValidator validator = newValidator(SHA1, MD5);
293 try {
294 validator.validate(checksums(SHA1, "not-foo", MD5, "bar"), checksums(MD5, "bar", SHA1, "foo"));
295 fail("expected exception");
296 } catch (ChecksumFailureException e) {
297 assertEquals("foo", e.getExpected());
298 assertEquals(ChecksumKind.REMOTE_INCLUDED.name(), e.getExpectedKind());
299 assertEquals("not-foo", e.getActual());
300 }
301 fetcher.assertFetchedFiles();
302 policy.assertCallbacks("match(MD5, REMOTE_INCLUDED)", "mismatch(SHA-1, REMOTE_INCLUDED)");
303 }
304
305 @Test
306 void testValidate_AcceptOnEnd() throws Exception {
307 policy.inspectAll = true;
308 ChecksumValidator validator = newValidator(SHA1, MD5);
309 fetcher.mock(SHA1, "foo");
310 fetcher.mock(MD5, "bar");
311 validator.validate(checksums(SHA1, "foo", MD5, "bar"), null);
312 fetcher.assertFetchedFiles(SHA1, MD5);
313 policy.assertCallbacks("match(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()");
314 }
315
316 @Test
317 void testValidate_FailOnEnd() {
318 policy.inspectAll = true;
319 ChecksumValidator validator = newValidator(SHA1, MD5);
320 fetcher.mock(SHA1, "foo");
321 fetcher.mock(MD5, "bar");
322 try {
323 validator.validate(checksums(SHA1, "not-foo", MD5, "bar"), null);
324 fail("expected exception");
325 } catch (ChecksumFailureException e) {
326 assertEquals("foo", e.getExpected());
327 assertEquals(ChecksumKind.REMOTE_EXTERNAL.name(), e.getExpectedKind());
328 assertEquals("not-foo", e.getActual());
329 assertTrue(e.isRetryWorthy());
330 }
331 fetcher.assertFetchedFiles(SHA1, MD5);
332 policy.assertCallbacks("mismatch(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()");
333 }
334
335 @Test
336 void testValidate_IncludedBeforeExternal() throws Exception {
337 policy.inspectAll = true;
338 HashMap<String, String> provided = new HashMap<>();
339 provided.put(SHA1, "foo");
340 ChecksumValidator validator = newValidator(provided, SHA1, MD5);
341 fetcher.mock(SHA1, "foo");
342 fetcher.mock(MD5, "bar");
343 validator.validate(checksums(SHA1, "foo", MD5, "bar"), checksums(SHA1, "foo", MD5, "bar"));
344 fetcher.assertFetchedFiles(SHA1, MD5);
345 policy.assertCallbacks(
346 "match(SHA-1, PROVIDED)",
347 "match(SHA-1, REMOTE_INCLUDED)",
348 "match(MD5, REMOTE_INCLUDED)",
349 "match(SHA-1, REMOTE_EXTERNAL)",
350 "match(MD5, REMOTE_EXTERNAL)",
351 "noMore()");
352 }
353
354 @Test
355 void testValidate_CaseInsensitive() throws Exception {
356 policy.inspectAll = true;
357 ChecksumValidator validator = newValidator(SHA1);
358 fetcher.mock(SHA1, "FOO");
359 validator.validate(checksums(SHA1, "foo"), checksums(SHA1, "foo"));
360 policy.assertCallbacks("match(SHA-1, REMOTE_INCLUDED)", "match(SHA-1, REMOTE_EXTERNAL)", "noMore()");
361 }
362
363 @Test
364 void testValidate_MissingRemoteChecksum() throws Exception {
365 ChecksumValidator validator = newValidator(SHA1, MD5);
366 fetcher.mock(MD5, "bar");
367 validator.validate(checksums(MD5, "bar"), null);
368 fetcher.assertFetchedFiles(SHA1, MD5);
369 policy.assertCallbacks("match(MD5, REMOTE_EXTERNAL)");
370 }
371
372 @Test
373 void testValidate_InaccessibleRemoteChecksum() throws Exception {
374 ChecksumValidator validator = newValidator(SHA1, MD5);
375 fetcher.mock(SHA1, new IOException("inaccessible"));
376 fetcher.mock(MD5, "bar");
377 validator.validate(checksums(MD5, "bar"), null);
378 fetcher.assertFetchedFiles(SHA1, MD5);
379 policy.assertCallbacks("error(SHA-1, REMOTE_EXTERNAL, inaccessible)", "match(MD5, REMOTE_EXTERNAL)");
380 }
381
382 @Test
383 void testValidate_InaccessibleLocalChecksum() throws Exception {
384 ChecksumValidator validator = newValidator(SHA1, MD5);
385 fetcher.mock(SHA1, "foo");
386 fetcher.mock(MD5, "bar");
387 validator.validate(checksums(SHA1, null, MD5, "bar"), null);
388 fetcher.assertFetchedFiles(MD5);
389 policy.assertCallbacks("error(SHA-1, REMOTE_EXTERNAL, error)", "match(MD5, REMOTE_EXTERNAL)");
390 }
391
392 @Test
393 void testHandle_Accept() {
394 policy.tolerateFailure = true;
395 ChecksumValidator validator = newValidator(SHA1);
396 assertTrue(validator.handle(new ChecksumFailureException("accept")));
397 policy.assertCallbacks("fail(accept)");
398 }
399
400 @Test
401 void testHandle_Reject() {
402 policy.tolerateFailure = false;
403 ChecksumValidator validator = newValidator(SHA1);
404 assertFalse(validator.handle(new ChecksumFailureException("reject")));
405 policy.assertCallbacks("fail(reject)");
406 }
407
408 @Test
409 void testRetry_ResetPolicy() {
410 ChecksumValidator validator = newValidator(SHA1);
411 validator.retry();
412 policy.assertCallbacks("retry()");
413 }
414
415 @Test
416 void testRetry_RemoveTempFiles() throws Exception {
417 ChecksumValidator validator = newValidator(SHA1);
418 fetcher.mock(SHA1, "foo");
419 validator.validate(checksums(SHA1, "foo"), null);
420 fetcher.assertFetchedFiles(SHA1);
421 assertEquals(1, fetcher.checksumFiles.size());
422 validator.retry();
423 for (Path file : fetcher.checksumFiles) {
424 assertFalse(Files.exists(file), file.toAbsolutePath().toString());
425 }
426 }
427
428 @Test
429 void testCommit_SaveChecksumFiles() throws Exception {
430 policy.inspectAll = true;
431 ChecksumValidator validator = newValidator(SHA1, MD5);
432 fetcher.mock(MD5, "bar");
433 validator.validate(checksums(SHA1, "foo", MD5, "bar"), checksums(SHA1, "foo"));
434 assertEquals(1, fetcher.checksumFiles.size());
435 validator.commit();
436 File checksumFile = new File(dataFile.getPath() + ".sha1");
437 assertTrue(checksumFile.isFile(), checksumFile.getAbsolutePath());
438 assertEquals("foo", TestFileUtils.readString(checksumFile));
439 checksumFile = new File(dataFile.getPath() + ".md5");
440 assertTrue(checksumFile.isFile(), checksumFile.getAbsolutePath());
441 assertEquals("bar", TestFileUtils.readString(checksumFile));
442 for (Path file : fetcher.checksumFiles) {
443 assertFalse(Files.exists(file), file.toAbsolutePath().toString());
444 }
445 }
446
447 @Test
448 void testNoCommit_NoTempFiles() throws Exception {
449 ChecksumValidator validator = newValidator(SHA1);
450 fetcher.mock(SHA1, "foo");
451 validator.validate(checksums(SHA1, "foo"), null);
452 fetcher.assertFetchedFiles(SHA1);
453 assertEquals(1, fetcher.checksumFiles.size());
454 for (Path file : fetcher.checksumFiles) {
455 assertFalse(Files.exists(file), file.toAbsolutePath().toString());
456 }
457 }
458 }