| // Copyright 2019 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 "chrome/browser/chromeos/supervision/onboarding_controller_impl.h" |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "ash/public/cpp/ash_pref_names.h" |
| #include "base/macros.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/chromeos/supervision/mojom/onboarding_controller.mojom.h" |
| #include "chrome/browser/chromeos/supervision/onboarding_constants.h" |
| #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "chromeos/constants/chromeos_switches.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "net/base/net_errors.h" |
| #include "services/identity/public/cpp/identity_manager.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chromeos { |
| namespace supervision { |
| namespace { |
| |
| const char kTestAccountId[] = "test-account-id"; |
| const char kFakeAccessToken[] = "fake-access-token"; |
| |
| } // namespace |
| |
| class FakeOnboardingWebviewHost : mojom::OnboardingWebviewHost { |
| public: |
| explicit FakeOnboardingWebviewHost( |
| mojom::OnboardingWebviewHostRequest request) |
| : binding_(this, std::move(request)) {} |
| |
| void ExpectPresentations(const std::vector<mojom::OnboardingPresentation>& |
| expected_presentations) { |
| ASSERT_EQ(presentations_.size(), expected_presentations.size()); |
| |
| for (std::size_t i = 0; i < expected_presentations.size(); ++i) { |
| const auto& expected_presentation = expected_presentations[i]; |
| const auto& presentation = presentations_[i]; |
| EXPECT_TRUE(presentation->Equals(expected_presentation)); |
| |
| // To yield more readable errors, we also test each property individually. |
| EXPECT_EQ(expected_presentation.state, presentation->state); |
| EXPECT_EQ(expected_presentation.can_show_next_page, |
| presentation->can_show_next_page); |
| EXPECT_EQ(expected_presentation.can_show_previous_page, |
| presentation->can_show_previous_page); |
| EXPECT_EQ(expected_presentation.can_skip_flow, |
| presentation->can_skip_flow); |
| } |
| } |
| |
| // Flushes the internal mojo message pipe. |
| void Flush() { binding_.FlushForTesting(); } |
| |
| void Reset() { |
| page_loaded_ = base::nullopt; |
| presentations_.clear(); |
| } |
| |
| bool exited_flow() const { return exited_flow_; } |
| |
| const base::Optional<mojom::OnboardingPage>& page_loaded() { |
| return page_loaded_; |
| } |
| |
| void set_load_page_result(const mojom::OnboardingLoadPageResult& result) { |
| load_page_result_ = result; |
| } |
| |
| private: |
| void SetPresentation(mojom::OnboardingPresentationPtr presentation) override { |
| presentations_.push_back(std::move(presentation)); |
| } |
| |
| void LoadPage(mojom::OnboardingPagePtr page, |
| LoadPageCallback callback) override { |
| ASSERT_FALSE(exited_flow_); |
| |
| page_loaded_ = *page; |
| |
| std::move(callback).Run(load_page_result_.Clone()); |
| } |
| |
| void ExitFlow() override { |
| ASSERT_FALSE(exited_flow_); |
| |
| exited_flow_ = true; |
| } |
| |
| mojo::Binding<mojom::OnboardingWebviewHost> binding_; |
| |
| mojom::OnboardingLoadPageResult load_page_result_{ |
| net::Error::OK, kDeviceOnboardingExperimentName}; |
| |
| bool exited_flow_ = false; |
| |
| std::vector<mojom::OnboardingPresentationPtr> presentations_; |
| base::Optional<mojom::OnboardingPage> page_loaded_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FakeOnboardingWebviewHost); |
| }; |
| |
| class OnboardingControllerBaseTest : public testing::Test { |
| protected: |
| void SetUp() override { |
| profile_ = IdentityTestEnvironmentProfileAdaptor:: |
| CreateProfileForIdentityTestEnvironment(); |
| identity_test_env_adaptor_ = |
| std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile_.get()); |
| |
| identity_test_env()->MakeAccountAvailable(kTestAccountId); |
| identity_test_env()->SetPrimaryAccount(kTestAccountId); |
| |
| controller_impl_ = std::make_unique<OnboardingControllerImpl>(profile()); |
| |
| controller_impl_->BindRequest(mojo::MakeRequest(&controller_)); |
| } |
| |
| void BindWebviewHost() { |
| mojom::OnboardingWebviewHostPtr webview_host_proxy; |
| webview_host_ = std::make_unique<FakeOnboardingWebviewHost>( |
| mojo::MakeRequest(&webview_host_proxy)); |
| |
| controller_->BindWebviewHost(std::move(webview_host_proxy)); |
| } |
| |
| void SetUpPageLoad(const mojom::OnboardingLoadPageResult& result) { |
| webview_host()->set_load_page_result(result); |
| identity_test_env() |
| ->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| kFakeAccessToken, |
| base::Time::Now() + base::TimeDelta::FromHours(1)); |
| |
| Flush(); |
| } |
| |
| void BindHostAndSetupFailedAuth() { |
| BindWebviewHost(); |
| |
| identity_test_env() |
| ->WaitForAccessTokenRequestIfNecessaryAndRespondWithError( |
| GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED)); |
| |
| Flush(); |
| } |
| |
| void BindHostAndReturnLoadPageSuccess() { |
| mojom::OnboardingLoadPageResult result; |
| result.net_error = net::Error::OK; |
| result.custom_header_value = kDeviceOnboardingExperimentName; |
| |
| BindWebviewHost(); |
| SetUpPageLoad(result); |
| } |
| |
| void BindHostAndReturnLoadPageError() { |
| mojom::OnboardingLoadPageResult result; |
| result.net_error = net::Error::ERR_FAILED; |
| result.custom_header_value = kDeviceOnboardingExperimentName; |
| |
| BindWebviewHost(); |
| SetUpPageLoad(result); |
| } |
| |
| void BindHostAndReturnMissingCustomHeader() { |
| mojom::OnboardingLoadPageResult result; |
| result.net_error = net::Error::OK; |
| result.custom_header_value = base::nullopt; |
| |
| BindWebviewHost(); |
| SetUpPageLoad(result); |
| } |
| |
| void BindHostAndReturnWrongCustomHeader() { |
| mojom::OnboardingLoadPageResult result; |
| result.net_error = net::Error::OK; |
| result.custom_header_value = "clearly-wrong-header-value"; |
| |
| BindWebviewHost(); |
| SetUpPageLoad(result); |
| } |
| |
| void HandleAction(mojom::OnboardingAction action) { |
| controller_->HandleAction(action); |
| Flush(); |
| } |
| |
| void Flush() { |
| controller_.FlushForTesting(); |
| webview_host()->Flush(); |
| } |
| |
| Profile* profile() { return profile_.get(); } |
| |
| identity::IdentityTestEnvironment* identity_test_env() { |
| return identity_test_env_adaptor_->identity_test_env(); |
| } |
| |
| FakeOnboardingWebviewHost* webview_host() { return webview_host_.get(); } |
| |
| private: |
| content::TestBrowserThreadBundle test_browser_thread_bundle_; |
| std::unique_ptr<TestingProfile> profile_; |
| std::unique_ptr<IdentityTestEnvironmentProfileAdaptor> |
| identity_test_env_adaptor_; |
| std::unique_ptr<OnboardingControllerImpl> controller_impl_; |
| mojom::OnboardingControllerPtr controller_; |
| std::unique_ptr<FakeOnboardingWebviewHost> webview_host_; |
| }; |
| |
| class OnboardingControllerFlowDisabledTest |
| : public OnboardingControllerBaseTest { |
| protected: |
| void SetUp() override { |
| scoped_feature_list_.InitAndDisableFeature( |
| features::kSupervisionOnboardingScreens); |
| |
| OnboardingControllerBaseTest::SetUp(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST_F(OnboardingControllerFlowDisabledTest, ExitFlowWhenFlowIsDisabled) { |
| BindHostAndReturnLoadPageSuccess(); |
| |
| EXPECT_TRUE(webview_host()->exited_flow()); |
| } |
| |
| TEST_F(OnboardingControllerFlowDisabledTest, |
| PresentOnlyLoadingStateWhenFlowIsDisabled) { |
| BindHostAndReturnLoadPageSuccess(); |
| |
| mojom::OnboardingPresentation loading; |
| loading.state = mojom::OnboardingPresentationState::kLoading; |
| |
| webview_host()->ExpectPresentations({loading}); |
| } |
| |
| TEST_F(OnboardingControllerFlowDisabledTest, |
| SetEligibleForKioskNextWhenFlowIsDisabled) { |
| BindHostAndReturnLoadPageSuccess(); |
| |
| EXPECT_TRUE( |
| profile()->GetPrefs()->GetBoolean(ash::prefs::kKioskNextShellEligible)); |
| } |
| |
| class OnboardingControllerTest : public OnboardingControllerBaseTest { |
| protected: |
| void SetUp() override { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kSupervisionOnboardingScreens); |
| |
| OnboardingControllerBaseTest::SetUp(); |
| } |
| |
| // Navigates to the details page by first loading the Start page, then faking |
| // a user pressing the "Next" button. It can optionally fake a failed page |
| // load for the Details page. |
| // Note: To make tests simpler we also reset the data from the |
| // FakeWebviewHost, this way we can write tests that only focus on the page |
| // being handled. |
| void NavigateToDetailsPage(bool return_error = false) { |
| BindHostAndReturnLoadPageSuccess(); |
| webview_host()->Reset(); |
| |
| HandleAction(mojom::OnboardingAction::kShowNextPage); |
| |
| mojom::OnboardingLoadPageResult result; |
| result.net_error = return_error ? net::Error::ERR_FAILED : net::Error::OK; |
| result.custom_header_value = kDeviceOnboardingExperimentName; |
| SetUpPageLoad(result); |
| } |
| |
| // Navigates to the "All Set!" page by first navigating to the Details page, |
| // then faking a user pressing the "Next" button. It can optionally fake a |
| // failed page load for the "All Set!" page. |
| // Note: To make tests simpler we also reset the data from the |
| // FakeWebviewHost, this way we can write tests that only focus on the page |
| // being handled. |
| void NavigateToAllSetPage(bool return_error = false) { |
| NavigateToDetailsPage(); |
| webview_host()->Reset(); |
| |
| HandleAction(mojom::OnboardingAction::kShowNextPage); |
| mojom::OnboardingLoadPageResult result; |
| result.net_error = return_error ? net::Error::ERR_FAILED : net::Error::OK; |
| result.custom_header_value = kDeviceOnboardingExperimentName; |
| SetUpPageLoad(result); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST_F(OnboardingControllerTest, RequestWebviewHostToLoadStartPageCorrectly) { |
| BindHostAndReturnLoadPageSuccess(); |
| |
| ASSERT_TRUE(webview_host()->page_loaded().has_value()); |
| EXPECT_EQ(webview_host()->page_loaded()->url, |
| GURL("https://families.google.com/kids/deviceonboarding/start")); |
| EXPECT_EQ(webview_host()->page_loaded()->access_token, kFakeAccessToken); |
| EXPECT_EQ(webview_host()->page_loaded()->custom_header_name, |
| kExperimentHeaderName); |
| EXPECT_EQ(webview_host()->page_loaded()->allowed_urls_prefix, |
| "https://families.google.com/"); |
| } |
| |
| TEST_F(OnboardingControllerTest, OverridePageUrlsByCommandLine) { |
| base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( |
| chromeos::switches::kSupervisionOnboardingUrlPrefix, |
| "https://example.com/"); |
| |
| BindHostAndReturnLoadPageSuccess(); |
| |
| ASSERT_TRUE(webview_host()->page_loaded().has_value()); |
| EXPECT_EQ(webview_host()->page_loaded()->url, |
| GURL("https://example.com/kids/deviceonboarding/start")); |
| EXPECT_EQ(webview_host()->page_loaded()->access_token, kFakeAccessToken); |
| EXPECT_EQ(webview_host()->page_loaded()->custom_header_name, |
| kExperimentHeaderName); |
| EXPECT_EQ(webview_host()->page_loaded()->allowed_urls_prefix, |
| "https://example.com/"); |
| } |
| |
| TEST_F(OnboardingControllerTest, StayInFlowWhenLoadSucceeds) { |
| BindHostAndReturnLoadPageSuccess(); |
| |
| EXPECT_FALSE(webview_host()->exited_flow()); |
| } |
| |
| TEST_F(OnboardingControllerTest, PresentReadyStateWhenLoadSucceeds) { |
| BindHostAndReturnLoadPageSuccess(); |
| |
| mojom::OnboardingPresentation loading; |
| loading.state = mojom::OnboardingPresentationState::kLoading; |
| |
| mojom::OnboardingPresentation ready; |
| ready.state = mojom::OnboardingPresentationState::kReady; |
| ready.can_show_next_page = true; |
| ready.can_skip_flow = true; |
| |
| webview_host()->ExpectPresentations({loading, ready}); |
| } |
| |
| TEST_F(OnboardingControllerTest, SetEligibleForKioskNextWhenLoadSucceeds) { |
| BindHostAndReturnLoadPageSuccess(); |
| |
| EXPECT_TRUE( |
| profile()->GetPrefs()->GetBoolean(ash::prefs::kKioskNextShellEligible)); |
| } |
| |
| TEST_F(OnboardingControllerTest, ExitFlowOnAuthError) { |
| BindHostAndSetupFailedAuth(); |
| |
| EXPECT_FALSE(webview_host()->page_loaded().has_value()); |
| EXPECT_TRUE(webview_host()->exited_flow()); |
| } |
| |
| TEST_F(OnboardingControllerTest, PresentOnlyLoadingStateOnAuthError) { |
| BindHostAndSetupFailedAuth(); |
| |
| mojom::OnboardingPresentation loading; |
| loading.state = mojom::OnboardingPresentationState::kLoading; |
| webview_host()->ExpectPresentations({loading}); |
| } |
| |
| TEST_F(OnboardingControllerTest, SetNotEligibleForKioskNextOnAuthError) { |
| BindHostAndSetupFailedAuth(); |
| |
| EXPECT_FALSE( |
| profile()->GetPrefs()->GetBoolean(ash::prefs::kKioskNextShellEligible)); |
| } |
| |
| TEST_F(OnboardingControllerTest, ExitFlowOnLoadPageError) { |
| BindHostAndReturnLoadPageError(); |
| |
| EXPECT_TRUE(webview_host()->exited_flow()); |
| } |
| |
| TEST_F(OnboardingControllerTest, PresentOnlyLoadingStateOnLoadPageError) { |
| BindHostAndReturnLoadPageError(); |
| |
| mojom::OnboardingPresentation loading; |
| loading.state = mojom::OnboardingPresentationState::kLoading; |
| webview_host()->ExpectPresentations({loading}); |
| } |
| |
| TEST_F(OnboardingControllerTest, SetNotEligibleForKioskNextOnLoadPageError) { |
| BindHostAndReturnLoadPageError(); |
| |
| EXPECT_FALSE( |
| profile()->GetPrefs()->GetBoolean(ash::prefs::kKioskNextShellEligible)); |
| } |
| |
| TEST_F(OnboardingControllerTest, ExitFlowWhenHeaderValueIsMissing) { |
| BindHostAndReturnMissingCustomHeader(); |
| |
| EXPECT_TRUE(webview_host()->exited_flow()); |
| } |
| |
| TEST_F(OnboardingControllerTest, |
| PresentOnlyLoadingStateWhenHeaderValueIsMissing) { |
| BindHostAndReturnMissingCustomHeader(); |
| |
| mojom::OnboardingPresentation loading; |
| loading.state = mojom::OnboardingPresentationState::kLoading; |
| |
| webview_host()->ExpectPresentations({loading}); |
| } |
| |
| TEST_F(OnboardingControllerTest, |
| SetNotEligibleForKioskNextWhenHeaderValueIsMissing) { |
| BindHostAndReturnMissingCustomHeader(); |
| |
| EXPECT_FALSE( |
| profile()->GetPrefs()->GetBoolean(ash::prefs::kKioskNextShellEligible)); |
| } |
| |
| TEST_F(OnboardingControllerTest, ExitFlowWhenHeaderValueIsWrong) { |
| BindHostAndReturnWrongCustomHeader(); |
| |
| EXPECT_TRUE(webview_host()->exited_flow()); |
| } |
| |
| TEST_F(OnboardingControllerTest, |
| PresentOnlyLoadingStateWhenHeaderValueIsWrong) { |
| BindHostAndReturnWrongCustomHeader(); |
| |
| mojom::OnboardingPresentation loading; |
| loading.state = mojom::OnboardingPresentationState::kLoading; |
| |
| webview_host()->ExpectPresentations({loading}); |
| } |
| |
| TEST_F(OnboardingControllerTest, |
| SetNotEligibleForKioskNextWhenHeaderValueIsWrong) { |
| BindHostAndReturnWrongCustomHeader(); |
| |
| EXPECT_FALSE( |
| profile()->GetPrefs()->GetBoolean(ash::prefs::kKioskNextShellEligible)); |
| } |
| |
| TEST_F(OnboardingControllerTest, StayInFlowWhenNavigatingToDetailsPage) { |
| NavigateToDetailsPage(); |
| |
| EXPECT_FALSE(webview_host()->exited_flow()); |
| } |
| |
| TEST_F(OnboardingControllerTest, DetailsPageExitsFlowOnFailedPageLoad) { |
| NavigateToDetailsPage(/*return_error=*/true); |
| |
| EXPECT_TRUE(webview_host()->exited_flow()); |
| } |
| |
| TEST_F(OnboardingControllerTest, DetailsPageIsPresentedCorrectly) { |
| NavigateToDetailsPage(); |
| |
| mojom::OnboardingPresentation loading; |
| loading.state = mojom::OnboardingPresentationState::kLoading; |
| |
| mojom::OnboardingPresentation ready; |
| ready.state = mojom::OnboardingPresentationState::kReady; |
| ready.can_show_next_page = true; |
| ready.can_show_previous_page = true; |
| |
| webview_host()->ExpectPresentations({loading, ready}); |
| } |
| |
| TEST_F(OnboardingControllerTest, DetailsPageIsLoadedCorrectly) { |
| NavigateToDetailsPage(); |
| |
| ASSERT_TRUE(webview_host()->page_loaded().has_value()); |
| EXPECT_EQ(webview_host()->page_loaded()->url, |
| GURL("https://families.google.com/kids/deviceonboarding/details")); |
| EXPECT_EQ(webview_host()->page_loaded()->access_token, kFakeAccessToken); |
| EXPECT_EQ(webview_host()->page_loaded()->custom_header_name, base::nullopt); |
| EXPECT_EQ(webview_host()->page_loaded()->allowed_urls_prefix, |
| "https://families.google.com/"); |
| } |
| |
| TEST_F(OnboardingControllerTest, StayInFlowWhenNavigatingToAllSetPage) { |
| NavigateToAllSetPage(); |
| |
| EXPECT_FALSE(webview_host()->exited_flow()); |
| } |
| |
| TEST_F(OnboardingControllerTest, AllSetPageExitsFlowOnFailedPageLoad) { |
| NavigateToAllSetPage(/*return_error=*/true); |
| |
| EXPECT_TRUE(webview_host()->exited_flow()); |
| } |
| |
| TEST_F(OnboardingControllerTest, AllSetPageIsPresentedCorrectly) { |
| NavigateToAllSetPage(); |
| |
| mojom::OnboardingPresentation loading; |
| loading.state = mojom::OnboardingPresentationState::kLoading; |
| |
| mojom::OnboardingPresentation ready; |
| ready.state = mojom::OnboardingPresentationState::kReady; |
| ready.can_show_next_page = true; |
| ready.can_show_previous_page = true; |
| |
| webview_host()->ExpectPresentations({loading, ready}); |
| } |
| |
| TEST_F(OnboardingControllerTest, AllSetPageIsLoadedCorrectly) { |
| NavigateToAllSetPage(); |
| |
| ASSERT_TRUE(webview_host()->page_loaded().has_value()); |
| EXPECT_EQ(webview_host()->page_loaded()->url, |
| GURL("https://families.google.com/kids/deviceonboarding/allset")); |
| EXPECT_EQ(webview_host()->page_loaded()->access_token, kFakeAccessToken); |
| EXPECT_EQ(webview_host()->page_loaded()->custom_header_name, base::nullopt); |
| EXPECT_EQ(webview_host()->page_loaded()->allowed_urls_prefix, |
| "https://families.google.com/"); |
| } |
| |
| TEST_F(OnboardingControllerTest, AllSetPageCanFinishFlow) { |
| NavigateToAllSetPage(); |
| |
| EXPECT_FALSE(webview_host()->exited_flow()); |
| HandleAction(mojom::OnboardingAction::kShowNextPage); |
| EXPECT_TRUE(webview_host()->exited_flow()); |
| } |
| |
| TEST_F(OnboardingControllerTest, AllSetPageEnablesKioskNext) { |
| NavigateToAllSetPage(); |
| |
| EXPECT_FALSE( |
| profile()->GetPrefs()->GetBoolean(ash::prefs::kKioskNextShellEnabled)); |
| HandleAction(mojom::OnboardingAction::kShowNextPage); |
| |
| EXPECT_TRUE( |
| profile()->GetPrefs()->GetBoolean(ash::prefs::kKioskNextShellEnabled)); |
| } |
| |
| } // namespace supervision |
| } // namespace chromeos |