blob: e7d9bf50c28ca3099c60028344f7f06894ff60f0 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/hats/hats_notification_controller.h"
#include "base/run_loop.h"
#include "base/strings/string_split.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/hats/hats_config.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/notifications/notification_handler.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/network/network_handler_test_helper.h"
#include "chromeos/ash/components/network/network_state.h"
#include "components/image_fetcher/core/request_metadata.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "net/base/url_util.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/message_center/public/cpp/notification.h"
#include "url/gurl.h"
using testing::_;
using testing::AtLeast;
using testing::Invoke;
using testing::NiceMock;
using testing::Return;
using testing::SaveArg;
namespace {
const char kLocaleKey[] = "locale";
const char kBrowserKey[] = "browser";
const char kPlatformKey[] = "platform";
const char kFirmwareKey[] = "firmware";
const char kPsdKey1[] = "psd1";
const char kPsdKey2[] = "psd2";
const char kPsdValue1[] = "psdValue1";
const char kPsdValue2[] = "psdValue2 =%^*$#&";
const char kLocaleValue1[] = "locale1";
const char kBrowserValue1[] = "browser1";
bool GetQueryParameter(const std::string& query,
const std::string& key,
std::string* value) {
// Name and scheme actually don't matter, but are required to get a valid URL
// for parsing.
GURL query_url("http://localhost?" + query);
return net::GetValueForKeyInQuery(query_url, key, value);
}
} // namespace
namespace ash {
class HatsNotificationControllerTest : public BrowserWithTestWindowTest {
public:
HatsNotificationControllerTest() {}
HatsNotificationControllerTest(const HatsNotificationControllerTest&) =
delete;
HatsNotificationControllerTest& operator=(
const HatsNotificationControllerTest&) = delete;
~HatsNotificationControllerTest() override {}
void SetUp() override {
BrowserWithTestWindowTest::SetUp();
display_service_ =
std::make_unique<NotificationDisplayServiceTester>(profile());
helper_ = std::make_unique<NetworkHandlerTestHelper>();
scoped_feature_list_.InitAndEnableFeature(kHatsGeneralSurvey.feature);
}
void TearDown() override {
helper_.reset();
display_service_.reset();
// Notifications may be deleted async.
base::RunLoop().RunUntilIdle();
BrowserWithTestWindowTest::TearDown();
}
scoped_refptr<HatsNotificationController> InstantiateHatsController() {
// The initialization will fail since the function IsNewDevice() will return
// true.
auto hats_notification_controller =
base::MakeRefCounted<HatsNotificationController>(profile(),
kHatsGeneralSurvey);
// HatsController::IsNewDevice() is run on a blocking thread.
content::RunAllTasksUntilIdle();
return hats_notification_controller;
}
void SendPortalState(scoped_refptr<HatsNotificationController>& controller,
NetworkState::PortalState portal_state) {
NetworkState network_state("");
controller->PortalStateChanged(&network_state, portal_state);
base::RunLoop().RunUntilIdle();
}
std::unique_ptr<NotificationDisplayServiceTester> display_service_;
std::unique_ptr<NetworkHandlerTestHelper> helper_;
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(HatsNotificationControllerTest, GetFormattedSiteContext) {
base::flat_map<std::string, std::string> product_specific_data = {
{kPsdKey1, kPsdValue1},
{kPsdKey2, kPsdValue2},
{kBrowserKey, kBrowserValue1}};
std::string context = HatsNotificationController::GetFormattedSiteContext(
kLocaleValue1, product_specific_data);
std::string value;
EXPECT_TRUE(GetQueryParameter(context, kLocaleKey, &value));
EXPECT_EQ(kLocaleValue1, value);
EXPECT_TRUE(GetQueryParameter(context, kBrowserKey, &value));
EXPECT_NE(kBrowserValue1, value);
EXPECT_TRUE(GetQueryParameter(context, kPlatformKey, &value));
EXPECT_TRUE(GetQueryParameter(context, kFirmwareKey, &value));
EXPECT_TRUE(GetQueryParameter(context, kPsdKey1, &value));
EXPECT_EQ(kPsdValue1, value);
EXPECT_TRUE(GetQueryParameter(context, kPsdKey2, &value));
EXPECT_EQ(kPsdValue2, value);
// Confirm that the values are properly url escaped.
EXPECT_NE(std::string::npos, context.find("psdValue2%20%3D%25%5E*%24%23%26"));
}
TEST_F(HatsNotificationControllerTest, NewDevice_ShouldNotShowNotification) {
int64_t initial_timestamp = base::Time::Now().ToInternalValue();
PrefService* pref_service = profile()->GetPrefs();
pref_service->SetInt64(prefs::kHatsLastInteractionTimestamp,
initial_timestamp);
auto hats_notification_controller = InstantiateHatsController();
hats_notification_controller->Initialize(true);
int64_t current_timestamp =
pref_service->GetInt64(prefs::kHatsLastInteractionTimestamp);
// When the device is new, the controller does not begin initialization and
// simply updates the timestamp to Time::Now().
ASSERT_TRUE(base::Time::FromInternalValue(current_timestamp) >
base::Time::FromInternalValue(initial_timestamp));
EXPECT_FALSE(display_service_->GetNotification(
HatsNotificationController::kNotificationId));
}
TEST_F(HatsNotificationControllerTest, OldDevice_ShouldShowNotification) {
auto hats_notification_controller = InstantiateHatsController();
hats_notification_controller->Initialize(false);
// Ensure notification was launched to confirm initialization.
EXPECT_TRUE(display_service_->GetNotification(
HatsNotificationController::kNotificationId));
// Simulate dismissing notification by the user to clean up the notification.
display_service_->RemoveNotification(
NotificationHandler::Type::TRANSIENT,
HatsNotificationController::kNotificationId, /*by_user=*/true);
}
TEST_F(HatsNotificationControllerTest, NoInternet_DoNotShowNotification) {
auto hats_notification_controller = InstantiateHatsController();
SendPortalState(hats_notification_controller,
NetworkState::PortalState::kUnknown);
EXPECT_FALSE(display_service_->GetNotification(
HatsNotificationController::kNotificationId));
SendPortalState(hats_notification_controller,
NetworkState::PortalState::kNoInternet);
EXPECT_FALSE(display_service_->GetNotification(
HatsNotificationController::kNotificationId));
SendPortalState(hats_notification_controller,
NetworkState::PortalState::kPortal);
EXPECT_FALSE(display_service_->GetNotification(
HatsNotificationController::kNotificationId));
SendPortalState(hats_notification_controller,
NetworkState::PortalState::kProxyAuthRequired);
EXPECT_FALSE(display_service_->GetNotification(
HatsNotificationController::kNotificationId));
}
TEST_F(HatsNotificationControllerTest, DismissNotification_ShouldUpdatePref) {
int64_t now_timestamp = base::Time::Now().ToInternalValue();
PrefService* pref_service = profile()->GetPrefs();
pref_service->SetInt64(prefs::kHatsLastInteractionTimestamp, now_timestamp);
auto hats_notification_controller = InstantiateHatsController();
// Simulate closing notification via user interaction.
hats_notification_controller->Close(true);
int64_t new_timestamp =
pref_service->GetInt64(prefs::kHatsLastInteractionTimestamp);
// The flag should be updated to a new timestamp.
EXPECT_TRUE(base::Time::FromInternalValue(new_timestamp) >
base::Time::FromInternalValue(now_timestamp));
}
TEST_F(HatsNotificationControllerTest,
Disconnected_RemoveNotification_Connected_AddNotification) {
auto hats_notification_controller = InstantiateHatsController();
hats_notification_controller->Initialize(false);
// Notification is launched.
EXPECT_TRUE(display_service_->GetNotification(
HatsNotificationController::kNotificationId));
// Notification is removed when Internet connection is lost.
SendPortalState(hats_notification_controller,
NetworkState::PortalState::kNoInternet);
EXPECT_FALSE(display_service_->GetNotification(
HatsNotificationController::kNotificationId));
// Notification is launched again when Internet connection is regained.
SendPortalState(hats_notification_controller,
NetworkState::PortalState::kOnline);
EXPECT_TRUE(display_service_->GetNotification(
HatsNotificationController::kNotificationId));
// Simulate dismissing notification by the user to clean up the notification.
display_service_->RemoveNotification(
NotificationHandler::Type::TRANSIENT,
HatsNotificationController::kNotificationId, /*by_user=*/true);
}
} // namespace ash