// Copyright 2020 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/common/credential_provider/archivable_credential_store.h"

#import "base/apple/backup_util.h"
#import "base/apple/foundation_util.h"
#import "base/files/file_path.h"
#import "base/test/ios/wait_util.h"
#import "ios/chrome/common/credential_provider/archivable_credential.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

namespace {

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

NSURL* testStorageFileURL() {
  NSURL* temporaryDirectory = [NSURL fileURLWithPath:NSTemporaryDirectory()];
  NSURL* URL = [temporaryDirectory URLByAppendingPathComponent:@"credentials"];
  return URL;
}

class ArchivableCredentialStoreTest : public PlatformTest {
 protected:
  void SetUp() override {
    PlatformTest::SetUp();
    [[NSFileManager defaultManager] removeItemAtURL:testStorageFileURL()
                                              error:nil];
  }
  void TearDown() override {
    PlatformTest::TearDown();
    [[NSFileManager defaultManager] removeItemAtURL:testStorageFileURL()
                                              error:nil];
  }
};

ArchivableCredential* TestCredential() {
  return [[ArchivableCredential alloc] initWithFavicon:@"favicon"
                                                  gaia:nil
                                              password:@"qwerty123"
                                                  rank:5
                                      recordIdentifier:@"recordIdentifier"
                                     serviceIdentifier:@"serviceIdentifier"
                                           serviceName:@"serviceName"
                              registryControlledDomain:@"example.com"
                                              username:@"user"
                                                  note:@"note"
                                          lastUsedTime:0];
}

// Tests that an ArchivableCredentialStore can be created.
TEST_F(ArchivableCredentialStoreTest, create) {
  ArchivableCredentialStore* credentialStore =
      [[ArchivableCredentialStore alloc] initWithFileURL:testStorageFileURL()];
  EXPECT_TRUE(credentialStore);
  EXPECT_TRUE(credentialStore.credentials);
}

// Tests that an ArchivableCredentialStore can add a credential.
TEST_F(ArchivableCredentialStoreTest, add) {
  ArchivableCredentialStore* credentialStore =
      [[ArchivableCredentialStore alloc] initWithFileURL:testStorageFileURL()];
  EXPECT_TRUE(credentialStore);
  [credentialStore addCredential:TestCredential()];
  EXPECT_EQ(1u, credentialStore.credentials.count);
}

// Tests that an ArchivableCredentialStore can update a credential.
TEST_F(ArchivableCredentialStoreTest, update) {
  ArchivableCredentialStore* credentialStore =
      [[ArchivableCredentialStore alloc] initWithFileURL:testStorageFileURL()];
  EXPECT_TRUE(credentialStore);
  ArchivableCredential* credential = TestCredential();
  [credentialStore addCredential:credential];
  EXPECT_EQ(1u, credentialStore.credentials.count);

  ArchivableCredential* updatedCredential =
      [[ArchivableCredential alloc] initWithFavicon:@"other_favicon"
                                               gaia:nil
                                           password:@"Qwerty123!"
                                               rank:credential.rank + 10
                                   recordIdentifier:@"recordIdentifier"
                                  serviceIdentifier:@"other_serviceIdentifier"
                                        serviceName:@"other_serviceName"
                           registryControlledDomain:@"otherexample.com"
                                           username:@"other_user"
                                               note:@"other_note"
                                       lastUsedTime:0];

  [credentialStore updateCredential:updatedCredential];
  EXPECT_EQ(1u, credentialStore.credentials.count);
  EXPECT_EQ(updatedCredential.rank,
            credentialStore.credentials.firstObject.rank);
}

// Tests that an ArchivableCredentialStore can remove a credential.
TEST_F(ArchivableCredentialStoreTest, remove) {
  ArchivableCredentialStore* credentialStore =
      [[ArchivableCredentialStore alloc] initWithFileURL:testStorageFileURL()];
  EXPECT_TRUE(credentialStore);
  ArchivableCredential* credential = TestCredential();
  [credentialStore addCredential:credential];
  EXPECT_EQ(1u, credentialStore.credentials.count);

  [credentialStore
      removeCredentialWithRecordIdentifier:credential.recordIdentifier];
  EXPECT_EQ(0u, credentialStore.credentials.count);
}

// Tests that ArchivableCredentialStore can save and retrieve from URLs.
TEST_F(ArchivableCredentialStoreTest, persist) {
  ArchivableCredentialStore* credentialStore =
      [[ArchivableCredentialStore alloc] initWithFileURL:testStorageFileURL()];
  EXPECT_TRUE(credentialStore);

  ArchivableCredential* credential = TestCredential();
  [credentialStore addCredential:credential];
  EXPECT_EQ(1u, credentialStore.credentials.count);

  __block BOOL blockWaitCompleted = false;
  [credentialStore saveDataWithCompletion:^(NSError* error) {
    EXPECT_FALSE(error);
    blockWaitCompleted = true;
  }];
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForFileOperationTimeout, ^bool {
    return blockWaitCompleted;
  }));

  ArchivableCredentialStore* freshCredentialStore =
      [[ArchivableCredentialStore alloc] initWithFileURL:testStorageFileURL()];
  EXPECT_TRUE(freshCredentialStore);
  EXPECT_TRUE(freshCredentialStore.credentials);
  EXPECT_EQ(1u, freshCredentialStore.credentials.count);
  EXPECT_TRUE(
      [credential isEqual:freshCredentialStore.credentials.firstObject]);
}

// Tests that ArchivableCredentialStore can save in a folder that doesn't exist.
TEST_F(ArchivableCredentialStoreTest, createFolder) {
  NSURL* deepFolderURL = [testStorageFileURL()
      URLByAppendingPathComponent:@"a/deep/path/component"];
  ArchivableCredentialStore* credentialStore =
      [[ArchivableCredentialStore alloc] initWithFileURL:deepFolderURL];
  EXPECT_TRUE(credentialStore);

  ArchivableCredential* credential = TestCredential();
  [credentialStore addCredential:credential];
  EXPECT_EQ(1u, credentialStore.credentials.count);

  __block BOOL blockWaitCompleted = false;
  [credentialStore saveDataWithCompletion:^(NSError* error) {
    EXPECT_FALSE(error);
    blockWaitCompleted = true;
  }];
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForFileOperationTimeout, ^bool {
    return blockWaitCompleted;
  }));
  NSError* error = nil;
  [deepFolderURL checkResourceIsReachableAndReturnError:&error];
  EXPECT_FALSE(error);
  EXPECT_TRUE(base::apple::GetBackupExclusion(
      base::apple::NSURLToFilePath(deepFolderURL)));
  EXPECT_TRUE(base::apple::GetBackupExclusion(base::apple::NSURLToFilePath(
      deepFolderURL.URLByDeletingLastPathComponent)));
}
}  // namespace
