chromium/ios/chrome/browser/ui/settings/downloads/downloads_settings_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/downloads/downloads_settings_coordinator.h"

#import "base/apple/foundation_util.h"
#import "base/test/task_environment.h"
#import "components/signin/public/identity_manager/identity_test_environment.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/show_signin_command.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service_factory.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/browser/signin/model/fake_system_identity_manager.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/signin/model/identity_test_environment_browser_state_adaptor.h"
#import "ios/chrome/browser/signin/model/system_identity.h"
#import "ios/chrome/browser/ui/authentication/signin/signin_completion_info.h"
#import "ios/chrome/browser/ui/authentication/signin/signin_constants.h"
#import "ios/chrome/browser/ui/settings/downloads/downloads_settings_table_view_controller.h"
#import "ios/chrome/browser/ui/settings/downloads/downloads_settings_table_view_controller_action_delegate.h"
#import "ios/chrome/browser/ui/settings/downloads/downloads_settings_table_view_controller_presentation_delegate.h"
#import "ios/chrome/browser/ui/settings/downloads/save_to_photos/save_to_photos_settings_account_selection_view_controller.h"
#import "ios/chrome/browser/ui/settings/downloads/save_to_photos/save_to_photos_settings_account_selection_view_controller_action_delegate.h"
#import "ios/chrome/browser/ui/settings/downloads/save_to_photos/save_to_photos_settings_account_selection_view_controller_presentation_delegate.h"
#import "ios/chrome/browser/ui/settings/downloads/save_to_photos/save_to_photos_settings_mediator.h"
#import "ios/chrome/test/fakes/fake_ui_navigation_controller.h"
#import "ios/chrome/test/fakes/fake_ui_view_controller.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "third_party/ocmock/gtest_support.h"

#pragma mark - DownloadsSettingsCoordinatorTest

// Unit tests for the DownloadsSettingsCoordinator.
class DownloadsSettingsCoordinatorTest : public PlatformTest {
 protected:
  void SetUp() final {
    PlatformTest::SetUp();

    TestChromeBrowserState::Builder builder;
    builder.AddTestingFactory(
        IdentityManagerFactory::GetInstance(),
        base::BindRepeating(IdentityTestEnvironmentBrowserStateAdaptor::
                                BuildIdentityManagerForTests));
    browser_state_ = std::move(builder).Build();
    browser_ = std::make_unique<TestBrowser>(browser_state_.get());

    base_navigation_controller_ = [[FakeUINavigationController alloc] init];

    mock_application_commands_handler_ =
        OCMStrictProtocolMock(@protocol(ApplicationCommands));
    [browser_->GetCommandDispatcher()
        startDispatchingToTarget:mock_application_commands_handler_
                     forProtocol:@protocol(ApplicationCommands)];
    mock_settings_commands_handler_ =
        OCMStrictProtocolMock(@protocol(SettingsCommands));
    [browser_->GetCommandDispatcher()
        startDispatchingToTarget:mock_settings_commands_handler_
                     forProtocol:@protocol(SettingsCommands)];
  }

  void TearDown() final {
    [mock_save_to_photos_settings_mediator_ stopMocking];
    [mock_downloads_settings_table_view_controller_ stopMocking];
    [mock_save_to_photos_settings_account_selection_view_controller_
        stopMocking];
    PlatformTest::TearDown();
  }

  // Creates a DownloadsSettingsCoordinator to test.
  DownloadsSettingsCoordinator* CreateDownloadsSettingsCoordinator() {
    return [[DownloadsSettingsCoordinator alloc]
        initWithBaseNavigationController:base_navigation_controller_
                                 browser:browser_.get()];
  }

  // Creates and returns a mock SaveToPhotosSettingsMediator instance. If
  // `stubbed` is true, then some methods are also stubbed to return the
  // expected values.
  void CreateMockSaveToPhotosSettingsMediatorStubbed(bool stubbed) {
    mock_save_to_photos_settings_mediator_ =
        OCMClassMock([SaveToPhotosSettingsMediator class]);
    if (stubbed) {
      OCMStub([mock_save_to_photos_settings_mediator_ alloc])
          .andReturn(mock_save_to_photos_settings_mediator_);
      OCMStub([mock_save_to_photos_settings_mediator_
                  initWithAccountManagerService:
                      ChromeAccountManagerServiceFactory::GetForBrowserState(
                          browser_state_.get())
                                    prefService:browser_state_->GetPrefs()
                                identityManager:IdentityManagerFactory::
                                                    GetForBrowserState(
                                                        browser_state_.get())])
          .andReturn(mock_save_to_photos_settings_mediator_);
    }
  }

  // Creates and returns a mock DownloadsSettingsTableViewController instance.
  // If `stubbed` is true, then some methods are also stubbed to return the
  // expected values.
  void CreateMockDownloadsSettingsTableViewControllerStubbed(bool stubbed) {
    mock_downloads_settings_table_view_controller_ =
        OCMClassMock([DownloadsSettingsTableViewController class]);
    if (stubbed) {
      OCMStub(
          [mock_downloads_settings_table_view_controller_ navigationController])
          .andReturn(base_navigation_controller_);
      OCMStub([mock_downloads_settings_table_view_controller_ alloc])
          .andReturn(mock_downloads_settings_table_view_controller_);
    }
  }

  // Creates and returns a mock
  // SaveToPhotosSettingsAccountSelectionViewController instance. If `stubbed`
  // is true, then some methods are also stubbed to return the expected values.
  void CreateMockSaveToPhotosSettingsAccountSelectionViewControllerStubbed(
      bool stubbed) {
    mock_save_to_photos_settings_account_selection_view_controller_ =
        OCMClassMock(
            [SaveToPhotosSettingsAccountSelectionViewController class]);
    if (stubbed) {
      OCMStub([mock_save_to_photos_settings_account_selection_view_controller_
                  navigationController])
          .andReturn(base_navigation_controller_);
      OCMStub([mock_save_to_photos_settings_account_selection_view_controller_
                  alloc])
          .andReturn(
              mock_save_to_photos_settings_account_selection_view_controller_);
    }
  }

  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  std::unique_ptr<TestBrowser> browser_;
  FakeUINavigationController* base_navigation_controller_;

  id mock_save_to_photos_settings_mediator_;
  id mock_downloads_settings_table_view_controller_;
  id mock_save_to_photos_settings_account_selection_view_controller_;
  id mock_application_commands_handler_;
  id mock_settings_commands_handler_;
};

#pragma mark - Tests

// Tests that starting the coordinator creates the expected mediator and the
// Downloads settings view controller, initializes them and pushes the VC on the
// navigation controller.
TEST_F(DownloadsSettingsCoordinatorTest,
       StartsAndStopsCreateAndDestroyMediatorsAndViewController) {
  DownloadsSettingsCoordinator* coordinator =
      CreateDownloadsSettingsCoordinator();

  // Mock mediators and expect that they are created with the expected services.
  CreateMockSaveToPhotosSettingsMediatorStubbed(false);
  OCMExpect([mock_save_to_photos_settings_mediator_ alloc])
      .andReturn(mock_save_to_photos_settings_mediator_);
  OCMExpect([mock_save_to_photos_settings_mediator_
                initWithAccountManagerService:
                    ChromeAccountManagerServiceFactory::GetForBrowserState(
                        browser_state_.get())
                                  prefService:browser_state_->GetPrefs()
                              identityManager:IdentityManagerFactory::
                                                  GetForBrowserState(
                                                      browser_state_.get())])
      .andReturn(mock_save_to_photos_settings_mediator_);

  // Mock VC and expect it is created and initialized as expected.
  CreateMockDownloadsSettingsTableViewControllerStubbed(false);
  OCMExpect([mock_downloads_settings_table_view_controller_ alloc])
      .andReturn(mock_downloads_settings_table_view_controller_);
  OCMStub([mock_downloads_settings_table_view_controller_ navigationController])
      .andReturn(base_navigation_controller_);
  ASSERT_TRUE([coordinator
      conformsToProtocol:
          @protocol(DownloadsSettingsTableViewControllerActionDelegate)]);
  OCMExpect([mock_downloads_settings_table_view_controller_
      setActionDelegate:
          static_cast<id<DownloadsSettingsTableViewControllerActionDelegate>>(
              coordinator)]);
  ASSERT_TRUE([coordinator
      conformsToProtocol:
          @protocol(DownloadsSettingsTableViewControllerPresentationDelegate)]);
  OCMExpect([mock_downloads_settings_table_view_controller_
      setPresentationDelegate:
          static_cast<
              id<DownloadsSettingsTableViewControllerPresentationDelegate>>(
              coordinator)]);

  // Expect that the mediators and view controllers are connected.
  OCMExpect([mock_save_to_photos_settings_mediator_
      setAccountConfirmationConsumer:
          mock_downloads_settings_table_view_controller_]);
  OCMExpect([mock_downloads_settings_table_view_controller_
      setSaveToPhotosSettingsMutator:mock_save_to_photos_settings_mediator_]);

  // Start the coordinator and verify that the mediator and view controller were
  // properly created and initialized.
  [coordinator start];
  EXPECT_OCMOCK_VERIFY(mock_save_to_photos_settings_mediator_);
  EXPECT_OCMOCK_VERIFY(mock_downloads_settings_table_view_controller_);
  // Check that the VC is pushed onto the navigation controller.
  EXPECT_EQ(mock_downloads_settings_table_view_controller_,
            base_navigation_controller_.viewControllers.firstObject);

  // Expect that the mediator is disconnect when the coordinator stops.
  OCMExpect([mock_save_to_photos_settings_mediator_ disconnect]);
  [coordinator stop];
  // Check that the VC has been popped from the navigation controller.
  EXPECT_FALSE(base_navigation_controller_.viewControllers.firstObject);
}

// Tests that the coordinator can present the Save to Photos account selection
// view controller through its
// DownloadsSettingsTableViewControllerActionDelegate interface.
TEST_F(DownloadsSettingsCoordinatorTest, CanOpenSaveToPhotosAccountSelection) {
  DownloadsSettingsCoordinator* coordinator =
      CreateDownloadsSettingsCoordinator();

  // Mock the mediators and the Downloads settings VC.
  CreateMockSaveToPhotosSettingsMediatorStubbed(true);
  CreateMockDownloadsSettingsTableViewControllerStubbed(true);
  [coordinator start];

  // Mock the account selection view controller and expect it is created and
  // initialized as expected.
  CreateMockSaveToPhotosSettingsAccountSelectionViewControllerStubbed(false);
  OCMStub([mock_save_to_photos_settings_account_selection_view_controller_
              navigationController])
      .andReturn(base_navigation_controller_);
  OCMExpect(
      [mock_save_to_photos_settings_account_selection_view_controller_ alloc])
      .andReturn(
          mock_save_to_photos_settings_account_selection_view_controller_);
  ASSERT_TRUE([coordinator
      conformsToProtocol:
          @protocol(
              SaveToPhotosSettingsAccountSelectionViewControllerActionDelegate)]);
  OCMExpect([mock_save_to_photos_settings_account_selection_view_controller_
      setActionDelegate:
          static_cast<id<
              SaveToPhotosSettingsAccountSelectionViewControllerActionDelegate>>(
              coordinator)]);
  ASSERT_TRUE([coordinator
      conformsToProtocol:
          @protocol(
              SaveToPhotosSettingsAccountSelectionViewControllerPresentationDelegate)]);
  OCMExpect([mock_save_to_photos_settings_account_selection_view_controller_
      setPresentationDelegate:
          static_cast<id<
              SaveToPhotosSettingsAccountSelectionViewControllerPresentationDelegate>>(
              coordinator)]);
  OCMExpect([mock_save_to_photos_settings_account_selection_view_controller_
      setMutator:mock_save_to_photos_settings_mediator_]);

  // Expect the mediator is given the account selection VC as account selection
  // consumer.
  OCMExpect([mock_save_to_photos_settings_mediator_
      setAccountSelectionConsumer:
          mock_save_to_photos_settings_account_selection_view_controller_]);

  // Call the coordinator through the action delegate protocol and verify the
  // account selection VC was presented.
  ASSERT_TRUE([coordinator
      conformsToProtocol:
          @protocol(DownloadsSettingsTableViewControllerActionDelegate)]);
  [static_cast<id<DownloadsSettingsTableViewControllerActionDelegate>>(
      coordinator)
      downloadsSettingsTableViewControllerOpenSaveToPhotosAccountSelection:
          mock_downloads_settings_table_view_controller_];
  EXPECT_EQ(mock_save_to_photos_settings_account_selection_view_controller_,
            base_navigation_controller_.viewControllers[1]);
  EXPECT_OCMOCK_VERIFY(mock_save_to_photos_settings_mediator_);
  EXPECT_OCMOCK_VERIFY(mock_downloads_settings_table_view_controller_);
  EXPECT_OCMOCK_VERIFY(
      mock_save_to_photos_settings_account_selection_view_controller_);

  // Expect the mediator is disconnected from account selection VC when it has
  // been dismissed.
  OCMExpect(
      [mock_save_to_photos_settings_mediator_ setAccountSelectionConsumer:nil]);
  ASSERT_TRUE([coordinator
      conformsToProtocol:
          @protocol(
              SaveToPhotosSettingsAccountSelectionViewControllerPresentationDelegate)]);
  [static_cast<id<
      SaveToPhotosSettingsAccountSelectionViewControllerPresentationDelegate>>(
      coordinator)
      saveToPhotosSettingsAccountSelectionViewControllerWasRemoved];
  EXPECT_OCMOCK_VERIFY(mock_save_to_photos_settings_mediator_);

  [coordinator stop];
}

// Tests that the coordinator can present the Add account view through the
// account selection VC action delegate implementation.
TEST_F(DownloadsSettingsCoordinatorTest,
       OpensAddAccountAndSelectsAddedIdentity) {
  DownloadsSettingsCoordinator* coordinator =
      CreateDownloadsSettingsCoordinator();

  // Mock the mediators and VCs.
  CreateMockSaveToPhotosSettingsMediatorStubbed(true);
  CreateMockDownloadsSettingsTableViewControllerStubbed(true);
  CreateMockSaveToPhotosSettingsAccountSelectionViewControllerStubbed(true);
  [coordinator start];

  // Expect that a ShowSigninCommand is dispatched to show the Add account view
  // when -saveToPhotosSettingsAccountSelectionViewControllerAddAccount is
  // called.
  __block ShowSigninCommandCompletionCallback show_signin_callback = nil;
  OCMExpect([mock_application_commands_handler_
              showSignin:[OCMArg checkWithBlock:^BOOL(
                                     ShowSigninCommand* command) {
                EXPECT_TRUE(command.callback);
                show_signin_callback = command.callback;
                EXPECT_EQ(AuthenticationOperation::kAddAccount,
                          command.operation);
                EXPECT_FALSE(command.identity);
                EXPECT_EQ(signin_metrics::AccessPoint::
                              ACCESS_POINT_SAVE_TO_PHOTOS_IOS,
                          command.accessPoint);
                EXPECT_EQ(
                    signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO,
                    command.promoAction);
                return YES;
              }]
      baseViewController:base_navigation_controller_]);

  // Call the coordinator through the action delegate protocol and verify the
  // ShowSigninCommand was dispatched.
  ASSERT_TRUE([coordinator
      conformsToProtocol:
          @protocol(
              SaveToPhotosSettingsAccountSelectionViewControllerActionDelegate)]);
  [static_cast<
      id<SaveToPhotosSettingsAccountSelectionViewControllerActionDelegate>>(
      coordinator)
      saveToPhotosSettingsAccountSelectionViewControllerAddAccount];
  EXPECT_OCMOCK_VERIFY(mock_application_commands_handler_);

  // Expect that the selected identity Gaia ID is set to the Gaia ID of the
  // identity that was just added.
  id<SystemIdentity> added_identity = [FakeSystemIdentity fakeIdentity1];
  ASSERT_TRUE(show_signin_callback);
  OCMExpect([mock_save_to_photos_settings_mediator_
      setSelectedIdentityGaiaID:added_identity.gaiaID]);
  show_signin_callback(
      SigninCoordinatorResultSuccess,
      [SigninCompletionInfo signinCompletionInfoWithIdentity:added_identity]);
  EXPECT_OCMOCK_VERIFY(mock_save_to_photos_settings_mediator_);

  [coordinator stop];
}