chromium/ios/chrome/credential_provider_extension/ui/credential_list_mediator_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 "base/test/ios/wait_util.h"
#import "ios/chrome/common/credential_provider/archivable_credential.h"
#import "ios/chrome/common/credential_provider/mock_credential_store.h"
#import "ios/chrome/credential_provider_extension/ui/credential_list_mediator+Testing.h"
#import "ios/chrome/credential_provider_extension/ui/feature_flags.h"
#import "ios/chrome/credential_provider_extension/ui/mock_credential_list_consumer.h"
#import "ios/chrome/credential_provider_extension/ui/mock_credential_list_ui_handler.h"
#import "ios/chrome/credential_provider_extension/ui/mock_credential_response_handler.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

namespace {

using base::test::ios::kWaitForFileOperationTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;

constexpr int64_t kJan1st2024 = 1704085200;

NSData* StringToData(std::string str) {
  return [NSData dataWithBytes:str.data() length:str.length()];
}

ArchivableCredential* TestPasskeyCredential() {
  return [[ArchivableCredential alloc]
       initWithFavicon:@"favicon1"
                  gaia:nil
      recordIdentifier:@"recordIdentifier1"
                syncId:StringToData("syncId1")
              username:@"username1"
       userDisplayName:@"userDisplayName1"
                userId:StringToData("userId1")
          credentialId:StringToData("credentialId1")
                  rpId:@"rpId1"
            privateKey:StringToData("privateKey1")
             encrypted:StringToData("encrypted1")
          creationTime:kJan1st2024
          lastUsedTime:kJan1st2024];
}

ArchivableCredential* TestPasskeyCredential2() {
  return [[ArchivableCredential alloc]
       initWithFavicon:@"favicon2"
                  gaia:nil
      recordIdentifier:@"recordIdentifier2"
                syncId:StringToData("syncId2")
              username:@"username2"
       userDisplayName:@"userDisplayName2"
                userId:StringToData("userId2")
          credentialId:StringToData("credentialId2")
                  rpId:@"rpId2"
            privateKey:StringToData("privateKey2")
             encrypted:StringToData("encrypted2")
          creationTime:kJan1st2024 + 1
          lastUsedTime:kJan1st2024 + 1];
}

ArchivableCredential* TestPasswordCredential() {
  return [[ArchivableCredential alloc] initWithFavicon:nil
                                                  gaia:nil
                                              password:@"qwerty123"
                                                  rank:1
                                      recordIdentifier:@"recordIdentifier"
                                     serviceIdentifier:@"http://www.example.com"
                                           serviceName:@"example.com"
                                              username:@"username_value"
                                                  note:@"note"];
}

ArchivableCredential* TestPasswordCredential2() {
  return
      [[ArchivableCredential alloc] initWithFavicon:nil
                                               gaia:nil
                                           password:@"qwerty1234"
                                               rank:2
                                   recordIdentifier:@"recordIdentifier2"
                                  serviceIdentifier:@"http://www.example2.com"
                                        serviceName:@"example2.com"
                                           username:@"username_value2"
                                               note:@"note2"];
}

NSArray<ASCredentialServiceIdentifier*>* ServiceIdentifierWithName(
    NSString* serviceName) {
  ASCredentialServiceIdentifier* serviceIdentifier =
      [[ASCredentialServiceIdentifier alloc]
          initWithIdentifier:serviceName
                        type:ASCredentialServiceIdentifierTypeDomain];
  return [NSArray arrayWithObject:serviceIdentifier];
}

id<CredentialListUIHandler> UIHandlerWithCredentialId(NSData* credentialId) {
  if (credentialId != nil) {
    NSArray<NSData*>* allowedCredentials =
        [NSArray arrayWithObject:credentialId];
    return [[MockCredentialListUIHandler alloc]
        initWithAllowedCredentials:allowedCredentials
               isRequestingPasskey:YES];
  } else {
    return [[MockCredentialListUIHandler alloc] initWithAllowedCredentials:nil
                                                       isRequestingPasskey:NO];
  }
}

}  // namespace

namespace credential_provider_extension {

class CredentialListMediatorTest : public PlatformTest {
 public:
  void SetUp() override;
  void TearDown() override;
};

void CredentialListMediatorTest::SetUp() {}

void CredentialListMediatorTest::TearDown() {}

// Tests that fetching a password credential works properly.
TEST_F(CredentialListMediatorTest, FetchPasswordCredential) {
  MockCredentialResponseHandler* credentialResponseHandler =
      [[MockCredentialResponseHandler alloc] init];

  ArchivableCredential* credential = TestPasswordCredential();

  id<CredentialListUIHandler> UIHandler = UIHandlerWithCredentialId(nil);

  NSArray<id<Credential>>* credentials = [NSArray arrayWithObject:credential];
  id<CredentialStore> credentialStore =
      [[MockCredentialStore alloc] initWithCredentials:credentials];

  MockCredentialListConsumer* consumer =
      [[MockCredentialListConsumer alloc] init];

  CredentialListMediator* credentialListMediator =
      [[CredentialListMediator alloc]
                   initWithConsumer:consumer
                          UIHandler:UIHandler
                    credentialStore:credentialStore
                 serviceIdentifiers:ServiceIdentifierWithName(
                                        credential.serviceName)
          credentialResponseHandler:credentialResponseHandler];

  __block BOOL blockWaitCompleted = NO;
  consumer.presentSuggestedCredentialsBlock =
      ^(NSArray<id<Credential>>* suggested, NSArray<id<Credential>>* all,
        BOOL showSearchBar, BOOL showNewPasswordOption) {
        ASSERT_EQ(suggested.count, 1u);
        ASSERT_EQ(all.count, 1u);
        EXPECT_NSEQ(suggested[0], credential);
        EXPECT_NSEQ(all[0], credential);
        EXPECT_TRUE(showSearchBar);
        EXPECT_EQ(showNewPasswordOption, IsPasswordCreationUserEnabled());
        blockWaitCompleted = YES;
      };

  [credentialListMediator fetchCredentials];
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForFileOperationTimeout, ^BOOL {
    return blockWaitCompleted;
  }));
}

// Tests that fetching a passkey credential works properly.
TEST_F(CredentialListMediatorTest, FetchPasskeyCredential) {
  if (@available(iOS 17.0, *)) {
    MockCredentialResponseHandler* credentialResponseHandler =
        [[MockCredentialResponseHandler alloc] init];

    ArchivableCredential* credential = TestPasskeyCredential();

    id<CredentialListUIHandler> UIHandler =
        UIHandlerWithCredentialId(credential.credentialId);

    NSArray<id<Credential>>* credentials = [NSArray arrayWithObject:credential];
    id<CredentialStore> credentialStore =
        [[MockCredentialStore alloc] initWithCredentials:credentials];

    MockCredentialListConsumer* consumer =
        [[MockCredentialListConsumer alloc] init];

    CredentialListMediator* credentialListMediator =
        [[CredentialListMediator alloc]
                     initWithConsumer:consumer
                            UIHandler:UIHandler
                      credentialStore:credentialStore
                   serviceIdentifiers:nil
            credentialResponseHandler:credentialResponseHandler];

    __block BOOL blockWaitCompleted = NO;
    consumer.presentSuggestedCredentialsBlock =
        ^(NSArray<id<Credential>>* suggested, NSArray<id<Credential>>* all,
          BOOL showSearchBar, BOOL showNewPasswordOption) {
          ASSERT_EQ(suggested.count, 1u);
          ASSERT_EQ(all.count, 1u);
          EXPECT_NSEQ(suggested[0], credential);
          EXPECT_NSEQ(all[0], credential);
          EXPECT_TRUE(showSearchBar);
          EXPECT_FALSE(showNewPasswordOption);
          blockWaitCompleted = YES;
        };

    [credentialListMediator fetchCredentials];
    EXPECT_TRUE(
        WaitUntilConditionOrTimeout(kWaitForFileOperationTimeout, ^BOOL {
          return blockWaitCompleted;
        }));
  }
}

// Tests that fetching all credentials works properly.
TEST_F(CredentialListMediatorTest, FetchAllCredentials) {
  id<CredentialListUIHandler> UIHandler = UIHandlerWithCredentialId(nil);

  NSMutableArray<id<Credential>>* credentials = [NSMutableArray array];
  [credentials addObject:TestPasswordCredential()];
  [credentials addObject:TestPasskeyCredential()];
  id<CredentialStore> credentialStore =
      [[MockCredentialStore alloc] initWithCredentials:credentials];

  CredentialListMediator* credentialListMediator =
      [[CredentialListMediator alloc] initWithConsumer:nil
                                             UIHandler:UIHandler
                                       credentialStore:credentialStore
                                    serviceIdentifiers:nil
                             credentialResponseHandler:nil];

  NSArray<id<Credential>>* allCredentials =
      [credentialListMediator fetchAllCredentials];
  ASSERT_EQ(allCredentials.count, 1u);
  EXPECT_FALSE(allCredentials[0].isPasskey);

  if (@available(iOS 17.0, *)) {
    UIHandler = UIHandlerWithCredentialId(credentials[1].credentialId);
    credentialListMediator =
        [[CredentialListMediator alloc] initWithConsumer:nil
                                               UIHandler:UIHandler
                                         credentialStore:credentialStore
                                      serviceIdentifiers:nil
                               credentialResponseHandler:nil];
    allCredentials = [credentialListMediator fetchAllCredentials];
    ASSERT_EQ(allCredentials.count, 1u);
    EXPECT_TRUE(allCredentials[0].isPasskey);
  }
}

// Tests that filtering passkey credentials works properly.
TEST_F(CredentialListMediatorTest, FilterPasskeyCredentials) {
  if (@available(iOS 17.0, *)) {
    ArchivableCredential* credential = TestPasskeyCredential();
    ArchivableCredential* credential2 = TestPasskeyCredential2();

    id<CredentialListUIHandler> UIHandler =
        UIHandlerWithCredentialId(credential2.credentialId);

    NSMutableArray<id<Credential>>* credentials = [NSMutableArray array];
    [credentials addObject:credential];
    [credentials addObject:credential2];
    id<CredentialStore> credentialStore =
        [[MockCredentialStore alloc] initWithCredentials:credentials];

    CredentialListMediator* credentialListMediator =
        [[CredentialListMediator alloc] initWithConsumer:nil
                                               UIHandler:UIHandler
                                         credentialStore:credentialStore
                                      serviceIdentifiers:nil
                               credentialResponseHandler:nil];

    credentialListMediator.allCredentials =
        [credentialListMediator fetchAllCredentials];

    NSArray<id<Credential>>* filteredCredentials =
        [credentialListMediator filterPasskeyCredentials];
    ASSERT_EQ(filteredCredentials.count, 1u);
    EXPECT_NSEQ(filteredCredentials[0], credential2);
  }
}

// Tests that filtering password credentials works properly.
TEST_F(CredentialListMediatorTest, FilterPasswordCredentials) {
  ArchivableCredential* credential = TestPasswordCredential();
  ArchivableCredential* credential2 = TestPasswordCredential2();

  NSMutableArray<id<Credential>>* credentials = [NSMutableArray array];
  [credentials addObject:credential];
  [credentials addObject:credential2];
  id<CredentialStore> credentialStore =
      [[MockCredentialStore alloc] initWithCredentials:credentials];

  CredentialListMediator* credentialListMediator =
      [[CredentialListMediator alloc]
                   initWithConsumer:nil
                          UIHandler:nil
                    credentialStore:credentialStore
                 serviceIdentifiers:ServiceIdentifierWithName(
                                        credential2.serviceName)
          credentialResponseHandler:nil];

  credentialListMediator.allCredentials =
      [credentialListMediator fetchAllCredentials];

  NSArray<id<Credential>>* filteredCredentials =
      [credentialListMediator filterPasswordCredentials];
  ASSERT_EQ(filteredCredentials.count, 1u);
  EXPECT_NSEQ(filteredCredentials[0], credential2);
}

}  // namespace credential_provider_extension