chromium/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_unittest.mm

// Copyright 2023 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/ui/settings/password/password_details/password_details_coordinator.h"

#import "base/test/bind.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/task_environment.h"
#import "components/password_manager/core/browser/password_manager_test_utils.h"
#import "components/password_manager/core/browser/password_store/test_password_store.h"
#import "components/password_manager/core/browser/ui/affiliated_group.h"
#import "components/password_manager/core/browser/ui/credential_ui_entry.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_profile_password_store_factory.h"
#import "ios/chrome/browser/passwords/model/metrics/ios_password_manager_metrics.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/sync/model/mock_sync_service_utils.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/ui/settings/password/password_details/password_details_handler.h"
#import "ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_metrics.h"
#import "ios/chrome/test/app/mock_reauthentication_module.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

using base::HistogramTester;

namespace {

// Creates an affiliated group with one credential.
password_manager::AffiliatedGroup GetTestAffiliatedGroup() {
  password_manager::PasswordForm form;
  form.url = GURL("https://example.com");
  form.username_value = u"user";
  form.password_value = u"password";
  password_manager::CredentialUIEntry credential(form);
  return password_manager::AffiliatedGroup(
      /*credentials=*/{credential},
      /*branding=*/affiliations::FacetBrandingInfo());
}

// Registers a mock command handler in the dispatcher.
void HandleCommand(Protocol* command_protocol, CommandDispatcher* dispatcher) {
  id mocked_handler = OCMStrictProtocolMock(command_protocol);
  [dispatcher startDispatchingToTarget:mocked_handler
                           forProtocol:command_protocol];
}

// Verifies that a given number of password details visits have been recorded.
void CheckPasswordDetailsVisitMetricsCount(
    int count,
    const HistogramTester& histogram_tester) {
  histogram_tester.ExpectUniqueSample(
      /*name=*/password_manager::kPasswordManagerSurfaceVisitHistogramName,
      /*sample=*/password_manager::PasswordManagerSurface::kPasswordDetails,
      /*count=*/count);
}

}  // namespace

// Test fixture for testing the PasswordDetailsCoordinatorTest class.
class PasswordDetailsCoordinatorTest : public PlatformTest {
 protected:
  PasswordDetailsCoordinatorTest() {
    TestChromeBrowserState::Builder builder;

    builder.AddTestingFactory(
        IOSChromeProfilePasswordStoreFactory::GetInstance(),
        base::BindRepeating(
            &password_manager::BuildPasswordStore<
                web::BrowserState, password_manager::TestPasswordStore>));

    builder.AddTestingFactory(SyncServiceFactory::GetInstance(),
                              base::BindRepeating(&CreateMockSyncService));

    // Create scene state for reauthentication coordinator.
    scene_state_ = [[SceneState alloc] initWithAppState:nil];
    scene_state_.activationLevel = SceneActivationLevelForegroundActive;

    browser_state_ = std::move(builder).Build();
    browser_ =
        std::make_unique<TestBrowser>(browser_state_.get(), scene_state_);

    CommandDispatcher* dispatcher = browser_->GetCommandDispatcher();
    // Mock ApplicationCommands and SettingsCommands
    HandleCommand(@protocol(ApplicationCommands), dispatcher);
    HandleCommand(@protocol(SettingsCommands), dispatcher);

    // Mock SnackbarCommands.
    HandleCommand(@protocol(SnackbarCommands), dispatcher);

    mock_reauth_module_ = [[MockReauthenticationModule alloc] init];
    // Delay auth result so auth doesn't pass right after requested by the
    // coordinator. Needed for verifying behavior when auth is required.
    mock_reauth_module_.shouldReturnSynchronously = NO;
    mock_reauth_module_.expectedResult = ReauthenticationResult::kSuccess;

    UINavigationController* navigation_controller =
        [[UINavigationController alloc] init];
    coordinator_ = [[PasswordDetailsCoordinator alloc]
        initWithBaseNavigationController:navigation_controller
                                 browser:browser_.get()
                         affiliatedGroup:GetTestAffiliatedGroup()
                            reauthModule:mock_reauth_module_
                                 context:DetailsContext::kPasswordSettings];
  }

  ~PasswordDetailsCoordinatorTest() override { [coordinator_ stop]; }

  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  std::unique_ptr<Browser> browser_;
  MockReauthenticationModule* mock_reauth_module_;
  SceneState* scene_state_;
  PasswordDetailsCoordinator* coordinator_;
};

#pragma mark - Tests

// Tests Password Visit metrics are logged only once after opening the surface.
TEST_F(PasswordDetailsCoordinatorTest, VisitMetricsAreLoggedOnlyOnce) {
  HistogramTester histogram_tester;
  CheckPasswordDetailsVisitMetricsCount(0, histogram_tester);

  // Starting the coordinator should record a visit.
  [coordinator_ start];
  CheckPasswordDetailsVisitMetricsCount(1, histogram_tester);

  // Simulate scene transitioning to the background and back to foreground. This
  // should trigger an auth request.
  scene_state_.activationLevel = SceneActivationLevelForegroundInactive;
  scene_state_.activationLevel = SceneActivationLevelBackground;
  scene_state_.activationLevel = SceneActivationLevelForegroundInactive;
  scene_state_.activationLevel = SceneActivationLevelForegroundActive;

  // Simulate successful auth.
  [mock_reauth_module_ returnMockedReauthenticationResult];

  // Validate no new visits were logged.
  CheckPasswordDetailsVisitMetricsCount(1, histogram_tester);
}

// Tests that onShareButtonPressed will result in metrics logged.
TEST_F(PasswordDetailsCoordinatorTest, OnShareButtonPressedMetricsLogged) {
  base::HistogramTester histogram_tester;

  // Call the tested function.
  ASSERT_TRUE(
      [coordinator_ conformsToProtocol:@protocol(PasswordDetailsHandler)]);
  [(id<PasswordDetailsHandler>)coordinator_ onShareButtonPressed];

  histogram_tester.ExpectUniqueSample(
      "PasswordManager.PasswordSharingIOS.UserAction",
      PasswordSharingInteraction::kPasswordDetailsShareButtonClicked, 1);
}