/**
   * Scenario: Download failed with network I/O error.
   *
   * <p>Verify that: * Error is not counted as failure
   */
  @Test
  public void testNetworkErrorIsNotCountedAsFailure() throws Exception {
    DownloadContent content = createFont();
    DownloadContentCatalog catalog = mockCatalogWithScheduledDownloads(content);

    DownloadAction action = spy(new DownloadAction(null));
    doReturn(true).when(action).isConnectedToNetwork(RuntimeEnvironment.application);
    doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
    doReturn(mockNotExistingFile())
        .when(action)
        .createTemporaryFile(RuntimeEnvironment.application, content);
    doReturn(mockNotExistingFile())
        .when(action)
        .getDestinationFile(RuntimeEnvironment.application, content);
    doReturn(true).when(action).hasEnoughDiskSpace(eq(content), any(File.class), any(File.class));

    HttpClient client = mock(HttpClient.class);
    doThrow(IOException.class).when(client).execute(any(HttpUriRequest.class));
    doReturn(client).when(action).buildHttpClient();

    action.perform(RuntimeEnvironment.application, catalog);

    verify(catalog, never()).rememberFailure(eq(content), anyInt());
    verify(catalog, never()).markAsDownloaded(content);
  }
  /**
   * Scenario: Download is completed but verification (checksum) failed.
   *
   * <p>Verify that: * Downloaded file is deleted * File will not be extracted * Content is not
   * marked as downloaded in the catalog
   */
  @Test
  public void testTemporaryFileWillBeDeletedIfVerificationFails() throws Exception {
    DownloadContent content =
        new DownloadContent.Builder()
            .setKind(DownloadContent.KIND_FONT)
            .setType(DownloadContent.TYPE_ASSET_ARCHIVE)
            .setSize(1337L)
            .build();

    DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
    doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();

    DownloadAction action = spy(new DownloadAction(null));
    doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
    doNothing().when(action).download(any(HttpClient.class), anyString(), any(File.class));
    doReturn(false).when(action).verify(any(File.class), anyString());

    File temporaryFile = mockNotExistingFile();
    doReturn(temporaryFile)
        .when(action)
        .createTemporaryFile(RuntimeEnvironment.application, content);

    File destinationFile = mockNotExistingFile();
    doReturn(destinationFile)
        .when(action)
        .getDestinationFile(RuntimeEnvironment.application, content);

    action.perform(RuntimeEnvironment.application, catalog);

    verify(temporaryFile).delete();
    verify(action, never()).extract(any(File.class), any(File.class), anyString());
    verify(catalog, never()).markAsDownloaded(content);
  }
  /**
   * Scenario: Enough storage space for temporary and destination file available.
   *
   * <p>Verify that: * hasEnoughDiskSpace() returns true
   */
  @Test
  public void testWithEnoughSpaceForEverything() throws Exception {
    DownloadContent content = createFontWithSize(2048);
    File destinationFile = mockNotExistingFileWithUsableSpace(4096);
    File temporaryFile = mockNotExistingFileWithUsableSpace(4096);

    DownloadAction action = new DownloadAction(null);
    Assert.assertTrue(action.hasEnoughDiskSpace(content, destinationFile, temporaryFile));
  }
  /**
   * Scenario: The current network is metered.
   *
   * <p>Verify that: * No download is performed on a metered network
   */
  @Test
  public void testNothingIsDoneOnMeteredNetwork() throws Exception {
    DownloadAction action = spy(new DownloadAction(null));
    doReturn(true).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);

    action.perform(RuntimeEnvironment.application, null);

    verify(action, never()).buildHttpClient();
    verify(action, never()).download(any(HttpClient.class), anyString(), any(File.class));
  }
  /**
   * Scenario: No (connected) network is available.
   *
   * <p>Verify that: * No download is performed
   */
  @Test
  public void testNothingIsDoneIfNoNetworkIsAvailable() throws Exception {
    DownloadAction action = spy(new DownloadAction(null));
    doReturn(false).when(action).isConnectedToNetwork(RuntimeEnvironment.application);

    action.perform(RuntimeEnvironment.application, null);

    verify(action, never()).isActiveNetworkMetered(any(Context.class));
    verify(action, never()).buildHttpClient();
    verify(action, never()).download(any(HttpClient.class), anyString(), any(File.class));
  }
  /**
   * Scenario: Server returns a client error (HTTP 404).
   *
   * <p>Verify that: * Situation is treated as unrecoverable (UnrecoverableDownloadContentException)
   */
  @Test(expected = BaseAction.UnrecoverableDownloadContentException.class)
  public void testClientErrorsAreUnrecoverable() throws Exception {
    HttpClient client = mockHttpClient(404, "");

    File temporaryFile = mock(File.class);
    doReturn(false).when(temporaryFile).exists();

    DownloadAction action = spy(new DownloadAction(null));
    action.download(client, TEST_URL, temporaryFile);

    verify(client).execute(any(HttpUriRequest.class));
  }
  /**
   * Scenario: Pretend a partially downloaded file already exists.
   *
   * <p>Verify that: * Range header is set in request * Content will be appended to existing file *
   * Content will be marked as downloaded in catalog
   */
  @Test
  public void testResumingDownloadFromExistingFile() throws Exception {
    DownloadContent content =
        new DownloadContent.Builder()
            .setKind(DownloadContent.KIND_FONT)
            .setType(DownloadContent.TYPE_ASSET_ARCHIVE)
            .setSize(4223)
            .build();

    DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
    doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();

    DownloadAction action = spy(new DownloadAction(null));
    doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);

    File temporaryFile = mockFileWithSize(1337L);
    doReturn(temporaryFile)
        .when(action)
        .createTemporaryFile(RuntimeEnvironment.application, content);

    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    doReturn(outputStream).when(action).openFile(eq(temporaryFile), anyBoolean());

    HttpClient client = mockHttpClient(HttpStatus.SC_PARTIAL_CONTENT, "HelloWorld");
    doReturn(client).when(action).buildHttpClient();

    File destinationFile = mockNotExistingFile();
    doReturn(destinationFile)
        .when(action)
        .getDestinationFile(RuntimeEnvironment.application, content);

    doReturn(true).when(action).verify(eq(temporaryFile), anyString());
    doNothing().when(action).extract(eq(temporaryFile), eq(destinationFile), anyString());

    action.perform(RuntimeEnvironment.application, catalog);

    ArgumentCaptor<HttpGet> argument = ArgumentCaptor.forClass(HttpGet.class);
    verify(client).execute(argument.capture());

    HttpGet request = argument.getValue();
    Assert.assertTrue(request.containsHeader("Range"));
    Assert.assertEquals("bytes=1337-", request.getFirstHeader("Range").getValue());
    Assert.assertEquals("HelloWorld", new String(outputStream.toByteArray(), "UTF-8"));

    verify(action).openFile(eq(temporaryFile), eq(true));
    verify(catalog).markAsDownloaded(content);
    verify(temporaryFile).delete();
  }
  /**
   * Scenario: Disk IO Error when extracting file.
   *
   * <p>Verify that: * Error is counted as failure * After multiple errors the content is marked as
   * permanently failed
   */
  @Test
  public void testDiskIOErrorIsCountedAsFailure() throws Exception {
    DownloadContent content = createFont();
    DownloadContentCatalog catalog = mockCatalogWithScheduledDownloads(content);
    doCallRealMethod().when(catalog).rememberFailure(eq(content), anyInt());
    doCallRealMethod().when(catalog).markAsPermanentlyFailed(content);

    Assert.assertEquals(DownloadContent.STATE_NONE, content.getState());

    DownloadAction action = spy(new DownloadAction(null));
    doReturn(true).when(action).isConnectedToNetwork(RuntimeEnvironment.application);
    doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
    doReturn(mockNotExistingFile())
        .when(action)
        .createTemporaryFile(RuntimeEnvironment.application, content);
    doReturn(mockNotExistingFile())
        .when(action)
        .getDestinationFile(RuntimeEnvironment.application, content);
    doReturn(true).when(action).hasEnoughDiskSpace(eq(content), any(File.class), any(File.class));
    doNothing().when(action).download(any(HttpClient.class), anyString(), any(File.class));
    doReturn(true).when(action).verify(any(File.class), anyString());

    File destinationFile = mock(File.class);
    doReturn(false).when(destinationFile).exists();
    File parentFile = mock(File.class);
    doReturn(false).when(parentFile).mkdirs();
    doReturn(false).when(parentFile).exists();
    doReturn(parentFile).when(destinationFile).getParentFile();
    doReturn(destinationFile)
        .when(action)
        .getDestinationFile(RuntimeEnvironment.application, content);

    for (int i = 0; i < 10; i++) {
      action.perform(RuntimeEnvironment.application, catalog);

      Assert.assertEquals(DownloadContent.STATE_NONE, content.getState());
    }

    action.perform(RuntimeEnvironment.application, catalog);

    Assert.assertEquals(DownloadContent.STATE_FAILED, content.getState());
    verify(catalog, times(11)).rememberFailure(eq(content), anyInt());
  }
  /**
   * Scenario: Content is scheduled for download but already exists locally (with correct checksum).
   *
   * <p>Verify that: * No download is performed for existing file * Content is marked as downloaded
   * in the catalog
   */
  @Test
  public void testExistingAndVerifiedFilesAreNotDownloadedAgain() throws Exception {
    DownloadContent content = new DownloadContent.Builder().build();

    DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
    doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();

    DownloadAction action = spy(new DownloadAction(null));
    doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);

    File file = mock(File.class);
    doReturn(true).when(file).exists();
    doReturn(file).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
    doReturn(file).when(action).getDestinationFile(RuntimeEnvironment.application, content);
    doReturn(true).when(action).verify(eq(file), anyString());

    action.perform(RuntimeEnvironment.application, catalog);

    verify(action, never()).download(any(HttpClient.class), anyString(), any(File.class));
    verify(catalog).markAsDownloaded(content);
  }
  /**
   * Scenario: Download fails with IOException.
   *
   * <p>Verify that: * Partially downloaded file will not be deleted * Content will not be marked as
   * downloaded in catalog
   */
  @Test
  public void testTemporaryFileIsNotDeletedAfterDownloadAborted() throws Exception {
    DownloadContent content =
        new DownloadContent.Builder()
            .setKind(DownloadContent.KIND_FONT)
            .setType(DownloadContent.TYPE_ASSET_ARCHIVE)
            .setSize(4223)
            .build();

    DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
    doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();

    DownloadAction action = spy(new DownloadAction(null));
    doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);

    File temporaryFile = mockFileWithSize(1337L);
    doReturn(temporaryFile)
        .when(action)
        .createTemporaryFile(RuntimeEnvironment.application, content);

    ByteArrayOutputStream outputStream = spy(new ByteArrayOutputStream());
    doReturn(outputStream).when(action).openFile(eq(temporaryFile), anyBoolean());
    doThrow(IOException.class).when(outputStream).write(any(byte[].class), anyInt(), anyInt());

    HttpClient client = mockHttpClient(HttpStatus.SC_PARTIAL_CONTENT, "HelloWorld");
    doReturn(client).when(action).buildHttpClient();

    doReturn(mockNotExistingFile())
        .when(action)
        .getDestinationFile(RuntimeEnvironment.application, content);

    action.perform(RuntimeEnvironment.application, catalog);

    verify(catalog, never()).markAsDownloaded(content);
    verify(action, never()).verify(any(File.class), anyString());
    verify(temporaryFile, never()).delete();
  }