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

#include <stddef.h>

#include <functional>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/memory/ref_counted_memory.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/repeating_test_future.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/api/printer_provider/printer_provider_api.h"
#include "extensions/browser/api/printer_provider/printer_provider_api_factory.h"
#include "extensions/browser/api/printer_provider/printer_provider_print_job.h"
#include "extensions/browser/api/usb/usb_device_manager.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/unloaded_extension_reason.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "services/device/public/cpp/test/fake_usb_device_manager.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

namespace {

using ContextType = extensions::browser_test_util::ContextType;

using GetPrintersRepeatingFuture =
    base::test::RepeatingTestFuture<base::ListValue, bool>;

base::ListValue FetchPrintersFromFuture(
    std::unique_ptr<GetPrintersRepeatingFuture> future) {
  base::ListValue printers_out;
  while (true) {
    auto [printers, done] = future->Take();
    for (auto& printer : printers) {
      EXPECT_TRUE(printer.is_dict());
      printers_out.Append(std::move(printer));
    }
    if (done) {
      break;
    }
  }
  return printers_out;
}

std::pair<bool, std::string> ParsePrintResult(const base::Value& status) {
  bool success = status.is_none();
  std::string status_str = success ? "OK" : status.GetString();
  return {success, status_str};
}

std::optional<std::string> SerializeDict(const base::DictValue& value) {
  return base::WriteJson(value);
}

// Tests for chrome.printerProvider API.
class PrinterProviderApiTest : public ExtensionApiTest {
 public:
  enum PrintRequestDataType {
    PRINT_REQUEST_DATA_TYPE_NOT_SET,
    PRINT_REQUEST_DATA_TYPE_BYTES
  };

  PrinterProviderApiTest() = default;
  ~PrinterProviderApiTest() override = default;
  PrinterProviderApiTest(const PrinterProviderApiTest&) = delete;
  PrinterProviderApiTest& operator=(const PrinterProviderApiTest&) = delete;

 protected:
  void StartGetPrintersRequest(
      const PrinterProviderAPI::GetPrintersCallback& callback) {
    PrinterProviderAPIFactory::GetInstance()
        ->GetForBrowserContext(profile())
        ->DispatchGetPrintersRequested(callback);
  }

  void StartPrintRequestWithNoData(const ExtensionId& extension_id,
                                   PrinterProviderAPI::PrintCallback callback) {
    PrinterProviderPrintJob job;
    job.printer_id = extension_id + ":printer_id";
    job.content_type = "application/pdf";

    PrinterProviderAPIFactory::GetInstance()
        ->GetForBrowserContext(profile())
        ->DispatchPrintRequested(std::move(job), std::move(callback));
  }

  void StartPrintRequestUsingDocumentBytes(
      const ExtensionId& extension_id,
      PrinterProviderAPI::PrintCallback callback) {
    PrinterProviderPrintJob job;
    job.printer_id = extension_id + ":printer_id";
    job.job_title = u"Print job";
    job.content_type = "application/pdf";
    const unsigned char kDocumentBytes[] = {'b', 'y', 't', 'e', 's'};
    job.document_bytes =
        base::MakeRefCounted<base::RefCountedBytes>(kDocumentBytes);

    PrinterProviderAPIFactory::GetInstance()
        ->GetForBrowserContext(profile())
        ->DispatchPrintRequested(std::move(job), std::move(callback));
  }

  void StartCapabilityRequest(
      const ExtensionId& extension_id,
      PrinterProviderAPI::GetCapabilityCallback callback) {
    PrinterProviderAPIFactory::GetInstance()
        ->GetForBrowserContext(profile())
        ->DispatchGetCapabilityRequested(extension_id + ":printer_id",
                                         std::move(callback));
  }

  // Loads chrome.printerProvider test extension and initializes it for test.
  // |test_param|.
  // When the extension's background page is loaded, it will send a 'loaded'
  // message. As a response to the message it will expect a string message
  // specifying the test that should be run. When the extension initializes its
  // state (e.g. registers listener for a chrome.printerProvider event) it will
  // send the message 'ready', at which point the test may be started.
  // If the extension is successfully initialized, |*extension_id_out| will be
  // set to the loaded extension's id, otherwise it will remain unchanged.
  void InitializePrinterProviderTestExtension(const std::string& extension_path,
                                              const std::string& test_param,
                                              ExtensionId* extension_id_out) {
    ExtensionTestMessageListener loaded_listener("loaded",
                                                 ReplyBehavior::kWillReply);
    ExtensionTestMessageListener ready_listener("ready");

    const Extension* extension =
        LoadExtension(test_data_dir_.AppendASCII(extension_path));
    ASSERT_TRUE(extension);
    ASSERT_TRUE(loaded_listener.WaitUntilSatisfied());

    loaded_listener.Reply(test_param);

    ASSERT_TRUE(ready_listener.WaitUntilSatisfied());

    *extension_id_out = extension->id();
  }

  // Runs a test for chrome.printerProvider.onPrintRequested event.
  // |test_param|: The test that should be run.
  // |expected_result|: The print result the extension is expected to report.
  void RunPrintRequestTestExtension(const std::string& test_param,
                                    PrintRequestDataType data_type,
                                    const std::string& expected_result) {
    ResultCatcher catcher;

    ExtensionId extension_id;
    InitializePrinterProviderTestExtension("printer_provider/request_print",
                                           test_param, &extension_id);
    if (extension_id.empty()) {
      return;
    }

    base::RunLoop run_loop;
    bool success;
    std::string status;
    // TestFuture cannot be used with <const base::Value&>.
    auto callback = base::BindLambdaForTesting([&](const base::Value& result) {
                      std::tie(success, status) = ParsePrintResult(result);
                    }).Then(run_loop.QuitClosure());
    switch (data_type) {
      case PRINT_REQUEST_DATA_TYPE_NOT_SET:
        StartPrintRequestWithNoData(extension_id, std::move(callback));
        break;
      case PRINT_REQUEST_DATA_TYPE_BYTES:
        StartPrintRequestUsingDocumentBytes(extension_id, std::move(callback));
        break;
    }

    if (data_type != PRINT_REQUEST_DATA_TYPE_NOT_SET) {
      ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
    }

    run_loop.Run();
    EXPECT_EQ(expected_result, status);
    EXPECT_EQ(expected_result == "OK", success);
  }

  // Runs a test for chrome.printerProvider.onGetCapabilityRequested
  // event.
  // |test_param|: The test that should be run.
  // |expected_result|: The capability the extension is expected to report.
  void RunPrinterCapabilitiesRequestTest(const std::string& test_param,
                                         const std::string& expected_result) {
    ResultCatcher catcher;

    ExtensionId extension_id;
    InitializePrinterProviderTestExtension(
        "printer_provider/request_capability", test_param, &extension_id);
    if (extension_id.empty()) {
      return;
    }

    base::test::TestFuture<base::DictValue> capability_future;
    StartCapabilityRequest(extension_id, capability_future.GetCallback());

    ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

    auto result = SerializeDict(capability_future.Get());
    ASSERT_TRUE(result);
    EXPECT_EQ(expected_result, *result);
  }

  bool SimulateExtensionUnload(const ExtensionId& extension_id) {
    ExtensionRegistry* extension_registry = ExtensionRegistry::Get(profile());

    scoped_refptr<const Extension> extension =
        extension_registry->enabled_extensions().GetByID(extension_id);
    if (!extension) {
      return false;
    }

    extension_registry->RemoveEnabled(extension_id);
    extension_registry->TriggerOnUnloaded(extension.get(),
                                          UnloadedExtensionReason::TERMINATE);
    return true;
  }

  // Validates that set of printers reported by test extensions via
  // chrome.printerProvider.onGetPritersRequested is the same as the set of
  // printers in |expected_printers|. |expected_printers| contains list of
  // printer objects formatted as a JSON string. It is assumed that the values
  // in |expected_printers| are unique.
  void ValidatePrinterListValue(
      const base::ListValue& printers,
      const std::vector<base::DictValue>& expected_printers) {
    ASSERT_EQ(expected_printers.size(), printers.size());
    for (const auto& printer_value : expected_printers) {
      EXPECT_TRUE(printers.contains(printer_value))
          << "Unable to find " << printer_value << " in " << printers;
    }
  }
};

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, PrintJobSuccess) {
  RunPrintRequestTestExtension("OK", PRINT_REQUEST_DATA_TYPE_BYTES, "OK");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, PrintJobAsyncSuccess) {
  RunPrintRequestTestExtension("ASYNC_RESPONSE", PRINT_REQUEST_DATA_TYPE_BYTES,
                               "OK");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, PrintJobFailed) {
  RunPrintRequestTestExtension("INVALID_TICKET", PRINT_REQUEST_DATA_TYPE_BYTES,
                               "INVALID_TICKET");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, NoPrintEventListener) {
  RunPrintRequestTestExtension("NO_LISTENER", PRINT_REQUEST_DATA_TYPE_BYTES,
                               "FAILED");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest,
                       PrintRequestInvalidCallbackParam) {
  RunPrintRequestTestExtension("INVALID_VALUE", PRINT_REQUEST_DATA_TYPE_BYTES,
                               "FAILED");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, PrintRequestDataNotSet) {
  RunPrintRequestTestExtension("IGNORE_CALLBACK",
                               PRINT_REQUEST_DATA_TYPE_NOT_SET, "FAILED");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, PrintRequestExtensionUnloaded) {
  ResultCatcher catcher;

  ExtensionId extension_id;
  InitializePrinterProviderTestExtension("printer_provider/request_print",
                                         "IGNORE_CALLBACK", &extension_id);
  ASSERT_FALSE(extension_id.empty());

  base::RunLoop run_loop;
  bool success;
  std::string status;
  // TestFuture cannot be used with <const base::Value&>.
  StartPrintRequestUsingDocumentBytes(
      extension_id, base::BindLambdaForTesting([&](const base::Value& result) {
                      std::tie(success, status) = ParsePrintResult(result);
                    }).Then(run_loop.QuitClosure()));

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  ASSERT_TRUE(SimulateExtensionUnload(extension_id));

  run_loop.Run();
  EXPECT_FALSE(success);
  EXPECT_EQ("FAILED", status);
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetCapabilitySuccess) {
  RunPrinterCapabilitiesRequestTest("OK", "{\"capability\":\"value\"}");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetCapabilityAsyncSuccess) {
  RunPrinterCapabilitiesRequestTest("ASYNC_RESPONSE",
                                    "{\"capability\":\"value\"}");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, EmptyCapability) {
  RunPrinterCapabilitiesRequestTest("EMPTY", "{}");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, NoCapabilityEventListener) {
  RunPrinterCapabilitiesRequestTest("NO_LISTENER", "{}");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, CapabilityInvalidValue) {
  RunPrinterCapabilitiesRequestTest("INVALID_VALUE", "{}");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetCapabilityExtensionUnloaded) {
  ResultCatcher catcher;

  ExtensionId extension_id;
  InitializePrinterProviderTestExtension("printer_provider/request_capability",
                                         "IGNORE_CALLBACK", &extension_id);
  ASSERT_FALSE(extension_id.empty());

  base::test::TestFuture<base::DictValue> capability_future;
  StartCapabilityRequest(extension_id, capability_future.GetCallback());
  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  ASSERT_TRUE(SimulateExtensionUnload(extension_id));
  auto result = SerializeDict(capability_future.Get());
  ASSERT_TRUE(result);
  EXPECT_EQ("{}", *result);
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersSuccess) {
  ResultCatcher catcher;

  ExtensionId extension_id;
  InitializePrinterProviderTestExtension("printer_provider/request_printers",
                                         "OK", &extension_id);
  ASSERT_FALSE(extension_id.empty());

  auto get_printers_repeating_future =
      std::make_unique<GetPrintersRepeatingFuture>();
  StartGetPrintersRequest(get_printers_repeating_future->GetCallback());

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  std::vector<base::DictValue> expected_printers;
  expected_printers.push_back(
      base::DictValue()
          .Set("description", "Test printer")
          .Set("extensionId", extension_id)
          .Set("extensionName", "Test printer provider")
          .Set("id", base::StringPrintf("%s:printer1", extension_id.c_str()))
          .Set("name", "Printer 1"));
  expected_printers.push_back(
      base::DictValue()
          .Set("extensionId", extension_id)
          .Set("extensionName", "Test printer provider")
          .Set("id",
               base::StringPrintf("%s:printerNoDesc", extension_id.c_str()))
          .Set("name", "Printer 2"));

  auto printers =
      FetchPrintersFromFuture(std::move(get_printers_repeating_future));
  ValidatePrinterListValue(printers, expected_printers);
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersAsyncSuccess) {
  ResultCatcher catcher;

  ExtensionId extension_id;
  InitializePrinterProviderTestExtension("printer_provider/request_printers",
                                         "ASYNC_RESPONSE", &extension_id);
  ASSERT_FALSE(extension_id.empty());

  auto get_printers_repeating_future =
      std::make_unique<GetPrintersRepeatingFuture>();
  StartGetPrintersRequest(get_printers_repeating_future->GetCallback());

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  std::vector<base::DictValue> expected_printers;
  expected_printers.push_back(
      base::DictValue()
          .Set("description", "Test printer")
          .Set("extensionId", extension_id)
          .Set("extensionName", "Test printer provider")
          .Set("id", base::StringPrintf("%s:printer1", extension_id.c_str()))
          .Set("name", "Printer 1"));

  auto printers =
      FetchPrintersFromFuture(std::move(get_printers_repeating_future));
  ValidatePrinterListValue(printers, expected_printers);
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersTwoExtensions) {
  ResultCatcher catcher;

  ExtensionId extension_id_1;
  InitializePrinterProviderTestExtension("printer_provider/request_printers",
                                         "OK", &extension_id_1);
  ASSERT_FALSE(extension_id_1.empty());

  ExtensionId extension_id_2;
  InitializePrinterProviderTestExtension(
      "printer_provider/request_printers_second", "OK", &extension_id_2);
  ASSERT_FALSE(extension_id_2.empty());

  auto get_printers_repeating_future =
      std::make_unique<GetPrintersRepeatingFuture>();
  StartGetPrintersRequest(get_printers_repeating_future->GetCallback());

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  std::vector<base::DictValue> expected_printers;
  expected_printers.push_back(
      base::DictValue()
          .Set("description", "Test printer")
          .Set("extensionId", extension_id_1)
          .Set("extensionName", "Test printer provider")
          .Set("id", base::StringPrintf("%s:printer1", extension_id_1.c_str()))
          .Set("name", "Printer 1"));
  expected_printers.push_back(
      base::DictValue()
          .Set("extensionId", extension_id_1)
          .Set("extensionName", "Test printer provider")
          .Set("id",
               base::StringPrintf("%s:printerNoDesc", extension_id_1.c_str()))
          .Set("name", "Printer 2"));
  expected_printers.push_back(
      base::DictValue()
          .Set("description", "Test printer")
          .Set("extensionId", extension_id_2)
          .Set("extensionName", "Test printer provider")
          .Set("id", base::StringPrintf("%s:printer1", extension_id_2.c_str()))
          .Set("name", "Printer 1"));
  expected_printers.push_back(
      base::DictValue()
          .Set("extensionId", extension_id_2)
          .Set("extensionName", "Test printer provider")
          .Set("id",
               base::StringPrintf("%s:printerNoDesc", extension_id_2.c_str()))
          .Set("name", "Printer 2"));

  auto printers =
      FetchPrintersFromFuture(std::move(get_printers_repeating_future));
  ValidatePrinterListValue(printers, expected_printers);
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest,
                       GetPrintersTwoExtensionsBothUnloaded) {
  ResultCatcher catcher;

  ExtensionId extension_id_1;
  InitializePrinterProviderTestExtension("printer_provider/request_printers",
                                         "IGNORE_CALLBACK", &extension_id_1);
  ASSERT_FALSE(extension_id_1.empty());

  ExtensionId extension_id_2;
  InitializePrinterProviderTestExtension(
      "printer_provider/request_printers_second", "IGNORE_CALLBACK",
      &extension_id_2);
  ASSERT_FALSE(extension_id_2.empty());

  auto get_printers_repeating_future =
      std::make_unique<GetPrintersRepeatingFuture>();
  StartGetPrintersRequest(get_printers_repeating_future->GetCallback());

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  ASSERT_TRUE(SimulateExtensionUnload(extension_id_1));
  ASSERT_TRUE(SimulateExtensionUnload(extension_id_2));

  auto printers =
      FetchPrintersFromFuture(std::move(get_printers_repeating_future));
  EXPECT_TRUE(printers.empty());
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest,
                       GetPrintersTwoExtensionsOneFails) {
  ResultCatcher catcher;

  ExtensionId extension_id_1;
  InitializePrinterProviderTestExtension("printer_provider/request_printers",
                                         "NOT_ARRAY", &extension_id_1);
  ASSERT_FALSE(extension_id_1.empty());

  ExtensionId extension_id_2;
  InitializePrinterProviderTestExtension(
      "printer_provider/request_printers_second", "OK", &extension_id_2);
  ASSERT_FALSE(extension_id_2.empty());

  auto get_printers_repeating_future =
      std::make_unique<GetPrintersRepeatingFuture>();
  StartGetPrintersRequest(get_printers_repeating_future->GetCallback());

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  std::vector<base::DictValue> expected_printers;
  expected_printers.push_back(
      base::DictValue()
          .Set("description", "Test printer")
          .Set("extensionId", extension_id_2)
          .Set("extensionName", "Test printer provider")
          .Set("id", base::StringPrintf("%s:printer1", extension_id_2.c_str()))
          .Set("name", "Printer 1"));
  expected_printers.push_back(
      base::DictValue()
          .Set("extensionId", extension_id_2)
          .Set("extensionName", "Test printer provider")
          .Set("id",
               base::StringPrintf("%s:printerNoDesc", extension_id_2.c_str()))
          .Set("name", "Printer 2"));

  auto printers =
      FetchPrintersFromFuture(std::move(get_printers_repeating_future));
  ValidatePrinterListValue(printers, expected_printers);
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest,
                       GetPrintersTwoExtensionsOneWithNoListener) {
  ResultCatcher catcher;

  ExtensionId extension_id_1;
  InitializePrinterProviderTestExtension("printer_provider/request_printers",
                                         "NO_LISTENER", &extension_id_1);
  ASSERT_FALSE(extension_id_1.empty());

  ExtensionId extension_id_2;
  InitializePrinterProviderTestExtension(
      "printer_provider/request_printers_second", "OK", &extension_id_2);
  ASSERT_FALSE(extension_id_2.empty());

  auto get_printers_repeating_future =
      std::make_unique<GetPrintersRepeatingFuture>();
  StartGetPrintersRequest(get_printers_repeating_future->GetCallback());

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  std::vector<base::DictValue> expected_printers;
  expected_printers.push_back(
      base::DictValue()
          .Set("description", "Test printer")
          .Set("extensionId", extension_id_2)
          .Set("extensionName", "Test printer provider")
          .Set("id", base::StringPrintf("%s:printer1", extension_id_2.c_str()))
          .Set("name", "Printer 1"));
  expected_printers.push_back(
      base::DictValue()
          .Set("extensionId", extension_id_2)
          .Set("extensionName", "Test printer provider")
          .Set("id",
               base::StringPrintf("%s:printerNoDesc", extension_id_2.c_str()))
          .Set("name", "Printer 2"));

  auto printers =
      FetchPrintersFromFuture(std::move(get_printers_repeating_future));
  ValidatePrinterListValue(printers, expected_printers);
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersNoListener) {
  ResultCatcher catcher;

  ExtensionId extension_id;
  InitializePrinterProviderTestExtension("printer_provider/request_printers",
                                         "NO_LISTENER", &extension_id);
  ASSERT_FALSE(extension_id.empty());

  auto get_printers_repeating_future =
      std::make_unique<GetPrintersRepeatingFuture>();
  StartGetPrintersRequest(get_printers_repeating_future->GetCallback());

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  auto printers =
      FetchPrintersFromFuture(std::move(get_printers_repeating_future));
  EXPECT_TRUE(printers.empty());
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersNotArray) {
  ResultCatcher catcher;

  ExtensionId extension_id;
  InitializePrinterProviderTestExtension("printer_provider/request_printers",
                                         "NOT_ARRAY", &extension_id);
  ASSERT_FALSE(extension_id.empty());

  auto get_printers_repeating_future =
      std::make_unique<GetPrintersRepeatingFuture>();
  StartGetPrintersRequest(get_printers_repeating_future->GetCallback());

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  auto printers =
      FetchPrintersFromFuture(std::move(get_printers_repeating_future));
  EXPECT_TRUE(printers.empty());
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest,
                       GetPrintersInvalidPrinterValueType) {
  ResultCatcher catcher;

  ExtensionId extension_id;
  InitializePrinterProviderTestExtension("printer_provider/request_printers",
                                         "INVALID_PRINTER_TYPE", &extension_id);
  ASSERT_FALSE(extension_id.empty());

  auto get_printers_repeating_future =
      std::make_unique<GetPrintersRepeatingFuture>();
  StartGetPrintersRequest(get_printers_repeating_future->GetCallback());

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  auto printers =
      FetchPrintersFromFuture(std::move(get_printers_repeating_future));
  EXPECT_TRUE(printers.empty());
}

IN_PROC_BROWSER_TEST_F(PrinterProviderApiTest, GetPrintersInvalidPrinterValue) {
  ResultCatcher catcher;

  ExtensionId extension_id;
  InitializePrinterProviderTestExtension("printer_provider/request_printers",
                                         "INVALID_PRINTER", &extension_id);
  ASSERT_FALSE(extension_id.empty());

  auto get_printers_repeating_future =
      std::make_unique<GetPrintersRepeatingFuture>();
  StartGetPrintersRequest(get_printers_repeating_future->GetCallback());

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();

  auto printers =
      FetchPrintersFromFuture(std::move(get_printers_repeating_future));
  EXPECT_TRUE(printers.empty());
}

#if BUILDFLAG(IS_CHROMEOS)
// These tests are separate out from the main test class because the USB api
// is only available on ChromeOS to Chrome Apps.
class PrinterProviderUsbApiTest : public PrinterProviderApiTest {
 public:
  PrinterProviderUsbApiTest() = default;
  PrinterProviderUsbApiTest(const PrinterProviderUsbApiTest&) = delete;
  PrinterProviderUsbApiTest& operator=(const PrinterProviderUsbApiTest&) =
      delete;

 protected:
  void StartGetUsbPrinterInfoRequest(
      const ExtensionId& extension_id,
      const device::mojom::UsbDeviceInfo& device,
      PrinterProviderAPI::GetPrinterInfoCallback callback) {
    PrinterProviderAPIFactory::GetInstance()
        ->GetForBrowserContext(profile())
        ->DispatchGetUsbPrinterInfoRequested(extension_id, device,
                                             std::move(callback));
  }

  // Run a test for the chrome.printerProvider.onGetUsbPrinterInfoRequested
  // event.
  // |test_param|: The test that should be run.
  // |expected_result|: The printer info that the app is expected to report.
  void RunUsbPrinterInfoRequestTest(const std::string& test_param) {
    ResultCatcher catcher;
    device::mojom::UsbDeviceInfoPtr device =
        usb_manager_.CreateAndAddDevice(0, 0, "Google", "USB Printer", "");

    ExtensionId extension_id;
    InitializePrinterProviderTestExtension("printer_provider/usb_printers",
                                           test_param, &extension_id);
    ASSERT_FALSE(extension_id.empty());

    base::test::TestFuture<base::DictValue> future;
    StartGetUsbPrinterInfoRequest(extension_id, *device, future.GetCallback());
    ASSERT_TRUE(future.Get().empty());

    ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
  }

  device::FakeUsbDeviceManager usb_manager_;
};

// These are only instantiated for event page-based packaged apps.

IN_PROC_BROWSER_TEST_F(PrinterProviderUsbApiTest, GetUsbPrinterInfo) {
  ResultCatcher catcher;
  device::mojom::UsbDeviceInfoPtr device =
      usb_manager_.CreateAndAddDevice(0, 0, "Google", "USB Printer", "");

  ExtensionId extension_id;
  InitializePrinterProviderTestExtension("printer_provider/usb_printers", "OK",
                                         &extension_id);
  ASSERT_FALSE(extension_id.empty());

  UsbDeviceManager* device_manager = UsbDeviceManager::Get(profile());
  base::DictValue expected_printer_info =
      base::DictValue()
          .Set("description", "This printer is a USB device.")
          .Set("extensionId", extension_id)
          .Set("extensionName", "Test USB printer provider")
          .Set("id",
               base::StringPrintf("%s:usbDevice-%u", extension_id.c_str(),
                                  device_manager->GetIdFromGuid(device->guid)))
          .Set("name", "Test Printer");
  base::test::TestFuture<base::DictValue> future;
  StartGetUsbPrinterInfoRequest(extension_id, *device, future.GetCallback());
  ASSERT_EQ(future.Get(), expected_printer_info);

  ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
}

IN_PROC_BROWSER_TEST_F(PrinterProviderUsbApiTest,
                       GetUsbPrinterInfoEmptyResponse) {
  RunUsbPrinterInfoRequestTest("EMPTY_RESPONSE");
}

IN_PROC_BROWSER_TEST_F(PrinterProviderUsbApiTest, GetUsbPrinterInfoNoListener) {
  RunUsbPrinterInfoRequestTest("NO_LISTENER");
}
#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace

}  // namespace extensions
