// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/overlays/model/overlay_request_queue_impl.h"
#import <vector>
#import "ios/chrome/browser/overlays/model/public/overlay_request.h"
#import "ios/chrome/browser/overlays/model/public/overlay_request_cancel_handler.h"
#import "ios/chrome/browser/overlays/model/test/fake_overlay_request_cancel_handler.h"
#import "ios/chrome/browser/overlays/model/test/fake_overlay_user_data.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/platform_test.h"
namespace {
// Fake queue delegate. Keeps ownership of all requests removed from a queue,
// recording whether the requests were removed for cancellation.
class FakeOverlayRequestQueueImplDelegate
: public OverlayRequestQueueImpl::Delegate {
public:
FakeOverlayRequestQueueImplDelegate() {}
~FakeOverlayRequestQueueImplDelegate() {}
void OverlayRequestRemoved(OverlayRequestQueueImpl* queue,
std::unique_ptr<OverlayRequest> request,
bool cancelled) override {
removed_requests_.emplace_back(std::move(request), cancelled);
}
void OverlayRequestQueueWillReplaceDelegate(
OverlayRequestQueueImpl* queue) override {}
// Whether `request` was removed from the queue.
bool WasRequestRemoved(OverlayRequest* request) {
return GetRemovedRequestStorage(request) != nullptr;
}
// Whether `request` was removed from the queue for cancellation.
bool WasRequestCancelled(OverlayRequest* request) {
const RemovedRequestStorage* storage = GetRemovedRequestStorage(request);
return storage && storage->cancelled;
}
private:
// Stores the removed requests and whether the requests were cancelled.
struct RemovedRequestStorage {
RemovedRequestStorage(std::unique_ptr<OverlayRequest> request,
bool cancelled)
: request(std::move(request)), cancelled(cancelled) {}
RemovedRequestStorage(RemovedRequestStorage&& storage)
: request(std::move(storage.request)), cancelled(storage.cancelled) {}
~RemovedRequestStorage() {}
std::unique_ptr<OverlayRequest> request;
bool cancelled;
};
// Returns the request storage for `request`.
const RemovedRequestStorage* GetRemovedRequestStorage(
OverlayRequest* request) {
for (auto& storage : removed_requests_) {
if (storage.request.get() == request)
return &storage;
}
return nullptr;
}
// Storages holding the requests that were removed from the queue being
// delegated. Keeps ownership of removed requests and whether they were
// cancelled.
std::vector<RemovedRequestStorage> removed_requests_;
};
// Mock queue observer.
class MockOverlayRequestQueueImplObserver
: public OverlayRequestQueueImpl::Observer {
public:
MockOverlayRequestQueueImplObserver() {}
~MockOverlayRequestQueueImplObserver() override {}
MOCK_METHOD3(RequestAddedToQueue,
void(OverlayRequestQueueImpl*, OverlayRequest*, size_t));
void OverlayRequestQueueDestroyed(OverlayRequestQueueImpl* queue) override {
queue->RemoveObserver(this);
}
};
// A cancel handler that never cancels its request.
class NoOpCancelHandler : public OverlayRequestCancelHandler {
public:
NoOpCancelHandler(OverlayRequest* request, OverlayRequestQueue* queue)
: OverlayRequestCancelHandler(request, queue) {}
~NoOpCancelHandler() override = default;
};
} // namespace
// Test fixture for RequestQueueImpl.
class OverlayRequestQueueImplTest : public PlatformTest {
public:
OverlayRequestQueueImplTest()
: PlatformTest(), web_state_(std::make_unique<web::FakeWebState>()) {
OverlayRequestQueueImpl::Container::CreateForWebState(web_state_.get());
queue()->SetDelegate(&delegate_);
queue()->AddObserver(&observer_);
}
~OverlayRequestQueueImplTest() override {
if (web_state_) {
queue()->SetDelegate(nullptr);
queue()->RemoveObserver(&observer_);
}
}
OverlayRequestQueueImpl* queue() {
// Use the kWebContentArea queue for testing.
return OverlayRequestQueueImpl::Container::FromWebState(web_state_.get())
->QueueForModality(OverlayModality::kWebContentArea);
}
MockOverlayRequestQueueImplObserver& observer() { return observer_; }
OverlayRequest* AddRequest() {
std::unique_ptr<OverlayRequest> passed_request =
OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
OverlayRequest* request = passed_request.get();
EXPECT_CALL(observer(),
RequestAddedToQueue(queue(), request, queue()->size()));
queue()->AddRequest(std::move(passed_request));
return request;
}
protected:
FakeOverlayRequestQueueImplDelegate delegate_;
MockOverlayRequestQueueImplObserver observer_;
std::unique_ptr<web::FakeWebState> web_state_;
};
// Tests that state is updated correctly and observer callbacks are received
// when adding requests to the back of the queue.
TEST_F(OverlayRequestQueueImplTest, AddRequest) {
OverlayRequest* first_request = AddRequest();
AddRequest();
EXPECT_EQ(first_request, queue()->front_request());
EXPECT_EQ(2U, queue()->size());
}
// Tests that GetRequest() returns the expected values.
TEST_F(OverlayRequestQueueImplTest, GetRequest) {
OverlayRequest* first_request = AddRequest();
OverlayRequest* second_request = AddRequest();
OverlayRequest* third_request = AddRequest();
// Verify GetRequest() results.
EXPECT_EQ(first_request, queue()->GetRequest(/*index=*/0));
EXPECT_EQ(second_request, queue()->GetRequest(/*index=*/1));
EXPECT_EQ(third_request, queue()->GetRequest(/*index=*/2));
// Verify array-syntax accessor results.
EXPECT_EQ(first_request, (*queue())[0]);
EXPECT_EQ(second_request, (*queue())[1]);
EXPECT_EQ(third_request, (*queue())[2]);
}
// Tests that state is updated correctly and observer callbacks are received
// when inserting requests into the middle of the queue.
TEST_F(OverlayRequestQueueImplTest, InsertRequest) {
AddRequest();
AddRequest();
ASSERT_EQ(2U, queue()->size());
// Insert a request into the middle of the queue.
void* kInsertedRequestConfigValue = &kInsertedRequestConfigValue;
std::unique_ptr<OverlayRequest> inserted_request =
OverlayRequest::CreateWithConfig<FakeOverlayUserData>(
kInsertedRequestConfigValue);
OverlayRequest* request = inserted_request.get();
EXPECT_CALL(observer(), RequestAddedToQueue(queue(), request, 1));
queue()->InsertRequest(1, std::move(inserted_request));
// Verify that the request is inserted correctly.
EXPECT_EQ(3U, queue()->size());
EXPECT_EQ(kInsertedRequestConfigValue,
queue()->GetRequest(1)->GetConfig<FakeOverlayUserData>()->value());
}
// Tests that PopFrontRequest() correctly updates state, notifies observers, and
// transfers ownership of the popped request to the delegate.
TEST_F(OverlayRequestQueueImplTest, PopFrontRequest) {
// Add two requests to the queue.
OverlayRequest* first_request = AddRequest();
OverlayRequest* second_request = AddRequest();
ASSERT_EQ(first_request, queue()->front_request());
ASSERT_EQ(2U, queue()->size());
// Pop the first request and check that the size and front request have been
// updated.
queue()->PopFrontRequest();
EXPECT_EQ(second_request, queue()->front_request());
EXPECT_EQ(1U, queue()->size());
EXPECT_TRUE(delegate_.WasRequestRemoved(first_request));
EXPECT_FALSE(delegate_.WasRequestCancelled(first_request));
}
// Tests that CancelAllRequests() correctly updates state and transfers requests
// to the delegate.
TEST_F(OverlayRequestQueueImplTest, CancelAllRequests) {
// Add two requests to the queue then cancel all requests, verifying that
// the observer callback is received for each.
OverlayRequest* first_request = AddRequest();
OverlayRequest* second_request = AddRequest();
queue()->CancelAllRequests();
EXPECT_EQ(0U, queue()->size());
EXPECT_TRUE(delegate_.WasRequestCancelled(first_request));
EXPECT_TRUE(delegate_.WasRequestCancelled(second_request));
}
// Tests that a cancellation via a cancel handler correctly updates state and
// transfers the caoncelled requests to the delegate.
TEST_F(OverlayRequestQueueImplTest, CustomCancelHandler) {
std::unique_ptr<OverlayRequest> passed_request =
OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
OverlayRequest* request = passed_request.get();
std::unique_ptr<FakeOverlayRequestCancelHandler> passed_cancel_handler =
std::make_unique<FakeOverlayRequestCancelHandler>(request, queue());
FakeOverlayRequestCancelHandler* cancel_handler = passed_cancel_handler.get();
EXPECT_CALL(observer(),
RequestAddedToQueue(queue(), request, queue()->size()));
queue()->AddRequest(std::move(passed_request),
std::move(passed_cancel_handler));
// Trigger cancellation via the cancel handler, and verify that the request is
// correctly removed.
cancel_handler->TriggerCancellation();
EXPECT_EQ(0U, queue()->size());
EXPECT_TRUE(delegate_.WasRequestCancelled(request));
}
// Tests that the request's WebState is set up when it is added to the queue.
TEST_F(OverlayRequestQueueImplTest, WebStateSetup) {
std::unique_ptr<OverlayRequest> added_request =
OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
OverlayRequest* request = added_request.get();
ASSERT_FALSE(request->GetQueueWebState());
// Add the request and verify that the WebState is set.
EXPECT_CALL(observer(),
RequestAddedToQueue(queue(), request, queue()->size()));
queue()->AddRequest(std::move(added_request));
EXPECT_EQ(web_state_.get(), request->GetQueueWebState());
}
// Tests that requests are cancelled upon queue destruction even if their cancel
// handlers do not explicitly handle cancellation on WebState destruction.
TEST_F(OverlayRequestQueueImplTest, CancellationUponDestruction) {
std::unique_ptr<OverlayRequest> passed_request =
OverlayRequest::CreateWithConfig<FakeOverlayUserData>();
OverlayRequest* request = passed_request.get();
std::unique_ptr<OverlayRequestCancelHandler> cancel_handler =
std::make_unique<NoOpCancelHandler>(passed_request.get(), queue());
EXPECT_CALL(observer(),
RequestAddedToQueue(queue(), request, queue()->size()));
queue()->AddRequest(std::move(passed_request), std::move(cancel_handler));
// Destroy the OverlayRequestQueue by destroying its owning WebState.
web_state_ = nullptr;
// Verify that the request was cancelled even though the cancel handler never
// executed CancelRequest().
EXPECT_TRUE(delegate_.WasRequestCancelled(request));
}