// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/printing/ppd_metadata_manager.h"
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/time/clock.h"
#include "chromeos/printing/fake_printer_config_cache.h"
#include "chromeos/printing/ppd_metadata_matchers.h"
#include "chromeos/printing/ppd_metadata_parser.h"
#include "chromeos/printing/ppd_provider.h"
#include "chromeos/printing/printer_config_cache.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace {
using ::testing::ElementsAre;
using ::testing::Pair;
using ::testing::StrEq;
using ::testing::UnorderedElementsAre;
// Default browser locale used to construct PpdMetadataManager instances
// in the test fixture. Arbitrarily chosen. Changeable by calling
// PpdMetadataManagerTest::NewManagerWithLocale().
constexpr std::string_view kBrowserLocaleForTesting = "en-US";
// Arbitrarily chosen TimeDelta used in test cases that are not
// time-senstive.
constexpr base::TimeDelta kArbitraryTimeDelta = base::Seconds(30LL);
// Arbitrarily malformed JSON used to exercise code paths in which
// parsing fails.
constexpr std::string_view kInvalidJson = "blah blah invalid JSON";
// Caller may bind a default-constructed base::RepeatingClosure to
// any Catch*() method, indicating that they don't want anything run.
class PpdMetadataManagerTest : public ::testing::Test {
public:
// Callback method appropriate for passing to
// PpdMetadataManager::GetLocale().
void CatchGetLocale(base::RepeatingClosure quit_closure, bool succeeded) {
results_.get_locale_succeeded = succeeded;
if (quit_closure) {
quit_closure.Run();
}
}
// Callback method appropriate for passing to
// PpdMetadataManager::GetManufacturers().
void CatchGetManufacturers(base::RepeatingClosure quit_closure,
PpdProvider::CallbackResultCode code,
const std::vector<std::string>& manufacturers) {
results_.get_manufacturers_code = code;
results_.manufacturers = manufacturers;
if (quit_closure) {
quit_closure.Run();
}
}
// Callback method appropriate for passing to
// PpdMetadataManager::GetPrinters().
void CatchGetPrinters(base::RepeatingClosure quit_closure,
bool succeeded,
const ParsedPrinters& printers) {
results_.get_printers_succeeded = succeeded;
results_.printers = printers;
if (quit_closure) {
quit_closure.Run();
}
}
// Callback method appropriate for passing to
// PpdMetadataManager::FindAllEmmsAvailableInIndex().
void CatchFindAllEmmsAvailableInIndex(
base::RepeatingClosure quit_closure,
const base::flat_map<std::string, ParsedIndexValues>& values) {
results_.available_effective_make_and_model_strings = values;
if (quit_closure) {
quit_closure.Run();
}
}
// Callback method appropriate for passing to
// PpdMetadataManager::FindDeviceInUsbIndex().
void CatchFindDeviceInUsbIndex(base::RepeatingClosure quit_closure,
const std::string& value) {
results_.effective_make_and_model_string_from_usb_index = value;
if (quit_closure) {
quit_closure.Run();
}
}
// Callback method appropriate for passing to
// PpdMetadataManager::GetUsbManufacturerName().
void CatchGetUsbManufacturerName(base::RepeatingClosure quit_closure,
const std::string& value) {
results_.usb_manufacturer_name = value;
if (quit_closure) {
quit_closure.Run();
}
}
// Callback method appropriate for passing to
// PpdMetadataManager::SplitMakeAndModel().
void CatchSplitMakeAndModel(base::RepeatingClosure quit_closure,
PpdProvider::CallbackResultCode code,
const std::string& make,
const std::string& model) {
results_.split_make_and_model_code = code;
results_.split_make = make;
results_.split_model = model;
if (quit_closure) {
quit_closure.Run();
}
}
protected:
// Convenience container that organizes all callback results.
struct CallbackLandingArea {
CallbackLandingArea()
: get_locale_succeeded(false), get_printers_succeeded(false) {}
~CallbackLandingArea() = default;
// Landing area for PpdMetadataManager::GetLocale().
bool get_locale_succeeded;
// Landing area for PpdMetadataManager::GetManufacturers().
PpdProvider::CallbackResultCode get_manufacturers_code;
std::vector<std::string> manufacturers;
// Landing area for PpdMetadataManager::GetPrinters().
bool get_printers_succeeded;
ParsedPrinters printers;
// Landing area for
// PpdMetadataManager::FindAllEmmsAvailableInIndex().
base::flat_map<std::string, ParsedIndexValues>
available_effective_make_and_model_strings;
// Landing area for
// PpdMetadataManager::FindDeviceInUsbIndex().
std::string effective_make_and_model_string_from_usb_index;
// Landing area for
// PpdMetadataManager::GetUsbManufacturerName().
std::string usb_manufacturer_name;
// Landing area for PpdMetadataManager::SplitMakeAndModel().
PpdProvider::CallbackResultCode split_make_and_model_code;
std::string split_make;
std::string split_model;
};
PpdMetadataManagerTest()
: task_environment_(base::test::TaskEnvironment::MainThreadType::IO),
manager_(PpdMetadataManager::Create(
kBrowserLocaleForTesting,
PpdIndexChannel::kProduction,
&clock_,
std::make_unique<FakePrinterConfigCache>())) {}
// Borrows and returns a pointer to the config cache owned by the
// |manager_|.
//
// Useful for adjusting availability of (fake) network resources.
FakePrinterConfigCache* GetFakeCache() {
return reinterpret_cast<FakePrinterConfigCache*>(
manager_->GetPrinterConfigCacheForTesting());
}
// Recreates |manager_| with a new |browser_locale|.
//
// Useful for testing the manager's ability to parse and select a
// proper metadata locale.
void NewManagerWithLocale(std::string_view browser_locale) {
manager_.reset();
manager_ = PpdMetadataManager::Create(
browser_locale, PpdIndexChannel::kProduction, &clock_,
std::make_unique<FakePrinterConfigCache>());
}
// Holder for all callback results.
CallbackLandingArea results_;
// Environment for task schedulers.
base::test::TaskEnvironment task_environment_;
// Controlled clock that dispenses times of Fetch().
base::SimpleTestClock clock_;
// Class under test.
std::unique_ptr<PpdMetadataManager> manager_;
};
// Verifies that the manager can fetch and parse the best-fit
// locale from the Chrome OS Printing serving root.
//
// This test is done against the default browser locale used
// throughout this suite, "en-US."
TEST_F(PpdMetadataManagerTest, CanGetLocale) {
// Known interaction: the manager will fetch the locales metadata.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/locales.json", R"({ "locales": [ "de", "en", "es" ] })");
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetLocale,
base::Unretained(this), loop.QuitClosure());
auto call =
base::BindOnce(&PpdMetadataManager::GetLocale,
base::Unretained(manager_.get()), std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_TRUE(results_.get_locale_succeeded);
EXPECT_THAT(manager_->ExposeMetadataLocaleForTesting(), StrEq("en"));
}
// Verifies that the manager defaults to the English ("en") locale
// when it can find no closer fit for the browser locale.
TEST_F(PpdMetadataManagerTest, DefaultsToEnglishLocale) {
// Sets an arbitrarily chosen locale quite distant from what the
// fake serving root will have available.
NewManagerWithLocale("ja-JP");
// Known interaction: the manager will fetch the locales metadata.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/locales.json",
R"({ "locales": [ "de", "en", "es", "wo" ] })");
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetLocale,
base::Unretained(this), loop.QuitClosure());
auto call =
base::BindOnce(&PpdMetadataManager::GetLocale,
base::Unretained(manager_.get()), std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_TRUE(results_.get_locale_succeeded);
EXPECT_THAT(manager_->ExposeMetadataLocaleForTesting(), StrEq("en"));
}
// Given that the browser locale is not "en-US," verifies that the
// manager can select a best-fit locale when one is available.
TEST_F(PpdMetadataManagerTest, CanSelectNonEnglishCloseFitLocale) {
// It's not "en-US" and is close to advertised metadata locale "es."
NewManagerWithLocale("es-MX");
// Known interaction: the manager will fetch the locales metadata.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/locales.json",
R"({ "locales": [ "de", "en", "es", "wo" ] })");
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetLocale,
base::Unretained(this), loop.QuitClosure());
auto call =
base::BindOnce(&PpdMetadataManager::GetLocale,
base::Unretained(manager_.get()), std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_TRUE(results_.get_locale_succeeded);
EXPECT_THAT(manager_->ExposeMetadataLocaleForTesting(), StrEq("es"));
}
// Verifies that the manager fails the GetLocaleCallback
// * if it finds no close fit for the browser locale and
// * if the serving root does not advertise availability of
// English-localized metadata.
TEST_F(PpdMetadataManagerTest, FailsToFindAnyCloseFitLocale) {
// Sets an arbitrarily chosen locale quite distant from what the
// fake serving root will have available.
NewManagerWithLocale("ja-JP");
// Known interaction: the manager will fetch the locales metadata.
//
// Note that we are canning well-formed JSON.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/locales.json", R"({ "locales": [ "de", "es", "wo" ] })");
// Jams the result to the opposite of what's expected.
results_.get_locale_succeeded = true;
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetLocale,
base::Unretained(this), loop.QuitClosure());
auto call =
base::BindOnce(&PpdMetadataManager::GetLocale,
base::Unretained(manager_.get()), std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_FALSE(results_.get_locale_succeeded);
EXPECT_THAT(manager_->ExposeMetadataLocaleForTesting(), StrEq(""));
}
// Verifies that the manager fails the GetLocaleCallback if it fails to
// fetch the locales metadata.
TEST_F(PpdMetadataManagerTest, FailsToGetLocaleOnFetchFailure) {
// This test deliberately doesn't can any response from the
// FakePrinterConfigCache. We want to see what happens when the
// manager fails to fetch the necessary networked resource.
//
// We do need some way to tell that GetLocale() failed, so we start
// by jamming it to the opposite of the expected value.
results_.get_locale_succeeded = true;
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetLocale,
base::Unretained(this), loop.QuitClosure());
auto call =
base::BindOnce(&PpdMetadataManager::GetLocale,
base::Unretained(manager_.get()), std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_FALSE(results_.get_locale_succeeded);
EXPECT_THAT(manager_->ExposeMetadataLocaleForTesting(), StrEq(""));
}
// Verifies that the manager fails the GetLocaleCallback if it fails to
// parse the locales metadata.
TEST_F(PpdMetadataManagerTest, FailsToGetLocaleOnParseFailure) {
// Known interaction: the manager will fetch the locales metadata.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/locales.json",
kInvalidJson);
// We've canned an unparsable response for the manager.
// To observe that GetLocale() fails, we jam the result to the
// opposite of the expected value.
results_.get_locale_succeeded = true;
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetLocale,
base::Unretained(this), loop.QuitClosure());
auto call =
base::BindOnce(&PpdMetadataManager::GetLocale,
base::Unretained(manager_.get()), std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_FALSE(results_.get_locale_succeeded);
EXPECT_THAT(manager_->ExposeMetadataLocaleForTesting(), StrEq(""));
}
// Verifies that the manager can fetch, parse, and return a list of
// manufacturers from the Chrome OS Printing serving root.
TEST_F(PpdMetadataManagerTest, CanGetManufacturers) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Known interaction: the manager will fetch manufacturers metadata
// localized in English ("en").
//
// In real life, the values of the filesMap dictionary have a
// hyphenated locale suffix attached; this is not something the
// manager actually cares about and is not something used directly
// in this test case.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/manufacturers-en.json",
R"({ "filesMap": {
"It": "Never_Ends-en.json",
"You Are": "Always-en.json",
"Playing": "Yellow_Car-en.json"
} })");
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetManufacturers,
base::Unretained(this), loop.QuitClosure());
auto call = base::BindOnce(&PpdMetadataManager::GetManufacturers,
base::Unretained(manager_.get()),
kArbitraryTimeDelta, std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_EQ(results_.get_manufacturers_code,
PpdProvider::CallbackResultCode::SUCCESS);
// PpdProvider::ResolveManufacturersCallback specifies that the list
// shall be sorted.
EXPECT_THAT(results_.manufacturers,
ElementsAre(StrEq("It"), StrEq("Playing"), StrEq("You Are")));
}
// Verifies that the manager fails the ResolveManufacturersCallback
// when it fails to fetch the manufacturers metadata.
TEST_F(PpdMetadataManagerTest, FailsToGetManufacturersOnFetchFailure) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Known interaction: the manager will fetch manufacturers metadata
// localized in English ("en"). In this test case, we do _not_
// populate the fake config cache with the appropriate metadata,
// causing the fetch to fail.
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetManufacturers,
base::Unretained(this), loop.QuitClosure());
auto call = base::BindOnce(&PpdMetadataManager::GetManufacturers,
base::Unretained(manager_.get()),
kArbitraryTimeDelta, std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_EQ(results_.get_manufacturers_code,
PpdProvider::CallbackResultCode::SERVER_ERROR);
}
// Verifies that the manager fails the ResolveManufacturersCallback
// when it fails to parse the manufacturers metadata.
TEST_F(PpdMetadataManagerTest, FailsToGetManufacturersOnParseFailure) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Known interaction: the manager will fetch manufacturers metadata
// localized in English ("en").
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/manufacturers-en.json", kInvalidJson);
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetManufacturers,
base::Unretained(this), loop.QuitClosure());
auto call = base::BindOnce(&PpdMetadataManager::GetManufacturers,
base::Unretained(manager_.get()),
kArbitraryTimeDelta, std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_EQ(results_.get_manufacturers_code,
PpdProvider::CallbackResultCode::INTERNAL_ERROR);
}
// Verifies that the manager fetches manufacturers metadata anew when
// caller asks it for metadata fresher than what it has cached.
TEST_F(PpdMetadataManagerTest, CanGetManufacturersTimeSensitive) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Known interaction: the manager will fetch manufacturers metadata
// localized in English ("en").
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/manufacturers-en.json",
R"({ "filesMap": {
"It": "Never_Ends-en.json",
"You Are": "Always-en.json",
"Playing": "Yellow_Car-en.json"
} })");
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetManufacturers,
base::Unretained(this), loop.QuitClosure());
// t = 0s: caller requests a list of manufacturers from |manager_|,
// using metadata parsed within the last thirty seconds. |manager_|
// performs a live fetch for the manufacturers metadata.
manager_->GetManufacturers(base::Seconds(30), std::move(callback));
loop.Run();
ASSERT_EQ(results_.get_manufacturers_code,
PpdProvider::CallbackResultCode::SUCCESS);
// PpdProvider::ResolveManufacturersCallback specifies that the list
// shall be sorted.
EXPECT_THAT(results_.manufacturers,
ElementsAre(StrEq("It"), StrEq("Playing"), StrEq("You Are")));
// Mutates the test data, ensuring that if the metadata manager
// attempts a live fetch from the PrinterConfigCache, it will get
// different data in response.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/manufacturers-en.json",
R"({ "filesMap": {
"It": "Never_Ends-en.json",
"You Are": "Always-en.json"
} })");
// Jams the result code to something bad to require that the
// |manager_| positively answer us.
results_.get_manufacturers_code =
PpdProvider::CallbackResultCode::INTERNAL_ERROR;
base::RunLoop second_loop;
auto second_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchGetManufacturers,
base::Unretained(this), second_loop.QuitClosure());
// t = 0s: caller requests a list of manufacturers from |manager_|.
// |manager_| responds using the cached metadata without performing
// a live fetch.
manager_->GetManufacturers(base::Seconds(30), std::move(second_callback));
second_loop.Run();
// We assert that the results are unchanged from before.
ASSERT_EQ(results_.get_manufacturers_code,
PpdProvider::CallbackResultCode::SUCCESS);
EXPECT_THAT(results_.manufacturers,
ElementsAre(StrEq("It"), StrEq("Playing"), StrEq("You Are")));
// t = 31s
clock_.Advance(base::Seconds(31));
// Jams the result code to something bad to require that the
// |manager_| positively answer us.
results_.get_manufacturers_code =
PpdProvider::CallbackResultCode::INTERNAL_ERROR;
base::RunLoop third_loop;
auto third_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchGetManufacturers,
base::Unretained(this), third_loop.QuitClosure());
// t = 31s: caller requests a list of manufacturers from |manager_|.
// |manager_| does not have sufficiently fresh metadata, so it
// performs a live fetch.
manager_->GetManufacturers(base::Seconds(30), std::move(third_callback));
third_loop.Run();
// We assert that the results have changed.
ASSERT_EQ(results_.get_manufacturers_code,
PpdProvider::CallbackResultCode::SUCCESS);
EXPECT_THAT(results_.manufacturers,
ElementsAre(StrEq("It"), StrEq("You Are")));
}
// Verifies that the manager can fetch, parse, and return a map of
// printers metadata from the Chrome OS Printing serving root.
TEST_F(PpdMetadataManagerTest, CanGetPrinters) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Bypasses prerequisite call to PpdMetadataManager::GetManufacturers().
ASSERT_TRUE(manager_->SetManufacturersForTesting(R"(
{
"filesMap": {
"Manufacturer A": "Manufacturer_A-en.json",
"Manufacturer B": "Manufacturer_B-en.json"
}
}
)"));
// Known interaction: the manager will fetch printers metadata named
// by the manufacturers map above.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/Manufacturer_A-en.json", R"(
{
"printers": [ {
"emm": "some emm a",
"name": "Some Printer A"
}, {
"emm": "some emm b",
"name": "Some Printer B"
} ]
}
)");
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetPrinters,
base::Unretained(this), loop.QuitClosure());
auto call = base::BindOnce(&PpdMetadataManager::GetPrinters,
base::Unretained(manager_.get()), "Manufacturer A",
kArbitraryTimeDelta, std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_TRUE(results_.get_printers_succeeded);
EXPECT_THAT(
results_.printers,
UnorderedElementsAre(ParsedPrinterLike("Some Printer A", "some emm a"),
ParsedPrinterLike("Some Printer B", "some emm b")));
}
// Verifies that the manager fails the GetPrintersCallback when it fails
// to fetch the printers metadata.
TEST_F(PpdMetadataManagerTest, FailsToGetPrintersOnFetchFailure) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Bypasses prerequisite call to PpdMetadataManager::GetManufacturers().
ASSERT_TRUE(manager_->SetManufacturersForTesting(R"(
{
"filesMap": {
"Manufacturer A": "Manufacturer_A-en.json",
"Manufacturer B": "Manufacturer_B-en.json"
}
}
)"));
// This test is set up like the CanGetPrinters test case above, but we
// elect _not_ to provide a response for any printers metadata,
// causing the fetch to fail.
//
// We set the result value to the opposite of what's expected to
// observe the change.
results_.get_printers_succeeded = true;
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetPrinters,
base::Unretained(this), loop.QuitClosure());
auto call = base::BindOnce(&PpdMetadataManager::GetPrinters,
base::Unretained(manager_.get()), "Manufacturer A",
kArbitraryTimeDelta, std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
EXPECT_FALSE(results_.get_printers_succeeded);
}
// Verifies that the manager fails the GetPrintersCallback when it fails
// to parse the printers metadata.
TEST_F(PpdMetadataManagerTest, FailsToGetPrintersOnParseFailure) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Bypasses prerequisite call to PpdMetadataManager::GetManufacturers().
ASSERT_TRUE(manager_->SetManufacturersForTesting(R"(
{
"filesMap": {
"Manufacturer A": "Manufacturer_A-en.json",
"Manufacturer B": "Manufacturer_B-en.json"
}
}
)"));
// This test is set up like the CanGetPrinters test case above, but we
// elect to provide a malformed JSON response for the printers
// metadata, which will cause the manager to fail parsing.
//
// Known interaction: the manager will fetch the printers metadata
// named by the map above.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/Manufacturer_A-en.json", kInvalidJson);
// We set the result value to the opposite of what's expected to
// observe the change.
results_.get_printers_succeeded = true;
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetPrinters,
base::Unretained(this), loop.QuitClosure());
auto call = base::BindOnce(&PpdMetadataManager::GetPrinters,
base::Unretained(manager_.get()), "Manufacturer A",
kArbitraryTimeDelta, std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
EXPECT_FALSE(results_.get_printers_succeeded);
}
// Verifies that the manager fetches printers metadata anew when caller
// asks it for metadata fresher than what it has cached.
TEST_F(PpdMetadataManagerTest, CanGetPrintersTimeSensitive) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Bypasses prerequisite call to PpdMetadataManager::GetManufacturers().
ASSERT_TRUE(manager_->SetManufacturersForTesting(R"(
{
"filesMap": {
"Manufacturer A": "Manufacturer_A-en.json",
"Manufacturer B": "Manufacturer_B-en.json"
}
}
)"));
// Known interaction: the manager will fetch printers metadata named
// by the manufacturers map above.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/Manufacturer_A-en.json", R"(
{
"printers": [ {
"emm": "some emm a",
"name": "Some Printer A"
}, {
"emm": "some emm b",
"name": "Some Printer B"
} ]
}
)");
// t = 0s: caller requests the printers for "Manufacturer A."
// |manager_| parses and caches the metadata successfully.
base::RunLoop loop;
auto callback = base::BindOnce(&PpdMetadataManagerTest::CatchGetPrinters,
base::Unretained(this), loop.QuitClosure());
manager_->GetPrinters("Manufacturer A", base::Seconds(30),
std::move(callback));
loop.Run();
ASSERT_TRUE(results_.get_printers_succeeded);
EXPECT_THAT(
results_.printers,
UnorderedElementsAre(ParsedPrinterLike("Some Printer A", "some emm a"),
ParsedPrinterLike("Some Printer B", "some emm b")));
// We change the data served by the PrinterConfigCache.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/Manufacturer_A-en.json", R"(
{
"printers": [ {
"emm": "some emm c",
"name": "Some Printer C"
}, {
"emm": "some emm d",
"name": "Some Printer D"
} ]
}
)");
// Jams the results to some bad value, requiring that the manager
// answer us positively.
results_.get_printers_succeeded = false;
base::RunLoop second_loop;
auto second_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchGetPrinters,
base::Unretained(this), second_loop.QuitClosure());
// t = 0s: caller requests the printers for "Manufacturer A."
// |manager_| re-uses the cached metadata.
manager_->GetPrinters("Manufacturer A", base::Seconds(30),
std::move(second_callback));
second_loop.Run();
// We assert that the results are unchanged from before.
ASSERT_TRUE(results_.get_printers_succeeded);
EXPECT_THAT(
results_.printers,
UnorderedElementsAre(ParsedPrinterLike("Some Printer A", "some emm a"),
ParsedPrinterLike("Some Printer B", "some emm b")));
// t = 31s
clock_.Advance(base::Seconds(31));
// Jams the results to some bad value, requiring that the manager
// answer us positively.
results_.get_printers_succeeded = false;
// t = 31s: caller requests the printers for "Manufacturer A."
// |manager_| does not have sufficiently fresh metadata, so it
// performs a live fetch.
base::RunLoop third_loop;
auto third_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchGetPrinters,
base::Unretained(this), third_loop.QuitClosure());
manager_->GetPrinters("Manufacturer A", base::Seconds(30),
std::move(third_callback));
third_loop.Run();
// We assert that the results have changed.
ASSERT_TRUE(results_.get_printers_succeeded);
EXPECT_THAT(
results_.printers,
UnorderedElementsAre(ParsedPrinterLike("Some Printer C", "some emm c"),
ParsedPrinterLike("Some Printer D", "some emm d")));
}
// Verifies that the manager can find all effective-make-and-model
// strings in forward index metadata.
TEST_F(PpdMetadataManagerTest, CanFindAllAvailableEmmsInIndex) {
// Known interaction: the manager will fetch forward index metadata
// numbered 14, 15, and 16.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/index-14.json", R"(
{
"ppdIndex": {
"some printer a": {
"ppdMetadata": [ {
"name": "some-ppd-basename-a.ppd.gz"
} ]
}
}
})");
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/index-15.json", R"(
{
"ppdIndex": {
"some printer b": {
"ppdMetadata": [ {
"name": "some-ppd-basename-b.ppd.gz"
} ]
}
}
})");
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/index-16.json", R"(
{
"ppdIndex": {
"some printer c": {
"ppdMetadata": [ {
"name": "some-ppd-basename-c.ppd.gz"
} ]
}
}
})");
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindAllEmmsAvailableInIndex,
base::Unretained(this), loop.QuitClosure());
manager_->FindAllEmmsAvailableInIndex(
{"some printer a", "some printer b", "some printer c"},
kArbitraryTimeDelta, std::move(callback));
loop.Run();
EXPECT_THAT(results_.available_effective_make_and_model_strings,
UnorderedElementsAre(
ParsedIndexEntryLike(
"some printer a",
UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-a.ppd.gz"))),
ParsedIndexEntryLike(
"some printer b",
UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-b.ppd.gz"))),
ParsedIndexEntryLike(
"some printer c",
UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-c.ppd.gz")))));
}
// Verifies that the manager invokes the
// FindAllAvailableEmmsInIndexCallback with a partially filled argument
// if it fails to fetch some of the necessary metadata.
TEST_F(PpdMetadataManagerTest,
CanPartiallyFindAllAvailableEmmsInIndexWithFetchFailure) {
// Known interaction: the manager will fetch forward index metadata
// numbered 14, 15, and 16.
//
// We deliberately omit forward index metadata no. 14 to simulate a
// fetch failure.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/index-15.json", R"(
{
"ppdIndex": {
"some printer b": {
"ppdMetadata": [ {
"name": "some-ppd-basename-b.ppd.gz"
} ]
}
}
})");
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/index-16.json", R"(
{
"ppdIndex": {
"some printer c": {
"ppdMetadata": [ {
"name": "some-ppd-basename-c.ppd.gz"
} ]
}
}
})");
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindAllEmmsAvailableInIndex,
base::Unretained(this), loop.QuitClosure());
manager_->FindAllEmmsAvailableInIndex(
{"some printer a", "some printer b", "some printer c"},
kArbitraryTimeDelta, std::move(callback));
loop.Run();
// The manager was unable to get the forward index metadata that would
// contain information for effective-make-and-model string
// "some printer a," but the other results should avail themselves.
EXPECT_THAT(results_.available_effective_make_and_model_strings,
UnorderedElementsAre(
ParsedIndexEntryLike(
"some printer b",
UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-b.ppd.gz"))),
ParsedIndexEntryLike(
"some printer c",
UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-c.ppd.gz")))));
}
// Verifies that the manager invokes the
// FindAllAvailableEmmsInIndexCallback with a partially filled argument
// if it fails to parse some of the necessary metadata.
TEST_F(PpdMetadataManagerTest,
CanPartiallyFindAllAvailableEmmsInIndexWithParseFailure) {
// Known interaction: the manager will fetch forward index metadata
// numbered 14, 15, and 16.
//
// We deliberately serve malformed JSON that will fail to parse for
// indices nos. 14 and 15 to exercise the manager's handling of parse
// failures.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/index-14.json",
kInvalidJson);
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/index-15.json",
kInvalidJson);
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/index-16.json", R"(
{
"ppdIndex": {
"some printer c": {
"ppdMetadata": [ {
"name": "some-ppd-basename-c.ppd.gz"
} ]
}
}
})");
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindAllEmmsAvailableInIndex,
base::Unretained(this), loop.QuitClosure());
manager_->FindAllEmmsAvailableInIndex(
{"some printer a", "some printer b", "some printer c"},
kArbitraryTimeDelta, std::move(callback));
loop.Run();
// The manager was unable to parse the forward index metadata that would
// contain information for effective-make-and-model strings
// "some printer a" and for "some printer b," but the last string
// "some printer c" should avail itself.
EXPECT_THAT(
results_.available_effective_make_and_model_strings,
UnorderedElementsAre(ParsedIndexEntryLike(
"some printer c", UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-c.ppd.gz")))));
}
// Verifies that the manager fetches forward index metadata anew when
// the caller asks it for metadata fresher than what it has cached.
TEST_F(PpdMetadataManagerTest, CanFindAllAvailableEmmsInIndexTimeSensitive) {
// Known interaction: the manager will fetch forward index metadata
// numbered 14, 15, and 16.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/index-14.json", R"(
{
"ppdIndex": {
"some printer a": {
"ppdMetadata": [ {
"name": "some-ppd-basename-a.ppd.gz"
} ]
}
}
})");
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/index-15.json", R"(
{
"ppdIndex": {
"some printer b": {
"ppdMetadata": [ {
"name": "some-ppd-basename-b.ppd.gz"
} ]
}
}
})");
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/index-16.json", R"(
{
"ppdIndex": {
"some printer c": {
"ppdMetadata": [ {
"name": "some-ppd-basename-c.ppd.gz"
} ]
}
}
})");
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindAllEmmsAvailableInIndex,
base::Unretained(this), loop.QuitClosure());
// t = 0s: caller requests the |manager_| to search forward index
// metadata for three effective-make-and-model strings. |manager_|
// fetches the appropriate forward index metadata (nos. 14, 15, and
// 16) and caches the result.
manager_->FindAllEmmsAvailableInIndex(
{"some printer a", "some printer b", "some printer c"}, base::Seconds(30),
std::move(callback));
loop.Run();
EXPECT_THAT(results_.available_effective_make_and_model_strings,
UnorderedElementsAre(
ParsedIndexEntryLike(
"some printer a",
UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-a.ppd.gz"))),
ParsedIndexEntryLike(
"some printer b",
UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-b.ppd.gz"))),
ParsedIndexEntryLike(
"some printer c",
UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-c.ppd.gz")))));
// We drop forward index metadata nos. 14 and 15 now. If the
// |manager_| attempts to fetch these again, it will fail to do so.
GetFakeCache()->Drop("metadata_v3/index-14.json");
GetFakeCache()->Drop("metadata_v3/index-15.json");
// Resets the results to require that the |manager_| answer us
// positively.
results_.available_effective_make_and_model_strings = {};
base::RunLoop second_loop;
auto second_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindAllEmmsAvailableInIndex,
base::Unretained(this), second_loop.QuitClosure());
// t = 0s: caller requests the |manager_| to search forward index
// metadata for the same three effective-make-and-model strings.
// Caller requests this using metadata fetched within the last thirty
// seconds, so |manager_| does not perform a live fetch of the data.
manager_->FindAllEmmsAvailableInIndex(
{"some printer a", "some printer b", "some printer c"}, base::Seconds(30),
std::move(second_callback));
second_loop.Run();
// We expect the results to be unchanged.
EXPECT_THAT(results_.available_effective_make_and_model_strings,
UnorderedElementsAre(
ParsedIndexEntryLike(
"some printer a",
UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-a.ppd.gz"))),
ParsedIndexEntryLike(
"some printer b",
UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-b.ppd.gz"))),
ParsedIndexEntryLike(
"some printer c",
UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-c.ppd.gz")))));
// t = 31s
clock_.Advance(base::Seconds(31));
base::RunLoop third_loop;
auto third_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindAllEmmsAvailableInIndex,
base::Unretained(this), third_loop.QuitClosure());
// t = 31s: caller requests the |manager_| to search forward index
// metadata for the same three effective-make-and-model strings.
// Caller requests this using metadata fetched within the last thirty
// thirds; |manager_| sees that its cached metadata is too old, and
// so performs a live fetch.
//
// Since we previously blocked forward index metadata nos. 14 and 15
// from being served, the |manager_| will fail to fetch these.
manager_->FindAllEmmsAvailableInIndex(
{"some printer a", "some printer b", "some printer c"}, base::Seconds(30),
std::move(third_callback));
third_loop.Run();
// We expect the results to have changed.
EXPECT_THAT(
results_.available_effective_make_and_model_strings,
UnorderedElementsAre(ParsedIndexEntryLike(
"some printer c", UnorderedElementsAre(ParsedIndexLeafWithPpdBasename(
"some-ppd-basename-c.ppd.gz")))));
}
// Verifies that the manager can find a USB device by fetching and
// parsing USB index metadata.
TEST_F(PpdMetadataManagerTest, CanFindDeviceInUsbIndex) {
// Known interaction: hex(1138) == 0x472. To fetch USB index metadata
// for a manufacturer with vendor ID 1138, the manager will fetch
// the metadata with the following name.
//
// This USB index describes one product for vendor ID 1138; its
// product ID is 13.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/usb-0472.json", R"({
"usbIndex": {
"13": {
"effectiveMakeAndModel": "some printer a"
}
}
})");
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindDeviceInUsbIndex,
base::Unretained(this), loop.QuitClosure());
manager_->FindDeviceInUsbIndex(1138, 13, kArbitraryTimeDelta,
std::move(callback));
loop.Run();
EXPECT_THAT(results_.effective_make_and_model_string_from_usb_index,
StrEq("some printer a"));
}
// Verifies that the manager invokes the FindDeviceInUsbIndexCallback
// with an empty argument if it fails to fetch the appropriate USB
// index.
TEST_F(PpdMetadataManagerTest, FailsToFindDeviceInUsbIndexOnFetchFailure) {
// Known interaction: hex(1138) == 0x472. To fetch USB index metadata
// for a manufacturer with vendor ID 1138, the manager will fetch
// the metadata with the following name.
//
// We populate nothing in the fake serving root, so any fetch request
// from the manager will fail.
// Jams the landing area to have a non-empty string. We expect the
// callback to fire with an empty string, which should empty this.
results_.effective_make_and_model_string_from_usb_index =
"non-empty string that will fail this test if it persists";
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindDeviceInUsbIndex,
base::Unretained(this), loop.QuitClosure());
manager_->FindDeviceInUsbIndex(1138, 13, kArbitraryTimeDelta,
std::move(callback));
loop.Run();
EXPECT_TRUE(results_.effective_make_and_model_string_from_usb_index.empty());
}
// Verifies that the manager invokes the FindDeviceInUsbIndexCallback
// with an empty argument if it fails to parse the appropriate USB
// index.
TEST_F(PpdMetadataManagerTest, FailsToFindDeviceInUsbIndexOnParseFailure) {
// Known interaction: hex(1138) == 0x472. To fetch USB index metadata
// for a manufacturer with vendor ID 1138, the manager will fetch
// the metadata with the following name.
//
// We populate the fake serving root with invalid JSON for the USB
// index metadata that the manager will fetch and fail to parse.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/usb-0472.json",
kInvalidJson);
// Jams the landing area to have a non-empty string. We expect the
// callback to fire with an empty string, which should empty this.
results_.effective_make_and_model_string_from_usb_index =
"non-empty string that will fail this test if it persists";
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindDeviceInUsbIndex,
base::Unretained(this), loop.QuitClosure());
manager_->FindDeviceInUsbIndex(1138, 13, kArbitraryTimeDelta,
std::move(callback));
loop.Run();
EXPECT_TRUE(results_.effective_make_and_model_string_from_usb_index.empty());
}
// Verifies that the manager fetches USB index metadata anew when caller
// asks it for metadata fresher than what it has cached.
TEST_F(PpdMetadataManagerTest, CanFindDeviceInUsbIndexTimeSensitive) {
// Known interaction: hex(1138) == 0x472. To fetch USB index metadata
// for a manufacturer with vendor ID 1138, the manager will fetch
// the metadata with the following name.
//
// This USB index describes one product for vendor ID 1138; its
// product ID is 13.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/usb-0472.json", R"({
"usbIndex": {
"13": {
"effectiveMakeAndModel": "some printer a"
}
}
})");
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindDeviceInUsbIndex,
base::Unretained(this), loop.QuitClosure());
// t = 0s: caller requests |manager_| to name a device with vendor ID
// 1138 and product ID 13. |manager_| fetches, parses, and caches
// the appropriate USB index metadata.
manager_->FindDeviceInUsbIndex(1138, 13, kArbitraryTimeDelta,
std::move(callback));
loop.Run();
EXPECT_THAT(results_.effective_make_and_model_string_from_usb_index,
StrEq("some printer a"));
// Sets the serving root to mutate the served USB index metadata; the
// device with product ID 13 now has the effective-make-and-model
// string "some printer b." If the |manager_| fetches this metadata
// anew, then it will observe the changed effective-make-and-model
// string.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/usb-0472.json", R"({
"usbIndex": {
"13": {
"effectiveMakeAndModel": "some printer b"
}
}
})");
// Jams the landing area to hold a test-failing string. We expect
// the successful callback to overwrite this.
results_.effective_make_and_model_string_from_usb_index =
"non-empty string that will fail this test if it persists";
base::RunLoop second_loop;
auto second_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindDeviceInUsbIndex,
base::Unretained(this), second_loop.QuitClosure());
// t = 0s: caller requests |manager_| to name a device with vendor ID
// 1138 and product ID 13. It asks that |manager_| do so with metadata
// parsed within the last 30 seconds. |manager_| responds with the
// cached USB index metadata without incurring a live fetch.
manager_->FindDeviceInUsbIndex(1138, 13, base::Seconds(30),
std::move(second_callback));
second_loop.Run();
// The manager will have responded with the cached
// effective-make-and-model string "some printer a."
EXPECT_THAT(results_.effective_make_and_model_string_from_usb_index,
StrEq("some printer a"));
// t = 31s
clock_.Advance(base::Seconds(31));
// Jams the landing area to hold a test-failing string. We expect
// the successful callback to overwrite this.
results_.effective_make_and_model_string_from_usb_index =
"non-empty string that will fail this test if it persists";
base::RunLoop third_loop;
auto third_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchFindDeviceInUsbIndex,
base::Unretained(this), third_loop.QuitClosure());
// t = 31s: caller requests |manager_| to name a device with vendor ID
// 1138 and product ID 13. It asks that |manager_| do so with metadata
// parsed within the last 30 seconds. |manager_| sees that the cached
// USB index metadata is too stale, and so incurs a live fetch. The
// fetch exposes the changed metadata.
manager_->FindDeviceInUsbIndex(1138, 13, base::Seconds(30),
std::move(third_callback));
third_loop.Run();
// The manager will have responded with the new and changed
// effective-make-and-model string "some printer b."
EXPECT_THAT(results_.effective_make_and_model_string_from_usb_index,
StrEq("some printer b"));
}
// Verifies that the manager can determine a USB manufacturer name
// by fetching and searching the USB vendor ID map.
TEST_F(PpdMetadataManagerTest, CanGetUsbManufacturerName) {
// Known interaction: |manager_| shall fetch the USB vendor ID map.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/usb_vendor_ids.json",
R"({
"entries": [ {
"vendorId": 1138,
"vendorName": "Some Vendor Name"
} ]
})");
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchGetUsbManufacturerName,
base::Unretained(this), loop.QuitClosure());
manager_->GetUsbManufacturerName(1138, kArbitraryTimeDelta,
std::move(callback));
loop.Run();
EXPECT_THAT(results_.usb_manufacturer_name, StrEq("Some Vendor Name"));
}
// Verifies that the manager invokes the GetUsbManufacturerNameCallback
// with an empty argument if it fails to fetch the USB vendor ID map.
TEST_F(PpdMetadataManagerTest, FailsToGetUsbManufacturerNameOnFetchFailure) {
// Known interaction: |manager_| shall fetch the USB vendor ID map.
//
// We deliberately don't set any fetch response in the fake serving
// root; |manager_| will fail to fetch the USB vendor ID map. However,
// we do jam the landing area with a sentinel value to ensure that the
// callback does fire with an empty string.
results_.usb_manufacturer_name =
"non-empty string that will fail this test if it persists";
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchGetUsbManufacturerName,
base::Unretained(this), loop.QuitClosure());
manager_->GetUsbManufacturerName(1138, kArbitraryTimeDelta,
std::move(callback));
loop.Run();
EXPECT_TRUE(results_.usb_manufacturer_name.empty());
}
// Verifies that the manager invokes the GetUsbManufacturerNameCallback
// with an empty argument if it fails to parse the USB vendor ID map.
TEST_F(PpdMetadataManagerTest, FailsToGetUsbManufacturerNameOnParseFailure) {
// Known interaction: |manager_| shall fetch the USB vendor ID map.
//
// We deliberately set a malformed response in the serving root;
// |manager_| will fetch the USB vendor ID map successfully, but will
// fail to parse it.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/usb_vendor_ids.json",
kInvalidJson);
// We also jam the landing area with a sentinel value to ensure that
// the callback fires with an empty string.
results_.usb_manufacturer_name =
"non-empty string that will fail this test if it persists";
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchGetUsbManufacturerName,
base::Unretained(this), loop.QuitClosure());
manager_->GetUsbManufacturerName(1138, kArbitraryTimeDelta,
std::move(callback));
loop.Run();
EXPECT_TRUE(results_.usb_manufacturer_name.empty());
}
// Verifies that the manager fetches the USB vendor ID map anew if the
// caller calls GetUsbManufacturerName() asking for metadata fresher
// than what it has cached.
TEST_F(PpdMetadataManagerTest, CanGetUsbManufacturerNameTimeSensitive) {
// Known interaction: |manager_| shall fetch the USB vendor ID map.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/usb_vendor_ids.json",
R"({
"entries": [ {
"vendorId": 1138,
"vendorName": "Vendor One One Three Eight"
} ]
})");
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchGetUsbManufacturerName,
base::Unretained(this), loop.QuitClosure());
// t = 0s: caller requests the name of a USB manufacturer whose vendor
// ID is 1138. |manager_| fetches, parses, and caches the USB vendor
// ID map, responding with the name.
manager_->GetUsbManufacturerName(1138, base::Seconds(30),
std::move(callback));
loop.Run();
EXPECT_THAT(results_.usb_manufacturer_name,
StrEq("Vendor One One Three Eight"));
// Mutates the USB vendor ID map served by the fake serving root.
// If the |manager_| fetches it now, it will see a changed name for
// the USB manufacturer with vendor ID 1138.
GetFakeCache()->SetFetchResponseForTesting("metadata_v3/usb_vendor_ids.json",
R"({
"entries": [ {
"vendorId": 1138,
"vendorName": "One One Three Eight LLC"
} ]
})");
base::RunLoop second_loop;
auto second_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchGetUsbManufacturerName,
base::Unretained(this), second_loop.QuitClosure());
// t = 0s: caller requests the name of a USB manufacturer whose vendor
// ID is 1138. |manager_| responds with the previously fetched
// metadata.
manager_->GetUsbManufacturerName(1138, base::Seconds(30),
std::move(second_callback));
second_loop.Run();
// Since |manager_| has not fetched the mutated USB vendor ID map,
// the results are unchanged from before.
EXPECT_THAT(results_.usb_manufacturer_name,
StrEq("Vendor One One Three Eight"));
// t = 31s
clock_.Advance(base::Seconds(31));
base::RunLoop third_loop;
auto third_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchGetUsbManufacturerName,
base::Unretained(this), third_loop.QuitClosure());
// t = 31s: caller requests the name of a USB manufacturer whose
// vendor ID is 1138. |manager_| notices that its cached metadata is
// too stale and performs a live fetch, receiving the mutated
// USB vendor ID map.
manager_->GetUsbManufacturerName(1138, base::Seconds(30),
std::move(third_callback));
third_loop.Run();
// Since |manager_| has not fetched the mutated USB vendor ID map,
// the results are unchanged from before.
EXPECT_THAT(results_.usb_manufacturer_name, StrEq("One One Three Eight LLC"));
}
// Verifies that the manager can split an effective-make-and-model
// string into its constituent parts (make and model).
TEST_F(PpdMetadataManagerTest, CanSplitMakeAndModel) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Known interaction: asking the manager to split the string
// "Hello there!" will cause it to fetch the reverse index metadata
// with shard number 2.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/reverse_index-en-02.json", R"(
{
"reverseIndex": {
"Hello there!": {
"manufacturer": "General",
"model": "Kenobi"
}
}
}
)");
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchSplitMakeAndModel,
base::Unretained(this), loop.QuitClosure());
auto call = base::BindOnce(&PpdMetadataManager::SplitMakeAndModel,
base::Unretained(manager_.get()), "Hello there!",
kArbitraryTimeDelta, std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_EQ(results_.split_make_and_model_code,
PpdProvider::CallbackResultCode::SUCCESS);
EXPECT_THAT(results_.split_make, StrEq("General"));
EXPECT_THAT(results_.split_model, StrEq("Kenobi"));
}
// Verifies that the manager fails the ReverseLookupCallback when it
// fails to fetch the necessary metadata from the Chrome OS Printing
// serving root.
TEST_F(PpdMetadataManagerTest, FailsToSplitMakeAndModelOnFetchFailure) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Known interaction: asking the manager to split the string
// "Hello there!" will cause it to fetch the reverse index metadata
// with shard number 2.
//
// We elect _not_ to fake a value for this s.t. the fetch will fail.
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchSplitMakeAndModel,
base::Unretained(this), loop.QuitClosure());
auto call = base::BindOnce(&PpdMetadataManager::SplitMakeAndModel,
base::Unretained(manager_.get()), "Hello there!",
kArbitraryTimeDelta, std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
EXPECT_EQ(results_.split_make_and_model_code,
PpdProvider::CallbackResultCode::SERVER_ERROR);
}
// Verifies that the manager fails the ReverseLookupCallback when it
// fails to parse the necessary metadata from the Chrome OS Printing
// serving root.
TEST_F(PpdMetadataManagerTest, FailsToSplitMakeAndModelOnParseFailure) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Known interaction: asking the manager to split the string
// "Hello there!" will cause it to fetch the reverse index metadata
// with shard number 2.
//
// We fake a fetch value that is invalid JSON s.t. the manager
// will fail to parse it.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/reverse_index-en-02.json", kInvalidJson);
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchSplitMakeAndModel,
base::Unretained(this), loop.QuitClosure());
auto call = base::BindOnce(&PpdMetadataManager::SplitMakeAndModel,
base::Unretained(manager_.get()), "Hello there!",
kArbitraryTimeDelta, std::move(callback));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(call));
loop.Run();
ASSERT_EQ(results_.split_make_and_model_code,
PpdProvider::CallbackResultCode::INTERNAL_ERROR);
}
// Verifies that the manager fetches reverse index metadata anew when
// caller asks it for metadata fresher than what it has cached.
TEST_F(PpdMetadataManagerTest, CanSplitMakeAndModelTimeSensitive) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
// Known interaction: asking the manager to split the string
// "Hello there!" will cause it to fetch the reverse index metadata
// with shard number 2.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/reverse_index-en-02.json", R"(
{
"reverseIndex": {
"Hello there!": {
"manufacturer": "General",
"model": "Kenobi"
}
}
}
)");
base::RunLoop loop;
auto callback =
base::BindOnce(&PpdMetadataManagerTest::CatchSplitMakeAndModel,
base::Unretained(this), loop.QuitClosure());
// t = 0s: caller requests that |manager_| split the
// effective-make-and-model string "Hello there!" using metadata
// parsed within the last thirty seconds. |manager_| fetches the
// appropriate reverse index metadata.
manager_->SplitMakeAndModel("Hello there!", base::Seconds(30),
std::move(callback));
loop.Run();
ASSERT_EQ(results_.split_make_and_model_code,
PpdProvider::CallbackResultCode::SUCCESS);
EXPECT_THAT(results_.split_make, StrEq("General"));
EXPECT_THAT(results_.split_model, StrEq("Kenobi"));
// Mutates the reverse index metadata we serve.
GetFakeCache()->SetFetchResponseForTesting(
"metadata_v3/reverse_index-en-02.json", R"(
{
"reverseIndex": {
"Hello there!": {
"manufacturer": "You are",
"model": "a bold one!"
}
}
}
)");
// Jams the result to a bad value, requiring that the |manager_|
// answer us positively.
results_.split_make_and_model_code =
PpdProvider::CallbackResultCode::INTERNAL_ERROR;
base::RunLoop second_loop;
auto second_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchSplitMakeAndModel,
base::Unretained(this), second_loop.QuitClosure());
// t = 0s: caller requests that |manager_| split the
// effective-make-and-model string "Hello there!"
// |manager_| re-uses the cached reverse index metadata.
manager_->SplitMakeAndModel("Hello there!", base::Seconds(30),
std::move(second_callback));
second_loop.Run();
// We assert that the results are currently unchanged.
ASSERT_EQ(results_.split_make_and_model_code,
PpdProvider::CallbackResultCode::SUCCESS);
EXPECT_THAT(results_.split_make, StrEq("General"));
EXPECT_THAT(results_.split_model, StrEq("Kenobi"));
// t = 31s
clock_.Advance(base::Seconds(31));
// Jams the result to a bad value, requiring that the |manager_|
// answer us positively.
results_.split_make_and_model_code =
PpdProvider::CallbackResultCode::INTERNAL_ERROR;
base::RunLoop third_loop;
auto third_callback =
base::BindOnce(&PpdMetadataManagerTest::CatchSplitMakeAndModel,
base::Unretained(this), third_loop.QuitClosure());
// t = 31s: caller requests that |manager_| split the
// effective-make-and-model string "Hello there!"
// |manager_| doesn't have sufficiently fresh metadata, so it performs
// a live fetch.
manager_->SplitMakeAndModel("Hello there!", base::Seconds(30),
std::move(third_callback));
third_loop.Run();
// We assert that the live fetch changed the results.
ASSERT_EQ(results_.split_make_and_model_code,
PpdProvider::CallbackResultCode::SUCCESS);
EXPECT_THAT(results_.split_make, StrEq("You are"));
EXPECT_THAT(results_.split_model, StrEq("a bold one!"));
}
class PpdMetadataManagerBase : public ::testing::Test {
public:
explicit PpdMetadataManagerBase(PpdIndexChannel channel)
: task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {
auto cache = std::make_unique<FakePrinterConfigCache>();
cache_ = cache.get();
manager_ = PpdMetadataManager::Create(kBrowserLocaleForTesting, channel,
&clock_, std::move(cache));
}
~PpdMetadataManagerBase() override = default;
protected:
bool CallGetLocale() {
base::RunLoop loop;
bool result;
auto callback = [&loop, &result](bool param) {
result = param;
loop.Quit();
};
manager_->GetLocale(base::BindLambdaForTesting(callback));
loop.Run();
return result;
}
bool CallGetManufacturers() {
base::RunLoop loop;
bool result;
auto callback = [&loop, &result](PpdProvider::CallbackResultCode param1,
const std::vector<std::string>& param2) {
result = (param1 == PpdProvider::CallbackResultCode::SUCCESS);
loop.Quit();
};
manager_->GetManufacturers(kArbitraryTimeDelta,
base::BindLambdaForTesting(callback));
loop.Run();
return result;
}
// Environment for task schedulers.
base::test::TaskEnvironment task_environment_;
// Controlled clock that dispenses times of Fetch().
base::SimpleTestClock clock_;
// Class under test.
std::unique_ptr<PpdMetadataManager> manager_;
raw_ptr<FakePrinterConfigCache> cache_;
};
class PpdMetadataManagerForStagingChannelTest : public PpdMetadataManagerBase {
public:
PpdMetadataManagerForStagingChannelTest()
: PpdMetadataManagerBase(PpdIndexChannel::kStaging) {}
~PpdMetadataManagerForStagingChannelTest() override = default;
};
TEST_F(PpdMetadataManagerForStagingChannelTest, CanGetLocale) {
cache_->SetFetchResponseForTesting("metadata_v3_staging/locales.json",
R"({ "locales": [ "de", "en", "es" ]
})");
EXPECT_TRUE(CallGetLocale());
}
TEST_F(PpdMetadataManagerForStagingChannelTest, CanGetManufacturers) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
cache_->SetFetchResponseForTesting(
"metadata_v3_staging/manufacturers-en.json",
R"({ "filesMap": {"It":"test-en.json"}})");
EXPECT_TRUE(CallGetManufacturers());
}
class PpdMetadataManagerForDevChannelTest : public PpdMetadataManagerBase {
public:
PpdMetadataManagerForDevChannelTest()
: PpdMetadataManagerBase(PpdIndexChannel::kDev) {}
~PpdMetadataManagerForDevChannelTest() override = default;
};
TEST_F(PpdMetadataManagerForDevChannelTest, CanGetLocale) {
cache_->SetFetchResponseForTesting("metadata_v3_dev/locales.json",
R"({ "locales": [ "de", "en", "es" ] })");
EXPECT_TRUE(CallGetLocale());
}
TEST_F(PpdMetadataManagerForDevChannelTest, CanGetManufacturers) {
// Bypasses mandatory call to PpdMetadataManager::GetLocale().
manager_->SetLocaleForTesting("en");
cache_->SetFetchResponseForTesting("metadata_v3_dev/manufacturers-en.json",
R"({ "filesMap": {"It":"test-en.json"}})");
EXPECT_TRUE(CallGetManufacturers());
}
} // namespace
} // namespace chromeos