public interface FileNameGenerator {

    String generate(String url);

} public interface HeaderInjector {

    /**
     * Adds headers to server's requests for corresponding url.
     *
     * @param url an url headers will be added for
     * @return a map with headers, where keys are header's names, and values are header's values. {@code null} is not acceptable!
     */
    Map<String, String> addHeaders(String url);

} public class ProxyCacheTestUtils {

    public static final String HTTP_DATA_URL = "https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/android.jpg";
    public static final String HTTP_DATA_URL_ONE_REDIRECT = "http://bit.ly/1LAJKAy";
    public static final String HTTP_DATA_URL_3_REDIRECTS = "http://bit.ly/1QtKJiB";
    public static final String HTTP_DATA_URL_6_REDIRECTS = "http://ow.ly/Z17wz";
    public static final String HTTP_DATA_BIG_URL = "https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/phones.jpg";
    public static final String HTTP_DATA_BIG_URL_ONE_REDIRECT = "http://bit.ly/24DdZ06";
    public static final String ASSETS_DATA_NAME = "android.jpg";
    public static final String ASSETS_DATA_BIG_NAME = "phones.jpg";
    public static final int HTTP_DATA_SIZE = 4768;
    public static final int HTTP_DATA_BIG_SIZE = 94363;

    public static byte[] getFileContent(File file) throws IOException {
        return Files.asByteSource(file).read();
    }

    public static Response readProxyResponse(HttpProxyCacheServer proxy, String url) throws IOException {
        return readProxyResponse(proxy, url, -1);
    }

    public static Response readProxyResponse(HttpProxyCacheServer proxy, String url, int offset) throws IOException {
        String proxyUrl = proxy.getProxyUrl(url, false);
        if (!proxyUrl.startsWith("http://127.0.0.1")) {
            throw new IllegalStateException("Proxy url " + proxyUrl + " is not proxied! Original url is " + url);
        }
        URL proxiedUrl = new URL(proxyUrl);
        HttpURLConnection connection = (HttpURLConnection) proxiedUrl.openConnection();
        try {
            if (offset >= 0) {
                connection.setRequestProperty("Range", "bytes=" + offset + "-");
            }
            return new Response(connection);
        } finally {
            connection.disconnect();
        }
    }

    public static byte[] loadTestData() throws IOException {
        return loadAssetFile(ASSETS_DATA_NAME);
    }

    public static byte[] loadAssetFile(String name) throws IOException {
        InputStream in = RuntimeEnvironment.application.getResources().getAssets().open(name);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        IoUtils.copy(in, out);
        IoUtils.closeSilently(in);
        IoUtils.closeSilently(out);
        return out.toByteArray();
    }

    public static File getTempFile(File file) {
        return new File(file.getParentFile(), file.getName() + ".download");
    }

    public static File newCacheFile() {
        return new File(RuntimeEnvironment.application.getCacheDir(), UUID.randomUUID().toString());
    }

    public static byte[] generate(int capacity) {
        Random random = new Random(System.currentTimeMillis());
        byte[] result = new byte[capacity];
        random.nextBytes(result);
        return result;
    }

    public static HttpUrlSource newAngryHttpUrlSource() throws ProxyCacheException {
        HttpUrlSource source = mock(HttpUrlSource.class);
        doThrow(new RuntimeException()).when(source).getMime();
        doThrow(new RuntimeException()).when(source).read(any(byte[].class));
        doThrow(new RuntimeException()).when(source).open(anyInt());
        doThrow(new RuntimeException()).when(source).length();
        doThrow(new RuntimeException()).when(source).getUrl();
        doThrow(new RuntimeException()).when(source).close();
        return source;
    }

    public static HttpUrlSource newNotOpenableHttpUrlSource(String url, SourceInfoStorage sourceInfoStorage) throws ProxyCacheException {
        HttpUrlSource httpUrlSource = new HttpUrlSource(url, sourceInfoStorage);
        HttpUrlSource source = spy(httpUrlSource);
        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                System.out.print("Can't open!!!");
                throw new RuntimeException();
            }
        }).when(source).open(anyInt());
        return source;
    }

    public static Source newPhlegmaticSource(byte[] data, final int maxDelayMs) throws ProxyCacheException {
        Source spySource = spy(new ByteArraySource(data));
        final Random delayGenerator = new Random(System.currentTimeMillis());
        doAnswer(new Answer() {

            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                Thread.sleep(delayGenerator.nextInt(maxDelayMs));
                return null;
            }
        }).doCallRealMethod().when(spySource).read(any(byte[].class));
        return spySource;
    }

    public static int getPort(HttpProxyCacheServer server) {
        String proxyUrl = server.getProxyUrl("test");
        Pattern pattern = Pattern.compile("http://127.0.0.1:(\\d*)/test");
        Matcher matcher = pattern.matcher(proxyUrl);
        assertThat(matcher.find()).isTrue();
        String portAsString = matcher.group(1);
        return Integer.parseInt(portAsString);
    }

    public static int getPortWithoutPing(HttpProxyCacheServer server) {
        return (Integer) ReflectUtil.getField(server, "port");
    }

    public static void installExternalSystemProxy() {
        // see proxies list at http://proxylist.hidemyass.com/
        Proxy systemProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("162.8.230.7", 11180));
        ProxySelector mockedProxySelector = Mockito.mock(ProxySelector.class);
        when(mockedProxySelector.select(Mockito.<URI>any())).thenReturn(Lists.newArrayList(systemProxy));
        ProxySelector.setDefault(mockedProxySelector);
    }

    public static void resetSystemProxy() {
        ProxySelector mockedProxySelector = Mockito.mock(ProxySelector.class);
        when(mockedProxySelector.select(Mockito.<URI>any())).thenReturn(Lists.newArrayList(Proxy.NO_PROXY));
        ProxySelector.setDefault(mockedProxySelector);
    }
} public class Response {

    private static final String CONTENT_TYPE_HEADER = "Content-Type";
    private static final String CONTENT_LENGTH_HEADER = "Content-Length";
    private static final Pattern STATUS_CODE_PATTERN = Pattern.compile("HTTP/1.1 (\\d{3}) ");

    public final int code;
    public final byte[] data;
    public final int contentLength;
    public final String contentType;
    public final Map<String, List<String>> headers;

    public Response(HttpURLConnection connection) throws IOException {
        this.code = connection.getResponseCode();
        this.contentLength = connection.getContentLength();
        this.contentType = connection.getContentType();
        this.headers = connection.getHeaderFields();
        this.data = ByteStreams.toByteArray(connection.getInputStream());
    }

    public Response(byte[] responseData) throws IOException {
        int read = 0;
        BufferedReader reader = new BufferedReader(new StringReader(new String(responseData, "ascii")));
        String statusLine = reader.readLine();
        read += statusLine.length() + 1;
        Matcher matcher = STATUS_CODE_PATTERN.matcher(statusLine);
        boolean hasCode = matcher.find();
        Preconditions.checkArgument(hasCode, "Status code not found in `" + statusLine + "`");
        this.code = Integer.parseInt(matcher.group(1));

        String header;
        this.headers = new HashMap<>();
        while (!TextUtils.isEmpty(header = reader.readLine())) {
            read += header.length() + 1;
            String[] keyValue = header.split(":");
            String headerName = keyValue[0].trim();
            String headerValue = keyValue[1].trim();
            headers.put(headerName, Collections.singletonList(headerValue));
        }
        read++;

        this.contentType = headers.containsKey(CONTENT_TYPE_HEADER) ? headers.get(CONTENT_TYPE_HEADER).get(0) : null;
        this.contentLength = headers.containsKey(CONTENT_LENGTH_HEADER) ? Integer.parseInt(headers.get(CONTENT_LENGTH_HEADER).get(0)) : -1;

        int bodySize = responseData.length - read;
        this.data = new byte[bodySize];
        System.arraycopy(responseData, read, data, 0, bodySize);
    }
} public static final String ASSETS_DATA_BIG_NAME = "phones.jpg"; public static final String ASSETS_DATA_NAME = "android.jpg"; public static final int HTTP_DATA_BIG_SIZE = 94363; public static final String HTTP_DATA_BIG_URL = "https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/phones.jpg"; public static final String HTTP_DATA_BIG_URL_ONE_REDIRECT = "http://bit.ly/24DdZ06"; public static final int HTTP_DATA_SIZE = 4768; public static final String HTTP_DATA_URL = "https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/android.jpg"; public static final String HTTP_DATA_URL_3_REDIRECTS = "http://bit.ly/1QtKJiB"; public static final String HTTP_DATA_URL_6_REDIRECTS = "http://ow.ly/Z17wz"; public static final String HTTP_DATA_URL_ONE_REDIRECT = "http://bit.ly/1LAJKAy"; public static byte[] getFileContent(File file) throws IOException {
    return Files.asByteSource(file).read();
} public static int getPort(HttpProxyCacheServer server) {
    String proxyUrl = server.getProxyUrl("test");
    Pattern pattern = Pattern.compile("http://127.0.0.1:(\\d*)/test");
    Matcher matcher = pattern.matcher(proxyUrl);
    assertThat(matcher.find()).isTrue();
    String portAsString = matcher.group(1);
    return Integer.parseInt(portAsString);
} public static void installExternalSystemProxy() {
    // see proxies list at http://proxylist.hidemyass.com/
    Proxy systemProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("162.8.230.7", 11180));
    ProxySelector mockedProxySelector = Mockito.mock(ProxySelector.class);
    when(mockedProxySelector.select(Mockito.<URI>any())).thenReturn(Lists.newArrayList(systemProxy));
    ProxySelector.setDefault(mockedProxySelector);
} public static byte[] loadAssetFile(String name) throws IOException {
    InputStream in = RuntimeEnvironment.application.getResources().getAssets().open(name);
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    IoUtils.copy(in, out);
    IoUtils.closeSilently(in);
    IoUtils.closeSilently(out);
    return out.toByteArray();
} public static Response readProxyResponse(HttpProxyCacheServer proxy, String url) throws IOException {
    return readProxyResponse(proxy, url, -1);
} public static void resetSystemProxy() {
    ProxySelector mockedProxySelector = Mockito.mock(ProxySelector.class);
    when(mockedProxySelector.select(Mockito.<URI>any())).thenReturn(Lists.newArrayList(Proxy.NO_PROXY));
    ProxySelector.setDefault(mockedProxySelector);
}
import android.net.Uri;
import android.util.Pair;
import com.danikula.android.garden.io.IoUtils;
import com.danikula.videocache.file.FileNameGenerator;
import com.danikula.videocache.file.Md5FileNameGenerator;
import com.danikula.videocache.headers.HeaderInjector;
import com.danikula.videocache.support.ProxyCacheTestUtils;
import com.danikula.videocache.support.Response;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.robolectric.RuntimeEnvironment;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import static com.danikula.android.garden.io.Files.cleanDirectory;
import static com.danikula.android.garden.io.Files.createDirectory;
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_BIG_NAME;
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_NAME;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_SIZE;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_URL;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_URL_ONE_REDIRECT;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_SIZE;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL_3_REDIRECTS;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL_6_REDIRECTS;
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL_ONE_REDIRECT;
import static com.danikula.videocache.support.ProxyCacheTestUtils.getFileContent;
import static com.danikula.videocache.support.ProxyCacheTestUtils.getPort;
import static com.danikula.videocache.support.ProxyCacheTestUtils.installExternalSystemProxy;
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile;
import static com.danikula.videocache.support.ProxyCacheTestUtils.readProxyResponse;
import static com.danikula.videocache.support.ProxyCacheTestUtils.resetSystemProxy;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
package com.danikula.videocache;






/**
 * @author Alexey Danilov (danikula@gmail.com).
 */
public class HttpProxyCacheServerTest extends BaseTest {

    private File cacheFolder;

    @Before
    public void setup() throws Exception {
        cacheFolder = ProxyCacheTestUtils.newCacheFile();
        createDirectory(cacheFolder);
        cleanDirectory(cacheFolder);
        resetSystemProxy();
    }

    @Test
    public void testHttpProxyCache() throws Exception {
        Pair<File, Response> response = readProxyData(HTTP_DATA_URL);

        assertThat(response.second.code).isEqualTo(200);
        assertThat(response.second.data).isEqualTo(getFileContent(response.first));
        assertThat(response.second.data).isEqualTo(loadAssetFile(ASSETS_DATA_NAME));
    }

    @Test
    public void testProxyContentWithPartialCache() throws Exception {
        File cacheDir = RuntimeEnvironment.application.getExternalCacheDir();
        File file = new File(cacheDir, new Md5FileNameGenerator().generate(HTTP_DATA_URL));
        int partialCacheSize = 1000;
        byte[] partialData = ProxyCacheTestUtils.generate(partialCacheSize);
        File partialCacheFile = ProxyCacheTestUtils.getTempFile(file);
        IoUtils.saveToFile(partialData, partialCacheFile);

        HttpProxyCacheServer proxy = newProxy(cacheDir);
        Response response = readProxyResponse(proxy, HTTP_DATA_URL);
        proxy.shutdown();

        byte[] expected = loadAssetFile(ASSETS_DATA_NAME);
        System.arraycopy(partialData, 0, expected, 0, partialCacheSize);
        assertThat(response.data).isEqualTo(expected);
    }

    @Test
    public void testMimeFromResponse() throws Exception {
        Pair<File, Response> response = readProxyData("https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/android");
        assertThat(response.second.contentType).isEqualTo("application/octet-stream");
    }

    @Test
    public void testProxyFullResponse() throws Exception {
        Pair<File, Response> response = readProxyData(HTTP_DATA_BIG_URL);

        assertThat(response.second.code).isEqualTo(200);
        assertThat(response.second.contentLength).isEqualTo(HTTP_DATA_BIG_SIZE);
        assertThat(response.second.contentType).isEqualTo("image/jpeg");
        assertThat(response.second.headers.containsKey("Accept-Ranges")).isTrue();
        assertThat(response.second.headers.get("Accept-Ranges").get(0)).isEqualTo("bytes");
        assertThat(response.second.headers.containsKey("Content-Range")).isFalse();
        assertThat(response.second.data).isEqualTo(getFileContent(response.first));
        assertThat(response.second.data).isEqualTo(loadAssetFile(ASSETS_DATA_BIG_NAME));
    }

    @Test
    public void testProxyFullResponseWithRedirect() throws Exception {
        Pair<File, Response> response = readProxyData(HTTP_DATA_BIG_URL_ONE_REDIRECT);

        assertThat(response.second.code).isEqualTo(200);
        assertThat(response.second.contentLength).isEqualTo(HTTP_DATA_BIG_SIZE);
        assertThat(response.second.contentType).isEqualTo("image/jpeg");
        assertThat(response.second.headers.containsKey("Accept-Ranges")).isTrue();
        assertThat(response.second.headers.get("Accept-Ranges").get(0)).isEqualTo("bytes");
        assertThat(response.second.headers.containsKey("Content-Range")).isFalse();
        assertThat(response.second.data).isEqualTo(getFileContent(response.first));
        assertThat(response.second.data).isEqualTo(loadAssetFile(ASSETS_DATA_BIG_NAME));
    }

    @Test
    public void testProxyPartialResponse() throws Exception {
        int offset = 18000;
        Pair<File, Response> response = readProxyData(HTTP_DATA_BIG_URL, offset);

        assertThat(response.second.code).isEqualTo(206);
        assertThat(response.second.contentLength).isEqualTo(HTTP_DATA_BIG_SIZE - offset);
        assertThat(response.second.contentType).isEqualTo("image/jpeg");
        assertThat(response.second.headers.containsKey("Accept-Ranges")).isTrue();
        assertThat(response.second.headers.get("Accept-Ranges").get(0)).isEqualTo("bytes");
        assertThat(response.second.headers.containsKey("Content-Range")).isTrue();
        String rangeHeader = String.format("bytes %d-%d/%d", offset, HTTP_DATA_BIG_SIZE - 1, HTTP_DATA_BIG_SIZE);
        assertThat(response.second.headers.get("Content-Range").get(0)).isEqualTo(rangeHeader);
        byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_BIG_NAME), offset, HTTP_DATA_BIG_SIZE);
        assertThat(response.second.data).isEqualTo(expectedData);
        assertThat(getFileContent(response.first)).isEqualTo(loadAssetFile(ASSETS_DATA_BIG_NAME));
    }

    @Test
    public void testProxyPartialResponseWithRedirect() throws Exception {
        int offset = 18000;
        Pair<File, Response> response = readProxyData(HTTP_DATA_BIG_URL_ONE_REDIRECT, offset);

        assertThat(response.second.code).isEqualTo(206);
        assertThat(response.second.contentLength).isEqualTo(HTTP_DATA_BIG_SIZE - offset);
        assertThat(response.second.contentType).isEqualTo("image/jpeg");
        assertThat(response.second.headers.containsKey("Accept-Ranges")).isTrue();
        assertThat(response.second.headers.get("Accept-Ranges").get(0)).isEqualTo("bytes");
        assertThat(response.second.headers.containsKey("Content-Range")).isTrue();
        String rangeHeader = String.format("bytes %d-%d/%d", offset, HTTP_DATA_BIG_SIZE - 1, HTTP_DATA_BIG_SIZE);
        assertThat(response.second.headers.get("Content-Range").get(0)).isEqualTo(rangeHeader);
        byte[] expectedData = Arrays.copyOfRange(loadAssetFile(ASSETS_DATA_BIG_NAME), offset, HTTP_DATA_BIG_SIZE);
        assertThat(response.second.data).isEqualTo(expectedData);
        assertThat(getFileContent(response.first)).isEqualTo(loadAssetFile(ASSETS_DATA_BIG_NAME));
    }

    @Test
    public void testMaxSizeCacheLimit() throws Exception {
        HttpProxyCacheServer proxy = new HttpProxyCacheServer.Builder(RuntimeEnvironment.application)
                .cacheDirectory(cacheFolder)
                .maxCacheSize(HTTP_DATA_SIZE * 3 - 1) // for 2 files
                .build();

        // use different url (doesn't matter than same content)
        readProxyResponse(proxy, HTTP_DATA_URL, 0);
        Thread.sleep(1050); // wait for new last modified date (file rounds time to second)

        readProxyResponse(proxy, HTTP_DATA_URL_ONE_REDIRECT, 0);
        Thread.sleep(1050);

        readProxyResponse(proxy, HTTP_DATA_URL_3_REDIRECTS, 0);