// 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.
#include "chrome/browser/password_manager/android/credential_leak_controller_android.h"
#include <memory>
#include <string>
#include "base/android/build_info.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "chrome/browser/password_manager/android/mock_password_checkup_launcher_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/browser_task_environment.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
constexpr ukm::SourceId kTestSourceId = 0x1234;
using password_manager::CreateLeakType;
using password_manager::IsReused;
using password_manager::IsSaved;
using password_manager::IsSyncing;
using password_manager::metrics_util::LeakDialogDismissalReason;
using password_manager::metrics_util::LeakDialogMetricsRecorder;
using password_manager::metrics_util::LeakDialogType;
using testing::_;
using testing::StrictMock;
using UkmEntry = ukm::builders::PasswordManager_LeakWarningDialog;
namespace {
constexpr char kOrigin[] = "https://example.com";
constexpr char16_t kUsername[] = u"test_username";
constexpr char kTestAccount[] = "[email protected]";
// The On*Dialog() methods used by the tests below all invoke `delete this;`,
// thus there is no memory leak here.
CredentialLeakControllerAndroid* MakeController(
Profile* profile,
std::unique_ptr<MockPasswordCheckupLauncherHelper> check_launcher,
IsSaved is_saved,
IsReused is_reused,
IsSyncing is_syncing,
std::string account_email) {
password_manager::CredentialLeakType leak_type =
CreateLeakType(is_saved, is_reused, is_syncing);
auto recorder = std::make_unique<LeakDialogMetricsRecorder>(
kTestSourceId, password_manager::GetLeakDialogType(leak_type));
// Set sampling rate to 100% to avoid flakiness.
recorder->SetSamplingRateForTesting(1.0);
return new CredentialLeakControllerAndroid(
leak_type, GURL(kOrigin), kUsername, profile,
/*window_android=*/nullptr, std::move(check_launcher),
std::move(recorder), account_email);
}
void CheckUkmMetricsExpectations(
ukm::TestAutoSetUkmRecorder& recorder,
LeakDialogType expected_dialog_type,
LeakDialogDismissalReason expected_dismissal_reason) {
const auto& entries = recorder.GetEntriesByName(UkmEntry::kEntryName);
EXPECT_EQ(1u, entries.size());
for (const ukm::mojom::UkmEntry* entry : entries) {
EXPECT_EQ(kTestSourceId, entry->source_id);
recorder.ExpectEntryMetric(entry,
UkmEntry::kPasswordLeakDetectionDialogTypeName,
static_cast<int64_t>(expected_dialog_type));
recorder.ExpectEntryMetric(
entry, UkmEntry::kPasswordLeakDetectionDialogDismissalReasonName,
static_cast<int64_t>(expected_dismissal_reason));
}
}
} // namespace
class CredentialLeakControllerAndroidTest : public testing::Test {
public:
TestingProfile* profile() { return testing_profile_.get(); }
private:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfile> testing_profile_ =
TestingProfile::Builder().Build();
};
TEST_F(CredentialLeakControllerAndroidTest, ClickedCancel) {
if (base::android::BuildInfo::GetInstance()->is_automotive()) {
GTEST_SKIP() << "This test should not run on automotive.";
}
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
MakeController(profile(),
std::make_unique<MockPasswordCheckupLauncherHelper>(),
IsSaved(false), IsReused(true), IsSyncing(true), kTestAccount)
->OnCancelDialog();
histogram_tester.ExpectUniqueSample(
"PasswordManager.LeakDetection.DialogDismissalReason",
LeakDialogDismissalReason::kClickedClose, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.LeakDetection.DialogDismissalReason.CheckupAndChange",
LeakDialogDismissalReason::kClickedClose, 1);
CheckUkmMetricsExpectations(test_ukm_recorder,
LeakDialogType::kCheckupAndChange,
LeakDialogDismissalReason::kClickedClose);
}
TEST_F(CredentialLeakControllerAndroidTest, ClickedOkDoesNotLaunchCheckup) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
std::unique_ptr<StrictMock<MockPasswordCheckupLauncherHelper>> mock_launcher =
std::make_unique<StrictMock<MockPasswordCheckupLauncherHelper>>();
MakeController(profile(), std::move(mock_launcher), IsSaved(false),
IsReused(false), IsSyncing(false), /* account_email = */ "")
->OnAcceptDialog();
histogram_tester.ExpectUniqueSample(
"PasswordManager.LeakDetection.DialogDismissalReason",
LeakDialogDismissalReason::kClickedOk, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.LeakDetection.DialogDismissalReason.Change",
LeakDialogDismissalReason::kClickedOk, 1);
CheckUkmMetricsExpectations(test_ukm_recorder, LeakDialogType::kChange,
LeakDialogDismissalReason::kClickedOk);
}
TEST_F(CredentialLeakControllerAndroidTest,
ClickedCheckPasswordsLaunchesCheckup) {
if (base::android::BuildInfo::GetInstance()->is_automotive()) {
GTEST_SKIP() << "This test should not run on automotive.";
}
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
std::unique_ptr<MockPasswordCheckupLauncherHelper> mock_launcher =
std::make_unique<MockPasswordCheckupLauncherHelper>();
EXPECT_CALL(*mock_launcher,
LaunchCheckupOnDevice(
_, profile(), _,
password_manager::PasswordCheckReferrerAndroid::kLeakDialog,
testing::Eq(kTestAccount)));
MakeController(profile(), std::move(mock_launcher), IsSaved(true),
IsReused(true), IsSyncing(true), kTestAccount)
->OnAcceptDialog();
histogram_tester.ExpectUniqueSample(
"PasswordManager.LeakDetection.DialogDismissalReason",
LeakDialogDismissalReason::kClickedCheckPasswords, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.LeakDetection.DialogDismissalReason.Checkup",
LeakDialogDismissalReason::kClickedCheckPasswords, 1);
CheckUkmMetricsExpectations(
test_ukm_recorder, LeakDialogType::kCheckup,
LeakDialogDismissalReason::kClickedCheckPasswords);
}
TEST_F(CredentialLeakControllerAndroidTest,
AutomotiveShowsOkButtonForSavedReusedSynced) {
if (!base::android::BuildInfo::GetInstance()->is_automotive()) {
GTEST_SKIP() << "This test should only run on automotive.";
}
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
std::unique_ptr<MockPasswordCheckupLauncherHelper> mock_launcher =
std::make_unique<MockPasswordCheckupLauncherHelper>();
EXPECT_CALL(*mock_launcher,
LaunchCheckupOnDevice(
_, profile(), _,
password_manager::PasswordCheckReferrerAndroid::kLeakDialog,
testing::Eq(kTestAccount)))
.Times(0);
MakeController(profile(), std::move(mock_launcher), IsSaved(true),
IsReused(true), IsSyncing(true), kTestAccount)
->OnAcceptDialog();
histogram_tester.ExpectUniqueSample(
"PasswordManager.LeakDetection.DialogDismissalReason",
LeakDialogDismissalReason::kClickedOk, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.LeakDetection.DialogDismissalReason.Change",
LeakDialogDismissalReason::kClickedOk, 1);
CheckUkmMetricsExpectations(test_ukm_recorder, LeakDialogType::kChange,
LeakDialogDismissalReason::kClickedOk);
}
TEST_F(CredentialLeakControllerAndroidTest, NoDirectInteraction) {
base::HistogramTester histogram_tester;
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
MakeController(profile(),
std::make_unique<MockPasswordCheckupLauncherHelper>(),
IsSaved(false), IsReused(false), IsSyncing(false),
/* account_email_ = */ "")
->OnCloseDialog();
histogram_tester.ExpectUniqueSample(
"PasswordManager.LeakDetection.DialogDismissalReason",
LeakDialogDismissalReason::kNoDirectInteraction, 1);
histogram_tester.ExpectUniqueSample(
"PasswordManager.LeakDetection.DialogDismissalReason.Change",
LeakDialogDismissalReason::kNoDirectInteraction, 1);
CheckUkmMetricsExpectations(test_ukm_recorder, LeakDialogType::kChange,
LeakDialogDismissalReason::kNoDirectInteraction);
}