/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.index.updater;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Properties;
import java.util.Set;

import org.apache.lucene.index.Term;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.ByteBuffersDirectory;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IOContext;
import org.apache.maven.index.ArtifactInfo;
import org.apache.maven.index.FlatSearchRequest;
import org.apache.maven.index.FlatSearchResponse;
import org.apache.maven.index.MAVEN;
import org.apache.maven.index.SearchType;
import org.apache.maven.index.context.DocumentFilter;
import org.apache.maven.index.context.IndexUtils;
import org.apache.maven.index.context.IndexingContext;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.api.Invocation;
import org.jmock.lib.action.ReturnValueAction;
import org.jmock.lib.action.VoidAction;
import org.junit.Ignore;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
 * @author Eugene Kuleshov
 */
public class DefaultIndexUpdaterTest extends AbstractIndexUpdaterTest {

    SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss.SSS Z");

    @Test
    public void testReplaceIndex() throws Exception {
        indexer.addArtifactToIndex(
                createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.2", null), context);

        Query q = indexer.constructQuery(MAVEN.ARTIFACT_ID, "commons-lang", SearchType.SCORED);

        FlatSearchResponse response1 = indexer.searchFlat(new FlatSearchRequest(q));
        Collection<ArtifactInfo> content1 = response1.getResults();

        assertEquals(content1.toString(), 1, content1.size());

        // updated index

        Directory tempIndexDirectory = new ByteBuffersDirectory();

        IndexingContext tempContext = indexer.addIndexingContext(
                repositoryId + "temp", repositoryId, null, tempIndexDirectory, repositoryUrl, null, MIN_CREATORS);

        indexer.addArtifactToIndex(
                createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.3", null), tempContext);

        indexer.addArtifactToIndex(
                createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.4", null), tempContext);

        FlatSearchResponse response2 = indexer.searchFlat(new FlatSearchRequest(q, tempContext));
        Collection<ArtifactInfo> tempContent = response2.getResults();
        assertEquals(tempContent.toString(), 2, tempContent.size());

        // RAMDirectory is closed with context, forcing timestamp update
        tempContext.updateTimestamp(true);

        // A change in RAMDirectory and Directory behavior in general: it will copy the Index files ONLY
        // So we must make sure that timestamp file is transferred correctly.
        ByteBuffersDirectory tempDir2 = new ByteBuffersDirectory();
        IndexUtils.copyDirectory(tempContext.getIndexDirectory(), tempDir2);

        Date newIndexTimestamp = tempContext.getTimestamp();

        indexer.removeIndexingContext(tempContext, false);

        context.replace(tempDir2);

        assertEquals(newIndexTimestamp, context.getTimestamp());

        FlatSearchResponse response3 = indexer.searchFlat(new FlatSearchRequest(q));
        Collection<ArtifactInfo> content2 = response3.getResults();
        assertEquals(content2.toString(), 2, content2.size());
    }

    @Test
    public void testMergeIndex() throws Exception {
        indexer.addArtifactToIndex(
                createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.2", null), context);

        Query q = indexer.constructQuery(MAVEN.ARTIFACT_ID, "commons-lang", SearchType.SCORED);

        {
            FlatSearchResponse response1 = indexer.searchFlat(new FlatSearchRequest(q));
            Collection<ArtifactInfo> content1 = response1.getResults();

            assertEquals(content1.toString(), 1, content1.size());
        }

        // updated index

        {
            Directory tempIndexDirectory = new ByteBuffersDirectory();

            IndexingContext tempContext = indexer.addIndexingContext(
                    repositoryId + "temp", repositoryId, null, tempIndexDirectory, repositoryUrl, null, MIN_CREATORS);

            // indexer.addArtifactToIndex(
            // createArtifactContext( repositoryId, "commons-lang", "commons-lang", "2.2", null ),
            // tempContext );

            indexer.addArtifactToIndex(
                    createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.3", null), tempContext);

            indexer.addArtifactToIndex(
                    createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.4", null), tempContext);

            FlatSearchResponse tempResponse = indexer.searchFlat(new FlatSearchRequest(q));
            Collection<ArtifactInfo> tempContent = tempResponse.getResults();
            assertEquals(tempContent.toString(), 3, tempContent.size());

            ByteBuffersDirectory tempDir2 = new ByteBuffersDirectory();
            for (String file : tempContext.getIndexDirectory().listAll()) {
                tempDir2.copyFrom(tempContext.getIndexDirectory(), file, file, IOContext.DEFAULT);
            }

            indexer.removeIndexingContext(tempContext, false);

            context.merge(tempDir2);

            FlatSearchResponse response2 = indexer.searchFlat(new FlatSearchRequest(q));
            Collection<ArtifactInfo> content2 = response2.getResults();
            assertEquals(content2.toString(), 3, content2.size());
        }
    }

    @Test
    public void testMergeIndexDeletes() throws Exception {
        indexer.addArtifactToIndex(
                createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.2", null), context);

        indexer.addArtifactToIndex(
                createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.3", null), context);

        indexer.addArtifactToIndex(
                createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.4", null), context);

        {
            Directory tempIndexDirectory = new ByteBuffersDirectory();

            IndexingContext tempContext = indexer.addIndexingContext(
                    repositoryId + "temp", repositoryId, null, tempIndexDirectory, repositoryUrl, null, MIN_CREATORS);

            indexer.addArtifactToIndex(
                    createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.4", null), tempContext);

            indexer.addArtifactToIndex(
                    createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.2", null), tempContext);

            indexer.deleteArtifactFromIndex(
                    createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.2", null), tempContext);

            indexer.deleteArtifactFromIndex(
                    createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.4", null), tempContext);

            ByteBuffersDirectory tempDir2 = new ByteBuffersDirectory();
            for (String file : tempContext.getIndexDirectory().listAll()) {
                tempDir2.copyFrom(tempContext.getIndexDirectory(), file, file, IOContext.DEFAULT);
            }

            indexer.removeIndexingContext(tempContext, false);

            context.merge(tempDir2);
        }

        Query q = indexer.constructQuery(MAVEN.ARTIFACT_ID, "commons-lang", SearchType.SCORED);

        FlatSearchResponse response = indexer.searchFlat(new FlatSearchRequest(q));
        Collection<ArtifactInfo> content2 = response.getResults();

        assertEquals(content2.toString(), 1, content2.size());
    }

    @Test
    public void testMergeSearch() throws Exception {
        File repo1 = new File(getBasedir(), "src/test/nexus-658");
        Directory indexDir1 = new ByteBuffersDirectory();

        IndexingContext context1 =
                indexer.addIndexingContext("nexus-658", "nexus-658", repo1, indexDir1, null, null, DEFAULT_CREATORS);
        indexer.scan(context1);

        File repo2 = new File(getBasedir(), "src/test/nexus-13");
        Directory indexDir2 = new ByteBuffersDirectory();

        IndexingContext context2 =
                indexer.addIndexingContext("nexus-13", "nexus-13", repo2, indexDir2, null, null, DEFAULT_CREATORS);
        indexer.scan(context2);

        context1.merge(indexDir2);

        Query q = new TermQuery(new Term(ArtifactInfo.SHA1, "b5e9d009320d11b9859c15d3ad3603b455fa1c85"));
        FlatSearchRequest request = new FlatSearchRequest(q, context1);
        FlatSearchResponse response = indexer.searchFlat(request);

        Set<ArtifactInfo> results = response.getResults();
        ArtifactInfo artifactInfo = results.iterator().next();
        assertEquals(artifactInfo.getArtifactId(), "dma.integration.tests");
    }

    @Test
    public void testMergeGroups() throws Exception {
        indexer.addArtifactToIndex(
                createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.2", null), context);

        indexer.addArtifactToIndex(
                createArtifactContext(repositoryId, "commons-collections", "commons-collections", "1.0", null),
                context);

        indexer.addArtifactToIndex(
                createArtifactContext(repositoryId, "org.slf4j", "slf4j-api", "1.4.2", null), context);

        indexer.addArtifactToIndex(
                createArtifactContext(repositoryId, "org.slf4j", "slf4j-log4j12", "1.4.2", null), context);

        {
            Directory tempIndexDirectory = new ByteBuffersDirectory();

            IndexingContext tempContext = indexer.addIndexingContext(
                    repositoryId + "temp", repositoryId, null, tempIndexDirectory, repositoryUrl, null, MIN_CREATORS);

            indexer.addArtifactToIndex(
                    createArtifactContext(repositoryId, "commons-lang", "commons-lang", "2.4", null), tempContext);

            indexer.addArtifactToIndex(createArtifactContext(repositoryId, "junit", "junit", "3.8", null), tempContext);

            indexer.addArtifactToIndex(
                    createArtifactContext(repositoryId, "org.slf4j.foo", "jcl104-over-slf4j", "1.4.2", null), context);

            ByteBuffersDirectory tempDir2 = new ByteBuffersDirectory();
            for (String file : tempContext.getIndexDirectory().listAll()) {
                tempDir2.copyFrom(tempContext.getIndexDirectory(), file, file, IOContext.DEFAULT);
            }

            indexer.removeIndexingContext(tempContext, false);

            context.merge(tempDir2);
        }

        Set<String> rootGroups = context.getRootGroups();

        assertEquals(rootGroups.toString(), 4, rootGroups.size());

        Set<String> allGroups = context.getAllGroups();

        assertEquals(allGroups.toString(), 5, allGroups.size());
    }

    @Test
    public void testNoIndexUpdate() throws Exception {
        Mockery mockery = new Mockery();

        final String indexUrl = repositoryUrl + ".index";
        final Date contextTimestamp = df.parse("20081125010000.000 -0600");

        final ResourceFetcher mockFetcher = mockery.mock(ResourceFetcher.class);

        final IndexingContext tempContext = mockery.mock(IndexingContext.class);

        final Properties localProps = new Properties();
        localProps.setProperty(IndexingContext.INDEX_CHUNK_COUNTER, "1");
        localProps.setProperty(IndexingContext.INDEX_CHAIN_ID, "someid");
        localProps.setProperty(IndexingContext.INDEX_TIMESTAMP, "20081125010000.000 -0600");

        mockery.checking(new Expectations() {
            {
                allowing(tempContext).getIndexDirectoryFile();
                will(new IndexDirectoryFileAction(localProps, testBasedir));

                allowing(tempContext).getTimestamp();
                will(returnValue(contextTimestamp));

                allowing(tempContext).getId();
                will(returnValue(repositoryId));

                allowing(tempContext).commit();

                allowing(tempContext).getIndexUpdateUrl();
                will(returnValue(indexUrl));

                allowing(tempContext).getIndexCreators();
                will(returnValue(DEFAULT_CREATORS));

                oneOf(mockFetcher).connect(repositoryId, indexUrl);

                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_REMOTE_PROPERTIES_FILE));
                will(new PropertiesAction() {
                    @Override
                    Properties getProperties() {
                        Properties properties = new Properties();
                        properties.setProperty(IndexingContext.INDEX_ID, "central");
                        properties.setProperty(IndexingContext.INDEX_TIMESTAMP, "20081125010000.000 -0600");
                        return properties;
                    }
                });

                allowing(tempContext).getIndexDirectoryFile();

                oneOf(mockFetcher).disconnect();
            }
        });

        // tempContext.updateTimestamp( true, contextTimestamp );

        IndexUpdateRequest updateRequest = new IndexUpdateRequest(tempContext, mockFetcher);

        IndexUpdateResult updateResult = updater.fetchAndUpdateIndex(updateRequest);

        mockery.assertIsSatisfied();
        assertIndexUpdateSucceeded(updateResult);
    }

    @Test
    public void testFullIndexUpdate() throws Exception {
        Mockery mockery = new Mockery();

        final String indexUrl = repositoryUrl + ".index";
        final Date contextTimestamp = df.parse("20081125010000.000 -0600");

        final ResourceFetcher mockFetcher = mockery.mock(ResourceFetcher.class);

        final IndexingContext tempContext = mockery.mock(IndexingContext.class);

        mockery.checking(new Expectations() {
            {
                allowing(tempContext).getIndexDirectoryFile();
                will(new ReturnValueAction(testBasedir));

                allowing(tempContext).getTimestamp();
                will(returnValue(contextTimestamp));

                allowing(tempContext).getId();
                will(returnValue(repositoryId));

                allowing(tempContext).getIndexUpdateUrl();
                will(returnValue(indexUrl));

                allowing(tempContext).commit();

                allowing(tempContext).getIndexCreators();
                will(returnValue(DEFAULT_CREATORS));

                allowing(tempContext).commit();

                oneOf(mockFetcher).connect(repositoryId, indexUrl);

                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_REMOTE_PROPERTIES_FILE));
                will(new PropertiesAction() {
                    @Override
                    Properties getProperties() {
                        Properties properties = new Properties();
                        properties.setProperty(IndexingContext.INDEX_ID, "central");
                        properties.setProperty(IndexingContext.INDEX_TIMESTAMP, "20081126010000.000 -0600");
                        return properties;
                    }
                });

                allowing(tempContext).getIndexDirectoryFile();

                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".gz"));
                will(returnValue(newInputStream("index-updater/server-root/nexus-maven-repository-index.gz")));

                oneOf(tempContext).replace(with(any(Directory.class)), with(any(Set.class)), with(any(Set.class)));

                oneOf(mockFetcher).disconnect();
            }
        });

        // tempContext.updateTimestamp( true, contextTimestamp );

        IndexUpdateRequest updateRequest = new IndexUpdateRequest(tempContext, mockFetcher);

        IndexUpdateResult updateResult = updater.fetchAndUpdateIndex(updateRequest);

        mockery.assertIsSatisfied();
        assertIndexUpdateSucceeded(updateResult);
    }

    @Test
    public void testIncrementalIndexUpdate() throws Exception {
        Mockery mockery = new Mockery();

        final String indexUrl = repositoryUrl + ".index";
        final Date contextTimestamp = df.parse("20081128000000.000 -0600");

        final ResourceFetcher mockFetcher = mockery.mock(ResourceFetcher.class);

        final IndexingContext tempContext = mockery.mock(IndexingContext.class);

        final Properties localProps = new Properties();
        localProps.setProperty(IndexingContext.INDEX_CHUNK_COUNTER, "1");
        localProps.setProperty(IndexingContext.INDEX_CHAIN_ID, "someid");

        mockery.checking(new Expectations() {
            {
                allowing(tempContext).getTimestamp();
                will(returnValue(contextTimestamp));

                allowing(tempContext).getId();
                will(returnValue(repositoryId));

                allowing(tempContext).getIndexUpdateUrl();
                will(returnValue(indexUrl));

                allowing(tempContext).commit();

                allowing(tempContext).getIndexCreators();
                will(returnValue(DEFAULT_CREATORS));

                oneOf(mockFetcher).connect(repositoryId, indexUrl);

                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_REMOTE_PROPERTIES_FILE));
                will(new PropertiesAction() {
                    @Override
                    Properties getProperties() {
                        Properties properties = new Properties();
                        properties.setProperty(IndexingContext.INDEX_ID, "central");
                        properties.setProperty(IndexingContext.INDEX_TIMESTAMP, "20081129174241.859 -0600");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_COUNTER, "3");
                        properties.setProperty(IndexingContext.INDEX_CHAIN_ID, "someid");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "0", "3");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "1", "2");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "2", "1");
                        return properties;
                    }
                });

                allowing(tempContext).getIndexDirectoryFile();
                will(new IndexDirectoryFileAction(localProps, testBasedir));

                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".2.gz"));
                will(returnValue(newInputStream("index-updater/server-root/nexus-maven-repository-index.gz")));
                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".3.gz"));
                will(returnValue(newInputStream("index-updater/server-root/nexus-maven-repository-index.gz")));
                // could create index archive there and verify that it is merged correctly

                oneOf(tempContext)
                        .merge(
                                with(any(Directory.class)),
                                with(aNull(DocumentFilter.class)),
                                with(any(Set.class)),
                                with(any(Set.class)));

                oneOf(tempContext)
                        .merge(
                                with(any(Directory.class)),
                                with(aNull(DocumentFilter.class)),
                                with(any(Set.class)),
                                with(any(Set.class)));

                oneOf(mockFetcher).disconnect();
            }
        });

        // tempContext.updateTimestamp( true, contextTimestamp );

        IndexUpdateRequest updateRequest = new IndexUpdateRequest(tempContext, mockFetcher);
        updateRequest.setIncrementalOnly(true);

        IndexUpdateResult updateResult = updater.fetchAndUpdateIndex(updateRequest);

        mockery.assertIsSatisfied();
        assertIndexUpdateSucceeded(updateResult);
    }

    @Test
    public void testIncrementalIndexUpdateNoCounter() throws Exception {
        Mockery mockery = new Mockery();

        final String indexUrl = repositoryUrl + ".index";
        final Date contextTimestamp = df.parse("20081128000000.000 -0600");

        final ResourceFetcher mockFetcher = mockery.mock(ResourceFetcher.class);

        final IndexingContext tempContext = mockery.mock(IndexingContext.class);

        mockery.checking(new Expectations() {
            {
                allowing(tempContext).getIndexDirectoryFile();
                will(new ReturnValueAction(testBasedir));

                allowing(tempContext).getTimestamp();
                will(returnValue(contextTimestamp));

                allowing(tempContext).getId();
                will(returnValue(repositoryId));

                allowing(tempContext).getIndexUpdateUrl();
                will(returnValue(indexUrl));

                allowing(tempContext).commit();

                allowing(tempContext).getIndexCreators();
                will(returnValue(DEFAULT_CREATORS));

                oneOf(mockFetcher).connect(repositoryId, indexUrl);

                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_REMOTE_PROPERTIES_FILE));
                will(new PropertiesAction() {
                    @Override
                    Properties getProperties() {
                        Properties properties = new Properties();
                        properties.setProperty(IndexingContext.INDEX_ID, "central");
                        properties.setProperty(IndexingContext.INDEX_TIMESTAMP, "20081129174241.859 -0600");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_COUNTER, "3");
                        properties.setProperty(IndexingContext.INDEX_CHAIN_ID, "someid");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "0", "3");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "1", "2");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "2", "1");
                        return properties;
                    }
                });

                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".gz"));
                will(returnValue(newInputStream("index-updater/server-root/nexus-maven-repository-index.gz")));
                // could create index archive there and verify that it is merged correctly

                oneOf(tempContext).replace(with(any(Directory.class)), with(any(Set.class)), with(any(Set.class)));

                never(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".2.gz"));

                never(tempContext).merge(with(any(Directory.class)));

                oneOf(mockFetcher).disconnect();
            }
        });

        // tempContext.updateTimestamp( true, contextTimestamp );

        IndexUpdateRequest updateRequest = new IndexUpdateRequest(tempContext, mockFetcher);

        IndexUpdateResult updateResult = updater.fetchAndUpdateIndex(updateRequest);

        mockery.assertIsSatisfied();
        assertIndexUpdateSucceeded(updateResult);
    }

    @Test
    public void testIncrementalOnlyIndexUpdateNoCounter() throws Exception {
        Mockery mockery = new Mockery();

        final String indexUrl = repositoryUrl + ".index";
        final Date contextTimestamp = df.parse("20081128000000.000 -0600");

        final ResourceFetcher mockFetcher = mockery.mock(ResourceFetcher.class);

        final IndexingContext tempContext = mockery.mock(IndexingContext.class);

        mockery.checking(new Expectations() {
            {
                allowing(tempContext).getIndexDirectoryFile();
                will(new ReturnValueAction(testBasedir));

                allowing(tempContext).getTimestamp();
                will(returnValue(contextTimestamp));

                allowing(tempContext).getId();
                will(returnValue(repositoryId));

                allowing(tempContext).getIndexUpdateUrl();
                will(returnValue(indexUrl));

                allowing(tempContext).getIndexCreators();
                will(returnValue(DEFAULT_CREATORS));

                oneOf(mockFetcher).connect(repositoryId, indexUrl);

                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_REMOTE_PROPERTIES_FILE));
                will(new PropertiesAction() {
                    @Override
                    Properties getProperties() {
                        Properties properties = new Properties();
                        properties.setProperty(IndexingContext.INDEX_ID, "central");
                        properties.setProperty(IndexingContext.INDEX_TIMESTAMP, "20081129174241.859 -0600");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_COUNTER, "3");
                        properties.setProperty(IndexingContext.INDEX_CHAIN_ID, "someid");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "0", "3");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "1", "2");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "2", "1");
                        return properties;
                    }
                });

                oneOf(mockFetcher).disconnect();
            }
        });

        IndexUpdateRequest updateRequest = new IndexUpdateRequest(tempContext, mockFetcher);
        updateRequest.setIncrementalOnly(true);

        IndexUpdateResult updateResult = updater.fetchAndUpdateIndex(updateRequest);

        mockery.assertIsSatisfied();
        assertIndexUpdateFailed(updateResult);
    }

    @Test
    public void testIncrementalIndexUpdateNoUpdateNecessary() throws Exception {
        Mockery mockery = new Mockery();

        final String indexUrl = repositoryUrl + ".index";
        final Date contextTimestamp = df.parse("20081128000000.000 -0600");

        final ResourceFetcher mockFetcher = mockery.mock(ResourceFetcher.class);

        final IndexingContext tempContext = mockery.mock(IndexingContext.class);

        final Properties localProps = new Properties();
        localProps.setProperty(IndexingContext.INDEX_CHUNK_COUNTER, "3");
        localProps.setProperty(IndexingContext.INDEX_CHAIN_ID, "someid");

        mockery.checking(new Expectations() {
            {
                allowing(tempContext).getTimestamp();
                will(returnValue(contextTimestamp));

                allowing(tempContext).getId();
                will(returnValue(repositoryId));

                allowing(tempContext).getIndexUpdateUrl();
                will(returnValue(indexUrl));

                allowing(tempContext).getIndexCreators();
                will(returnValue(DEFAULT_CREATORS));

                allowing(tempContext).commit();

                oneOf(mockFetcher).connect(repositoryId, indexUrl);

                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_REMOTE_PROPERTIES_FILE));
                will(new PropertiesAction() {
                    @Override
                    Properties getProperties() {
                        Properties properties = new Properties();
                        properties.setProperty(IndexingContext.INDEX_ID, "central");
                        properties.setProperty(IndexingContext.INDEX_TIMESTAMP, "20081129174241.859 -0600");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_COUNTER, "3");
                        properties.setProperty(IndexingContext.INDEX_CHAIN_ID, "someid");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "0", "3");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "1", "2");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "2", "1");
                        return properties;
                    }
                });

                allowing(tempContext).getIndexDirectoryFile();
                will(new IndexDirectoryFileAction(localProps, testBasedir));

                never(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".gz"));
                // could create index archive there and verify that it is merged correctly

                never(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".1.gz"));

                never(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".2.gz"));

                never(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".3.gz"));

                never(tempContext).merge(with(any(Directory.class)));

                never(tempContext).replace(with(any(Directory.class)));

                oneOf(mockFetcher).disconnect();
            }
        });

        // tempContext.updateTimestamp( true, contextTimestamp );

        IndexUpdateRequest updateRequest = new IndexUpdateRequest(tempContext, mockFetcher);

        IndexUpdateResult updateResult = updater.fetchAndUpdateIndex(updateRequest);

        mockery.assertIsSatisfied();
        assertIndexUpdateSucceeded(updateResult);
    }

    @Test
    public void testUpdateForceFullUpdate() throws Exception {
        Mockery mockery = new Mockery();

        final String indexUrl = repositoryUrl + ".index";
        final Date contextTimestamp = df.parse("20081128000000.000 -0600");

        final ResourceFetcher mockFetcher = mockery.mock(ResourceFetcher.class);

        final IndexingContext tempContext = mockery.mock(IndexingContext.class);

        mockery.checking(new Expectations() {
            {
                allowing(tempContext).getIndexDirectoryFile();
                will(new ReturnValueAction(testBasedir));

                allowing(tempContext).getTimestamp();
                will(returnValue(contextTimestamp));

                allowing(tempContext).getId();
                will(returnValue(repositoryId));

                allowing(tempContext).getIndexUpdateUrl();
                will(returnValue(indexUrl));

                allowing(tempContext).commit();

                allowing(tempContext).getIndexCreators();
                will(returnValue(DEFAULT_CREATORS));

                oneOf(mockFetcher).connect(repositoryId, indexUrl);

                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_REMOTE_PROPERTIES_FILE));
                will(new PropertiesAction() {
                    @Override
                    Properties getProperties() {
                        Properties properties = new Properties();
                        properties.setProperty(IndexingContext.INDEX_ID, "central");
                        properties.setProperty(IndexingContext.INDEX_TIMESTAMP, "20081129174241.859 -0600");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_COUNTER, "3");
                        properties.setProperty(IndexingContext.INDEX_CHAIN_ID, "someid");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "0", "3");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "1", "2");
                        properties.setProperty(IndexingContext.INDEX_CHUNK_PREFIX + "2", "1");
                        return properties;
                    }
                });

                never(tempContext).getIndexDirectoryFile();

                never(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".1.gz"));

                never(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".2.gz"));

                never(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_FILE_PREFIX + ".3.gz"));

                oneOf(mockFetcher).retrieve(with(IndexingContext.INDEX_FILE_PREFIX + ".gz"));
                will(returnValue(newInputStream("index-updater/server-root/nexus-maven-repository-index.gz")));

                never(tempContext).merge(with(any(Directory.class)));

                never(tempContext).merge(with(any(Directory.class)));

                oneOf(tempContext).replace(with(any(Directory.class)), with(any(Set.class)), with(any(Set.class)));

                oneOf(mockFetcher).disconnect();
            }
        });

        // tempContext.updateTimestamp( true, contextTimestamp );

        IndexUpdateRequest updateRequest = new IndexUpdateRequest(tempContext, mockFetcher);

        updateRequest.setForceFullUpdate(true);

        IndexUpdateResult updateResult = updater.fetchAndUpdateIndex(updateRequest);

        mockery.assertIsSatisfied();
        assertIndexUpdateSucceeded(updateResult);
    }

    @Test
    @Ignore("Legacy format no longer supported with Lucene 4")
    public void ignoreTestUpdateForceFullUpdateNoGZ() throws Exception {
        Mockery mockery = new Mockery();

        final String indexUrl = repositoryUrl + ".index";
        final Date contextTimestamp = df.parse("20081128000000.000 -0600");

        final ResourceFetcher mockFetcher = mockery.mock(ResourceFetcher.class);

        final IndexingContext tempContext = mockery.mock(IndexingContext.class);

        mockery.checking(new Expectations() {
            {
                allowing(tempContext).getIndexDirectoryFile();
                will(new ReturnValueAction(testBasedir));

                allowing(tempContext).getTimestamp();
                will(returnValue(contextTimestamp));

                allowing(tempContext).commit();

                allowing(tempContext).getId();
                will(returnValue(repositoryId));

                allowing(tempContext).getIndexUpdateUrl();
                will(returnValue(indexUrl));

                allowing(tempContext).getIndexCreators();
                will(returnValue(DEFAULT_CREATORS));

                oneOf(mockFetcher).connect(repositoryId, indexUrl);

                oneOf(mockFetcher)
                        .retrieve( //
                                with(IndexingContext.INDEX_REMOTE_PROPERTIES_FILE));
                will(new PropertiesAction() {
                    @Override
                    Properties getProperties() {
                        Properties properties = new Properties();
                        properties.setProperty(IndexingContext.INDEX_ID, "central");
                        properties.setProperty(IndexingContext.INDEX_LEGACY_TIMESTAMP, "20081129174241.859 -0600");
                        return properties;
                    }
                });

                never(tempContext).getIndexDirectoryFile();

                oneOf(mockFetcher).retrieve(with(IndexingContext.INDEX_FILE_PREFIX + ".gz"));

                will(throwException(new IOException()));

                oneOf(mockFetcher).retrieve(with(IndexingContext.INDEX_FILE_PREFIX + ".zip"));

                will(returnValue(newInputStream("index-updater/server-root/legacy/nexus-maven-repository-index.zip")));

                never(tempContext).merge(with(any(Directory.class)));

                never(tempContext).merge(with(any(Directory.class)));

                oneOf(tempContext).replace(with(any(Directory.class)));

                oneOf(mockFetcher).disconnect();
            }
        });

        // tempContext.updateTimestamp( true, contextTimestamp );

        IndexUpdateRequest updateRequest = new IndexUpdateRequest(tempContext, mockFetcher);

        updateRequest.setForceFullUpdate(true);

        IndexUpdateResult updateResult = updater.fetchAndUpdateIndex(updateRequest);

        mockery.assertIsSatisfied();
        assertIndexUpdateSucceeded(updateResult);
    }

    protected InputStream newInputStream(String path) throws IOException {
        File file = getTestFile("src/test/resources/" + path);
        if (file.isFile()) {
            return new FileInputStream(file);
        }
        return null;
    }

    abstract static class PropertiesAction extends VoidAction {
        @Override
        public Object invoke(Invocation invocation) throws Throwable {
            Properties properties = getProperties();

            try (ByteArrayOutputStream buf = new ByteArrayOutputStream()) {
                properties.store(buf, null);
                buf.flush();
                return new ByteArrayInputStream(buf.toByteArray());
            }
        }

        abstract Properties getProperties();
    }

    private static class IndexDirectoryFileAction extends VoidAction {
        File file;

        public IndexDirectoryFileAction(Properties properties, File basedir) throws Exception {
            basedir.mkdirs();

            this.file = new File(basedir, IndexingContext.INDEX_UPDATER_PROPERTIES_FILE);

            try (FileOutputStream fos = new FileOutputStream(this.file)) {
                properties.store(fos, "");
            }
        }

        @Override
        public Object invoke(Invocation invocation) throws Throwable {
            return this.file.getParentFile();
        }
    }

    private void assertIndexUpdateSucceeded(IndexUpdateResult updateResult) {
        assertTrue("Index update should have succeeded, but says it failed", updateResult.isSuccessful());
    }

    private void assertIndexUpdateFailed(IndexUpdateResult updateResult) {
        assertFalse("Index update should have failed, but says it succeeded", updateResult.isSuccessful());
    }
}
