chromium/chrome/browser/ash/printing/zeroconf_printer_detector_unittest.cc

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

#include "chrome/browser/ash/printing/zeroconf_printer_detector.h"

#include <algorithm>
#include <functional>
#include <map>
#include <memory>
#include <random>
#include <string>
#include <utility>
#include <vector>

#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/task_environment.h"
#include "chrome/browser/local_discovery/fake_service_discovery_device_lister.h"
#include "chrome/browser/local_discovery/service_discovery_device_lister.h"
#include "net/base/ip_address.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace {

using ::local_discovery::FakeServiceDiscoveryDeviceLister;
using ::local_discovery::ServiceDescription;
using ::local_discovery::ServiceDiscoveryDeviceLister;

// Determine basic printer attributes deterministically but pseudorandomly based
// on the printer name.  The exact values returned here are not really
// important, the important parts are that there's variety based on the name,
// and it's deterministic.

// Should this printer provide usb_MFG and usb_MDL fields?
bool GetUsbFor(const std::string& name) {
  return std::hash<std::string>()(name) & 1;
}

// Get an IP address for this printer.  The returned address may be IPv4 or IPv6
net::IPAddress GetIPAddressFor(const std::string& name) {
  std::mt19937 rng(std::hash<std::string>()(name));
  if (rng() & 1) {
    // Give an IPv4 address.
    return net::IPAddress(rng(), rng(), rng(), rng());
  } else {
    // Give an IPv6 address.
    return net::IPAddress(rng(), rng(), rng(), rng(), rng(), rng(), rng(),
                          rng(), rng(), rng(), rng(), rng(), rng(), rng(),
                          rng(), rng());
  }
}

int GetPortFor(const std::string& name) {
  return (std::hash<std::string>()(name) % 1000) + 1;
}

// Enums for MakeExpectedPrinter().
enum class ServiceType {
  kIpp,     // IPP
  kIpps,    // IPPS
  kIppE,    // IPP-Everywhere
  kIppsE,   // IPPS-Everywhere
  kSocket,  // Socket
  kLpd,     // LPD
};

// This corresponds to MakeServiceDescription() below. Given the same name (and
// the correct service type), this generates the DetectedPrinter record we
// expect from ZeroconfPrinterDetector when it gets that ServiceDescription.
// This needs to be kept in sync with MakeServiceDescription().
PrinterDetector::DetectedPrinter MakeExpectedPrinter(const std::string& name,
                                                     ServiceType service_type) {
  PrinterDetector::DetectedPrinter detected;
  chromeos::Printer& printer = detected.printer;
  net::IPAddress ip_address = GetIPAddressFor(name);
  int port = GetPortFor(name);
  std::string scheme;
  std::string rp = base::StrCat({name, "_rp"});
  switch (service_type) {
    case ServiceType::kIpp:
      scheme = "ipp";
      break;
    case ServiceType::kIpps:
      scheme = "ipps";
      break;
    case ServiceType::kIppE:
      scheme = "ipp";
      printer.mutable_ppd_reference()->autoconf = true;
      break;
    case ServiceType::kIppsE:
      scheme = "ipps";
      printer.mutable_ppd_reference()->autoconf = true;
      break;
    case ServiceType::kSocket:
      scheme = "socket";
      rp = "";
      break;
    case ServiceType::kLpd:
      scheme = "lpd";
      break;
  }
  printer.SetUri(base::StringPrintf("%s://%s.local:%d/%s", scheme.c_str(),
                                    name.c_str(), port, rp.c_str()));

  printer.set_uuid(base::StrCat({name, "_UUID"}));
  printer.set_display_name(name);
  printer.set_description(base::StrCat({name, "_note"}));
  printer.set_make_and_model(base::StrCat({name, "_ty"}));
  detected.ppd_search_data.make_and_model.push_back(printer.make_and_model());
  detected.ppd_search_data.make_and_model.push_back(
      base::StrCat({name, "_product"}));
  if (GetUsbFor(name)) {
    // We should get an effective make and model guess from the usb fields
    // if they exist.
    detected.ppd_search_data.make_and_model.push_back(
        base::StrCat({name, "_usb_MFG ", name, "_usb_MDL"}));
  }

  return detected;
}

// Creates a deterministic ServiceDescription based on the service name and
// type. See the note on MakeExpectedPrinter() above. This must be kept in sync
// with MakeExpectedPrinter().
ServiceDescription MakeServiceDescription(const std::string& name,
                                          const std::string& service_type) {
  ServiceDescription sd;
  sd.service_name = base::StrCat({name, ".", service_type});
  sd.metadata.push_back(base::StrCat({"ty=", name, "_ty"}));
  sd.metadata.push_back(base::StrCat({"product=(", name, "_product)"}));
  if (GetUsbFor(name)) {
    sd.metadata.push_back(base::StrCat({"usb_MFG=", name, "_usb_MFG"}));
    sd.metadata.push_back(base::StrCat({"usb_MDL=", name, "_usb_MDL"}));
  }
  sd.metadata.push_back(base::StrCat({"rp=", name, "_rp"}));
  sd.metadata.push_back(base::StrCat({"note=", name, "_note"}));
  sd.metadata.push_back(base::StrCat({"UUID=", name, "_UUID"}));
  sd.address.set_host(base::StrCat({name, ".local"}));
  sd.ip_address = GetIPAddressFor(name);
  sd.address.set_port(GetPortFor(name));
  return sd;
}

class ZeroconfPrinterDetectorTest : public testing::Test {
 public:
  ZeroconfPrinterDetectorTest() {
    auto* runner = task_environment_.GetMainThreadTaskRunner().get();
    auto ipp_lister = std::make_unique<FakeServiceDiscoveryDeviceLister>(
        runner, ZeroconfPrinterDetector::kIppServiceName);
    ipp_lister_ = ipp_lister.get();
    auto ipps_lister = std::make_unique<FakeServiceDiscoveryDeviceLister>(
        runner, ZeroconfPrinterDetector::kIppsServiceName);
    ipps_lister_ = ipps_lister.get();
    auto ippe_lister = std::make_unique<FakeServiceDiscoveryDeviceLister>(
        runner, ZeroconfPrinterDetector::kIppEverywhereServiceName);
    ippe_lister_ = ippe_lister.get();
    auto ippse_lister = std::make_unique<FakeServiceDiscoveryDeviceLister>(
        runner, ZeroconfPrinterDetector::kIppsEverywhereServiceName);
    ippse_lister_ = ippse_lister.get();
    auto socket_lister = std::make_unique<FakeServiceDiscoveryDeviceLister>(
        runner, ZeroconfPrinterDetector::kSocketServiceName);
    socket_lister_ = socket_lister.get();
    auto lpd_lister = std::make_unique<FakeServiceDiscoveryDeviceLister>(
        runner, ZeroconfPrinterDetector::kLpdServiceName);
    lpd_lister_ = lpd_lister.get();

    listers_[ZeroconfPrinterDetector::kIppServiceName] = std::move(ipp_lister);
    listers_[ZeroconfPrinterDetector::kIppsServiceName] =
        std::move(ipps_lister);
    listers_[ZeroconfPrinterDetector::kIppEverywhereServiceName] =
        std::move(ippe_lister);
    listers_[ZeroconfPrinterDetector::kIppsEverywhereServiceName] =
        std::move(ippse_lister);
    listers_[ZeroconfPrinterDetector::kSocketServiceName] =
        std::move(socket_lister);
    listers_[ZeroconfPrinterDetector::kLpdServiceName] = std::move(lpd_lister);
  }
  ~ZeroconfPrinterDetectorTest() override = default;

  void CreateDetectorWithIppRejectList(
      base::flat_set<std::string> ipp_reject_list) {
    detector_ = ZeroconfPrinterDetector::CreateForTesting(
        &listers_, std::move(ipp_reject_list));
    // The previously allocated listers_ are swapped into the detector_, and so
    // the unique_ptr values of the listers_ map are no longer valid at this
    // point.  The ipp[se]_lister_ raw pointers are kept as seperate members to
    // keep the lister fakes accessible after ownership is transferred into the
    // detector.
    listers_.clear();
    detector_->RegisterPrintersFoundCallback(base::BindRepeating(
        &ZeroconfPrinterDetectorTest::OnPrintersFound, base::Unretained(this)));
    ipp_lister_->SetDelegate(detector_.get());
    ipps_lister_->SetDelegate(detector_.get());
    ippe_lister_->SetDelegate(detector_.get());
    ippse_lister_->SetDelegate(detector_.get());
    socket_lister_->SetDelegate(detector_.get());
    lpd_lister_->SetDelegate(detector_.get());
  }

  void CreateDetector() { CreateDetectorWithIppRejectList({}); }

  // Expect that the most up-to-date results from the detector match those
  // in printers.
  void ExpectPrintersAre(
      const std::vector<PrinterDetector::DetectedPrinter>& printers) {
    // The last observer callback should tell us the same thing as the querying
    // the detector manually.
    ASSERT_FALSE(printers_found_callbacks_.empty());
    ExpectPrintersEq(printers, printers_found_callbacks_.back());
    ExpectPrintersEq(printers, detector_->GetPrinters());
  }

  // Expect that the detected printers list is empty.
  void ExpectPrintersEmpty() {
    // Assert that the most recent callbacks are empty.
    ASSERT_FALSE(printers_found_callbacks_.empty());
    ASSERT_TRUE(printers_found_callbacks_.back().empty());
    ASSERT_TRUE(detector_->GetPrinters().empty());
  }

  // Expect that the given vectors have the same contents.  The ordering
  // may be different.
  void ExpectPrintersEq(
      const std::vector<PrinterDetector::DetectedPrinter>& expected,
      const std::vector<PrinterDetector::DetectedPrinter>& actual) {
    if (expected.size() != actual.size()) {
      ADD_FAILURE() << "Printers size mismatch, found " << actual.size()
                    << " expected " << expected.size();
      return;
    }
    std::vector<PrinterDetector::DetectedPrinter> sorted_expected = expected;
    std::vector<PrinterDetector::DetectedPrinter> sorted_actual = actual;

    std::sort(sorted_expected.begin(), sorted_expected.end(),
              [](const PrinterDetector::DetectedPrinter& a,
                 const PrinterDetector::DetectedPrinter& b) -> bool {
                return a.printer.uuid() < b.printer.uuid();
              });
    std::sort(sorted_actual.begin(), sorted_actual.end(),
              [](const PrinterDetector::DetectedPrinter& a,
                 const PrinterDetector::DetectedPrinter& b) -> bool {
                return a.printer.uuid() < b.printer.uuid();
              });
    for (size_t i = 0; i < sorted_expected.size(); ++i) {
      ExpectPrinterEq(sorted_expected[i], sorted_actual[i]);
    }
  }

  void ExpectPrinterEq(const PrinterDetector::DetectedPrinter& expected,
                       const PrinterDetector::DetectedPrinter& actual) {
    EXPECT_EQ(expected.printer.uri(), actual.printer.uri());
    // We don't have a good way to directly check for an expected id.
    EXPECT_EQ(expected.printer.uuid(), actual.printer.uuid());
    EXPECT_EQ(expected.printer.display_name(), actual.printer.display_name());
    EXPECT_EQ(expected.printer.description(), actual.printer.description());
    EXPECT_EQ(expected.printer.IsIppEverywhere(),
              actual.printer.IsIppEverywhere());
    EXPECT_EQ(expected.printer.make_and_model(),
              actual.printer.make_and_model());
    EXPECT_EQ(expected.ppd_search_data.usb_vendor_id,
              actual.ppd_search_data.usb_vendor_id);
    EXPECT_EQ(expected.ppd_search_data.usb_product_id,
              actual.ppd_search_data.usb_product_id);
    EXPECT_EQ(expected.ppd_search_data.make_and_model,
              actual.ppd_search_data.make_and_model);
  }

  // PrinterDetector callback.
  void OnPrintersFound(
      const std::vector<PrinterDetector::DetectedPrinter>& printers) {
    printers_found_callbacks_.push_back(printers);
  }

 protected:
  // Runs pending tasks regardless of delay.
  void CompleteTasks() { task_environment_.FastForwardUntilNoTasksRemain(); }

  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

  // Device listers fakes.  These are initialized when the test is constructed.
  // These pointers don't involve ownership; ownership of the listers starts
  // with this class in listers_ when the test starts, and is transferred to
  // detector_ when the detector is created.  Throughout, the listers remain
  // available to the test via these pointers.
  raw_ptr<FakeServiceDiscoveryDeviceLister, DanglingUntriaged> ipp_lister_;
  raw_ptr<FakeServiceDiscoveryDeviceLister, DanglingUntriaged> ipps_lister_;
  raw_ptr<FakeServiceDiscoveryDeviceLister, DanglingUntriaged> ippe_lister_;
  raw_ptr<FakeServiceDiscoveryDeviceLister, DanglingUntriaged> ippse_lister_;
  raw_ptr<FakeServiceDiscoveryDeviceLister, DanglingUntriaged> socket_lister_;
  raw_ptr<FakeServiceDiscoveryDeviceLister, DanglingUntriaged> lpd_lister_;

  // Detector under test.
  std::unique_ptr<ZeroconfPrinterDetector> detector_;

  // Saved copies of all the things given to OnPrintersFound.
  std::vector<std::vector<PrinterDetector::DetectedPrinter>>
      printers_found_callbacks_;

 private:
  // Temporary storage for the device listers, between the time the test is
  // constructed and the detector is created.  Tests shouldn't access this
  // directly, use the ipp*_lister_ variables instead.
  std::map<std::string, std::unique_ptr<ServiceDiscoveryDeviceLister>> listers_;
};

// Very basic stuff, one printer of each protocol we support.
TEST_F(ZeroconfPrinterDetectorTest, SingleIppPrinter) {
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer1", ZeroconfPrinterDetector::kIppServiceName));
  CreateDetector();
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer1", ServiceType::kIpp)});
}

TEST_F(ZeroconfPrinterDetectorTest, SingleIppsPrinter) {
  ipps_lister_->Announce(MakeServiceDescription(
      "Printer2", ZeroconfPrinterDetector::kIppsServiceName));
  CreateDetector();
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer2", ServiceType::kIpps)});
}

TEST_F(ZeroconfPrinterDetectorTest, SingleIppEverywherePrinter) {
  ippe_lister_->Announce(MakeServiceDescription(
      "Printer3", ZeroconfPrinterDetector::kIppEverywhereServiceName));
  CreateDetector();
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer3", ServiceType::kIppE)});
}

TEST_F(ZeroconfPrinterDetectorTest, SingleIppsEverywherePrinter) {
  ippse_lister_->Announce(MakeServiceDescription(
      "Printer4", ZeroconfPrinterDetector::kIppsEverywhereServiceName));
  CreateDetector();
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer4", ServiceType::kIppsE)});
}

TEST_F(ZeroconfPrinterDetectorTest, SingleSocketPrinter) {
  socket_lister_->Announce(MakeServiceDescription(
      "Printer5", ZeroconfPrinterDetector::kSocketServiceName));
  CreateDetector();
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kSocket)});
}

TEST_F(ZeroconfPrinterDetectorTest, SingleLpdPrinter) {
  lpd_lister_->Announce(MakeServiceDescription(
      "Printer6", ZeroconfPrinterDetector::kLpdServiceName));
  CreateDetector();
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer6", ServiceType::kLpd)});
}

// Test that an announce after the detector creation shows up as a printer.
TEST_F(ZeroconfPrinterDetectorTest, AnnounceAfterDetectorCreation) {
  CreateDetector();
  CompleteTasks();
  ippse_lister_->Announce(MakeServiceDescription(
      "Printer4", ZeroconfPrinterDetector::kIppsEverywhereServiceName));
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer4", ServiceType::kIppsE)});
}

// Test that we use the same printer ID regardless of which service type it
// comes to us from.
TEST_F(ZeroconfPrinterDetectorTest, StableIds) {
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer1", ZeroconfPrinterDetector::kIppServiceName));
  CreateDetector();
  CompleteTasks();
  ASSERT_FALSE(printers_found_callbacks_.empty());
  ASSERT_EQ(1U, printers_found_callbacks_.back().size());
  // Grab the id when it's an IPPS printer We should continue to get the same id
  // regardless of service type.
  std::string id = printers_found_callbacks_.back()[0].printer.id();

  // Remove it as an IPP printer, add it as an IPPS printer.
  ipp_lister_->Remove("Printer1");
  CompleteTasks();
  ASSERT_TRUE(printers_found_callbacks_.back().empty());
  ipps_lister_->Announce(MakeServiceDescription(
      "Printer1", ZeroconfPrinterDetector::kIppsServiceName));
  CompleteTasks();
  ASSERT_EQ(1U, printers_found_callbacks_.back().size());
  // Id should be the same.
  ASSERT_EQ(id, printers_found_callbacks_.back()[0].printer.id());

  // Remove it as an IPPS printer, add it as an IPP-Everywhere printer.
  ipps_lister_->Remove("Printer1");
  CompleteTasks();
  ASSERT_TRUE(printers_found_callbacks_.back().empty());
  ippe_lister_->Announce(MakeServiceDescription(
      "Printer1", ZeroconfPrinterDetector::kIppEverywhereServiceName));
  CompleteTasks();
  ASSERT_EQ(1U, printers_found_callbacks_.back().size());
  // Id should be the same.
  ASSERT_EQ(id, printers_found_callbacks_.back()[0].printer.id());

  // Remove it as an IPP-Everywhere printer, add it as an IPPS-Everywhere
  // printer.
  ippe_lister_->Remove("Printer1");
  CompleteTasks();
  ASSERT_TRUE(printers_found_callbacks_.back().empty());
  ippse_lister_->Announce(MakeServiceDescription(
      "Printer1", ZeroconfPrinterDetector::kIppsEverywhereServiceName));
  CompleteTasks();
  ASSERT_EQ(1U, printers_found_callbacks_.back().size());
  // Id should be the same.
  ASSERT_EQ(id, printers_found_callbacks_.back()[0].printer.id());

  // Remove it as an IPPS-Everywhere printer, add it as a socket printer.
  ippse_lister_->Remove("Printer1");
  CompleteTasks();
  ASSERT_TRUE(printers_found_callbacks_.back().empty());
  socket_lister_->Announce(MakeServiceDescription(
      "Printer1", ZeroconfPrinterDetector::kSocketServiceName));
  CompleteTasks();
  ASSERT_EQ(1U, printers_found_callbacks_.back().size());
  // Id should be the same.
  ASSERT_EQ(id, printers_found_callbacks_.back()[0].printer.id());

  // Remove it as a socket printer, add it as an LPD printer.
  socket_lister_->Remove("Printer1");
  CompleteTasks();
  ASSERT_TRUE(printers_found_callbacks_.back().empty());
  lpd_lister_->Announce(MakeServiceDescription(
      "Printer1", ZeroconfPrinterDetector::kLpdServiceName));
  CompleteTasks();
  ASSERT_EQ(1U, printers_found_callbacks_.back().size());
  // Id should be the same.
  ASSERT_EQ(id, printers_found_callbacks_.back()[0].printer.id());
}

// Test a basic removal.
TEST_F(ZeroconfPrinterDetectorTest, Removal) {
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer5", ZeroconfPrinterDetector::kIppServiceName));
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer6", ZeroconfPrinterDetector::kIppServiceName));
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer7", ZeroconfPrinterDetector::kIppServiceName));
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer8", ZeroconfPrinterDetector::kIppServiceName));
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer9", ZeroconfPrinterDetector::kIppServiceName));
  CreateDetector();
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kIpp),
                     MakeExpectedPrinter("Printer6", ServiceType::kIpp),
                     MakeExpectedPrinter("Printer7", ServiceType::kIpp),
                     MakeExpectedPrinter("Printer8", ServiceType::kIpp),
                     MakeExpectedPrinter("Printer9", ServiceType::kIpp)});
  ipp_lister_->Remove("Printer7");
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kIpp),
                     MakeExpectedPrinter("Printer6", ServiceType::kIpp),
                     MakeExpectedPrinter("Printer8", ServiceType::kIpp),
                     MakeExpectedPrinter("Printer9", ServiceType::kIpp)});
}

// Test that, when the same printer appears in multiple services, we
// use the highest priority one.  Priorities, from highest to lowest
// are IPPS-E, IPP-E, IPPS, IPP, Socket, LPD.
TEST_F(ZeroconfPrinterDetectorTest, ServiceTypePriorities) {
  // Advertise on all services.
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer5", ZeroconfPrinterDetector::kIppServiceName));
  ipps_lister_->Announce(MakeServiceDescription(
      "Printer5", ZeroconfPrinterDetector::kIppsServiceName));
  ippe_lister_->Announce(MakeServiceDescription(
      "Printer5", ZeroconfPrinterDetector::kIppEverywhereServiceName));
  ippse_lister_->Announce(MakeServiceDescription(
      "Printer5", ZeroconfPrinterDetector::kIppsEverywhereServiceName));
  socket_lister_->Announce(MakeServiceDescription(
      "Printer5", ZeroconfPrinterDetector::kSocketServiceName));
  lpd_lister_->Announce(MakeServiceDescription(
      "Printer5", ZeroconfPrinterDetector::kLpdServiceName));
  CreateDetector();
  CompleteTasks();
  // IPPS-E is highest priority.
  ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kIppsE)});
  ippse_lister_->Remove("Printer5");
  CompleteTasks();
  // IPP-E is highest remaining priority.
  ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kIppE)});

  ippe_lister_->Remove("Printer5");
  CompleteTasks();
  // IPPS is highest remaining priority.
  ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kIpps)});

  ipps_lister_->Remove("Printer5");
  CompleteTasks();
  // IPP is highest remaining priority.
  ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kIpp)});

  ipp_lister_->Remove("Printer5");
  CompleteTasks();
  // Socket is highest remaining entry.
  ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kSocket)});

  socket_lister_->Remove("Printer5");
  CompleteTasks();
  // LPD is only remaining entry.
  ExpectPrintersAre({MakeExpectedPrinter("Printer5", ServiceType::kLpd)});

  lpd_lister_->Remove("Printer5");
  CompleteTasks();
  // No entries left.
  ExpectPrintersEmpty();
}

// Test a printer that is known not to work with IPP/IPPS and make sure a
// different protocol is chosen.
TEST_F(ZeroconfPrinterDetectorTest, RejectIpp) {
  std::string bad_ipp_printer = "manufacturer awesome printer-name";
  base::flat_set<std::string> reject_list;
  // We have to add the _ty suffix to match how MakeServiceDescription and
  // MakeExpectedPrinter work.
  reject_list.insert(bad_ipp_printer + "_ty");
  // Advertise on IPP and LPD services.
  ipp_lister_->Announce(MakeServiceDescription(
      bad_ipp_printer, ZeroconfPrinterDetector::kIppServiceName));
  ipps_lister_->Announce(MakeServiceDescription(
      bad_ipp_printer, ZeroconfPrinterDetector::kIppsServiceName));
  lpd_lister_->Announce(MakeServiceDescription(
      bad_ipp_printer, ZeroconfPrinterDetector::kLpdServiceName));
  CreateDetectorWithIppRejectList(reject_list);
  CompleteTasks();

  // Should be rejected for IPPS-E and IPP-E, so it should only exist in LPD.
  ExpectPrintersAre({MakeExpectedPrinter(bad_ipp_printer, ServiceType::kLpd)});

  lpd_lister_->Remove(bad_ipp_printer);
  CompleteTasks();
  // No entries left.
  ExpectPrintersEmpty();
}

// Test that cache flushes appropriately remove entries.
TEST_F(ZeroconfPrinterDetectorTest, CacheFlushes) {
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer6", ZeroconfPrinterDetector::kIppServiceName));
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer7", ZeroconfPrinterDetector::kIppServiceName));
  ipps_lister_->Announce(MakeServiceDescription(
      "Printer7", ZeroconfPrinterDetector::kIppsServiceName));
  ipps_lister_->Announce(MakeServiceDescription(
      "Printer8", ZeroconfPrinterDetector::kIppsServiceName));
  ippe_lister_->Announce(MakeServiceDescription(
      "Printer8", ZeroconfPrinterDetector::kIppEverywhereServiceName));
  ippe_lister_->Announce(MakeServiceDescription(
      "Printer9", ZeroconfPrinterDetector::kIppEverywhereServiceName));
  ippse_lister_->Announce(MakeServiceDescription(
      "Printer9", ZeroconfPrinterDetector::kIppsEverywhereServiceName));
  ippse_lister_->Announce(MakeServiceDescription(
      "Printer10", ZeroconfPrinterDetector::kIppsEverywhereServiceName));
  socket_lister_->Announce(MakeServiceDescription(
      "Printer10", ZeroconfPrinterDetector::kSocketServiceName));
  socket_lister_->Announce(MakeServiceDescription(
      "Printer11", ZeroconfPrinterDetector::kSocketServiceName));
  lpd_lister_->Announce(MakeServiceDescription(
      "Printer11", ZeroconfPrinterDetector::kLpdServiceName));
  lpd_lister_->Announce(MakeServiceDescription(
      "Printer12", ZeroconfPrinterDetector::kLpdServiceName));

  CreateDetector();
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer6", ServiceType::kIpp),
                     MakeExpectedPrinter("Printer7", ServiceType::kIpps),
                     MakeExpectedPrinter("Printer8", ServiceType::kIppE),
                     MakeExpectedPrinter("Printer9", ServiceType::kIppsE),
                     MakeExpectedPrinter("Printer10", ServiceType::kIppsE),
                     MakeExpectedPrinter("Printer11", ServiceType::kSocket),
                     MakeExpectedPrinter("Printer12", ServiceType::kLpd)});

  ipps_lister_->Clear();

  CompleteTasks();
  // With the IPPS lister cleared, all printers should be cleared.
  ExpectPrintersEmpty();

  // We should have restarted discovery after dealing with the cache flush.
  EXPECT_TRUE(ipps_lister_->discovery_started());

  // Just for kicks, announce something new at this point.
  ipps_lister_->Announce(MakeServiceDescription(
      "Printer13", ZeroconfPrinterDetector::kIppsServiceName));
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer13", ServiceType::kIpps)});

  // Clear out the IPPS lister, which will clear all printers too.
  ipps_lister_->Clear();
  CompleteTasks();

  // With the IPPS lister cleared, Printer13 should disappear.
  ExpectPrintersEmpty();
  EXPECT_TRUE(ippe_lister_->discovery_started());
}

// Test some general traffic with a mix of everything we expect to handle.
TEST_F(ZeroconfPrinterDetectorTest, GeneralMixedTraffic) {
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer12", ZeroconfPrinterDetector::kIppServiceName));
  ipps_lister_->Announce(MakeServiceDescription(
      "Printer12", ZeroconfPrinterDetector::kIppsServiceName));
  ipps_lister_->Announce(MakeServiceDescription(
      "Printer13", ZeroconfPrinterDetector::kIppsServiceName));
  ippse_lister_->Announce(MakeServiceDescription(
      "Printer14", ZeroconfPrinterDetector::kIppsEverywhereServiceName));
  ipps_lister_->Announce(MakeServiceDescription(
      "Printer15", ZeroconfPrinterDetector::kIppsServiceName));
  socket_lister_->Announce(MakeServiceDescription(
      "Printer16", ZeroconfPrinterDetector::kSocketServiceName));
  lpd_lister_->Announce(MakeServiceDescription(
      "Printer17", ZeroconfPrinterDetector::kLpdServiceName));

  CreateDetector();
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer12", ServiceType::kIpps),
                     MakeExpectedPrinter("Printer13", ServiceType::kIpps),
                     MakeExpectedPrinter("Printer14", ServiceType::kIppsE),
                     MakeExpectedPrinter("Printer15", ServiceType::kIpps),
                     MakeExpectedPrinter("Printer16", ServiceType::kSocket),
                     MakeExpectedPrinter("Printer17", ServiceType::kLpd)});

  ippe_lister_->Announce(MakeServiceDescription(
      "Printer13", ZeroconfPrinterDetector::kIppEverywhereServiceName));
  ipp_lister_->Announce(MakeServiceDescription(
      "Printer18", ZeroconfPrinterDetector::kIppServiceName));
  CompleteTasks();
  ExpectPrintersAre({MakeExpectedPrinter("Printer12", ServiceType::kIpps),
                     MakeExpectedPrinter("Printer13", ServiceType::kIppE),
                     MakeExpectedPrinter("Printer14", ServiceType::kIppsE),
                     MakeExpectedPrinter("Printer15", ServiceType::kIpps),
                     MakeExpectedPrinter("Printer16", ServiceType::kSocket),
                     MakeExpectedPrinter("Printer17", ServiceType::kLpd),
                     MakeExpectedPrinter("Printer18", ServiceType::kIpp)});

  ipp_lister_->Remove("NonexistantPrinter");
  ipps_lister_->Remove("Printer12");
  ipps_lister_->Clear();
  CompleteTasks();
  ExpectPrintersEmpty();
}

// Verify tasks are cleaned up properly when class is destroyed.
TEST_F(ZeroconfPrinterDetectorTest, DestroyedWithTasksPending) {
  CreateDetector();
  // Cause a callback to be queued.
  ipp_lister_->Announce(MakeServiceDescription(
      "TestPrinter", ZeroconfPrinterDetector::kIppServiceName));
  // Run listers but don't run the delayed tasks.
  task_environment_.RunUntilIdle();

  // Delete the detector.
  detector_.reset();

  // Clear task queues where we would crash if we did something wrong.
  task_environment_.FastForwardUntilNoTasksRemain();
  SUCCEED();
}

}  // namespace
}  // namespace ash