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