001package org.eclipse.aether.connector.basic;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import static org.eclipse.aether.connector.basic.TestChecksumAlgorithmSelector.MD5;
023import static org.eclipse.aether.connector.basic.TestChecksumAlgorithmSelector.SHA1;
024import static org.junit.Assert.*;
025
026import java.io.File;
027import java.io.IOException;
028import java.net.URI;
029import java.util.ArrayList;
030import java.util.Arrays;
031import java.util.HashMap;
032import java.util.LinkedHashMap;
033import java.util.List;
034import java.util.Map;
035
036import org.eclipse.aether.internal.test.util.TestFileProcessor;
037import org.eclipse.aether.internal.test.util.TestFileUtils;
038import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
039import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
040import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind;
041import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
042import org.eclipse.aether.transfer.ChecksumFailureException;
043import org.junit.Before;
044import org.junit.Test;
045
046public class ChecksumValidatorTest
047{
048
049    private static class StubChecksumPolicy
050        implements ChecksumPolicy
051    {
052
053        boolean inspectAll;
054
055        boolean tolerateFailure;
056
057        private final ArrayList<String> callbacks = new ArrayList<>();
058
059        private Object conclusion;
060
061        @Override
062        public boolean onChecksumMatch( String algorithm, ChecksumKind kind )
063        {
064            callbacks.add( String.format( "match(%s, %s)", algorithm, kind ) );
065            if ( inspectAll )
066            {
067                if ( conclusion == null )
068                {
069                    conclusion = true;
070                }
071                return false;
072            }
073            return true;
074        }
075
076        @Override
077        public void onChecksumMismatch( String algorithm, ChecksumKind kind, ChecksumFailureException exception )
078            throws ChecksumFailureException
079        {
080            callbacks.add( String.format( "mismatch(%s, %s)", algorithm, kind ) );
081            if ( inspectAll )
082            {
083                conclusion = exception;
084                return;
085            }
086            throw exception;
087        }
088
089        @Override
090        public void onChecksumError( String algorithm, ChecksumKind kind, ChecksumFailureException exception )
091        {
092            callbacks.add( String.format( "error(%s, %s, %s)", algorithm, kind, exception.getCause().getMessage() ) );
093        }
094
095        @Override
096        public void onNoMoreChecksums()
097            throws ChecksumFailureException
098        {
099            callbacks.add( String.format( "noMore()" ) );
100            if ( conclusion instanceof ChecksumFailureException )
101            {
102                throw (ChecksumFailureException) conclusion;
103            }
104            else if ( !Boolean.TRUE.equals( conclusion ) )
105            {
106                throw new ChecksumFailureException( "no checksums" );
107            }
108        }
109
110        @Override
111        public void onTransferRetry()
112        {
113            callbacks.add( String.format( "retry()" ) );
114        }
115
116        @Override
117        public boolean onTransferChecksumFailure( ChecksumFailureException exception )
118        {
119            callbacks.add( String.format( "fail(%s)", exception.getMessage() ) );
120            return tolerateFailure;
121        }
122
123        void assertCallbacks( String... callbacks )
124        {
125            assertEquals( Arrays.asList( callbacks ), this.callbacks );
126        }
127
128    }
129
130    private static class StubChecksumFetcher
131        implements ChecksumValidator.ChecksumFetcher
132    {
133
134        HashMap<URI, Object> checksums = new HashMap<>();
135
136        ArrayList<File> checksumFiles = new ArrayList<>();
137
138        private final ArrayList<URI> fetchedFiles = new ArrayList<>();
139
140        @Override
141        public boolean fetchChecksum( URI remote, File local )
142            throws Exception
143        {
144            fetchedFiles.add( remote );
145            Object checksum = checksums.get( remote );
146            if ( checksum == null )
147            {
148                return false;
149            }
150            if ( checksum instanceof Exception )
151            {
152                throw (Exception) checksum;
153            }
154            TestFileUtils.writeString( local, checksum.toString() );
155            checksumFiles.add( local );
156            return true;
157        }
158
159        void mock( String algo, Object value )
160        {
161            checksums.put( toUri( algo ), value );
162        }
163
164        void assertFetchedFiles( String... algos )
165        {
166            List<URI> expected = new ArrayList<>();
167            for ( String algo : algos )
168            {
169                expected.add( toUri( algo ) );
170            }
171            assertEquals( expected, fetchedFiles );
172        }
173
174        private static URI toUri( String algo )
175        {
176            return newChecksum( algo ).getLocation();
177        }
178
179    }
180
181    private StubChecksumPolicy policy;
182
183    private StubChecksumFetcher fetcher;
184
185    private File dataFile;
186
187    private static final TestChecksumAlgorithmSelector selector = new TestChecksumAlgorithmSelector();
188
189    private List<ChecksumAlgorithmFactory> newChecksumAlgorithmFactories( String... factories )
190    {
191        List<ChecksumAlgorithmFactory> checksums = new ArrayList<>();
192        for ( String factory : factories )
193        {
194            checksums.add( selector.select( factory ) );
195        }
196        return checksums;
197    }
198
199    private static RepositoryLayout.ChecksumLocation newChecksum( String factory )
200    {
201        return RepositoryLayout.ChecksumLocation.forLocation( URI.create( "file" ), selector.select( factory ) );
202    }
203
204    private List<RepositoryLayout.ChecksumLocation> newChecksums( List<ChecksumAlgorithmFactory> checksumAlgorithmFactories )
205    {
206        List<RepositoryLayout.ChecksumLocation> checksums = new ArrayList<>();
207        for ( ChecksumAlgorithmFactory factory : checksumAlgorithmFactories )
208        {
209            checksums.add( RepositoryLayout.ChecksumLocation.forLocation( URI.create( "file" ), factory ) );
210        }
211        return checksums;
212    }
213
214    private ChecksumValidator newValidator( String... factories )
215    {
216        return newValidator( null, factories );
217    }
218
219    private ChecksumValidator newValidator( Map<String, String> providedChecksums, String... factories )
220    {
221        List<ChecksumAlgorithmFactory> checksumAlgorithmFactories = newChecksumAlgorithmFactories( factories );
222        return new ChecksumValidator( dataFile, checksumAlgorithmFactories, new TestFileProcessor(), fetcher, policy, providedChecksums, newChecksums( checksumAlgorithmFactories ) );
223    }
224
225    private Map<String, ?> checksums( String... algoDigestPairs )
226    {
227        Map<String, Object> checksums = new LinkedHashMap<>();
228        for ( int i = 0; i < algoDigestPairs.length; i += 2 )
229        {
230            String algo = algoDigestPairs[i];
231            String digest = algoDigestPairs[i + 1];
232            if ( digest == null )
233            {
234                checksums.put( algo, new IOException( "error" ) );
235            }
236            else
237            {
238                checksums.put( algo, digest );
239            }
240        }
241        return checksums;
242    }
243
244    @Before
245    public void init()
246        throws Exception
247    {
248        dataFile = TestFileUtils.createTempFile( "" );
249        dataFile.delete();
250        policy = new StubChecksumPolicy();
251        fetcher = new StubChecksumFetcher();
252    }
253
254    @Test
255    public void testValidate_NullPolicy()
256        throws Exception
257    {
258        policy = null;
259        ChecksumValidator validator = newValidator( SHA1 );
260        validator.validate( checksums( SHA1, "ignored" ), null );
261        fetcher.assertFetchedFiles();
262    }
263
264    @Test
265    public void testValidate_AcceptOnFirstMatch()
266        throws Exception
267    {
268        ChecksumValidator validator = newValidator( SHA1 );
269        fetcher.mock( SHA1, "foo" );
270        validator.validate( checksums( SHA1, "foo" ), null );
271        fetcher.assertFetchedFiles( SHA1 );
272        policy.assertCallbacks( "match(SHA-1, REMOTE_EXTERNAL)" );
273    }
274
275    @Test
276    public void testValidate_FailOnFirstMismatch()
277    {
278        ChecksumValidator validator = newValidator( SHA1 );
279        fetcher.mock( SHA1, "foo" );
280        try
281        {
282            validator.validate( checksums( SHA1, "not-foo" ), null );
283            fail( "expected exception" );
284        }
285        catch ( ChecksumFailureException e )
286        {
287            assertEquals( "foo", e.getExpected() );
288            assertEquals( ChecksumKind.REMOTE_EXTERNAL.name(), e.getExpectedKind() );
289            assertEquals( "not-foo", e.getActual() );
290            assertTrue( e.isRetryWorthy() );
291        }
292        fetcher.assertFetchedFiles( SHA1 );
293        policy.assertCallbacks( "mismatch(SHA-1, REMOTE_EXTERNAL)" );
294    }
295
296    @Test
297    public void testValidate_AcceptOnEnd()
298        throws Exception
299    {
300        policy.inspectAll = true;
301        ChecksumValidator validator = newValidator( SHA1, MD5 );
302        fetcher.mock( SHA1, "foo" );
303        fetcher.mock( MD5, "bar" );
304        validator.validate( checksums( SHA1, "foo", MD5, "bar" ), null );
305        fetcher.assertFetchedFiles( SHA1, MD5 );
306        policy.assertCallbacks( "match(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()" );
307    }
308
309    @Test
310    public void testValidate_FailOnEnd()
311    {
312        policy.inspectAll = true;
313        ChecksumValidator validator = newValidator( SHA1, MD5 );
314        fetcher.mock( SHA1, "foo" );
315        fetcher.mock( MD5, "bar" );
316        try
317        {
318            validator.validate( checksums( SHA1, "not-foo", MD5, "bar" ), null );
319            fail( "expected exception" );
320        }
321        catch ( ChecksumFailureException e )
322        {
323            assertEquals( "foo", e.getExpected() );
324            assertEquals( ChecksumKind.REMOTE_EXTERNAL.name(), e.getExpectedKind() );
325            assertEquals( "not-foo", e.getActual() );
326            assertTrue( e.isRetryWorthy() );
327        }
328        fetcher.assertFetchedFiles( SHA1, MD5 );
329        policy.assertCallbacks( "mismatch(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()" );
330    }
331
332    @Test
333    public void testValidate_IncludedBeforeExternal()
334        throws Exception
335    {
336        policy.inspectAll = true;
337        HashMap<String, String> provided = new HashMap<>();
338        provided.put( SHA1, "foo" );
339        ChecksumValidator validator = newValidator( provided, SHA1, MD5 );
340        fetcher.mock( SHA1, "foo" );
341        fetcher.mock( MD5, "bar" );
342        validator.validate( checksums( SHA1, "foo", MD5, "bar" ), checksums( SHA1, "foo", MD5, "bar" ) );
343        fetcher.assertFetchedFiles( SHA1, MD5 );
344        policy.assertCallbacks( "match(SHA-1, PROVIDED)", "match(SHA-1, REMOTE_INCLUDED)", "match(MD5, REMOTE_INCLUDED)",
345            "match(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()" );
346    }
347
348    @Test
349    public void testValidate_CaseInsensitive()
350        throws Exception
351    {
352        policy.inspectAll = true;
353        ChecksumValidator validator = newValidator( SHA1 );
354        fetcher.mock( SHA1, "FOO" );
355        validator.validate( checksums( SHA1, "foo" ), checksums( SHA1, "foo" ) );
356        policy.assertCallbacks( "match(SHA-1, REMOTE_INCLUDED)", "match(SHA-1, REMOTE_EXTERNAL)", "noMore()" );
357    }
358
359    @Test
360    public void testValidate_MissingRemoteChecksum()
361        throws Exception
362    {
363        ChecksumValidator validator = newValidator( SHA1, MD5 );
364        fetcher.mock( MD5, "bar" );
365        validator.validate( checksums( MD5, "bar" ), null );
366        fetcher.assertFetchedFiles( SHA1, MD5 );
367        policy.assertCallbacks( "match(MD5, REMOTE_EXTERNAL)" );
368    }
369
370    @Test
371    public void testValidate_InaccessibleRemoteChecksum()
372        throws Exception
373    {
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    public void testValidate_InaccessibleLocalChecksum()
384        throws Exception
385    {
386        ChecksumValidator validator = newValidator( SHA1, MD5 );
387        fetcher.mock( SHA1, "foo" );
388        fetcher.mock( MD5, "bar" );
389        validator.validate( checksums( SHA1, null, MD5, "bar" ), null );
390        fetcher.assertFetchedFiles( MD5 );
391        policy.assertCallbacks( "error(SHA-1, REMOTE_EXTERNAL, error)", "match(MD5, REMOTE_EXTERNAL)" );
392    }
393
394    @Test
395    public void testHandle_Accept()
396    {
397        policy.tolerateFailure = true;
398        ChecksumValidator validator = newValidator( SHA1 );
399        assertTrue( validator.handle( new ChecksumFailureException( "accept" ) ) );
400        policy.assertCallbacks( "fail(accept)" );
401    }
402
403    @Test
404    public void testHandle_Reject()
405    {
406        policy.tolerateFailure = false;
407        ChecksumValidator validator = newValidator( SHA1 );
408        assertFalse( validator.handle( new ChecksumFailureException( "reject" ) ) );
409        policy.assertCallbacks( "fail(reject)" );
410    }
411
412    @Test
413    public void testRetry_ResetPolicy()
414    {
415        ChecksumValidator validator = newValidator( SHA1 );
416        validator.retry();
417        policy.assertCallbacks( "retry()" );
418    }
419
420    @Test
421    public void testRetry_RemoveTempFiles()
422        throws Exception
423    {
424        ChecksumValidator validator = newValidator( SHA1 );
425        fetcher.mock( SHA1, "foo" );
426        validator.validate( checksums( SHA1, "foo" ), null );
427        fetcher.assertFetchedFiles( SHA1 );
428        assertEquals( 1, fetcher.checksumFiles.size() );
429        for ( File file : fetcher.checksumFiles )
430        {
431            assertTrue( file.getAbsolutePath(), file.isFile() );
432        }
433        validator.retry();
434        for ( File file : fetcher.checksumFiles )
435        {
436            assertFalse( file.getAbsolutePath(), file.exists() );
437        }
438    }
439
440    @Test
441    public void testCommit_SaveChecksumFiles()
442        throws Exception
443    {
444        policy.inspectAll = true;
445        ChecksumValidator validator = newValidator( SHA1, MD5 );
446        fetcher.mock( MD5, "bar" );
447        validator.validate( checksums( SHA1, "foo", MD5, "bar" ), checksums( SHA1, "foo" ) );
448        assertEquals( 1, fetcher.checksumFiles.size() );
449        for ( File file : fetcher.checksumFiles )
450        {
451            assertTrue( file.getAbsolutePath(), file.isFile() );
452        }
453        validator.commit();
454        File checksumFile = new File( dataFile.getPath() + ".sha1" );
455        assertTrue( checksumFile.getAbsolutePath(), checksumFile.isFile() );
456        assertEquals( "foo", TestFileUtils.readString( checksumFile ) );
457        checksumFile = new File( dataFile.getPath() + ".md5" );
458        assertTrue( checksumFile.getAbsolutePath(), checksumFile.isFile() );
459        assertEquals( "bar", TestFileUtils.readString( checksumFile ) );
460        for ( File file : fetcher.checksumFiles )
461        {
462            assertFalse( file.getAbsolutePath(), file.exists() );
463        }
464    }
465
466    @Test
467    public void testClose_RemoveTempFiles()
468        throws Exception
469    {
470        ChecksumValidator validator = newValidator( SHA1 );
471        fetcher.mock( SHA1, "foo" );
472        validator.validate( checksums( SHA1, "foo" ), null );
473        fetcher.assertFetchedFiles( SHA1 );
474        assertEquals( 1, fetcher.checksumFiles.size() );
475        for ( File file : fetcher.checksumFiles )
476        {
477            assertTrue( file.getAbsolutePath(), file.isFile() );
478        }
479        validator.close();
480        for ( File file : fetcher.checksumFiles )
481        {
482            assertFalse( file.getAbsolutePath(), file.exists() );
483        }
484    }
485
486}