blob: 2ae4d721411d66b8736f4523321a86224ced01c1 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/offline_pages/core/background/request_queue.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/offline_pages/core/background/device_conditions.h"
#include "components/offline_pages/core/background/offliner_policy.h"
#include "components/offline_pages/core/background/request_coordinator.h"
#include "components/offline_pages/core/background/request_coordinator_event_logger.h"
#include "components/offline_pages/core/background/request_notifier.h"
#include "components/offline_pages/core/background/request_queue_store.h"
#include "components/offline_pages/core/background/save_page_request.h"
#include "components/offline_pages/core/background/test_request_queue_store.h"
#include "components/offline_pages/core/offline_clock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace offline_pages {
using AddRequestResult = AddRequestResult;
using GetRequestsResult = GetRequestsResult;
using UpdateRequestResult = UpdateRequestResult;
namespace {
// Data for request 1.
const int64_t kRequestId = 42;
const GURL kUrl("http://example.com");
const ClientId kClientId("bookmark", "1234");
// Data for request 2.
const int64_t kRequestId2 = 77;
const GURL kUrl2("http://test.com");
const ClientId kClientId2("bookmark", "567");
const bool kUserRequested = true;
const int64_t kRequestId3 = 99;
const int kOneWeekInSeconds = 7 * 24 * 60 * 60;
// Default request
const SavePageRequest kEmptyRequest(0UL,
GURL(""),
ClientId("", ""),
base::Time(),
true);
} // namespace
// Helper class needed by the PickRequestTask
class RequestNotifierStub : public RequestNotifier {
public:
RequestNotifierStub()
: last_expired_request_(kEmptyRequest), total_expired_requests_(0) {}
void NotifyAdded(const SavePageRequest& request) override {}
void NotifyChanged(const SavePageRequest& request) override {}
void NotifyCompleted(const SavePageRequest& request,
BackgroundSavePageResult status) override {
last_expired_request_ = request;
last_request_expiration_status_ = status;
total_expired_requests_++;
}
void NotifyNetworkProgress(const SavePageRequest& request,
int64_t received_bytes) override {
// This has nothing to do with the queue (progress is ephemeral) - no test.
}
const SavePageRequest& last_expired_request() {
return last_expired_request_;
}
RequestCoordinator::BackgroundSavePageResult
last_request_expiration_status() {
return last_request_expiration_status_;
}
int32_t total_expired_requests() { return total_expired_requests_; }
private:
BackgroundSavePageResult last_request_expiration_status_;
SavePageRequest last_expired_request_;
int32_t total_expired_requests_;
};
class RequestQueueTest : public testing::Test {
public:
RequestQueueTest();
~RequestQueueTest() override;
// Test overrides.
void SetUp() override;
void TearDown() override {
store_->Close();
PumpLoop();
}
void PumpLoop();
// Callback for adding requests.
void AddRequestDone(AddRequestResult result, const SavePageRequest& request);
// Callback for getting requests.
void GetRequestsDone(GetRequestsResult result,
std::vector<std::unique_ptr<SavePageRequest>> requests);
void UpdateRequestDone(UpdateRequestResult result);
void UpdateRequestsDone(UpdateRequestsResult result);
void ClearResults();
RequestQueue* queue() { return queue_.get(); }
AddRequestResult last_add_result() const { return last_add_result_; }
SavePageRequest* last_added_request() { return last_added_request_.get(); }
UpdateRequestResult last_update_result() const { return last_update_result_; }
GetRequestsResult last_get_requests_result() const {
return last_get_requests_result_;
}
const std::vector<std::unique_ptr<SavePageRequest>>& last_requests() const {
return last_requests_;
}
UpdateRequestsResult* update_requests_result() const {
return update_requests_result_.get();
}
void RequestPickedCallback(const SavePageRequest& request) {}
void RequestNotPickedCallback(bool non_user_requested_tasks_remain) {}
void RequestCountCallback(size_t total_count, size_t available_count) {}
private:
AddRequestResult last_add_result_;
std::unique_ptr<SavePageRequest> last_added_request_;
std::unique_ptr<UpdateRequestsResult> update_requests_result_;
UpdateRequestResult last_update_result_;
GetRequestsResult last_get_requests_result_;
std::vector<std::unique_ptr<SavePageRequest>> last_requests_;
std::unique_ptr<RequestQueue> queue_;
TestRequestQueueStore* store_; // Owned by queue_.
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
base::ThreadTaskRunnerHandle task_runner_handle_;
};
RequestQueueTest::RequestQueueTest()
: last_add_result_(AddRequestResult::STORE_FAILURE),
last_update_result_(UpdateRequestResult::STORE_FAILURE),
last_get_requests_result_(GetRequestsResult::STORE_FAILURE),
task_runner_(new base::TestMockTimeTaskRunner),
task_runner_handle_(task_runner_) {}
RequestQueueTest::~RequestQueueTest() {}
void RequestQueueTest::SetUp() {
auto store = std::make_unique<TestRequestQueueStore>();
store_ = store.get();
queue_.reset(new RequestQueue(std::move(store)));
}
void RequestQueueTest::PumpLoop() {
task_runner_->RunUntilIdle();
}
void RequestQueueTest::AddRequestDone(AddRequestResult result,
const SavePageRequest& request) {
last_add_result_ = result;
last_added_request_.reset(new SavePageRequest(request));
}
void RequestQueueTest::GetRequestsDone(
GetRequestsResult result,
std::vector<std::unique_ptr<SavePageRequest>> requests) {
last_get_requests_result_ = result;
last_requests_ = std::move(requests);
}
void RequestQueueTest::UpdateRequestDone(UpdateRequestResult result) {
last_update_result_ = result;
}
void RequestQueueTest::UpdateRequestsDone(UpdateRequestsResult result) {
update_requests_result_ =
std::make_unique<UpdateRequestsResult>(std::move(result));
}
void RequestQueueTest::ClearResults() {
last_add_result_ = AddRequestResult::STORE_FAILURE;
last_update_result_ = UpdateRequestResult::STORE_FAILURE;
last_get_requests_result_ = GetRequestsResult::STORE_FAILURE;
last_added_request_.reset(nullptr);
update_requests_result_.reset(nullptr);
last_requests_.clear();
}
TEST_F(RequestQueueTest, GetRequestsEmpty) {
queue()->GetRequests(base::BindOnce(&RequestQueueTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(GetRequestsResult::SUCCESS, last_get_requests_result());
ASSERT_EQ(0ul, last_requests().size());
}
TEST_F(RequestQueueTest, AddRequest) {
base::Time creation_time = OfflineTimeNow();
SavePageRequest request(kRequestId, kUrl, kClientId, creation_time,
kUserRequested);
queue()->AddRequest(request, base::BindOnce(&RequestQueueTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(AddRequestResult::SUCCESS, last_add_result());
ASSERT_TRUE(last_added_request());
ASSERT_EQ(kRequestId, last_added_request()->request_id());
queue()->GetRequests(base::BindOnce(&RequestQueueTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(GetRequestsResult::SUCCESS, last_get_requests_result());
ASSERT_EQ(1ul, last_requests().size());
}
TEST_F(RequestQueueTest, RemoveRequest) {
base::Time creation_time = OfflineTimeNow();
SavePageRequest request(kRequestId, kUrl, kClientId, creation_time,
kUserRequested);
queue()->AddRequest(request, base::BindOnce(&RequestQueueTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(kRequestId, last_added_request()->request_id());
std::vector<int64_t> remove_requests{kRequestId};
queue()->RemoveRequests(remove_requests,
base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
EXPECT_EQ(1ul, update_requests_result()->item_statuses.size());
EXPECT_EQ(kRequestId, update_requests_result()->item_statuses.at(0).first);
EXPECT_EQ(ItemActionStatus::SUCCESS,
update_requests_result()->item_statuses.at(0).second);
EXPECT_EQ(1UL, update_requests_result()->updated_items.size());
EXPECT_EQ(request, update_requests_result()->updated_items.at(0));
queue()->GetRequests(base::BindOnce(&RequestQueueTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(GetRequestsResult::SUCCESS, last_get_requests_result());
ASSERT_EQ(0ul, last_requests().size());
}
TEST_F(RequestQueueTest, RemoveSeveralRequests) {
base::Time creation_time = OfflineTimeNow();
SavePageRequest request(kRequestId, kUrl, kClientId, creation_time,
kUserRequested);
queue()->AddRequest(request, base::BindOnce(&RequestQueueTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(kRequestId, last_added_request()->request_id());
SavePageRequest request2(kRequestId2, kUrl2, kClientId2, creation_time,
kUserRequested);
queue()->AddRequest(request2,
base::BindOnce(&RequestQueueTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(kRequestId2, last_added_request()->request_id());
std::vector<int64_t> remove_requests;
remove_requests.push_back(kRequestId);
remove_requests.push_back(kRequestId2);
remove_requests.push_back(kRequestId3);
queue()->RemoveRequests(remove_requests,
base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(3ul, update_requests_result()->item_statuses.size());
ASSERT_EQ(kRequestId, update_requests_result()->item_statuses.at(0).first);
ASSERT_EQ(ItemActionStatus::SUCCESS,
update_requests_result()->item_statuses.at(0).second);
ASSERT_EQ(kRequestId2, update_requests_result()->item_statuses.at(1).first);
ASSERT_EQ(ItemActionStatus::SUCCESS,
update_requests_result()->item_statuses.at(1).second);
ASSERT_EQ(kRequestId3, update_requests_result()->item_statuses.at(2).first);
ASSERT_EQ(ItemActionStatus::NOT_FOUND,
update_requests_result()->item_statuses.at(2).second);
EXPECT_EQ(2UL, update_requests_result()->updated_items.size());
EXPECT_EQ(request, update_requests_result()->updated_items.at(0));
EXPECT_EQ(request2, update_requests_result()->updated_items.at(1));
queue()->GetRequests(base::BindOnce(&RequestQueueTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// Verify both requests are no longer in the queue.
ASSERT_EQ(GetRequestsResult::SUCCESS, last_get_requests_result());
ASSERT_EQ(0ul, last_requests().size());
}
TEST_F(RequestQueueTest, PauseAndResume) {
base::Time creation_time = OfflineTimeNow();
SavePageRequest request(kRequestId, kUrl, kClientId, creation_time,
kUserRequested);
queue()->AddRequest(request, base::BindOnce(&RequestQueueTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(kRequestId, last_added_request()->request_id());
queue()->GetRequests(base::BindOnce(&RequestQueueTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(GetRequestsResult::SUCCESS, last_get_requests_result());
ASSERT_EQ(1ul, last_requests().size());
std::vector<int64_t> request_ids;
request_ids.push_back(kRequestId);
// Pause the request.
queue()->ChangeRequestsState(
request_ids, SavePageRequest::RequestState::PAUSED,
base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(1ul, update_requests_result()->item_statuses.size());
ASSERT_EQ(kRequestId, update_requests_result()->item_statuses.at(0).first);
ASSERT_EQ(ItemActionStatus::SUCCESS,
update_requests_result()->item_statuses.at(0).second);
ASSERT_EQ(1ul, update_requests_result()->updated_items.size());
ASSERT_EQ(SavePageRequest::RequestState::PAUSED,
update_requests_result()->updated_items.at(0).request_state());
queue()->GetRequests(base::BindOnce(&RequestQueueTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// Verify the request is paused.
ASSERT_EQ(GetRequestsResult::SUCCESS, last_get_requests_result());
ASSERT_EQ(1ul, last_requests().size());
ASSERT_EQ(SavePageRequest::RequestState::PAUSED,
last_requests().at(0)->request_state());
// Resume the request.
queue()->ChangeRequestsState(
request_ids, SavePageRequest::RequestState::AVAILABLE,
base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(1ul, update_requests_result()->item_statuses.size());
ASSERT_EQ(kRequestId, update_requests_result()->item_statuses.at(0).first);
ASSERT_EQ(ItemActionStatus::SUCCESS,
update_requests_result()->item_statuses.at(0).second);
ASSERT_EQ(1ul, update_requests_result()->updated_items.size());
ASSERT_EQ(SavePageRequest::RequestState::AVAILABLE,
update_requests_result()->updated_items.at(0).request_state());
queue()->GetRequests(base::BindOnce(&RequestQueueTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
// Verify the request is no longer paused.
ASSERT_EQ(GetRequestsResult::SUCCESS, last_get_requests_result());
ASSERT_EQ(1ul, last_requests().size());
ASSERT_EQ(SavePageRequest::RequestState::AVAILABLE,
last_requests().at(0)->request_state());
}
// A longer test populating the request queue with more than one item, properly
// listing multiple items and removing the right item.
TEST_F(RequestQueueTest, MultipleRequestsAddGetRemove) {
base::Time creation_time = OfflineTimeNow();
SavePageRequest request1(kRequestId, kUrl, kClientId, creation_time,
kUserRequested);
queue()->AddRequest(request1,
base::BindOnce(&RequestQueueTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(request1.request_id(), last_added_request()->request_id());
SavePageRequest request2(kRequestId2, kUrl2, kClientId2, creation_time,
kUserRequested);
queue()->AddRequest(request2,
base::BindOnce(&RequestQueueTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(request2.request_id(), last_added_request()->request_id());
queue()->GetRequests(base::BindOnce(&RequestQueueTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(GetRequestsResult::SUCCESS, last_get_requests_result());
ASSERT_EQ(2ul, last_requests().size());
std::vector<int64_t> remove_requests;
remove_requests.push_back(request1.request_id());
queue()->RemoveRequests(remove_requests,
base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(1ul, update_requests_result()->item_statuses.size());
ASSERT_EQ(kRequestId, update_requests_result()->item_statuses.at(0).first);
ASSERT_EQ(ItemActionStatus::SUCCESS,
update_requests_result()->item_statuses.at(0).second);
queue()->GetRequests(base::BindOnce(&RequestQueueTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(GetRequestsResult::SUCCESS, last_get_requests_result());
ASSERT_EQ(1ul, last_requests().size());
ASSERT_EQ(request2.request_id(), last_requests().at(0)->request_id());
}
TEST_F(RequestQueueTest, MarkAttemptStarted) {
// First add a request. Retry count will be set to 0.
base::Time creation_time = OfflineTimeNow();
SavePageRequest request(kRequestId, kUrl, kClientId, creation_time,
kUserRequested);
queue()->AddRequest(request, base::BindOnce(&RequestQueueTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
base::Time before_time = OfflineTimeNow();
// Update the request, ensure it succeeded.
queue()->MarkAttemptStarted(
kRequestId, base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(1ul, update_requests_result()->item_statuses.size());
EXPECT_EQ(kRequestId, update_requests_result()->item_statuses.at(0).first);
EXPECT_EQ(ItemActionStatus::SUCCESS,
update_requests_result()->item_statuses.at(0).second);
EXPECT_EQ(1UL, update_requests_result()->updated_items.size());
EXPECT_LE(before_time,
update_requests_result()->updated_items.at(0).last_attempt_time());
EXPECT_GE(OfflineTimeNow(),
update_requests_result()->updated_items.at(0).last_attempt_time());
EXPECT_EQ(
1, update_requests_result()->updated_items.at(0).started_attempt_count());
EXPECT_EQ(SavePageRequest::RequestState::OFFLINING,
update_requests_result()->updated_items.at(0).request_state());
queue()->GetRequests(base::BindOnce(&RequestQueueTest::GetRequestsDone,
base::Unretained(this)));
PumpLoop();
EXPECT_EQ(GetRequestsResult::SUCCESS, last_get_requests_result());
ASSERT_EQ(1ul, last_requests().size());
EXPECT_EQ(update_requests_result()->updated_items.at(0),
*last_requests().at(0));
}
TEST_F(RequestQueueTest, MarkAttempStartedRequestNotPresent) {
// First add a request. Retry count will be set to 0.
base::Time creation_time = OfflineTimeNow();
// This request is never put into the queue.
SavePageRequest request1(kRequestId, kUrl, kClientId, creation_time,
kUserRequested);
queue()->MarkAttemptStarted(
kRequestId, base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(1ul, update_requests_result()->item_statuses.size());
EXPECT_EQ(kRequestId, update_requests_result()->item_statuses.at(0).first);
EXPECT_EQ(ItemActionStatus::NOT_FOUND,
update_requests_result()->item_statuses.at(0).second);
EXPECT_EQ(0ul, update_requests_result()->updated_items.size());
}
TEST_F(RequestQueueTest, MarkAttemptAborted) {
base::Time creation_time = OfflineTimeNow();
SavePageRequest request(kRequestId, kUrl, kClientId, creation_time,
kUserRequested);
queue()->AddRequest(request, base::BindOnce(&RequestQueueTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
// Start request.
queue()->MarkAttemptStarted(
kRequestId, base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
ClearResults();
queue()->MarkAttemptAborted(
kRequestId, base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_TRUE(update_requests_result());
EXPECT_EQ(1UL, update_requests_result()->item_statuses.size());
EXPECT_EQ(kRequestId, update_requests_result()->item_statuses.at(0).first);
EXPECT_EQ(ItemActionStatus::SUCCESS,
update_requests_result()->item_statuses.at(0).second);
EXPECT_EQ(1UL, update_requests_result()->updated_items.size());
EXPECT_EQ(SavePageRequest::RequestState::AVAILABLE,
update_requests_result()->updated_items.at(0).request_state());
}
TEST_F(RequestQueueTest, MarkAttemptAbortedRequestNotPresent) {
// First add a request. Retry count will be set to 0.
base::Time creation_time = OfflineTimeNow();
// This request is never put into the queue.
SavePageRequest request1(kRequestId, kUrl, kClientId, creation_time,
kUserRequested);
queue()->MarkAttemptAborted(
kRequestId, base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_EQ(1ul, update_requests_result()->item_statuses.size());
EXPECT_EQ(kRequestId, update_requests_result()->item_statuses.at(0).first);
EXPECT_EQ(ItemActionStatus::NOT_FOUND,
update_requests_result()->item_statuses.at(0).second);
EXPECT_EQ(0ul, update_requests_result()->updated_items.size());
}
TEST_F(RequestQueueTest, MarkAttemptCompleted) {
base::Time creation_time = OfflineTimeNow();
SavePageRequest request(kRequestId, kUrl, kClientId, creation_time,
kUserRequested);
queue()->AddRequest(request, base::BindOnce(&RequestQueueTest::AddRequestDone,
base::Unretained(this)));
PumpLoop();
// Start request.
queue()->MarkAttemptStarted(
kRequestId, base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
ClearResults();
queue()->MarkAttemptCompleted(
kRequestId, FailState::CANNOT_DOWNLOAD,
base::BindOnce(&RequestQueueTest::UpdateRequestsDone,
base::Unretained(this)));
PumpLoop();
ASSERT_TRUE(update_requests_result());
EXPECT_EQ(1UL, update_requests_result()->item_statuses.size());
EXPECT_EQ(kRequestId, update_requests_result()->item_statuses.at(0).first);
EXPECT_EQ(ItemActionStatus::SUCCESS,
update_requests_result()->item_statuses.at(0).second);
EXPECT_EQ(1UL, update_requests_result()->updated_items.size());
EXPECT_EQ(SavePageRequest::RequestState::AVAILABLE,
update_requests_result()->updated_items.at(0).request_state());
}
TEST_F(RequestQueueTest, CleanStaleRequests) {
// Create a request that is already expired.
base::Time creation_time =
OfflineTimeNow() - base::TimeDelta::FromSeconds(2 * kOneWeekInSeconds);
SavePageRequest original_request(kRequestId, kUrl, kClientId, creation_time,
kUserRequested);
queue()->AddRequest(original_request,
base::BindOnce(&RequestQueueTest::AddRequestDone,
base::Unretained(this)));
this->PumpLoop();
this->ClearResults();
// Set up a picker factory pointing to our fake notifier.
OfflinerPolicy policy;
RequestNotifierStub notifier;
RequestCoordinatorEventLogger event_logger;
std::unique_ptr<CleanupTaskFactory> cleanup_factory(
new CleanupTaskFactory(&policy, &notifier, &event_logger));
queue()->SetCleanupFactory(std::move(cleanup_factory));
// Do a pick and clean operation, which will remove stale entries.
DeviceConditions conditions;
std::set<int64_t> disabled_list;
queue()->CleanupRequestQueue();
this->PumpLoop();
// Notifier should have been notified that the request was removed.
ASSERT_EQ(notifier.last_expired_request().request_id(), kRequestId);
ASSERT_EQ(notifier.last_request_expiration_status(),
RequestNotifier::BackgroundSavePageResult::EXPIRED);
// Doing a get should show no entries left in the queue since the expired
// request has been removed.
queue()->GetRequests(base::BindOnce(&RequestQueueTest::GetRequestsDone,
base::Unretained(this)));
this->PumpLoop();
ASSERT_EQ(GetRequestsResult::SUCCESS, this->last_get_requests_result());
ASSERT_TRUE(this->last_requests().empty());
}
} // namespace offline_pages