Commit dcd50fae authored by Majid Valipour's avatar Majid Valipour Committed by Commit Bot

[WebOTP] Refactor sms service tests to not require a particular backend

Currently the sms_service_unittests only exercises the verification
backend which is not actually active by default since we are
currently using User Consent backend.

More than half the tests (10 out of 10) seems to apply to both backends.
This patch refactors the logic so that the backend specific part of
sms service are well encapsulated.

It then updates the test so these common tests are exercised with
both backends.

Changes:
 - Introduce UserConsentHandler with two specific implementation
    1. Noop: which does not show any UI to user and is used with user
       consent backend.
    2. PromptBased: which prompts user to give consent and is used with
       verification backend. This encapsulates previous OpenInfoBar,
       OnCancel, OnConfirm methods from sms service.
 - The consent handler is created on construction of the service and is
   used for its lifetime. This makes it possible to easily inject mock
   consent handlers for testing.
 - Update mock Service to use a simple Noop consent handler for common
   tests which don't involve consent.
 - Introduce a more specialized ServiceWithPrompt that uses a prompt
   based consent handler for tests specific to prompt based logic.
 - Add unit tests for consent handlers.
 - Minor renaming, comments to clarify the code further.


There is not expected to be any functional changes in this CL.


Change-Id: I411bf9d12cb7a2051e545ce6dd8a50fe37204326
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2254447Reviewed-by: default avatarSam Goto <goto@chromium.org>
Commit-Queue: Majid Valipour <majidvp@chromium.org>
Cr-Commit-Position: refs/heads/master@{#789970}
parent 2a03c3dd
...@@ -1793,6 +1793,8 @@ jumbo_source_set("browser") { ...@@ -1793,6 +1793,8 @@ jumbo_source_set("browser") {
"sms/sms_queue.h", "sms/sms_queue.h",
"sms/sms_service.cc", "sms/sms_service.cc",
"sms/sms_service.h", "sms/sms_service.h",
"sms/user_consent_handler.cc",
"sms/user_consent_handler.h",
"speech/speech_recognition_dispatcher_host.cc", "speech/speech_recognition_dispatcher_host.cc",
"speech/speech_recognition_dispatcher_host.h", "speech/speech_recognition_dispatcher_host.h",
"speech/speech_recognition_manager_impl.cc", "speech/speech_recognition_manager_impl.cc",
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "content/browser/sms/sms_service.h" #include "content/browser/sms/sms_service.h"
#include <iterator> #include <iterator>
#include <memory>
#include <queue> #include <queue>
#include <string> #include <string>
#include <utility> #include <utility>
...@@ -16,6 +17,7 @@ ...@@ -16,6 +17,7 @@
#include "base/optional.h" #include "base/optional.h"
#include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/sms/sms_metrics.h" #include "content/browser/sms/sms_metrics.h"
#include "content/browser/sms/user_consent_handler.h"
#include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_type.h" #include "content/public/browser/navigation_type.h"
#include "content/public/browser/sms_fetcher.h" #include "content/public/browser/sms_fetcher.h"
...@@ -23,6 +25,7 @@ ...@@ -23,6 +25,7 @@
#include "content/public/browser/web_contents_delegate.h" #include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_features.h" #include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "third_party/blink/public/mojom/sms/sms_receiver.mojom-shared.h"
using blink::SmsReceiverDestroyedReason; using blink::SmsReceiverDestroyedReason;
using blink::mojom::SmsStatus; using blink::mojom::SmsStatus;
...@@ -31,11 +34,13 @@ namespace content { ...@@ -31,11 +34,13 @@ namespace content {
SmsService::SmsService( SmsService::SmsService(
SmsFetcher* fetcher, SmsFetcher* fetcher,
std::unique_ptr<UserConsentHandler> consent_handler,
const url::Origin& origin, const url::Origin& origin,
RenderFrameHost* host, RenderFrameHost* host,
mojo::PendingReceiver<blink::mojom::SmsReceiver> receiver) mojo::PendingReceiver<blink::mojom::SmsReceiver> receiver)
: FrameServiceBase(host, std::move(receiver)), : FrameServiceBase(host, std::move(receiver)),
fetcher_(fetcher), fetcher_(fetcher),
consent_handler_(std::move(consent_handler)),
origin_(origin) { origin_(origin) {
DCHECK(fetcher_); DCHECK(fetcher_);
} }
...@@ -45,13 +50,24 @@ SmsService::SmsService( ...@@ -45,13 +50,24 @@ SmsService::SmsService(
RenderFrameHost* host, RenderFrameHost* host,
mojo::PendingReceiver<blink::mojom::SmsReceiver> receiver) mojo::PendingReceiver<blink::mojom::SmsReceiver> receiver)
: SmsService(fetcher, : SmsService(fetcher,
nullptr,
host->GetLastCommittedOrigin(), host->GetLastCommittedOrigin(),
host, host,
std::move(receiver)) {} std::move(receiver)) {
bool needs_user_prompt =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kWebOtpBackend) == switches::kWebOtpBackendSmsVerification;
if (needs_user_prompt) {
consent_handler_ = std::make_unique<PromptBasedUserConsentHandler>();
} else {
consent_handler_ = std::make_unique<NoopUserConsentHandler>();
}
}
SmsService::~SmsService() { SmsService::~SmsService() {
if (callback_) if (callback_)
Process(SmsStatus::kTimeout, base::nullopt); CompleteRequest(SmsStatus::kTimeout);
DCHECK(!callback_); DCHECK(!callback_);
} }
...@@ -72,6 +88,13 @@ void SmsService::Create( ...@@ -72,6 +88,13 @@ void SmsService::Create(
void SmsService::Receive(ReceiveCallback callback) { void SmsService::Receive(ReceiveCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(majidvp): The comment below seems incorrect. This flow is used for
// both prompted and unprompted backends so it is not clear if we should
// always cancel early. Also I don't believe that we are actually silently
// dropping the sms but in fact the logic cancels the request once
// an sms comes in and there is no delegate.
// This flow relies on the delegate to display an infobar for user // This flow relies on the delegate to display an infobar for user
// confirmation. Cancelling the call early if no delegate is available is // confirmation. Cancelling the call early if no delegate is available is
// easier to debug then silently dropping SMSes later on. // easier to debug then silently dropping SMSes later on.
...@@ -82,6 +105,7 @@ void SmsService::Receive(ReceiveCallback callback) { ...@@ -82,6 +105,7 @@ void SmsService::Receive(ReceiveCallback callback) {
return; return;
} }
// Abort the last request if there is we have not yet handled it.
if (callback_) { if (callback_) {
std::move(callback_).Run(SmsStatus::kCancelled, base::nullopt); std::move(callback_).Run(SmsStatus::kCancelled, base::nullopt);
fetcher_->Unsubscribe(origin_, this); fetcher_->Unsubscribe(origin_, this);
...@@ -90,9 +114,11 @@ void SmsService::Receive(ReceiveCallback callback) { ...@@ -90,9 +114,11 @@ void SmsService::Receive(ReceiveCallback callback) {
start_time_ = base::TimeTicks::Now(); start_time_ = base::TimeTicks::Now();
callback_ = std::move(callback); callback_ = std::move(callback);
// |one_time_code_| and prompt are still present from the previous // |one_time_code_| and prompt are still present from the previous request so
// request so a new subscription is unnecessary. // a new subscription is unnecessary. Note that it is only safe for us to use
if (prompt_open_) { // the in flight otp with the new request since both requests belong to the
// same origin.
if (consent_handler_->is_active()) {
// TODO(crbug.com/1024598): Add UMA histogram. // TODO(crbug.com/1024598): Add UMA histogram.
return; return;
} }
...@@ -102,29 +128,23 @@ void SmsService::Receive(ReceiveCallback callback) { ...@@ -102,29 +128,23 @@ void SmsService::Receive(ReceiveCallback callback) {
void SmsService::OnReceive(const std::string& one_time_code) { void SmsService::OnReceive(const std::string& one_time_code) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!one_time_code_); DCHECK(!one_time_code_);
DCHECK(!start_time_.is_null()); DCHECK(!start_time_.is_null());
auto now = base::TimeTicks::Now(); receive_time_ = base::TimeTicks::Now();
RecordSmsReceiveTime(now - start_time_); RecordSmsReceiveTime(receive_time_ - start_time_);
one_time_code_ = one_time_code; one_time_code_ = one_time_code;
if (base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( consent_handler_->RequestUserConsent(
switches::kWebOtpBackend) != render_frame_host(), origin_, one_time_code,
switches::kWebOtpBackendSmsVerification) { base::BindOnce(&SmsService::CompleteRequest,
Process(SmsStatus::kSuccess, one_time_code_); weak_ptr_factory_.GetWeakPtr()));
return;
}
receive_time_ = now;
OpenInfoBar(one_time_code);
} }
void SmsService::Abort() { void SmsService::Abort() {
DCHECK(callback_); DCHECK(callback_);
Process(SmsStatus::kAborted, base::nullopt); CompleteRequest(SmsStatus::kAborted);
} }
void SmsService::NavigationEntryCommitted( void SmsService::NavigationEntryCommitted(
...@@ -145,68 +165,39 @@ void SmsService::NavigationEntryCommitted( ...@@ -145,68 +165,39 @@ void SmsService::NavigationEntryCommitted(
} }
} }
void SmsService::OpenInfoBar(const std::string& one_time_code) { void SmsService::CompleteRequest(blink::mojom::SmsStatus status) {
WebContents* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host());
if (!web_contents->GetDelegate()) {
Process(SmsStatus::kCancelled, base::nullopt);
return;
}
prompt_open_ = true;
web_contents->GetDelegate()->CreateSmsPrompt(
render_frame_host(), origin_, one_time_code,
base::BindOnce(&SmsService::OnConfirm, weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&SmsService::OnCancel, weak_ptr_factory_.GetWeakPtr()));
}
void SmsService::Process(blink::mojom::SmsStatus status,
base::Optional<std::string> one_time_code) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback_);
std::move(callback_).Run(status, one_time_code);
CleanUp();
}
void SmsService::OnConfirm() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(one_time_code_); base::Optional<std::string> code = base::nullopt;
DCHECK(!receive_time_.is_null()); if (status == SmsStatus::kSuccess) {
RecordContinueOnSuccessTime(base::TimeTicks::Now() - receive_time_); DCHECK(one_time_code_);
code = one_time_code_;
prompt_open_ = false;
if (!callback_) {
// Cleanup since request has been aborted while prompt is up.
CleanUp();
return;
} }
Process(SmsStatus::kSuccess, one_time_code_);
}
void SmsService::OnCancel() { // Record ContinueOn timing values only if we are using an asynchronous
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // consent handler (i.e. showing user prompts).
if (consent_handler_->is_async()) {
// Record only when SMS has already been received. if (status == SmsStatus::kSuccess) {
DCHECK(!receive_time_.is_null()); DCHECK(!receive_time_.is_null());
RecordCancelOnSuccessTime(base::TimeTicks::Now() - receive_time_); RecordContinueOnSuccessTime(base::TimeTicks::Now() - receive_time_);
} else if (status == SmsStatus::kCancelled) {
prompt_open_ = false; DCHECK(!receive_time_.is_null());
RecordCancelOnSuccessTime(base::TimeTicks::Now() - receive_time_);
}
}
if (!callback_) { if (callback_) {
// Cleanup since request has been aborted while prompt is up. std::move(callback_).Run(status, code);
CleanUp();
return;
} }
Process(SmsStatus::kCancelled, base::nullopt);
CleanUp();
} }
void SmsService::CleanUp() { void SmsService::CleanUp() {
// Skip resetting |one_time_code_|, |sms| and |receive_time_| while prompt is // Skip resetting |one_time_code_|, |sms| and |receive_time_| while prompt is
// still open in case it needs to be returned to the next incoming request // still open in case it needs to be returned to the next incoming request
// upon prompt confirmation. // upon prompt confirmation.
if (!prompt_open_) { if (!consent_handler_->is_active()) {
one_time_code_.reset(); one_time_code_.reset();
receive_time_ = base::TimeTicks(); receive_time_ = base::TimeTicks();
} }
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/sequence_checker.h" #include "base/sequence_checker.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "content/browser/sms/sms_queue.h" #include "content/browser/sms/sms_queue.h"
#include "content/browser/sms/user_consent_handler.h"
#include "content/common/content_export.h" #include "content/common/content_export.h"
#include "content/public/browser/frame_service_base.h" #include "content/public/browser/frame_service_base.h"
#include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_receiver.h"
...@@ -44,6 +45,7 @@ class CONTENT_EXPORT SmsService ...@@ -44,6 +45,7 @@ class CONTENT_EXPORT SmsService
RenderFrameHost*, RenderFrameHost*,
mojo::PendingReceiver<blink::mojom::SmsReceiver>); mojo::PendingReceiver<blink::mojom::SmsReceiver>);
SmsService(SmsFetcher*, SmsService(SmsFetcher*,
std::unique_ptr<UserConsentHandler> consent_handler,
const url::Origin&, const url::Origin&,
RenderFrameHost*, RenderFrameHost*,
mojo::PendingReceiver<blink::mojom::SmsReceiver>); mojo::PendingReceiver<blink::mojom::SmsReceiver>);
...@@ -56,31 +58,27 @@ class CONTENT_EXPORT SmsService ...@@ -56,31 +58,27 @@ class CONTENT_EXPORT SmsService
// content::SmsQueue::Subscriber // content::SmsQueue::Subscriber
void OnReceive(const std::string& one_time_code) override; void OnReceive(const std::string& one_time_code) override;
// Completes the in-flight sms otp code request. Invokes the receive callback,
// if one is available, with the provided status code and the existing one
// time code.
void CompleteRequest(blink::mojom::SmsStatus);
protected: protected:
// content::WebContentsObserver: // content::WebContentsObserver:
void NavigationEntryCommitted( void NavigationEntryCommitted(
const content::LoadCommittedDetails& load_details) override; const content::LoadCommittedDetails& load_details) override;
private: private:
void OpenInfoBar(const std::string& one_time_code);
void Process(blink::mojom::SmsStatus,
base::Optional<std::string> one_time_code);
void CleanUp(); void CleanUp();
// Called when the user manually clicks the 'Enter code' button.
void OnConfirm();
// Called when the user manually dismisses the infobar.
void OnCancel();
// |fetcher_| is safe because all instances of SmsFetcher are owned // |fetcher_| is safe because all instances of SmsFetcher are owned
// by the browser context, which transitively (through RenderFrameHost) owns // by the browser context, which transitively (through RenderFrameHost) owns
// and outlives this class. // and outlives this class.
SmsFetcher* fetcher_; SmsFetcher* fetcher_;
std::unique_ptr<UserConsentHandler> consent_handler_;
const url::Origin origin_; const url::Origin origin_;
bool prompt_open_ = false;
ReceiveCallback callback_; ReceiveCallback callback_;
base::Optional<std::string> one_time_code_; base::Optional<std::string> one_time_code_;
base::TimeTicks start_time_; base::TimeTicks start_time_;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "content/browser/sms/sms_service.h" #include "content/browser/sms/sms_service.h"
#include <memory>
#include <string> #include <string>
#include <utility> #include <utility>
...@@ -20,6 +21,8 @@ ...@@ -20,6 +21,8 @@
#include "content/browser/sms/sms_fetcher_impl.h" #include "content/browser/sms/sms_fetcher_impl.h"
#include "content/browser/sms/test/mock_sms_provider.h" #include "content/browser/sms/test/mock_sms_provider.h"
#include "content/browser/sms/test/mock_sms_web_contents_delegate.h" #include "content/browser/sms/test/mock_sms_web_contents_delegate.h"
#include "content/browser/sms/test/mock_user_consent_handler.h"
#include "content/browser/sms/user_consent_handler.h"
#include "content/browser/web_contents/web_contents_impl.h" #include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/sms_fetcher.h" #include "content/public/browser/sms_fetcher.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
...@@ -32,6 +35,7 @@ ...@@ -32,6 +35,7 @@
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/sms/sms_receiver_destroyed_reason.h" #include "third_party/blink/public/common/sms/sms_receiver_destroyed_reason.h"
#include "third_party/blink/public/mojom/sms/sms_receiver.mojom-shared.h"
#include "third_party/blink/public/mojom/sms/sms_receiver.mojom.h" #include "third_party/blink/public/mojom/sms/sms_receiver.mojom.h"
using base::BindLambdaForTesting; using base::BindLambdaForTesting;
...@@ -56,6 +60,8 @@ namespace { ...@@ -56,6 +60,8 @@ namespace {
const char kTestUrl[] = "https://www.google.com"; const char kTestUrl[] = "https://www.google.com";
class StubWebContentsDelegate : public WebContentsDelegate {};
// Service encapsulates a SmsService endpoint, with all of its dependencies // Service encapsulates a SmsService endpoint, with all of its dependencies
// mocked out (and the common plumbing needed to inject them), and a // mocked out (and the common plumbing needed to inject them), and a
// mojo::Remote<SmsReceiver> endpoint that tests can use to make requests. // mojo::Remote<SmsReceiver> endpoint that tests can use to make requests.
...@@ -63,55 +69,32 @@ const char kTestUrl[] = "https://www.google.com"; ...@@ -63,55 +69,32 @@ const char kTestUrl[] = "https://www.google.com";
// also exposes the low level mocks that enables tests to set expectations and // also exposes the low level mocks that enables tests to set expectations and
// control the testing environment. // control the testing environment.
class Service { class Service {
public: protected:
Service(WebContents* web_contents, const Origin& origin) Service(WebContents* web_contents,
: fetcher_(web_contents->GetBrowserContext(), &provider_) { const Origin& origin,
WebContentsImpl* web_contents_impl = std::unique_ptr<UserConsentHandler> user_consent_handler)
reinterpret_cast<WebContentsImpl*>(web_contents); : fetcher_(web_contents->GetBrowserContext(), &provider_),
web_contents_impl->SetDelegate(&delegate_); consent_handler_(user_consent_handler.get()) {
// Set a stub delegate because sms service checks existence of delegate and
// cancels requests early if one does not exist.
web_contents->SetDelegate(&contents_delegate_);
service_ = std::make_unique<SmsService>( service_ = std::make_unique<SmsService>(
&fetcher_, origin, web_contents->GetMainFrame(), &fetcher_, std::move(user_consent_handler), origin,
web_contents->GetMainFrame(),
service_remote_.BindNewPipeAndPassReceiver()); service_remote_.BindNewPipeAndPassReceiver());
} }
Service(WebContents* web_contents) public:
explicit Service(WebContents* web_contents)
: Service(web_contents, : Service(web_contents,
web_contents->GetMainFrame()->GetLastCommittedOrigin()) {} web_contents->GetMainFrame()->GetLastCommittedOrigin(),
/* avoid showing user prompts */
std::make_unique<NoopUserConsentHandler>()) {}
NiceMock<MockSmsProvider>* provider() { return &provider_; } NiceMock<MockSmsProvider>* provider() { return &provider_; }
SmsFetcher* fetcher() { return &fetcher_; } SmsFetcher* fetcher() { return &fetcher_; }
UserConsentHandler* consent_handler() { return consent_handler_; }
void CreateSmsPrompt(RenderFrameHost* rfh) {
EXPECT_CALL(delegate_, CreateSmsPrompt(rfh, _, _, _, _))
.WillOnce(Invoke([=](RenderFrameHost*, const Origin& origin,
const std::string&, base::OnceClosure on_confirm,
base::OnceClosure on_cancel) {
confirm_callback_ = std::move(on_confirm);
dismiss_callback_ = std::move(on_cancel);
}));
}
void ConfirmPrompt() {
if (!confirm_callback_.is_null()) {
std::move(confirm_callback_).Run();
dismiss_callback_.Reset();
return;
}
FAIL() << "SmsInfobar not available";
}
void DismissPrompt() {
if (dismiss_callback_.is_null()) {
FAIL() << "SmsInfobar not available";
return;
}
std::move(dismiss_callback_).Run();
confirm_callback_.Reset();
}
bool IsPromptOpen() const {
return !confirm_callback_.is_null() || !dismiss_callback_.is_null();
}
void MakeRequest(SmsReceiver::ReceiveCallback callback) { void MakeRequest(SmsReceiver::ReceiveCallback callback) {
service_remote_->Receive(std::move(callback)); service_remote_->Receive(std::move(callback));
...@@ -124,13 +107,12 @@ class Service { ...@@ -124,13 +107,12 @@ class Service {
} }
private: private:
NiceMock<MockSmsWebContentsDelegate> delegate_; StubWebContentsDelegate contents_delegate_;
NiceMock<MockSmsProvider> provider_; NiceMock<MockSmsProvider> provider_;
SmsFetcherImpl fetcher_; SmsFetcherImpl fetcher_;
UserConsentHandler* consent_handler_;
mojo::Remote<blink::mojom::SmsReceiver> service_remote_; mojo::Remote<blink::mojom::SmsReceiver> service_remote_;
std::unique_ptr<SmsService> service_; std::unique_ptr<SmsService> service_;
base::OnceClosure confirm_callback_;
base::OnceClosure dismiss_callback_;
}; };
class SmsServiceTest : public RenderViewHostTestHarness { class SmsServiceTest : public RenderViewHostTestHarness {
...@@ -138,12 +120,6 @@ class SmsServiceTest : public RenderViewHostTestHarness { ...@@ -138,12 +120,6 @@ class SmsServiceTest : public RenderViewHostTestHarness {
SmsServiceTest() = default; SmsServiceTest() = default;
~SmsServiceTest() override = default; ~SmsServiceTest() override = default;
void SetUp() override {
RenderViewHostTestHarness::SetUp();
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kWebOtpBackend, switches::kWebOtpBackendSmsVerification);
}
void ExpectDestroyedReasonCount(SmsReceiverDestroyedReason bucket, void ExpectDestroyedReasonCount(SmsReceiverDestroyedReason bucket,
int32_t count) { int32_t count) {
histogram_tester_.ExpectBucketCount("Blink.Sms.Receive.DestroyedReason", histogram_tester_.ExpectBucketCount("Blink.Sms.Receive.DestroyedReason",
...@@ -169,11 +145,9 @@ TEST_F(SmsServiceTest, Basic) { ...@@ -169,11 +145,9 @@ TEST_F(SmsServiceTest, Basic) {
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi"); service.NotifyReceive(GURL(kTestUrl), "hi");
service.ConfirmPrompt();
})); }));
service.MakeRequest(BindLambdaForTesting( service.MakeRequest(BindLambdaForTesting(
...@@ -196,11 +170,8 @@ TEST_F(SmsServiceTest, HandlesMultipleCalls) { ...@@ -196,11 +170,8 @@ TEST_F(SmsServiceTest, HandlesMultipleCalls) {
{ {
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "first"); service.NotifyReceive(GURL(kTestUrl), "first");
service.ConfirmPrompt();
})); }));
service.MakeRequest(BindLambdaForTesting( service.MakeRequest(BindLambdaForTesting(
...@@ -216,11 +187,8 @@ TEST_F(SmsServiceTest, HandlesMultipleCalls) { ...@@ -216,11 +187,8 @@ TEST_F(SmsServiceTest, HandlesMultipleCalls) {
{ {
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "second"); service.NotifyReceive(GURL(kTestUrl), "second");
service.ConfirmPrompt();
})); }));
service.MakeRequest(BindLambdaForTesting( service.MakeRequest(BindLambdaForTesting(
...@@ -244,14 +212,11 @@ TEST_F(SmsServiceTest, IgnoreFromOtherOrigins) { ...@@ -244,14 +212,11 @@ TEST_F(SmsServiceTest, IgnoreFromOtherOrigins) {
base::RunLoop sms_loop; base::RunLoop sms_loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() {
// Delivers an SMS from an unrelated origin first and expect the // Delivers an SMS from an unrelated origin first and expect the
// receiver to ignore it. // receiver to ignore it.
service.NotifyReceive(GURL("http://b.com"), "wrong"); service.NotifyReceive(GURL("http://b.com"), "wrong");
service.NotifyReceive(GURL(kTestUrl), "right"); service.NotifyReceive(GURL(kTestUrl), "right");
service.ConfirmPrompt();
})); }));
service.MakeRequest( service.MakeRequest(
...@@ -278,14 +243,12 @@ TEST_F(SmsServiceTest, ExpectOneReceiveTwo) { ...@@ -278,14 +243,12 @@ TEST_F(SmsServiceTest, ExpectOneReceiveTwo) {
base::RunLoop sms_loop; base::RunLoop sms_loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() {
// Delivers two SMSes for the same origin, even if only one was being // Delivers two SMSes for the same origin, even if only one was being
// expected. // expected.
ASSERT_TRUE(service.fetcher()->HasSubscribers()); ASSERT_TRUE(service.fetcher()->HasSubscribers());
service.NotifyReceive(GURL(kTestUrl), "first"); service.NotifyReceive(GURL(kTestUrl), "first");
service.ConfirmPrompt();
ASSERT_FALSE(service.fetcher()->HasSubscribers()); ASSERT_FALSE(service.fetcher()->HasSubscribers());
service.NotifyReceive(GURL(kTestUrl), "second"); service.NotifyReceive(GURL(kTestUrl), "second");
})); }));
...@@ -316,14 +279,10 @@ TEST_F(SmsServiceTest, AtMostOneSmsRequestPerOrigin) { ...@@ -316,14 +279,10 @@ TEST_F(SmsServiceTest, AtMostOneSmsRequestPerOrigin) {
base::RunLoop sms1_loop, sms2_loop; base::RunLoop sms1_loop, sms2_loop;
// Expect SMS Prompt to be created once.
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve(_)) EXPECT_CALL(*service.provider(), Retrieve(_))
.WillOnce(Return()) .WillOnce(Return())
.WillOnce(Invoke([&service]() { .WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "second"); service.NotifyReceive(GURL(kTestUrl), "second");
service.ConfirmPrompt();
})); }));
service.MakeRequest( service.MakeRequest(
...@@ -354,52 +313,6 @@ TEST_F(SmsServiceTest, AtMostOneSmsRequestPerOrigin) { ...@@ -354,52 +313,6 @@ TEST_F(SmsServiceTest, AtMostOneSmsRequestPerOrigin) {
EXPECT_EQ(SmsStatus::kSuccess, sms_status2); EXPECT_EQ(SmsStatus::kSuccess, sms_status2);
} }
TEST_F(SmsServiceTest, SecondRequestDuringPrompt) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
SmsStatus sms_status1;
Optional<string> response1;
SmsStatus sms_status2;
Optional<string> response2;
base::RunLoop sms_loop;
// Expect SMS Prompt to be created once.
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "second");
}));
// First request.
service.MakeRequest(
BindLambdaForTesting([&sms_status1, &response1, &service](
SmsStatus status, const Optional<string>& otp) {
sms_status1 = status;
response1 = otp;
service.ConfirmPrompt();
}));
// Make second request before confirming prompt.
service.MakeRequest(
BindLambdaForTesting([&sms_status2, &response2, &sms_loop](
SmsStatus status, const Optional<string>& otp) {
sms_status2 = status;
response2 = otp;
sms_loop.Quit();
}));
sms_loop.Run();
EXPECT_EQ(base::nullopt, response1);
EXPECT_EQ(SmsStatus::kCancelled, sms_status1);
EXPECT_EQ("second", response2.value());
EXPECT_EQ(SmsStatus::kSuccess, sms_status2);
}
TEST_F(SmsServiceTest, CleansUp) { TEST_F(SmsServiceTest, CleansUp) {
NavigateAndCommit(GURL(kTestUrl)); NavigateAndCommit(GURL(kTestUrl));
...@@ -440,60 +353,59 @@ TEST_F(SmsServiceTest, CleansUp) { ...@@ -440,60 +353,59 @@ TEST_F(SmsServiceTest, CleansUp) {
ASSERT_FALSE(fetcher.HasSubscribers()); ASSERT_FALSE(fetcher.HasSubscribers());
} }
TEST_F(SmsServiceTest, PromptsDialog) { TEST_F(SmsServiceTest, CancelForNoDelegate) {
NavigateAndCommit(GURL(kTestUrl)); NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents()); NiceMock<MockSmsProvider> provider;
SmsFetcherImpl fetcher(web_contents()->GetBrowserContext(), &provider);
mojo::Remote<blink::mojom::SmsReceiver> service;
SmsService::Create(&fetcher, main_rfh(),
service.BindNewPipeAndPassReceiver());
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh()); service->Receive(base::BindLambdaForTesting(
EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi");
service.ConfirmPrompt();
}));
service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp) { [&loop](SmsStatus status, const Optional<string>& otp) {
EXPECT_EQ("hi", otp.value()); EXPECT_EQ(SmsStatus::kCancelled, status);
EXPECT_EQ(SmsStatus::kSuccess, status); EXPECT_EQ(base::nullopt, otp);
loop.Quit(); loop.Quit();
})); }));
loop.Run(); loop.Run();
ASSERT_FALSE(service.fetcher()->HasSubscribers()); ASSERT_FALSE(fetcher.HasSubscribers());
} }
TEST_F(SmsServiceTest, Cancel) { TEST_F(SmsServiceTest, Abort) {
NavigateAndCommit(GURL(kTestUrl)); NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents()); Service service(web_contents());
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
service.MakeRequest(BindLambdaForTesting( service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp) { [&loop](SmsStatus status, const Optional<string>& otp) {
EXPECT_EQ(SmsStatus::kCancelled, status); EXPECT_EQ(SmsStatus::kAborted, status);
EXPECT_EQ(base::nullopt, otp); EXPECT_EQ(base::nullopt, otp);
loop.Quit(); loop.Quit();
})); }));
EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() { service.AbortRequest();
service.NotifyReceive(GURL(kTestUrl), "hi");
service.DismissPrompt();
}));
loop.Run(); loop.Run();
ASSERT_FALSE(service.fetcher()->HasSubscribers()); ASSERT_FALSE(service.fetcher()->HasSubscribers());
} }
TEST_F(SmsServiceTest, CancelForNoDelegate) { TEST_F(SmsServiceTest, RecordMetricsForNewPage) {
// This test depends on the page being destroyed on navigation.
web_contents()->GetController().GetBackForwardCache().DisableForTesting(
content::BackForwardCache::TEST_ASSUMES_NO_CACHING);
NavigateAndCommit(GURL(kTestUrl)); NavigateAndCommit(GURL(kTestUrl));
NiceMock<MockSmsWebContentsDelegate> delegate;
WebContentsImpl* web_contents_impl =
reinterpret_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetDelegate(&delegate);
NiceMock<MockSmsProvider> provider; NiceMock<MockSmsProvider> provider;
SmsFetcherImpl fetcher(web_contents()->GetBrowserContext(), &provider); SmsFetcherImpl fetcher(web_contents()->GetBrowserContext(), &provider);
...@@ -501,49 +413,179 @@ TEST_F(SmsServiceTest, CancelForNoDelegate) { ...@@ -501,49 +413,179 @@ TEST_F(SmsServiceTest, CancelForNoDelegate) {
SmsService::Create(&fetcher, main_rfh(), SmsService::Create(&fetcher, main_rfh(),
service.BindNewPipeAndPassReceiver()); service.BindNewPipeAndPassReceiver());
base::RunLoop loop; base::RunLoop navigate;
EXPECT_CALL(provider, Retrieve(_)).WillOnce(Invoke([&navigate]() {
navigate.Quit();
}));
base::RunLoop reload;
service->Receive(base::BindLambdaForTesting( service->Receive(base::BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp) { [&reload](SmsStatus status, const Optional<string>& otp) {
EXPECT_EQ(SmsStatus::kCancelled, status); EXPECT_EQ(SmsStatus::kTimeout, status);
EXPECT_EQ(base::nullopt, otp); EXPECT_EQ(base::nullopt, otp);
loop.Quit(); reload.Quit();
})); }));
loop.Run(); navigate.Run();
ASSERT_FALSE(fetcher.HasSubscribers()); // Simulates the user navigating to a new page.
NavigateAndCommit(GURL("https://www.example.com"));
reload.Run();
ExpectDestroyedReasonCount(SmsReceiverDestroyedReason::kNavigateNewPage, 1);
} }
TEST_F(SmsServiceTest, Abort) { TEST_F(SmsServiceTest, RecordMetricsForSamePage) {
NavigateAndCommit(GURL(kTestUrl)); NavigateAndCommit(GURL(kTestUrl));
NiceMock<MockSmsWebContentsDelegate> delegate;
WebContentsImpl* web_contents_impl =
reinterpret_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetDelegate(&delegate);
Service service(web_contents()); NiceMock<MockSmsProvider> provider;
SmsFetcherImpl fetcher(web_contents()->GetBrowserContext(), &provider);
mojo::Remote<blink::mojom::SmsReceiver> service;
SmsService::Create(&fetcher, main_rfh(),
service.BindNewPipeAndPassReceiver());
base::RunLoop loop; base::RunLoop navigate;
service.MakeRequest(BindLambdaForTesting( EXPECT_CALL(provider, Retrieve(_)).WillOnce(Invoke([&navigate]() {
[&loop](SmsStatus status, const Optional<string>& otp) { navigate.Quit();
EXPECT_EQ(SmsStatus::kAborted, status); }));
base::RunLoop reload;
service->Receive(base::BindLambdaForTesting(
[&reload](SmsStatus status, const Optional<string>& otp) {
EXPECT_EQ(SmsStatus::kTimeout, status);
EXPECT_EQ(base::nullopt, otp); EXPECT_EQ(base::nullopt, otp);
loop.Quit(); reload.Quit();
})); }));
service.AbortRequest(); navigate.Run();
loop.Run(); // Simulates the user re-navigating to the same page through the omni-box.
NavigateAndCommit(GURL(kTestUrl));
ASSERT_FALSE(service.fetcher()->HasSubscribers()); reload.Run();
ExpectDestroyedReasonCount(SmsReceiverDestroyedReason::kNavigateSamePage, 1);
}
// Following tests exercise parts of sms service logic that depend on user
// prompting. In particular how we handle incoming request while there is an
// active in-flight prompts.
class ServiceWithPrompt : public Service {
public:
explicit ServiceWithPrompt(WebContents* web_contents)
: Service(web_contents,
web_contents->GetMainFrame()->GetLastCommittedOrigin(),
base::WrapUnique(new NiceMock<MockUserConsentHandler>())) {
mock_handler_ =
static_cast<NiceMock<MockUserConsentHandler>*>(consent_handler());
}
void ExpectRequestUserConsent() {
EXPECT_CALL(*mock_handler_, RequestUserConsent(_, _, _, _))
.WillOnce(
Invoke([=](RenderFrameHost*, const Origin& origin,
const std::string&, CompletionCallback on_complete) {
on_complete_callback_ = std::move(on_complete);
}));
EXPECT_CALL(*mock_handler_, is_async()).WillRepeatedly(Return(true));
EXPECT_CALL(*mock_handler_, is_active()).WillRepeatedly(Invoke([=]() {
return !on_complete_callback_.is_null();
}));
}
void ConfirmPrompt() {
if (on_complete_callback_.is_null()) {
FAIL() << "User prompt is not available";
return;
}
std::move(on_complete_callback_).Run(SmsStatus::kSuccess);
on_complete_callback_.Reset();
}
void DismissPrompt() {
if (on_complete_callback_.is_null()) {
FAIL() << "User prompt is not available";
return;
}
std::move(on_complete_callback_).Run(SmsStatus::kCancelled);
on_complete_callback_.Reset();
}
bool IsPromptOpen() const { return !on_complete_callback_.is_null(); }
private:
// The actual consent handler is owned by SmsService but we keep a ptr to
// it so it can be used to set expectations for it. It is safe since the
// sms service lifetime is the same as this object.
NiceMock<MockUserConsentHandler>* mock_handler_;
CompletionCallback on_complete_callback_;
};
TEST_F(SmsServiceTest, SecondRequestDuringPrompt) {
NavigateAndCommit(GURL(kTestUrl));
ServiceWithPrompt service(web_contents());
SmsStatus sms_status1;
Optional<string> response1;
SmsStatus sms_status2;
Optional<string> response2;
base::RunLoop sms_loop;
// Expect SMS Prompt to be created once.
service.ExpectRequestUserConsent();
EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "second");
}));
// First request.
service.MakeRequest(
BindLambdaForTesting([&sms_status1, &response1, &service](
SmsStatus status, const Optional<string>& otp) {
sms_status1 = status;
response1 = otp;
service.ConfirmPrompt();
}));
// Make second request before confirming prompt.
service.MakeRequest(
BindLambdaForTesting([&sms_status2, &response2, &sms_loop](
SmsStatus status, const Optional<string>& otp) {
sms_status2 = status;
response2 = otp;
sms_loop.Quit();
}));
sms_loop.Run();
EXPECT_EQ(base::nullopt, response1);
EXPECT_EQ(SmsStatus::kCancelled, sms_status1);
EXPECT_EQ("second", response2.value());
EXPECT_EQ(SmsStatus::kSuccess, sms_status2);
} }
TEST_F(SmsServiceTest, AbortWhilePrompt) { TEST_F(SmsServiceTest, AbortWhilePrompt) {
NavigateAndCommit(GURL(kTestUrl)); NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents()); ServiceWithPrompt service(web_contents());
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh()); service.ExpectRequestUserConsent();
service.MakeRequest(BindLambdaForTesting( service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp) { [&loop](SmsStatus status, const Optional<string>& otp) {
...@@ -568,12 +610,12 @@ TEST_F(SmsServiceTest, AbortWhilePrompt) { ...@@ -568,12 +610,12 @@ TEST_F(SmsServiceTest, AbortWhilePrompt) {
TEST_F(SmsServiceTest, RequestAfterAbortWhilePrompt) { TEST_F(SmsServiceTest, RequestAfterAbortWhilePrompt) {
NavigateAndCommit(GURL(kTestUrl)); NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents()); ServiceWithPrompt service(web_contents());
{ {
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh()); service.ExpectRequestUserConsent();
service.MakeRequest(BindLambdaForTesting( service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp) { [&loop](SmsStatus status, const Optional<string>& otp) {
...@@ -599,7 +641,7 @@ TEST_F(SmsServiceTest, RequestAfterAbortWhilePrompt) { ...@@ -599,7 +641,7 @@ TEST_F(SmsServiceTest, RequestAfterAbortWhilePrompt) {
{ {
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh()); service.ExpectRequestUserConsent();
service.MakeRequest(BindLambdaForTesting( service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp) { [&loop](SmsStatus status, const Optional<string>& otp) {
...@@ -622,11 +664,11 @@ TEST_F(SmsServiceTest, RequestAfterAbortWhilePrompt) { ...@@ -622,11 +664,11 @@ TEST_F(SmsServiceTest, RequestAfterAbortWhilePrompt) {
TEST_F(SmsServiceTest, SecondRequestWhilePrompt) { TEST_F(SmsServiceTest, SecondRequestWhilePrompt) {
NavigateAndCommit(GURL(kTestUrl)); NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents()); ServiceWithPrompt service(web_contents());
base::RunLoop callback_loop1, callback_loop2, req_loop; base::RunLoop callback_loop1, callback_loop2, req_loop;
service.CreateSmsPrompt(main_rfh()); service.ExpectRequestUserConsent();
service.MakeRequest(BindLambdaForTesting( service.MakeRequest(BindLambdaForTesting(
[&callback_loop1](SmsStatus status, const Optional<string>& otp) { [&callback_loop1](SmsStatus status, const Optional<string>& otp) {
...@@ -666,11 +708,11 @@ TEST_F(SmsServiceTest, SecondRequestWhilePrompt) { ...@@ -666,11 +708,11 @@ TEST_F(SmsServiceTest, SecondRequestWhilePrompt) {
TEST_F(SmsServiceTest, RecordTimeMetricsForContinueOnSuccess) { TEST_F(SmsServiceTest, RecordTimeMetricsForContinueOnSuccess) {
NavigateAndCommit(GURL(kTestUrl)); NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents()); ServiceWithPrompt service(web_contents());
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh()); service.ExpectRequestUserConsent();
EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "ABC"); service.NotifyReceive(GURL(kTestUrl), "ABC");
...@@ -690,12 +732,12 @@ TEST_F(SmsServiceTest, RecordTimeMetricsForContinueOnSuccess) { ...@@ -690,12 +732,12 @@ TEST_F(SmsServiceTest, RecordTimeMetricsForContinueOnSuccess) {
TEST_F(SmsServiceTest, RecordMetricsForCancelOnSuccess) { TEST_F(SmsServiceTest, RecordMetricsForCancelOnSuccess) {
NavigateAndCommit(GURL(kTestUrl)); NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents()); ServiceWithPrompt service(web_contents());
// Histogram will be recorded if the SMS has already arrived. // Histogram will be recorded if the SMS has already arrived.
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh()); service.ExpectRequestUserConsent();
EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve(_)).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi"); service.NotifyReceive(GURL(kTestUrl), "hi");
...@@ -712,85 +754,6 @@ TEST_F(SmsServiceTest, RecordMetricsForCancelOnSuccess) { ...@@ -712,85 +754,6 @@ TEST_F(SmsServiceTest, RecordMetricsForCancelOnSuccess) {
histogram_tester().ExpectTotalCount("Blink.Sms.Receive.TimeSmsReceive", 1); histogram_tester().ExpectTotalCount("Blink.Sms.Receive.TimeSmsReceive", 1);
} }
TEST_F(SmsServiceTest, RecordMetricsForNewPage) {
// This test depends on the page being destroyed on navigation.
web_contents()->GetController().GetBackForwardCache().DisableForTesting(
content::BackForwardCache::TEST_ASSUMES_NO_CACHING);
NavigateAndCommit(GURL(kTestUrl));
NiceMock<MockSmsWebContentsDelegate> delegate;
WebContentsImpl* web_contents_impl =
reinterpret_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetDelegate(&delegate);
NiceMock<MockSmsProvider> provider;
SmsFetcherImpl fetcher(web_contents()->GetBrowserContext(), &provider);
mojo::Remote<blink::mojom::SmsReceiver> service;
SmsService::Create(&fetcher, main_rfh(),
service.BindNewPipeAndPassReceiver());
base::RunLoop navigate;
EXPECT_CALL(provider, Retrieve(_)).WillOnce(Invoke([&navigate]() {
navigate.Quit();
}));
base::RunLoop reload;
service->Receive(base::BindLambdaForTesting(
[&reload](SmsStatus status, const Optional<string>& otp) {
EXPECT_EQ(SmsStatus::kTimeout, status);
EXPECT_EQ(base::nullopt, otp);
reload.Quit();
}));
navigate.Run();
// Simulates the user navigating to a new page.
NavigateAndCommit(GURL("https://www.example.com"));
reload.Run();
ExpectDestroyedReasonCount(SmsReceiverDestroyedReason::kNavigateNewPage, 1);
}
TEST_F(SmsServiceTest, RecordMetricsForSamePage) {
NavigateAndCommit(GURL(kTestUrl));
NiceMock<MockSmsWebContentsDelegate> delegate;
WebContentsImpl* web_contents_impl =
reinterpret_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetDelegate(&delegate);
NiceMock<MockSmsProvider> provider;
SmsFetcherImpl fetcher(web_contents()->GetBrowserContext(), &provider);
mojo::Remote<blink::mojom::SmsReceiver> service;
SmsService::Create(&fetcher, main_rfh(),
service.BindNewPipeAndPassReceiver());
base::RunLoop navigate;
EXPECT_CALL(provider, Retrieve(_)).WillOnce(Invoke([&navigate]() {
navigate.Quit();
}));
base::RunLoop reload;
service->Receive(base::BindLambdaForTesting(
[&reload](SmsStatus status, const Optional<string>& otp) {
EXPECT_EQ(SmsStatus::kTimeout, status);
EXPECT_EQ(base::nullopt, otp);
reload.Quit();
}));
navigate.Run();
// Simulates the user re-navigating to the same page through the omni-box.
NavigateAndCommit(GURL(kTestUrl));
reload.Run();
ExpectDestroyedReasonCount(SmsReceiverDestroyedReason::kNavigateSamePage, 1);
}
TEST_F(SmsServiceTest, RecordMetricsForExistingPage) { TEST_F(SmsServiceTest, RecordMetricsForExistingPage) {
// This test depends on the page being destroyed on navigation. // This test depends on the page being destroyed on navigation.
web_contents()->GetController().GetBackForwardCache().DisableForTesting( web_contents()->GetController().GetBackForwardCache().DisableForTesting(
......
// Copyright 2020 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 "content/browser/sms/test/mock_user_consent_handler.h"
namespace content {
MockUserConsentHandler::MockUserConsentHandler() = default;
MockUserConsentHandler::~MockUserConsentHandler() = default;
} // namespace content
\ No newline at end of file
// Copyright 2020 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.
#ifndef CONTENT_BROWSER_SMS_TEST_MOCK_USER_CONSENT_HANDLER_H_
#define CONTENT_BROWSER_SMS_TEST_MOCK_USER_CONSENT_HANDLER_H_
#include "base/macros.h"
#include "content/browser/sms/user_consent_handler.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace content {
class MockUserConsentHandler : public UserConsentHandler {
public:
MockUserConsentHandler();
~MockUserConsentHandler();
MOCK_METHOD(void,
RequestUserConsent,
(RenderFrameHost * frame_host,
const url::Origin& origin,
const std::string& one_time_code,
CompletionCallback on_complete),
(override));
MOCK_METHOD(bool, is_active, (), (const, override));
MOCK_METHOD(bool, is_async, (), (const, override));
};
} // namespace content
#endif // CONTENT_BROWSER_SMS_TEST_MOCK_USER_CONSENT_HANDLER_H_
\ No newline at end of file
// Copyright 2020 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 "content/browser/sms/user_consent_handler.h"
#include "base/callback.h"
#include "content/browser/sms/sms_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
using blink::mojom::SmsStatus;
namespace content {
NoopUserConsentHandler::~NoopUserConsentHandler() = default;
void NoopUserConsentHandler::RequestUserConsent(
RenderFrameHost* frame_host,
const url::Origin& origin,
const std::string& one_time_code,
CompletionCallback on_complete) {
std::move(on_complete).Run(SmsStatus::kSuccess);
}
bool NoopUserConsentHandler::is_active() const {
return false;
}
bool NoopUserConsentHandler::is_async() const {
return false;
}
PromptBasedUserConsentHandler::PromptBasedUserConsentHandler() = default;
PromptBasedUserConsentHandler::~PromptBasedUserConsentHandler() = default;
void PromptBasedUserConsentHandler::RequestUserConsent(
RenderFrameHost* frame_host,
const url::Origin& origin,
const std::string& one_time_code,
CompletionCallback on_complete) {
WebContents* web_contents =
content::WebContents::FromRenderFrameHost(frame_host);
if (!web_contents->GetDelegate()) {
std::move(on_complete).Run(SmsStatus::kCancelled);
return;
}
on_complete_ = std::move(on_complete);
is_prompt_open_ = true;
web_contents->GetDelegate()->CreateSmsPrompt(
frame_host, origin, one_time_code,
base::BindOnce(&PromptBasedUserConsentHandler::OnConfirm,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&PromptBasedUserConsentHandler::OnCancel,
weak_ptr_factory_.GetWeakPtr()));
}
bool PromptBasedUserConsentHandler::is_active() const {
return is_prompt_open_;
}
bool PromptBasedUserConsentHandler::is_async() const {
return true;
}
void PromptBasedUserConsentHandler::OnConfirm() {
is_prompt_open_ = false;
std::move(on_complete_).Run(SmsStatus::kSuccess);
}
void PromptBasedUserConsentHandler::OnCancel() {
is_prompt_open_ = false;
std::move(on_complete_).Run(SmsStatus::kCancelled);
}
} // namespace content
\ No newline at end of file
// Copyright 2020 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.
#ifndef CONTENT_BROWSER_SMS_USER_CONSENT_HANDLER_H_
#define CONTENT_BROWSER_SMS_USER_CONSENT_HANDLER_H_
#include "base/callback_forward.h"
#include "content/common/content_export.h"
#include "third_party/blink/public/mojom/sms/sms_receiver.mojom-shared.h"
#include "url/origin.h"
namespace content {
class RenderFrameHost;
using CompletionCallback = base::OnceCallback<void(blink::mojom::SmsStatus)>;
class CONTENT_EXPORT UserConsentHandler {
public:
virtual ~UserConsentHandler() = default;
// Ask for the user consent. Once the process is complete it invokes
// |on_complete| callback with the appropriate status.
virtual void RequestUserConsent(RenderFrameHost* frame_host,
const url::Origin& origin,
const std::string& one_time_code,
CompletionCallback on_complete) = 0;
// Returns true if it is still processing an inflight request.
// Note that this always returns false for not asynchronous handlers.
virtual bool is_active() const = 0;
// Returns true if this handler processes request asynchronously.
virtual bool is_async() const = 0;
};
class CONTENT_EXPORT NoopUserConsentHandler : public UserConsentHandler {
public:
~NoopUserConsentHandler() override;
void RequestUserConsent(RenderFrameHost* frame_host,
const url::Origin& origin,
const std::string& one_time_code,
CompletionCallback on_complete) override;
bool is_active() const override;
bool is_async() const override;
};
class CONTENT_EXPORT PromptBasedUserConsentHandler : public UserConsentHandler {
public:
PromptBasedUserConsentHandler();
~PromptBasedUserConsentHandler() override;
void RequestUserConsent(RenderFrameHost* frame_host,
const url::Origin& origin,
const std::string& one_time_code,
CompletionCallback on_complete) override;
bool is_active() const override;
bool is_async() const override;
void OnConfirm();
void OnCancel();
private:
bool is_prompt_open_{false};
CompletionCallback on_complete_;
base::WeakPtrFactory<PromptBasedUserConsentHandler> weak_ptr_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_SMS_USER_CONSENT_HANDLER_H_
\ No newline at end of file
// Copyright 2020 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 "content/browser/sms/user_consent_handler.h"
#include "base/callback.h"
#include "base/test/bind_test_util.h"
#include "content/browser/sms/test/mock_sms_web_contents_delegate.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using std::string;
using ::testing::_;
using ::testing::ByMove;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::StrictMock;
using url::Origin;
namespace content {
namespace {
using blink::mojom::SmsStatus;
const char kTestUrl[] = "https://testing.test";
class PromptBasedUserConsentHandlerTest : public RenderViewHostTestHarness {
public:
void SetUp() override {
RenderViewHostTestHarness::SetUp();
WebContentsImpl* web_contents_impl =
reinterpret_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetDelegate(&delegate_);
}
void ExpectCreateSmsPrompt(RenderFrameHost* rfh,
const url::Origin& origin,
const std::string& one_time_code) {
EXPECT_CALL(delegate_, CreateSmsPrompt(rfh, origin, one_time_code, _, _))
.WillOnce(Invoke([=](RenderFrameHost*, const Origin& origin,
const std::string&, base::OnceClosure on_confirm,
base::OnceClosure on_cancel) {
confirm_callback_ = std::move(on_confirm);
dismiss_callback_ = std::move(on_cancel);
}));
}
void ExpectNoSmsPrompt() {
EXPECT_CALL(delegate_, CreateSmsPrompt(_, _, _, _, _)).Times(0);
}
void ConfirmPrompt() {
if (!confirm_callback_.is_null()) {
std::move(confirm_callback_).Run();
dismiss_callback_.Reset();
return;
}
FAIL() << "SmsInfobar not available";
}
void DismissPrompt() {
if (dismiss_callback_.is_null()) {
FAIL() << "SmsInfobar not available";
return;
}
std::move(dismiss_callback_).Run();
confirm_callback_.Reset();
}
private:
NiceMock<MockSmsWebContentsDelegate> delegate_;
base::OnceClosure confirm_callback_;
base::OnceClosure dismiss_callback_;
};
TEST_F(PromptBasedUserConsentHandlerTest, PromptsUser) {
NavigateAndCommit(GURL(kTestUrl));
const url::Origin& origin =
web_contents()->GetMainFrame()->GetLastCommittedOrigin();
base::RunLoop loop;
ExpectCreateSmsPrompt(main_rfh(), origin, "12345");
CompletionCallback callback;
PromptBasedUserConsentHandler consent_handler;
consent_handler.RequestUserConsent(main_rfh(), origin, "12345",
std::move(callback));
}
TEST_F(PromptBasedUserConsentHandlerTest, ConfirmInvokedCallback) {
NavigateAndCommit(GURL(kTestUrl));
const url::Origin& origin =
web_contents()->GetMainFrame()->GetLastCommittedOrigin();
ExpectCreateSmsPrompt(main_rfh(), origin, "12345");
PromptBasedUserConsentHandler consent_handler;
EXPECT_FALSE(consent_handler.is_active());
bool succeed;
auto callback = base::BindLambdaForTesting(
[&](SmsStatus status) { succeed = (status == SmsStatus::kSuccess); });
consent_handler.RequestUserConsent(main_rfh(), origin, "12345",
std::move(callback));
EXPECT_TRUE(consent_handler.is_active());
ConfirmPrompt();
EXPECT_FALSE(consent_handler.is_active());
EXPECT_TRUE(succeed);
}
TEST_F(PromptBasedUserConsentHandlerTest, CancelingInvokedCallback) {
NavigateAndCommit(GURL(kTestUrl));
const url::Origin& origin =
web_contents()->GetMainFrame()->GetLastCommittedOrigin();
ExpectCreateSmsPrompt(main_rfh(), origin, "12345");
PromptBasedUserConsentHandler consent_handler;
EXPECT_FALSE(consent_handler.is_active());
bool cancelled;
auto callback = base::BindLambdaForTesting(
[&](SmsStatus status) { cancelled = (status == SmsStatus::kCancelled); });
consent_handler.RequestUserConsent(main_rfh(), origin, "12345",
std::move(callback));
EXPECT_TRUE(consent_handler.is_active());
DismissPrompt();
EXPECT_FALSE(consent_handler.is_active());
EXPECT_TRUE(cancelled);
}
TEST_F(PromptBasedUserConsentHandlerTest, CancelsWhenNoDelegate) {
NavigateAndCommit(GURL(kTestUrl));
const url::Origin& origin =
web_contents()->GetMainFrame()->GetLastCommittedOrigin();
WebContentsImpl* web_contents_impl =
reinterpret_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetDelegate(nullptr);
ExpectNoSmsPrompt();
PromptBasedUserConsentHandler consent_handler;
bool cancelled;
auto callback = base::BindLambdaForTesting(
[&](SmsStatus status) { cancelled = (status == SmsStatus::kCancelled); });
consent_handler.RequestUserConsent(main_rfh(), origin, "12345",
std::move(callback));
EXPECT_TRUE(cancelled);
}
} // namespace
} // namespace content
\ No newline at end of file
...@@ -83,6 +83,8 @@ jumbo_static_library("test_support") { ...@@ -83,6 +83,8 @@ jumbo_static_library("test_support") {
"../browser/sms/test/mock_sms_provider.h", "../browser/sms/test/mock_sms_provider.h",
"../browser/sms/test/mock_sms_web_contents_delegate.cc", "../browser/sms/test/mock_sms_web_contents_delegate.cc",
"../browser/sms/test/mock_sms_web_contents_delegate.h", "../browser/sms/test/mock_sms_web_contents_delegate.h",
"../browser/sms/test/mock_user_consent_handler.cc",
"../browser/sms/test/mock_user_consent_handler.h",
"../browser/web_package/mock_signed_exchange_handler.cc", "../browser/web_package/mock_signed_exchange_handler.cc",
"../browser/web_package/mock_signed_exchange_handler.h", "../browser/web_package/mock_signed_exchange_handler.h",
"../browser/web_package/mock_web_bundle_reader_factory.cc", "../browser/web_package/mock_web_bundle_reader_factory.cc",
...@@ -1921,6 +1923,7 @@ test("content_unittests") { ...@@ -1921,6 +1923,7 @@ test("content_unittests") {
"../browser/sms/sms_fetcher_impl_unittest.cc", "../browser/sms/sms_fetcher_impl_unittest.cc",
"../browser/sms/sms_parser_unittest.cc", "../browser/sms/sms_parser_unittest.cc",
"../browser/sms/sms_service_unittest.cc", "../browser/sms/sms_service_unittest.cc",
"../browser/sms/user_consent_handler_unittest.cc",
"../browser/speech/tts_controller_unittest.cc", "../browser/speech/tts_controller_unittest.cc",
"../browser/startup_task_runner_unittest.cc", "../browser/startup_task_runner_unittest.cc",
"../browser/storage_partition_impl_map_unittest.cc", "../browser/storage_partition_impl_map_unittest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment