// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// MtpManagerClientChromeOS unit tests.

#include "components/storage_monitor/mtp_manager_client_chromeos.h"

#include <memory>
#include <string>
#include <utility>

#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "components/storage_monitor/mock_removable_storage_observer.h"
#include "components/storage_monitor/storage_info.h"
#include "components/storage_monitor/storage_info_utils.h"
#include "components/storage_monitor/storage_monitor.h"
#include "components/storage_monitor/test_storage_monitor.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "services/device/public/mojom/mtp_manager.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace storage_monitor {

namespace {

// Sample MTP device storage information.
const char kStorageWithInvalidInfo[] = "usb:2,3:11111";
const char kStorageWithValidInfo[] = "usb:2,2:88888";
const char kStorageVendor[] = "ExampleVendor";
const uint32_t kStorageVendorId = 0x040a;
const char kStorageProduct[] = "ExampleCamera";
const uint32_t kStorageProductId = 0x0160;
const uint32_t kStorageDeviceFlags = 0x0004000;
const uint32_t kStorageType = 3;                         // Fixed RAM
const uint32_t kStorageFilesystemType = 2;               // Generic Hierarchical
const uint32_t kStorageAccessCapability = 0;             // Read-Write
const uint64_t kStorageMaxCapacity = 0x40000000;         // 1G in total
const uint64_t kStorageFreeSpaceInBytes = 0x20000000;    // 512M bytes left
const uint64_t kStorageFreeSpaceInObjects = 0x04000000;  // 64M Objects left
const char kStorageDescription[] = "ExampleDescription";
const char kStorageVolumeIdentifier[] = "ExampleVolumeId";

base::LazyInstance<std::map<std::string, device::mojom::MtpStorageInfo>>::Leaky
    g_fake_storage_info_map = LAZY_INSTANCE_INITIALIZER;

const device::mojom::MtpStorageInfo* GetFakeMtpStorageInfoSync(
    const std::string& storage_name) {
  // Fill the map out if it is empty.
  if (g_fake_storage_info_map.Get().empty()) {
    // Add the invalid MTP storage info.
    auto storage_info = device::mojom::MtpStorageInfo();
    storage_info.storage_name = kStorageWithInvalidInfo;
    g_fake_storage_info_map.Get().insert(
        std::make_pair(kStorageWithInvalidInfo, storage_info));
    // Add the valid MTP storage info.
    g_fake_storage_info_map.Get().insert(std::make_pair(
        kStorageWithValidInfo,
        device::mojom::MtpStorageInfo(
            kStorageWithValidInfo, kStorageVendor, kStorageVendorId,
            kStorageProduct, kStorageProductId, kStorageDeviceFlags,
            kStorageType, kStorageFilesystemType, kStorageAccessCapability,
            kStorageMaxCapacity, kStorageFreeSpaceInBytes,
            kStorageFreeSpaceInObjects, kStorageDescription,
            kStorageVolumeIdentifier)));
  }

  const auto it = g_fake_storage_info_map.Get().find(storage_name);
  return it != g_fake_storage_info_map.Get().end() ? &it->second : nullptr;
}

class FakeMtpManagerClientChromeOS : public MtpManagerClientChromeOS {
 public:
  FakeMtpManagerClientChromeOS(StorageMonitor::Receiver* receiver,
                               device::mojom::MtpManager* mtp_manager)
      : MtpManagerClientChromeOS(receiver, mtp_manager) {}

  // Notifies MtpManagerClientChromeOS about the attachment of MTP storage
  // device given the |storage_name|.
  void MtpStorageAttached(const std::string& storage_name) {
    auto* storage_info = GetFakeMtpStorageInfoSync(storage_name);
    DCHECK(storage_info);

    StorageAttached(storage_info->Clone());
    base::RunLoop().RunUntilIdle();
  }

  // Notifies MtpManagerClientChromeOS about the detachment of MTP storage
  // device given the |storage_name|.
  void MtpStorageDetached(const std::string& storage_name) {
    StorageDetached(storage_name);
    base::RunLoop().RunUntilIdle();
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(FakeMtpManagerClientChromeOS);
};

}  // namespace

// A class to test the functionality of MtpManagerClientChromeOS member
// functions.
class MtpManagerClientChromeOSTest : public testing::Test {
 public:
  MtpManagerClientChromeOSTest()
      : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}

  ~MtpManagerClientChromeOSTest() override {}

 protected:
  void SetUp() override {
    mock_storage_observer_.reset(new MockRemovableStorageObserver);
    TestStorageMonitor* monitor = TestStorageMonitor::CreateAndInstall();
    mtp_device_observer_ = std::make_unique<FakeMtpManagerClientChromeOS>(
        monitor->receiver(), monitor->media_transfer_protocol_manager());
    monitor->AddObserver(mock_storage_observer_.get());
  }

  void TearDown() override {
    StorageMonitor* monitor = StorageMonitor::GetInstance();
    monitor->RemoveObserver(mock_storage_observer_.get());
    mtp_device_observer_.reset();
    TestStorageMonitor::Destroy();
  }

  // Returns the device changed observer object.
  MockRemovableStorageObserver& observer() { return *mock_storage_observer_; }

  FakeMtpManagerClientChromeOS* mtp_device_observer() {
    return mtp_device_observer_.get();
  }

 private:
  content::TestBrowserThreadBundle thread_bundle_;

  std::unique_ptr<FakeMtpManagerClientChromeOS> mtp_device_observer_;
  std::unique_ptr<MockRemovableStorageObserver> mock_storage_observer_;

  DISALLOW_COPY_AND_ASSIGN(MtpManagerClientChromeOSTest);
};

// Test to verify basic MTP storage attach and detach notifications.
TEST_F(MtpManagerClientChromeOSTest, BasicAttachDetach) {
  auto* mtpStorageInfo = GetFakeMtpStorageInfoSync(kStorageWithValidInfo);
  std::string device_id = GetDeviceIdFromStorageInfo(*mtpStorageInfo);

  // Attach a MTP storage.
  mtp_device_observer()->MtpStorageAttached(kStorageWithValidInfo);

  EXPECT_EQ(1, observer().attach_calls());
  EXPECT_EQ(0, observer().detach_calls());
  EXPECT_EQ(device_id, observer().last_attached().device_id());
  EXPECT_EQ(GetDeviceLocationFromStorageName(kStorageWithValidInfo),
            observer().last_attached().location());
  EXPECT_EQ(base::ASCIIToUTF16(kStorageVendor),
            observer().last_attached().vendor_name());
  EXPECT_EQ(base::ASCIIToUTF16(kStorageProduct),
            observer().last_attached().model_name());

  // Detach the attached storage.
  mtp_device_observer()->MtpStorageDetached(kStorageWithValidInfo);

  EXPECT_EQ(1, observer().attach_calls());
  EXPECT_EQ(1, observer().detach_calls());
  EXPECT_EQ(device_id, observer().last_detached().device_id());
}

// When a MTP storage device with invalid storage label and id is
// attached/detached, there should not be any device attach/detach
// notifications.
TEST_F(MtpManagerClientChromeOSTest, StorageWithInvalidInfo) {
  // Attach the mtp storage with invalid storage info.
  mtp_device_observer()->MtpStorageAttached(kStorageWithInvalidInfo);

  // Detach the attached storage.
  mtp_device_observer()->MtpStorageDetached(kStorageWithInvalidInfo);

  EXPECT_EQ(0, observer().attach_calls());
  EXPECT_EQ(0, observer().detach_calls());
}

}  // namespace storage_monitor
