chromium/components/download/internal/common/download_item_impl_unittest.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "components/download/public/common/download_item_impl.h"

#include <stdint.h>

#include <iterator>
#include <map>
#include <memory>
#include <utility>
#include <vector>

#include "base/containers/circular_deque.h"
#include "base/containers/queue.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/gmock_move_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/download/public/common/download_create_info.h"
#include "components/download/public/common/download_destination_observer.h"
#include "components/download/public/common/download_features.h"
#include "components/download/public/common/download_file_factory.h"
#include "components/download/public/common/download_interrupt_reasons.h"
#include "components/download/public/common/download_item_impl_delegate.h"
#include "components/download/public/common/download_target_info.h"
#include "components/download/public/common/download_url_parameters.h"
#include "components/download/public/common/mock_download_file.h"
#include "crypto/secure_hash.h"
#include "net/http/http_response_headers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

_;
ByMove;
DoAll;
Invoke;
InvokeWithoutArgs;
NiceMock;
Property;
Return;
ReturnRefOfCopy;
SaveArg;
StrictMock;
WithArg;

const int kDownloadChunkSize =;
const int kDownloadSpeed =;
const base::FilePath::CharType kDummyTargetPath[] =);
const base::FilePath::CharType kDummyIntermediatePath[] =);

namespace download {
namespace {

template <typename T>
base::HistogramBase::Sample ToHistogramSample(T t) {}

class MockDelegate : public DownloadItemImplDelegate {};

class TestDownloadItemObserver : public DownloadItem::Observer {};

// Schedules a task to invoke a callback that's bound to the specified
// parameter.
// E.g.:
//
//   EXPECT_CALL(foo, Bar(1, _))
//     .WithArg<1>(ScheduleCallbackWithParams(0, 0, task_runner));
//
//   .. will invoke the second argument to Bar with 0 as the parameter.
ACTION_P3(ScheduleCallbackWithParams, param1, param2, task_runner) {}

const char kTestData1[] =;

// SHA256 hash of TestData1
const uint8_t kHashOfTestData1[] =;

class DownloadItemTest : public testing::Test {};

// Tests to ensure calls that change a DownloadItem generate an update
// to observers. State changing functions not tested:
//  void OpenDownload();
//  void ShowDownloadInShell();
//  void CompleteDelayedDownload();
//  set_* mutators

TEST_F(DownloadItemTest, NotificationAfterUpdate) {}

TEST_F(DownloadItemTest, NotificationAfterCancel) {}

TEST_F(DownloadItemTest, NotificationAfterComplete) {}

TEST_F(DownloadItemTest, NotificationAfterDownloadedFileRemoved) {}

TEST_F(DownloadItemTest, NotificationAfterInterrupted) {}

TEST_F(DownloadItemTest, NotificationAfterDestroyed) {}

TEST_F(DownloadItemTest, NotificationAfterRemove) {}

TEST_F(DownloadItemTest, NotificationAfterOnContentCheckCompleted) {}

// DownloadItemImpl::OnDownloadTargetDetermined will schedule a task to run
// DownloadFile::Rename(). Once the rename
// completes, DownloadItemImpl receives a notification with the new file
// name. Check that observers are updated when the new filename is available and
// not before.
TEST_F(DownloadItemTest, NotificationAfterOnDownloadTargetDetermined) {}

TEST_F(DownloadItemTest, NotificationAfterTogglePause) {}

// Test that a download is resumed automatically after a continuable interrupt.
TEST_F(DownloadItemTest, AutomaticResumption_Continue) {}

// Automatic resumption should restart and discard the intermediate file if the
// interrupt reason requires it.
TEST_F(DownloadItemTest, AutomaticResumption_Restart) {}

// Test that automatic resumption doesn't happen after an interrupt that
// requires user action to resolve.
TEST_F(DownloadItemTest, AutomaticResumption_NeedsUserAction) {}

// Test that a download is resumed automatically after a content length mismatch
// error.
TEST_F(DownloadItemTest, AutomaticResumption_ContentLengthMismatch) {}

// Check we do correct cleanup for RESUME_MODE_INVALID interrupts.
TEST_F(DownloadItemTest, UnresumableInterrupt) {}

TEST_F(DownloadItemTest, AutomaticResumption_AttemptLimit) {}

// If the download attempts to resume and the resumption request fails, the
// subsequent Start() call shouldn't update the origin state (URL redirect
// chains, Content-Disposition, download URL, etc..)
TEST_F(DownloadItemTest, FailedResumptionDoesntUpdateOriginState) {}

// If the download resumption request succeeds, the origin state should be
// updated.
TEST_F(DownloadItemTest, SucceededResumptionUpdatesOriginState) {}

// Ensure when strong validators changed on resumption, the received
// slices should be cleared.
TEST_F(DownloadItemTest, ClearReceivedSliceIfEtagChanged) {}

// Ensure when a network socket error happens on resumption, the received slices
// info should be kept if the download is not restarted from beginning, so the
// download progress will not move backward.
TEST_F(DownloadItemTest, KeepReceivedSliceIfNetworkError) {}

// Test that resumption uses the final URL in a URL chain when resuming.
TEST_F(DownloadItemTest, ResumeUsesFinalURL) {}

TEST_F(DownloadItemTest, DisplayName) {}

// Test to make sure that Start method calls DF initialize properly.
TEST_F(DownloadItemTest, Start) {}

// Download file and the request should be cancelled as a result of download
// file initialization failing.
TEST_F(DownloadItemTest, InitDownloadFileFails) {}

// Handling of downloads initiated via a failed request. In this case, Start()
// will get called with a DownloadCreateInfo with a non-zero interrupt_reason.
TEST_F(DownloadItemTest, StartFailedDownload) {}

// Test that the delegate is invoked after the download file is renamed.
TEST_F(DownloadItemTest, CallbackAfterRename) {}

// Test that the delegate is invoked after the download file is renamed and the
// download item is in an interrupted state.
TEST_F(DownloadItemTest, CallbackAfterInterruptedRename) {}

TEST_F(DownloadItemTest, Interrupted) {}

// Destination errors that occur before the intermediate rename shouldn't cause
// the download to be marked as interrupted until after the intermediate rename.
TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Restart) {}

// As above. But if the download can be resumed by continuing, then the
// intermediate path should be retained when the download is interrupted after
// the intermediate rename succeeds.
TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Continue) {}

// As above. If the intermediate rename fails, then the interrupt reason should
// be set to the file error and the intermediate path should be empty.
TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Failed) {}

TEST_F(DownloadItemTest, Canceled) {}

TEST_F(DownloadItemTest, DownloadTargetDetermined_Cancel) {}

TEST_F(DownloadItemTest, DownloadTargetDetermined_CancelWithEmptyName) {}

TEST_F(DownloadItemTest, DownloadTargetDetermined_Conflict) {}

TEST_F(DownloadItemTest, DownloadTargetDetermined_NewMimeType) {}

TEST_F(DownloadItemTest, FileRemoved) {}

TEST_F(DownloadItemTest, DestinationUpdate) {}

TEST_F(DownloadItemTest, DestinationError_NoRestartRequired) {}

TEST_F(DownloadItemTest, DestinationError_RestartRequired) {}

TEST_F(DownloadItemTest, DestinationCompleted) {}

TEST_F(DownloadItemTest, EnabledActionsForNormalDownload) {}

TEST_F(DownloadItemTest, EnabledActionsForTemporaryDownload) {}

TEST_F(DownloadItemTest, EnabledActionsForInterruptedDownload) {}

TEST_F(DownloadItemTest, EnabledActionsForCancelledDownload) {}

// Test various aspects of the delegate completion blocker.

// Just allowing completion.
TEST_F(DownloadItemTest, CompleteDelegate_ReturnTrue) {}

// Just delaying completion.
TEST_F(DownloadItemTest, CompleteDelegate_BlockOnce) {}

// Delay and set danger.
TEST_F(DownloadItemTest, CompleteDelegate_SetDanger) {}

// Just delaying completion twice.
TEST_F(DownloadItemTest, CompleteDelegate_BlockTwice) {}

TEST_F(DownloadItemTest, StealDangerousDownloadAndDiscard) {}

TEST_F(DownloadItemTest, StealDangerousDownloadAndKeep) {}

TEST_F(DownloadItemTest, StealInterruptedContinuableDangerousDownload) {}

TEST_F(DownloadItemTest, StealInterruptedNonContinuableDangerousDownload) {}

// Tests that for an incognito download, the target file is annotated with an
// empty source URL.
TEST_F(DownloadItemTest, AnnotationWithEmptyURLInIncognito) {}

// The DownloadItemDestinationUpdateRaceTest fixture (defined below) is used to
// test for race conditions between download destination events received via the
// DownloadDestinationObserver interface, and the target determination logic.
//
// The general control flow for DownloadItemImpl looks like this:
//
// * Start() called, which in turn calls DownloadFile::Initialize().
//
//   Even though OnDownloadFileInitialized hasn't been called, there could now
//   be destination observer calls queued prior to the task that calls
//   OnDownloadFileInitialized. Let's call this point in the workflow "A".
//
// * DownloadItemImpl::OnDownloadFileInitialized() called.
//
// * Assuming the result is successful, DII now invokes the delegate's
//   DetermineDownloadTarget method.
//
//   At this point DownloadFile acts as the source of
//   DownloadDestinationObserver events, and may invoke callbacks. Let's call
//   this point in the workflow "B".
//
// * DII::OnDownloadTargetDetermined() invoked after delegate is done with
//   target determination.
//
// * DII attempts to rename the DownloadFile to its intermediate name.
//
//   More DownloadDestinationObserver events can happen here. Let's call this
//   point in the workflow "C".
//
// * DII::OnDownloadRenamedToIntermediateName() invoked. Assuming all went well,
//   DII is now in IN_PROGRESS state.
//
//   More DownloadDestinationObserver events can happen here. Let's call this
//   point in the workflow "D".
//
// The DownloadItemDestinationUpdateRaceTest works by generating various
// combinations of DownloadDestinationObserver events that might occur at the
// points "A", "B", "C", and "D" above. Each test in this suite cranks a
// DownloadItemImpl through the states listed above and invokes the events
// assigned to each position.

// This type of callback represents a call to a DownloadDestinationObserver
// method that's missing the DownloadDestinationObserver object. Currying this
// way allows us to bind a call prior to constructing the object on which the
// method would be invoked.  This is necessary since we are going to construct
// various permutations of observer calls that will then be applied to a
// DownloadItem in a state as yet undetermined.
CurriedObservation;

// A list of observations that are to be made during some event in the
// DownloadItemImpl control flow. Ordering of the observations is significant.
ObservationList;

// An ordered list of events.
//
// An "event" in this context refers to some stage in the DownloadItemImpl's
// workflow described as "A", "B", "C", or "D" above. An EventList is expected
// to always contains kEventCount events.
EventList;

// Number of events in an EventList. This is always 4 for now as described
// above.
const int kEventCount =;

// The following functions help us with currying the calls to
// DownloadDestinationObserver. If std::bind was allowed along with
// std::placeholders, it is possible to avoid these functions, but currently
// Chromium doesn't allow using std::bind for good reasons.
void DestinationUpdateInvoker(
    int64_t bytes_so_far,
    int64_t bytes_per_sec,
    base::WeakPtr<DownloadDestinationObserver> observer) {}

void DestinationErrorInvoker(
    DownloadInterruptReason reason,
    int64_t bytes_so_far,
    base::WeakPtr<DownloadDestinationObserver> observer) {}

void DestinationCompletedInvoker(
    int64_t total_bytes,
    base::WeakPtr<DownloadDestinationObserver> observer) {}

// Given a set of observations (via the range |begin|..|end|), constructs a list
// of EventLists such that:
//
// * There are exactly |event_count| ObservationSets in each EventList.
//
// * Each ObservationList in each EventList contains a subrange (possibly empty)
//   of observations from the input range, in the same order as the input range.
//
// * The ordering of the ObservationList in each EventList is such that all
//   observations in one ObservationList occur earlier than all observations in
//   an ObservationList that follows it.
//
// * The list of EventLists together describe all the possible ways in which the
//   list of observations can be distributed into |event_count| events.
std::vector<EventList> DistributeObservationsIntoEvents(
    const std::vector<CurriedObservation>::iterator begin,
    const std::vector<CurriedObservation>::iterator end,
    int event_count) {}

// For the purpose of this tests, we are only concerned with 3 events:
//
// 1. Immediately after the DownloadFile is initialized.
// 2. Immediately after the DownloadTargetCallback is invoked.
// 3. Immediately after the intermediate file is renamed.
//
// We are going to take a couple of sets of DownloadDestinationObserver events
// and distribute them into the three events described above. And then we are
// going to invoke the observations while a DownloadItemImpl is carefully
// stepped through its stages.

std::vector<EventList> GenerateSuccessfulEventLists() {}

std::vector<EventList> GenerateFailingEventLists() {}

class DownloadItemDestinationUpdateRaceTest
    : public DownloadItemTest,
      public ::testing::WithParamInterface<EventList> {};

INSTANTIATE_TEST_SUITE_P();

INSTANTIATE_TEST_SUITE_P();

// Run through the DII workflow but the embedder cancels the download at target
// determination.
TEST_P(DownloadItemDestinationUpdateRaceTest, DownloadCancelledByUser) {}

// Run through the DII workflow, but the intermediate rename fails.
TEST_P(DownloadItemDestinationUpdateRaceTest, IntermediateRenameFails) {}

// Run through the DII workflow. Download file initialization, target
// determination and intermediate rename all succeed.
TEST_P(DownloadItemDestinationUpdateRaceTest, IntermediateRenameSucceeds) {}

}  // namespace
}  // namespace download