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.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 }