chromium/ios/web_view/internal/passwords/cwv_reuse_check_service_unittest.mm

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <string_view>

#import "base/run_loop.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/gmock_callback_support.h"
#import "base/test/ios/wait_util.h"
#import "base/test/mock_callback.h"
#import "base/test/task_environment.h"
#import "components/affiliations/core/browser/affiliation_utils.h"
#import "components/affiliations/core/browser/fake_affiliation_service.h"
#import "components/affiliations/core/browser/mock_affiliation_service.h"
#import "components/password_manager/core/browser/password_form.h"
#import "components/password_manager/core/browser/password_ui_utils.h"
#import "ios/web_view/internal/passwords/cwv_password_internal.h"
#import "ios/web_view/internal/passwords/cwv_reuse_check_service_internal.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

namespace ios_web_view {

namespace {

using affiliations::Facet;
using affiliations::FacetURI;
using base::test::ios::kWaitForActionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using password_manager::PasswordForm;

PasswordForm GenerateSavedPassword(
    std::string_view signon_realm,
    std::u16string_view username,
    std::u16string_view password,
    std::u16string_view username_element = u"",
    PasswordForm::Store store = PasswordForm::Store::kProfileStore) {
  PasswordForm form;
  form.signon_realm = std::string(signon_realm);
  form.url = GURL(signon_realm);
  form.username_value = std::u16string(username);
  form.password_value = std::u16string(password);
  form.username_element = std::u16string(username_element);
  form.in_store = store;
  return form;
}

constexpr int kDelay = 2000;

class CWVReuseCheckServiceTest : public PlatformTest {
 public:
  CWVReuseCheckServiceTest() { RunUntilIdle(); }

  void AdvanceClock(base::TimeDelta time) { task_env_.AdvanceClock(time); }

  void RunUntilIdle() { task_env_.RunUntilIdle(); }

 private:
  base::test::TaskEnvironment task_env_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
};

}  // namespace

TEST_F(CWVReuseCheckServiceTest, CheckPasswords) {
  affiliations::MockAffiliationService mock_affiliation_service;
  affiliations::FakeAffiliationService affiliation_service_;
  CWVReuseCheckService* reuseCheckService = [[CWVReuseCheckService alloc]
      initWithAffiliationService:&affiliation_service_];

  PasswordForm form1 =
      GenerateSavedPassword("https://google.com/", u"user", u"pass");
  PasswordForm form2 =
      GenerateSavedPassword("https://yahoo.com/", u"user", u"pass");

  CWVPassword* password1 = [[CWVPassword alloc] initWithPasswordForm:form1];
  CWVPassword* password2 = [[CWVPassword alloc] initWithPasswordForm:form2];

  // Setup affiliated groups.
  std::vector<affiliations::GroupedFacets> grouped_facets(2);
  Facet facet1(FacetURI::FromPotentiallyInvalidSpec(form1.signon_realm));
  grouped_facets[0].facets.push_back(facet1);
  Facet facet2(FacetURI::FromPotentiallyInvalidSpec(form2.signon_realm));
  grouped_facets[1].facets.push_back(facet2);
  EXPECT_CALL(mock_affiliation_service, GetGroupingInfo)
      .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<1>(grouped_facets));

  __block bool reuseCheckCompletionCalled = NO;
  id reuseCompletion = ^(NSSet<NSString*>* reusedPasswords) {
    ASSERT_EQ(1U, reusedPasswords.count);
    reuseCheckCompletionCalled = YES;
  };
  [reuseCheckService checkReusedPasswords:@[ password1, password2 ]
                        completionHandler:reuseCompletion];
  AdvanceClock(base::Milliseconds(kDelay));
  RunUntilIdle();

  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool {
    RunUntilIdle();
    return reuseCheckCompletionCalled;
  }));
}

}  // namespace ios_web_view