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.junit.Assert.*;
023
024import java.io.File;
025import java.io.IOException;
026import java.net.URI;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.HashMap;
030import java.util.LinkedHashMap;
031import java.util.List;
032import java.util.Map;
033
034import org.eclipse.aether.internal.test.util.TestFileProcessor;
035import org.eclipse.aether.internal.test.util.TestFileUtils;
036import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
037import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
038import org.eclipse.aether.transfer.ChecksumFailureException;
039import org.junit.Before;
040import org.junit.Test;
041
042public class ChecksumValidatorTest
043{
044
045    private static class StubChecksumPolicy
046        implements ChecksumPolicy
047    {
048
049        boolean inspectAll;
050
051        boolean tolerateFailure;
052
053        private List<String> callbacks = new ArrayList<>();
054
055        private Object conclusion;
056
057        public boolean onChecksumMatch( String algorithm, int kind )
058        {
059            callbacks.add( String.format( "match(%s, %04x)", algorithm, kind ) );
060            if ( inspectAll )
061            {
062                if ( conclusion == null )
063                {
064                    conclusion = true;
065                }
066                return false;
067            }
068            return true;
069        }
070
071        public void onChecksumMismatch( String algorithm, int kind, ChecksumFailureException exception )
072            throws ChecksumFailureException
073        {
074            callbacks.add( String.format( "mismatch(%s, %04x)", algorithm, kind ) );
075            if ( inspectAll )
076            {
077                conclusion = exception;
078                return;
079            }
080            throw exception;
081        }
082
083        public void onChecksumError( String algorithm, int kind, ChecksumFailureException exception )
084        {
085            callbacks.add( String.format( "error(%s, %04x, %s)", algorithm, kind, exception.getCause().getMessage() ) );
086        }
087
088        public void onNoMoreChecksums()
089            throws ChecksumFailureException
090        {
091            callbacks.add( String.format( "noMore()" ) );
092            if ( conclusion instanceof ChecksumFailureException )
093            {
094                throw (ChecksumFailureException) conclusion;
095            }
096            else if ( !Boolean.TRUE.equals( conclusion ) )
097            {
098                throw new ChecksumFailureException( "no checksums" );
099            }
100        }
101
102        public void onTransferRetry()
103        {
104            callbacks.add( String.format( "retry()" ) );
105        }
106
107        public boolean onTransferChecksumFailure( ChecksumFailureException exception )
108        {
109            callbacks.add( String.format( "fail(%s)", exception.getMessage() ) );
110            return tolerateFailure;
111        }
112
113        void assertCallbacks( String... callbacks )
114        {
115            assertEquals( Arrays.asList( callbacks ), this.callbacks );
116        }
117
118    }
119
120    private static class StubChecksumFetcher
121        implements ChecksumValidator.ChecksumFetcher
122    {
123
124        Map<URI, Object> checksums = new HashMap<>();
125
126        List<File> checksumFiles = new ArrayList<>();
127
128        private List<URI> fetchedFiles = new ArrayList<>();
129
130        public boolean fetchChecksum( URI remote, File local )
131            throws Exception
132        {
133            fetchedFiles.add( remote );
134            Object checksum = checksums.get( remote );
135            if ( checksum == null )
136            {
137                return false;
138            }
139            if ( checksum instanceof Exception )
140            {
141                throw (Exception) checksum;
142            }
143            TestFileUtils.writeString( local, checksum.toString() );
144            checksumFiles.add( local );
145            return true;
146        }
147
148        void mock( String algo, Object value )
149        {
150            checksums.put( toUri( algo ), value );
151        }
152
153        void assertFetchedFiles( String... algos )
154        {
155            List<URI> expected = new ArrayList<>();
156            for ( String algo : algos )
157            {
158                expected.add( toUri( algo ) );
159            }
160            assertEquals( expected, fetchedFiles );
161        }
162
163        private static URI toUri( String algo )
164        {
165            return newChecksum( algo ).getLocation();
166        }
167
168    }
169
170    private static final String SHA1 = "SHA-1";
171
172    private static final String MD5 = "MD5";
173
174    private StubChecksumPolicy policy;
175
176    private StubChecksumFetcher fetcher;
177
178    private File dataFile;
179
180    private static RepositoryLayout.Checksum newChecksum( String algo )
181    {
182        return RepositoryLayout.Checksum.forLocation( URI.create( "file" ), algo );
183    }
184
185    private List<RepositoryLayout.Checksum> newChecksums( String... algos )
186    {
187        List<RepositoryLayout.Checksum> checksums = new ArrayList<>();
188        for ( String algo : algos )
189        {
190            checksums.add( newChecksum( algo ) );
191        }
192        return checksums;
193    }
194
195    private ChecksumValidator newValidator( String... algos )
196    {
197        return new ChecksumValidator( dataFile, new TestFileProcessor(), fetcher, policy, newChecksums( algos ) );
198    }
199
200    private Map<String, ?> checksums( String... algoDigestPairs )
201    {
202        Map<String, Object> checksums = new LinkedHashMap<>();
203        for ( int i = 0; i < algoDigestPairs.length; i += 2 )
204        {
205            String algo = algoDigestPairs[i];
206            String digest = algoDigestPairs[i + 1];
207            if ( digest == null )
208            {
209                checksums.put( algo, new IOException( "error" ) );
210            }
211            else
212            {
213                checksums.put( algo, digest );
214            }
215        }
216        return checksums;
217    }
218
219    @Before
220    public void init()
221        throws Exception
222    {
223        dataFile = TestFileUtils.createTempFile( "" );
224        dataFile.delete();
225        policy = new StubChecksumPolicy();
226        fetcher = new StubChecksumFetcher();
227    }
228
229    @Test
230    public void testValidate_NullPolicy()
231        throws Exception
232    {
233        policy = null;
234        ChecksumValidator validator = newValidator( SHA1 );
235        validator.validate( checksums( SHA1, "ignored" ), null );
236        fetcher.assertFetchedFiles();
237    }
238
239    @Test
240    public void testValidate_AcceptOnFirstMatch()
241        throws Exception
242    {
243        ChecksumValidator validator = newValidator( SHA1 );
244        fetcher.mock( SHA1, "foo" );
245        validator.validate( checksums( SHA1, "foo" ), null );
246        fetcher.assertFetchedFiles( SHA1 );
247        policy.assertCallbacks( "match(SHA-1, 0000)" );
248    }
249
250    @Test
251    public void testValidate_FailOnFirstMismatch()
252    {
253        ChecksumValidator validator = newValidator( SHA1 );
254        fetcher.mock( SHA1, "foo" );
255        try
256        {
257            validator.validate( checksums( SHA1, "not-foo" ), null );
258            fail( "expected exception" );
259        }
260        catch ( ChecksumFailureException e )
261        {
262            assertEquals( "foo", e.getExpected() );
263            assertEquals( "not-foo", e.getActual() );
264            assertTrue( e.isRetryWorthy() );
265        }
266        fetcher.assertFetchedFiles( SHA1 );
267        policy.assertCallbacks( "mismatch(SHA-1, 0000)" );
268    }
269
270    @Test
271    public void testValidate_AcceptOnEnd()
272        throws Exception
273    {
274        policy.inspectAll = true;
275        ChecksumValidator validator = newValidator( SHA1, MD5 );
276        fetcher.mock( SHA1, "foo" );
277        fetcher.mock( MD5, "bar" );
278        validator.validate( checksums( SHA1, "foo", MD5, "bar" ), null );
279        fetcher.assertFetchedFiles( SHA1, MD5 );
280        policy.assertCallbacks( "match(SHA-1, 0000)", "match(MD5, 0000)", "noMore()" );
281    }
282
283    @Test
284    public void testValidate_FailOnEnd()
285    {
286        policy.inspectAll = true;
287        ChecksumValidator validator = newValidator( SHA1, MD5 );
288        fetcher.mock( SHA1, "foo" );
289        fetcher.mock( MD5, "bar" );
290        try
291        {
292            validator.validate( checksums( SHA1, "not-foo", MD5, "bar" ), null );
293            fail( "expected exception" );
294        }
295        catch ( ChecksumFailureException e )
296        {
297            assertEquals( "foo", e.getExpected() );
298            assertEquals( "not-foo", e.getActual() );
299            assertTrue( e.isRetryWorthy() );
300        }
301        fetcher.assertFetchedFiles( SHA1, MD5 );
302        policy.assertCallbacks( "mismatch(SHA-1, 0000)", "match(MD5, 0000)", "noMore()" );
303    }
304
305    @Test
306    public void testValidate_InlinedBeforeExternal()
307        throws Exception
308    {
309        policy.inspectAll = true;
310        ChecksumValidator validator = newValidator( SHA1, MD5 );
311        fetcher.mock( SHA1, "foo" );
312        fetcher.mock( MD5, "bar" );
313        validator.validate( checksums( SHA1, "foo", MD5, "bar" ), checksums( SHA1, "foo", MD5, "bar" ) );
314        fetcher.assertFetchedFiles( SHA1, MD5 );
315        policy.assertCallbacks( "match(SHA-1, 0001)", "match(MD5, 0001)", "match(SHA-1, 0000)", "match(MD5, 0000)",
316                                "noMore()" );
317    }
318
319    @Test
320    public void testValidate_CaseInsensitive()
321        throws Exception
322    {
323        policy.inspectAll = true;
324        ChecksumValidator validator = newValidator( SHA1 );
325        fetcher.mock( SHA1, "FOO" );
326        validator.validate( checksums( SHA1, "foo" ), checksums( SHA1, "foo" ) );
327        policy.assertCallbacks( "match(SHA-1, 0001)", "match(SHA-1, 0000)", "noMore()" );
328    }
329
330    @Test
331    public void testValidate_MissingRemoteChecksum()
332        throws Exception
333    {
334        ChecksumValidator validator = newValidator( SHA1, MD5 );
335        fetcher.mock( MD5, "bar" );
336        validator.validate( checksums( MD5, "bar" ), null );
337        fetcher.assertFetchedFiles( SHA1, MD5 );
338        policy.assertCallbacks( "match(MD5, 0000)" );
339    }
340
341    @Test
342    public void testValidate_InaccessibleRemoteChecksum()
343        throws Exception
344    {
345        ChecksumValidator validator = newValidator( SHA1, MD5 );
346        fetcher.mock( SHA1, new IOException( "inaccessible" ) );
347        fetcher.mock( MD5, "bar" );
348        validator.validate( checksums( MD5, "bar" ), null );
349        fetcher.assertFetchedFiles( SHA1, MD5 );
350        policy.assertCallbacks( "error(SHA-1, 0000, inaccessible)", "match(MD5, 0000)" );
351    }
352
353    @Test
354    public void testValidate_InaccessibleLocalChecksum()
355        throws Exception
356    {
357        ChecksumValidator validator = newValidator( SHA1, MD5 );
358        fetcher.mock( SHA1, "foo" );
359        fetcher.mock( MD5, "bar" );
360        validator.validate( checksums( SHA1, null, MD5, "bar" ), null );
361        fetcher.assertFetchedFiles( MD5 );
362        policy.assertCallbacks( "error(SHA-1, 0000, error)", "match(MD5, 0000)" );
363    }
364
365    @Test
366    public void testHandle_Accept()
367    {
368        policy.tolerateFailure = true;
369        ChecksumValidator validator = newValidator( SHA1 );
370        assertEquals( true, validator.handle( new ChecksumFailureException( "accept" ) ) );
371        policy.assertCallbacks( "fail(accept)" );
372    }
373
374    @Test
375    public void testHandle_Reject()
376    {
377        policy.tolerateFailure = false;
378        ChecksumValidator validator = newValidator( SHA1 );
379        assertEquals( false, validator.handle( new ChecksumFailureException( "reject" ) ) );
380        policy.assertCallbacks( "fail(reject)" );
381    }
382
383    @Test
384    public void testRetry_ResetPolicy()
385    {
386        ChecksumValidator validator = newValidator( SHA1 );
387        validator.retry();
388        policy.assertCallbacks( "retry()" );
389    }
390
391    @Test
392    public void testRetry_RemoveTempFiles()
393        throws Exception
394    {
395        ChecksumValidator validator = newValidator( SHA1 );
396        fetcher.mock( SHA1, "foo" );
397        validator.validate( checksums( SHA1, "foo" ), null );
398        fetcher.assertFetchedFiles( SHA1 );
399        assertEquals( 1, fetcher.checksumFiles.size() );
400        for ( File file : fetcher.checksumFiles )
401        {
402            assertTrue( file.getAbsolutePath(), file.isFile() );
403        }
404        validator.retry();
405        for ( File file : fetcher.checksumFiles )
406        {
407            assertFalse( file.getAbsolutePath(), file.exists() );
408        }
409    }
410
411    @Test
412    public void testCommit_SaveChecksumFiles()
413        throws Exception
414    {
415        policy.inspectAll = true;
416        ChecksumValidator validator = newValidator( SHA1, MD5 );
417        fetcher.mock( MD5, "bar" );
418        validator.validate( checksums( SHA1, "foo", MD5, "bar" ), checksums( SHA1, "foo" ) );
419        assertEquals( 1, fetcher.checksumFiles.size() );
420        for ( File file : fetcher.checksumFiles )
421        {
422            assertTrue( file.getAbsolutePath(), file.isFile() );
423        }
424        validator.commit();
425        File checksumFile = new File( dataFile.getPath() + ".sha1" );
426        assertTrue( checksumFile.getAbsolutePath(), checksumFile.isFile() );
427        assertEquals( "foo", TestFileUtils.readString( checksumFile ) );
428        checksumFile = new File( dataFile.getPath() + ".md5" );
429        assertTrue( checksumFile.getAbsolutePath(), checksumFile.isFile() );
430        assertEquals( "bar", TestFileUtils.readString( checksumFile ) );
431        for ( File file : fetcher.checksumFiles )
432        {
433            assertFalse( file.getAbsolutePath(), file.exists() );
434        }
435    }
436
437    @Test
438    public void testClose_RemoveTempFiles()
439        throws Exception
440    {
441        ChecksumValidator validator = newValidator( SHA1 );
442        fetcher.mock( SHA1, "foo" );
443        validator.validate( checksums( SHA1, "foo" ), null );
444        fetcher.assertFetchedFiles( SHA1 );
445        assertEquals( 1, fetcher.checksumFiles.size() );
446        for ( File file : fetcher.checksumFiles )
447        {
448            assertTrue( file.getAbsolutePath(), file.isFile() );
449        }
450        validator.close();
451        for ( File file : fetcher.checksumFiles )
452        {
453            assertFalse( file.getAbsolutePath(), file.exists() );
454        }
455    }
456
457}