// Copyright 2017 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.

#include "chrome/browser/ui/webui/print_preview/print_preview_handler.h"

#include <map>
#include <utility>
#include <vector>

#include "base/base64.h"
#include "base/containers/flat_set.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/ref_counted_memory.h"
#include "base/optional.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/icu_test_util.h"
#include "base/values.h"
#include "chrome/browser/printing/print_test_utils.h"
#include "chrome/browser/printing/print_view_manager.h"
#include "chrome/browser/ui/webui/print_preview/policy_settings.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h"
#include "chrome/browser/ui/webui/print_preview/printer_handler.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/prefs/pref_service.h"
#include "components/printing/common/print_messages.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui_controller.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_web_ui.h"
#include "ipc/ipc_test_sink.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace printing {

namespace {

const char kDummyInitiatorName[] = "TestInitiator";
const char kTestData[] = "abc";

// Array of all PrinterType values.
const PrinterType kAllTypes[] = {kPrivetPrinter, kExtensionPrinter, kPdfPrinter,
                                 kLocalPrinter};

struct PrinterInfo {
  std::string id;
  bool is_default;
  base::Value basic_info = base::Value(base::Value::Type::DICTIONARY);
  base::Value capabilities = base::Value(base::Value::Type::DICTIONARY);
};

PrinterInfo GetSimplePrinterInfo(const std::string& name, bool is_default) {
  PrinterInfo simple_printer;
  simple_printer.id = name;
  simple_printer.is_default = is_default;
  simple_printer.basic_info.SetKey("printer_name",
                                   base::Value(simple_printer.id));
  simple_printer.basic_info.SetKey("printer_description",
                                   base::Value("Printer for test"));
  simple_printer.basic_info.SetKey("printer_status", base::Value(1));
  base::Value cdd(base::Value::Type::DICTIONARY);
  base::Value capabilities(base::Value::Type::DICTIONARY);
  simple_printer.capabilities.SetKey("printer",
                                     simple_printer.basic_info.Clone());
  simple_printer.capabilities.SetKey("capabilities", cdd.Clone());
  return simple_printer;
}

PrinterInfo GetEmptyPrinterInfo() {
  PrinterInfo empty_printer;
  empty_printer.id = "EmptyPrinter";
  empty_printer.is_default = false;
  empty_printer.basic_info.SetKey("printer_name",
                                  base::Value(empty_printer.id));
  empty_printer.basic_info.SetKey("printer_description",
                                  base::Value("Printer with no capabilities"));
  empty_printer.basic_info.SetKey("printer_status", base::Value(0));
  empty_printer.capabilities.SetKey("printer",
                                    empty_printer.basic_info.Clone());
  return empty_printer;
}

base::Value GetPrintPreviewTicket(bool is_pdf) {
  base::Value print_ticket = GetPrintTicket(kLocalPrinter, false);

  // Make some modifications to match a preview print ticket.
  print_ticket.SetKey(kSettingPageRange, base::Value());
  print_ticket.SetKey(kIsFirstRequest, base::Value(true));
  print_ticket.SetKey(kPreviewRequestID, base::Value(0));
  print_ticket.SetKey(kSettingPreviewModifiable, base::Value(is_pdf));
  print_ticket.RemoveKey(kSettingPageWidth);
  print_ticket.RemoveKey(kSettingPageHeight);
  print_ticket.RemoveKey(kSettingShowSystemDialog);

  return print_ticket;
}

std::unique_ptr<base::ListValue> ConstructPreviewArgs(
    base::StringPiece callback_id,
    const base::Value& print_ticket) {
  base::Value args(base::Value::Type::LIST);
  args.GetList().emplace_back(callback_id);
  std::string json;
  base::JSONWriter::Write(print_ticket, &json);
  args.GetList().emplace_back(json);
  return base::ListValue::From(base::Value::ToUniquePtrValue(std::move(args)));
}

class TestPrinterHandler : public PrinterHandler {
 public:
  explicit TestPrinterHandler(const std::vector<PrinterInfo>& printers) {
    SetPrinters(printers);
  }

  ~TestPrinterHandler() override {}

  void Reset() override {}

  void GetDefaultPrinter(DefaultPrinterCallback cb) override {
    std::move(cb).Run(default_printer_);
  }

  void StartGetPrinters(AddedPrintersCallback added_printers_callback,
                        GetPrintersDoneCallback done_callback) override {
    if (!printers_.empty())
      added_printers_callback.Run(printers_);
    std::move(done_callback).Run();
  }

  void StartGetCapability(const std::string& destination_id,
                          GetCapabilityCallback callback) override {
    std::move(callback).Run(printer_capabilities_[destination_id]->Clone());
  }

  void StartGrantPrinterAccess(const std::string& printer_id,
                               GetPrinterInfoCallback callback) override {}

  void StartPrint(const base::string16& job_title,
                  base::Value settings,
                  scoped_refptr<base::RefCountedMemory> print_data,
                  PrintCallback callback) override {
    std::move(callback).Run(base::Value());
  }

  void SetPrinters(const std::vector<PrinterInfo>& printers) {
    base::Value::ListStorage printer_list;
    for (const auto& printer : printers) {
      if (printer.is_default)
        default_printer_ = printer.id;
      printer_list.push_back(printer.basic_info.Clone());
      printer_capabilities_[printer.id] = base::DictionaryValue::From(
          std::make_unique<base::Value>(printer.capabilities.Clone()));
    }
    printers_ = base::ListValue(printer_list);
  }

 private:
  std::string default_printer_;
  base::ListValue printers_;
  std::map<std::string, std::unique_ptr<base::DictionaryValue>>
      printer_capabilities_;

  DISALLOW_COPY_AND_ASSIGN(TestPrinterHandler);
};

class FakePrintPreviewUI : public PrintPreviewUI {
 public:
  FakePrintPreviewUI(content::WebUI* web_ui,
                     std::unique_ptr<PrintPreviewHandler> handler)
      : PrintPreviewUI(web_ui, std::move(handler)) {}

  ~FakePrintPreviewUI() override {}

  void GetPrintPreviewDataForIndex(
      int index,
      scoped_refptr<base::RefCountedMemory>* data) const override {
    *data = base::MakeRefCounted<base::RefCountedStaticMemory>(
        reinterpret_cast<const unsigned char*>(kTestData),
        sizeof(kTestData) - 1);
  }

  void OnPrintPreviewRequest(int request_id) override {}
  void OnCancelPendingPreviewRequest() override {}
  void OnHidePreviewDialog() override {}
  void OnClosePrintPreviewDialog() override {}

 private:
  DISALLOW_COPY_AND_ASSIGN(FakePrintPreviewUI);
};

class TestPrintPreviewHandler : public PrintPreviewHandler {
 public:
  TestPrintPreviewHandler(std::unique_ptr<PrinterHandler> printer_handler,
                          content::WebContents* initiator)
      : bad_messages_(0),
        test_printer_handler_(std::move(printer_handler)),
        initiator_(initiator) {}

  PrinterHandler* GetPrinterHandler(PrinterType printer_type) override {
    called_for_type_.insert(printer_type);
    return test_printer_handler_.get();
  }

  bool IsCloudPrintEnabled() override { return true; }

  void RegisterForGaiaCookieChanges() override {}
  void UnregisterForGaiaCookieChanges() override {}

  void BadMessageReceived() override { bad_messages_++; }

  content::WebContents* GetInitiator() const override { return initiator_; }

  bool CalledOnlyForType(PrinterType printer_type) {
    return (called_for_type_.size() == 1 &&
            *called_for_type_.begin() == printer_type);
  }

  bool NotCalled() { return called_for_type_.empty(); }

  void reset_calls() { called_for_type_.clear(); }

  int bad_messages() { return bad_messages_; }

 private:
  int bad_messages_;
  base::flat_set<PrinterType> called_for_type_;
  std::unique_ptr<PrinterHandler> test_printer_handler_;
  content::WebContents* const initiator_;

  DISALLOW_COPY_AND_ASSIGN(TestPrintPreviewHandler);
};

}  // namespace

class PrintPreviewHandlerTest : public testing::Test {
 public:
  PrintPreviewHandlerTest() = default;
  ~PrintPreviewHandlerTest() override = default;

  void SetUp() override {
    TestingProfile::Builder builder;
    profile_ = builder.Build();
    initiator_web_contents_ = content::WebContents::Create(
        content::WebContents::CreateParams(profile_.get()));
    content::WebContents* initiator = initiator_web_contents_.get();
    preview_web_contents_ = content::WebContents::Create(
        content::WebContents::CreateParams(profile_.get()));
    PrintViewManager::CreateForWebContents(initiator);
    PrintViewManager::FromWebContents(initiator)->PrintPreviewNow(
        initiator->GetMainFrame(), false);
    web_ui_ = std::make_unique<content::TestWebUI>();
    web_ui_->set_web_contents(preview_web_contents_.get());

    printers_.push_back(GetSimplePrinterInfo(kDummyPrinterName, true));
    auto printer_handler = CreatePrinterHandler(printers_);
    printer_handler_ = printer_handler.get();

    auto preview_handler = std::make_unique<TestPrintPreviewHandler>(
        std::move(printer_handler), initiator);
    preview_handler->set_web_ui(web_ui());
    handler_ = preview_handler.get();

    auto preview_ui = std::make_unique<FakePrintPreviewUI>(
        web_ui(), std::move(preview_handler));
    preview_ui->SetInitiatorTitle(base::ASCIIToUTF16(kDummyInitiatorName));
    web_ui()->SetController(std::move(preview_ui));
  }

  void TearDown() override {
    PrintViewManager::FromWebContents(initiator_web_contents_.get())
        ->PrintPreviewDone();
  }

  virtual std::unique_ptr<TestPrinterHandler> CreatePrinterHandler(
      const std::vector<PrinterInfo>& printers) {
    return std::make_unique<TestPrinterHandler>(printers);
  }

  void Initialize() {
    // Set locale since the delimeters we check in VerifyInitialSettings()
    // depend on it.
    base::test::ScopedRestoreICUDefaultLocale scoped_locale("en");

    // Sending this message will enable javascript, so it must always be called
    // before any other messages are sent.
    base::Value args(base::Value::Type::LIST);
    args.GetList().emplace_back("test-callback-id-0");
    std::unique_ptr<base::ListValue> list_args =
        base::ListValue::From(base::Value::ToUniquePtrValue(std::move(args)));
    handler()->HandleGetInitialSettings(list_args.get());

    // In response to get initial settings, the initial settings are sent back.
    ASSERT_EQ(1u, web_ui()->call_data().size());
  }

  void AssertWebUIEventFired(const content::TestWebUI::CallData& data,
                             const std::string& event_id) {
    EXPECT_EQ("cr.webUIListenerCallback", data.function_name());
    std::string event_fired;
    ASSERT_TRUE(data.arg1()->GetAsString(&event_fired));
    EXPECT_EQ(event_id, event_fired);
  }

  void CheckWebUIResponse(const content::TestWebUI::CallData& data,
                          const std::string& callback_id_in,
                          bool expect_success) {
    EXPECT_EQ("cr.webUIResponse", data.function_name());
    std::string callback_id;
    ASSERT_TRUE(data.arg1()->GetAsString(&callback_id));
    EXPECT_EQ(callback_id_in, callback_id);
    bool success = false;
    ASSERT_TRUE(data.arg2()->GetAsBoolean(&success));
    EXPECT_EQ(expect_success, success);
  }

  // Validates the initial settings structure in the response matches the
  // print_preview.NativeInitialSettings type in
  // chrome/browser/resources/print_preview/native_layer.js. Checks that:
  //   - |default_printer_name| is the printer name returned
  //   - |initiator_title| is the initiator title returned
  //   - |expected_header_footer| is the header/footer state returned, if any
  // Also validates that delimeters are correct for "en" locale (set in
  // Initialize()).  Assumes "test-callback-id-0" was used as the callback id.
  void ValidateInitialSettings(const content::TestWebUI::CallData& data,
                               const std::string& default_printer_name,
                               const std::string& initiator_title,
                               base::Optional<bool> expected_header_footer) {
    CheckWebUIResponse(data, "test-callback-id-0", true);
    const base::Value* settings = data.arg3();
    ASSERT_TRUE(settings->FindKeyOfType("isInKioskAutoPrintMode",
                                        base::Value::Type::BOOLEAN));
    ASSERT_TRUE(settings->FindKeyOfType("isInAppKioskMode",
                                        base::Value::Type::BOOLEAN));

    const base::Value* locale =
        settings->FindKeyOfType("uiLocale", base::Value::Type::STRING);
    ASSERT_TRUE(locale);
    EXPECT_EQ("en", locale->GetString());
    const base::Value* thousands_delimeter = settings->FindKeyOfType(
        "thousandsDelimeter", base::Value::Type::STRING);
    ASSERT_TRUE(thousands_delimeter);
    EXPECT_EQ(",", thousands_delimeter->GetString());
    const base::Value* decimal_delimeter =
        settings->FindKeyOfType("decimalDelimeter", base::Value::Type::STRING);
    ASSERT_TRUE(decimal_delimeter);
    EXPECT_EQ(".", decimal_delimeter->GetString());

    ASSERT_TRUE(
        settings->FindKeyOfType("unitType", base::Value::Type::INTEGER));
    ASSERT_TRUE(settings->FindKeyOfType("previewModifiable",
                                        base::Value::Type::BOOLEAN));
    const base::Value* title =
        settings->FindKeyOfType("documentTitle", base::Value::Type::STRING);
    ASSERT_TRUE(title);
    EXPECT_EQ(initiator_title, title->GetString());
    ASSERT_TRUE(settings->FindKeyOfType("documentHasSelection",
                                        base::Value::Type::BOOLEAN));
    ASSERT_TRUE(settings->FindKeyOfType("shouldPrintSelectionOnly",
                                        base::Value::Type::BOOLEAN));
    const base::Value* printer =
        settings->FindKeyOfType("printerName", base::Value::Type::STRING);
    ASSERT_TRUE(printer);
    EXPECT_EQ(default_printer_name, printer->GetString());

    const base::Value* header_footer =
        settings->FindKeyOfType("headerFooter", base::Value::Type::BOOLEAN);
    ASSERT_EQ(expected_header_footer.has_value(), !!header_footer);
    if (expected_header_footer.has_value())
      EXPECT_EQ(expected_header_footer.value(), header_footer->GetBool());

    ASSERT_TRUE(
        settings->FindKeyOfType("cloudPrintURL", base::Value::Type::STRING));
    ASSERT_TRUE(
        settings->FindKeyOfType("userAccounts", base::Value::Type::LIST));
    ASSERT_TRUE(
        settings->FindKeyOfType("syncAvailable", base::Value::Type::BOOLEAN));
  }

  IPC::TestSink& initiator_sink() {
    content::RenderFrameHost* rfh = initiator_web_contents_->GetMainFrame();
    auto* rph = static_cast<content::MockRenderProcessHost*>(rfh->GetProcess());
    return rph->sink();
  }

  IPC::TestSink& preview_sink() {
    content::RenderFrameHost* rfh = preview_web_contents_->GetMainFrame();
    auto* rph = static_cast<content::MockRenderProcessHost*>(rfh->GetProcess());
    return rph->sink();
  }

  base::DictionaryValue VerifyPreviewMessage() {
    // Verify that the preview was requested from the renderer
    EXPECT_TRUE(
        initiator_sink().GetUniqueMessageMatching(PrintMsg_PrintPreview::ID));
    const IPC::Message* msg =
        initiator_sink().GetFirstMessageMatching(PrintMsg_PrintPreview::ID);
    EXPECT_TRUE(msg);
    PrintMsg_PrintPreview::Param param;
    PrintMsg_PrintPreview::Read(msg, &param);
    return std::move(std::get<0>(param));
  }

  const Profile* profile() { return profile_.get(); }
  PrefService* prefs() { return profile_->GetPrefs(); }
  content::TestWebUI* web_ui() { return web_ui_.get(); }
  TestPrintPreviewHandler* handler() { return handler_; }
  TestPrinterHandler* printer_handler() { return printer_handler_; }
  std::vector<PrinterInfo>& printers() { return printers_; }

 private:
  content::TestBrowserThreadBundle thread_bundle_;
  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<content::TestWebUI> web_ui_;
  content::RenderViewHostTestEnabler rvh_test_enabler_;
  std::unique_ptr<content::WebContents> preview_web_contents_;
  std::unique_ptr<content::WebContents> initiator_web_contents_;
  std::vector<PrinterInfo> printers_;
  TestPrinterHandler* printer_handler_;
  TestPrintPreviewHandler* handler_;

  DISALLOW_COPY_AND_ASSIGN(PrintPreviewHandlerTest);
};

TEST_F(PrintPreviewHandlerTest, InitialSettingsSimple) {
  Initialize();

  // Verify initial settings were sent.
  ValidateInitialSettings(*web_ui()->call_data().back(), kDummyPrinterName,
                          kDummyInitiatorName, {});
}

TEST_F(PrintPreviewHandlerTest, InitialSettingsEnableHeaderFooter) {
  // Set a pref that should take priority over StickySettings.
  prefs()->SetBoolean(prefs::kPrintHeaderFooter, true);
  Initialize();
  ValidateInitialSettings(*web_ui()->call_data().back(), kDummyPrinterName,
                          kDummyInitiatorName, true);
}

TEST_F(PrintPreviewHandlerTest, InitialSettingsDisableHeaderFooter) {
  // Set a pref that should take priority over StickySettings.
  prefs()->SetBoolean(prefs::kPrintHeaderFooter, false);
  Initialize();
  ValidateInitialSettings(*web_ui()->call_data().back(), kDummyPrinterName,
                          kDummyInitiatorName, false);
}

TEST_F(PrintPreviewHandlerTest, GetPrinters) {
  Initialize();

  // Check all three printer types that implement
  // PrinterHandler::StartGetPrinters().
  const PrinterType types[] = {kPrivetPrinter, kExtensionPrinter,
                               kLocalPrinter};
  for (size_t i = 0; i < base::size(types); i++) {
    PrinterType type = types[i];
    handler()->reset_calls();
    base::Value args(base::Value::Type::LIST);
    std::string callback_id_in =
        "test-callback-id-" + base::NumberToString(i + 1);
    args.GetList().emplace_back(callback_id_in);
    args.GetList().emplace_back(type);
    std::unique_ptr<base::ListValue> list_args =
        base::ListValue::From(base::Value::ToUniquePtrValue(std::move(args)));
    handler()->HandleGetPrinters(list_args.get());
    EXPECT_TRUE(handler()->CalledOnlyForType(type));

    // Start with 1 call from initial settings, then add 2 more for each loop
    // iteration (one for printers-added, and one for the response).
    ASSERT_EQ(1u + 2 * (i + 1), web_ui()->call_data().size());

    // Validate printers-added
    const content::TestWebUI::CallData& add_data =
        *web_ui()->call_data()[web_ui()->call_data().size() - 2];
    AssertWebUIEventFired(add_data, "printers-added");
    int type_out;
    ASSERT_TRUE(add_data.arg2()->GetAsInteger(&type_out));
    EXPECT_EQ(type, type_out);
    ASSERT_TRUE(add_data.arg3());
    const base::Value::ListStorage& printer_list = add_data.arg3()->GetList();
    ASSERT_EQ(printer_list.size(), 1u);
    EXPECT_TRUE(printer_list[0].FindKeyOfType("printer_name",
                                              base::Value::Type::STRING));

    // Verify getPrinters promise was resolved successfully.
    const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
    CheckWebUIResponse(data, callback_id_in, true);
  }
}

TEST_F(PrintPreviewHandlerTest, GetPrinterCapabilities) {
  // Add an empty printer to the handler.
  printers().push_back(GetEmptyPrinterInfo());
  printer_handler()->SetPrinters(printers());

  // Initial settings first to enable javascript.
  Initialize();

  // Check all four printer types that implement
  // PrinterHandler::StartGetCapability().
  for (size_t i = 0; i < base::size(kAllTypes); i++) {
    PrinterType type = kAllTypes[i];
    handler()->reset_calls();
    base::Value args(base::Value::Type::LIST);
    std::string callback_id_in =
        "test-callback-id-" + base::NumberToString(i + 1);
    args.GetList().emplace_back(callback_id_in);
    args.GetList().emplace_back(kDummyPrinterName);
    args.GetList().emplace_back(type);
    std::unique_ptr<base::ListValue> list_args =
        base::ListValue::From(base::Value::ToUniquePtrValue(std::move(args)));
    handler()->HandleGetPrinterCapabilities(list_args.get());
    EXPECT_TRUE(handler()->CalledOnlyForType(type));

    // Start with 1 call from initial settings, then add 1 more for each loop
    // iteration.
    ASSERT_EQ(1u + (i + 1), web_ui()->call_data().size());

    // Verify that the printer capabilities promise was resolved correctly.
    const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
    CheckWebUIResponse(data, callback_id_in, true);
    const base::Value* settings = data.arg3();
    ASSERT_TRUE(settings);
    EXPECT_TRUE(settings->FindKeyOfType(kSettingCapabilities,
                                        base::Value::Type::DICTIONARY));
  }

  // Run through the loop again, this time with a printer that has no
  // capabilities.
  for (size_t i = 0; i < base::size(kAllTypes); i++) {
    PrinterType type = kAllTypes[i];
    handler()->reset_calls();
    base::Value args(base::Value::Type::LIST);
    std::string callback_id_in =
        "test-callback-id-" +
        base::NumberToString(i + base::size(kAllTypes) + 1);
    args.GetList().emplace_back(callback_id_in);
    args.GetList().emplace_back("EmptyPrinter");
    args.GetList().emplace_back(type);
    std::unique_ptr<base::ListValue> list_args =
        base::ListValue::From(base::Value::ToUniquePtrValue(std::move(args)));
    handler()->HandleGetPrinterCapabilities(list_args.get());
    EXPECT_TRUE(handler()->CalledOnlyForType(type));

    // Start with 1 call from initial settings plus base::size(kAllTypes) from
    // first loop, then add 1 more for each loop iteration.
    ASSERT_EQ(1u + base::size(kAllTypes) + (i + 1),
              web_ui()->call_data().size());

    // Verify printer capabilities promise was rejected.
    const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
    CheckWebUIResponse(data, callback_id_in, false);
  }
}

TEST_F(PrintPreviewHandlerTest, Print) {
  Initialize();

  // All four printer types can print, as well as cloud printers.
  for (size_t i = 0; i <= base::size(kAllTypes); i++) {
    // Also check cloud print. Use dummy type value of Privet (will be ignored).
    bool cloud = i == base::size(kAllTypes);
    PrinterType type = cloud ? kPrivetPrinter : kAllTypes[i];
    handler()->reset_calls();
    base::Value args(base::Value::Type::LIST);
    std::string callback_id_in =
        "test-callback-id-" + base::NumberToString(i + 1);
    args.GetList().emplace_back(callback_id_in);
    base::Value print_ticket = GetPrintTicket(type, cloud);
    std::string json;
    base::JSONWriter::Write(print_ticket, &json);
    args.GetList().emplace_back(json);
    std::unique_ptr<base::ListValue> list_args =
        base::ListValue::From(base::Value::ToUniquePtrValue(std::move(args)));
    handler()->HandlePrint(list_args.get());

    // Verify correct PrinterHandler was called or that no handler was requested
    // for cloud printers.
    if (cloud) {
      EXPECT_TRUE(handler()->NotCalled());
    } else {
      EXPECT_TRUE(handler()->CalledOnlyForType(type));
    }

    // Verify print promise was resolved successfully.
    const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
    CheckWebUIResponse(data, callback_id_in, true);

    // For cloud print, should also get the encoded data back as a string.
    if (cloud) {
      std::string print_data;
      ASSERT_TRUE(data.arg3()->GetAsString(&print_data));
      std::string expected_data;
      base::Base64Encode(kTestData, &expected_data);
      EXPECT_EQ(print_data, expected_data);
    }
  }
}

TEST_F(PrintPreviewHandlerTest, GetPreview) {
  Initialize();

  base::Value print_ticket = GetPrintPreviewTicket(false);
  std::unique_ptr<base::ListValue> list_args =
      ConstructPreviewArgs("test-callback-id-1", print_ticket);
  handler()->HandleGetPreview(list_args.get());

  // Verify that the preview was requested from the renderer with the
  // appropriate settings.
  base::DictionaryValue preview_params = VerifyPreviewMessage();
  bool preview_id_found = false;
  for (const auto& it : preview_params.DictItems()) {
    if (it.first == kPreviewUIID) {  // This is added by the handler.
      preview_id_found = true;
      continue;
    }
    base::Value* value_in = print_ticket.FindKey(it.first);
    ASSERT_TRUE(value_in);
    EXPECT_EQ(*value_in, it.second);
  }
  EXPECT_TRUE(preview_id_found);
}

TEST_F(PrintPreviewHandlerTest, SendPreviewUpdates) {
  Initialize();

  const char callback_id_in[] = "test-callback-id-1";
  base::Value print_ticket = GetPrintPreviewTicket(false);
  std::unique_ptr<base::ListValue> list_args =
      ConstructPreviewArgs(callback_id_in, print_ticket);
  handler()->HandleGetPreview(list_args.get());
  base::DictionaryValue preview_params = VerifyPreviewMessage();

  // Read the preview UI ID and request ID
  const base::Value* request_value = preview_params.FindKey(kPreviewRequestID);
  ASSERT_TRUE(request_value);
  ASSERT_TRUE(request_value->is_int());
  int preview_request_id = request_value->GetInt();

  const base::Value* ui_value = preview_params.FindKey(kPreviewUIID);
  ASSERT_TRUE(ui_value);
  ASSERT_TRUE(ui_value->is_int());
  int preview_ui_id = ui_value->GetInt();

  // Simulate renderer responses: PageLayoutReady, PageCountReady,
  // PagePreviewReady, and OnPrintPreviewReady will be called in that order.
  base::DictionaryValue layout;
  layout.SetKey(kSettingMarginTop, base::Value(34.0));
  layout.SetKey(kSettingMarginLeft, base::Value(34.0));
  layout.SetKey(kSettingMarginBottom, base::Value(34.0));
  layout.SetKey(kSettingMarginRight, base::Value(34.0));
  layout.SetKey(kSettingContentWidth, base::Value(544.0));
  layout.SetKey(kSettingContentHeight, base::Value(700.0));
  layout.SetKey(kSettingPrintableAreaX, base::Value(17));
  layout.SetKey(kSettingPrintableAreaY, base::Value(17));
  layout.SetKey(kSettingPrintableAreaWidth, base::Value(578));
  layout.SetKey(kSettingPrintableAreaHeight, base::Value(734));
  handler()->SendPageLayoutReady(layout, false, preview_request_id);

  // Verify that page-layout-ready webUI event was fired.
  AssertWebUIEventFired(*web_ui()->call_data().back(), "page-layout-ready");

  // 1 page document. Modifiable so send default 100 scaling.
  handler()->SendPageCountReady(1, 100, preview_request_id);
  AssertWebUIEventFired(*web_ui()->call_data().back(), "page-count-ready");

  // Page at index 0 is ready.
  handler()->SendPagePreviewReady(0, preview_ui_id, preview_request_id);
  AssertWebUIEventFired(*web_ui()->call_data().back(), "page-preview-ready");

  // Print preview is ready.
  handler()->OnPrintPreviewReady(preview_ui_id, preview_request_id);
  CheckWebUIResponse(*web_ui()->call_data().back(), callback_id_in, true);

  // Renderer responses have been as expected.
  EXPECT_EQ(handler()->bad_messages(), 0);

  // None of these should work since there has been no new preview request.
  // Check that there are no new web UI messages sent.
  size_t message_count = web_ui()->call_data().size();
  handler()->SendPageLayoutReady(base::DictionaryValue(), false,
                                 preview_request_id);
  EXPECT_EQ(message_count, web_ui()->call_data().size());
  handler()->SendPageCountReady(1, -1, 0);
  EXPECT_EQ(message_count, web_ui()->call_data().size());
  handler()->OnPrintPreviewReady(0, 0);
  EXPECT_EQ(message_count, web_ui()->call_data().size());

  // Handler should have tried to kill the renderer for each of these.
  EXPECT_EQ(handler()->bad_messages(), 3);
}

class FailingTestPrinterHandler : public TestPrinterHandler {
 public:
  explicit FailingTestPrinterHandler(const std::vector<PrinterInfo>& printers)
      : TestPrinterHandler(printers) {}

  ~FailingTestPrinterHandler() override = default;

  void StartGetCapability(const std::string& destination_id,
                          GetCapabilityCallback callback) override {
    std::move(callback).Run(base::Value());
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(FailingTestPrinterHandler);
};

class PrintPreviewHandlerFailingTest : public PrintPreviewHandlerTest {
 public:
  PrintPreviewHandlerFailingTest() = default;
  ~PrintPreviewHandlerFailingTest() override = default;

  std::unique_ptr<TestPrinterHandler> CreatePrinterHandler(
      const std::vector<PrinterInfo>& printers) override {
    return std::make_unique<FailingTestPrinterHandler>(printers);
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(PrintPreviewHandlerFailingTest);
};

// This test is similar to PrintPreviewHandlerTest.GetPrinterCapabilities, but
// uses FailingTestPrinterHandler instead of TestPrinterHandler. As a result,
// StartGetCapability() always fails, to exercise its callback's failure
// handling path. Failure is different from getting no capabilities.
TEST_F(PrintPreviewHandlerFailingTest, GetPrinterCapabilities) {
  // Add an empty printer to the handler.
  printers().push_back(GetEmptyPrinterInfo());
  printer_handler()->SetPrinters(printers());

  // Initial settings first to enable javascript.
  Initialize();

  // Check all four printer types that implement
  // PrinterHandler::StartGetCapability().
  for (size_t i = 0; i < base::size(kAllTypes); i++) {
    PrinterType type = kAllTypes[i];
    handler()->reset_calls();
    base::Value args(base::Value::Type::LIST);
    std::string callback_id_in =
        "test-callback-id-" + base::NumberToString(i + 1);
    args.GetList().emplace_back(callback_id_in);
    args.GetList().emplace_back(kDummyPrinterName);
    args.GetList().emplace_back(type);
    std::unique_ptr<base::ListValue> list_args =
        base::ListValue::From(base::Value::ToUniquePtrValue(std::move(args)));
    handler()->HandleGetPrinterCapabilities(list_args.get());
    EXPECT_TRUE(handler()->CalledOnlyForType(type));

    // Start with 1 call from initial settings, then add 1 more for each loop
    // iteration.
    ASSERT_EQ(1u + (i + 1), web_ui()->call_data().size());

    // Verify printer capabilities promise was rejected.
    const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
    CheckWebUIResponse(data, callback_id_in, false);
    const base::Value* settings = data.arg3();
    ASSERT_TRUE(settings);
    EXPECT_TRUE(settings->is_none());
  }
}

}  // namespace printing
