// Copyright 2016 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 "chromeos/printing/ppd_provider.h" #include #include #include #include #include #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/run_loop.h" #include "base/strings/strcat.h" #include "base/strings/stringprintf.h" #include "base/task/single_thread_task_runner.h" #include "base/test/bind.h" #include "base/test/simple_test_clock.h" #include "base/test/task_environment.h" #include "base/test/test_message_loop.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h" #include "base/version.h" #include "chromeos/printing/fake_printer_config_cache.h" #include "chromeos/printing/ppd_cache.h" #include "chromeos/printing/ppd_metadata_manager.h" #include "chromeos/printing/printer_config_cache.h" #include "chromeos/printing/printer_configuration.h" #include "testing/gmock/include/gmock/gmock-matchers.h" #include "testing/gtest/include/gtest/gtest.h" namespace chromeos { namespace { using PrinterDiscoveryType = PrinterSearchData::PrinterDiscoveryType; using ::testing::AllOf; using ::testing::Eq; using ::testing::Field; using ::testing::StrEq; using ::testing::UnorderedElementsAre; // A pseudo-ppd that should get cupsFilter lines extracted from it. const char kCupsFilterPpdContents[] = R"( Other random contents that we don't care about. *cupsFilter: "application/vnd.cups-raster 0 my_filter" More random contents that we don't care about *cupsFilter: "application/vnd.cups-awesome 0 a_different_filter" *cupsFilter: "application/vnd.cups-awesomesauce 0 filter3" Yet more randome contents that we don't care about. More random contents that we don't care about. )"; // A pseudo-ppd that should get cupsFilter2 lines extracted from it. // We also have cupsFilter lines in here, but since cupsFilter2 lines // exist, the cupsFilter lines should be ignored. const char kCupsFilter2PpdContents[] = R"( Other random contents that we don't care about. *cupsFilter: "application/vnd.cups-raster 0 my_filter" More random contents that we don't care about *cupsFilter2: "foo bar 0 the_real_filter" *cupsFilter2: "bar baz 381 another_real_filter" Yet more randome contents that we don't care about. More random contents that we don't care about. )"; // Known number of public method calls that the PpdProvider will defer // before posting failures directly. // * This value is left unspecified in the header. // * This value must be kept in sync with the exact value in the // implementation of PpdProvider. constexpr int kMethodDeferralLimitForTesting = 20; // Default manufacturers metadata used for these tests. const char kDefaultManufacturersJson[] = R"({ "filesMap": { "Manufacturer A": "manufacturer_a-en.json", "Manufacturer B": "manufacturer_b-en.json" } })"; // Unowned raw pointers to helper classes composed into the // PpdProvider at construct time. Used throughout to activate testing // codepaths. struct PpdProviderComposedMembers { FakePrinterConfigCache* config_cache = nullptr; FakePrinterConfigCache* manager_config_cache = nullptr; PpdMetadataManager* metadata_manager = nullptr; }; class PpdProviderTest : public ::testing::Test { public: // * Determines where the PpdCache class runs. // * If set to kOnTestThread, the PpdCache class will use the // task environment of the test fixture. // * If set to kInBackgroundThreads, the PpdCache class will // spawn its own background threads. // * Prefer only to run cache on the test thread if you need to // manipulate its sequencing independently of PpdProvider; // otherwise, allowing it spawn its own background threads // should be safe and good for exercising its codepaths. enum class PpdCacheRunLocation { kOnTestThread, kInBackgroundThreads, }; // * Determines whether the browser locale given to PpdProvider // should be propagated to the composed PpdMetadataManager as its // metadata locale as well. // * Useful to the caller depending on whether or not one is // interested in the codepaths that fetch and parse the locales // metadata. enum class PropagateLocaleToMetadataManager { kDoNotPropagate, kDoPropagate, }; // Options passed to CreateProvider(). struct CreateProviderOptions { std::string browser_locale; PpdCacheRunLocation where_ppd_cache_runs; PropagateLocaleToMetadataManager propagate_locale; }; PpdProviderTest() : task_environment_(base::test::TaskEnvironment::MainThreadType::IO, base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} void SetUp() override { ASSERT_TRUE(ppd_cache_temp_dir_.CreateUniqueTempDir()); } // Creates and return a provider for a test that uses the given |options|. scoped_refptr CreateProvider( const CreateProviderOptions& options) { switch (options.where_ppd_cache_runs) { case PpdCacheRunLocation::kOnTestThread: ppd_cache_ = PpdCache::CreateForTesting( ppd_cache_temp_dir_.GetPath(), task_environment_.GetMainThreadTaskRunner()); break; case PpdCacheRunLocation::kInBackgroundThreads: default: ppd_cache_ = PpdCache::Create(ppd_cache_temp_dir_.GetPath()); break; } auto manager_config_cache = std::make_unique(); provider_backdoor_.manager_config_cache = manager_config_cache.get(); auto manager = PpdMetadataManager::Create(options.browser_locale, &clock_, std::move(manager_config_cache)); provider_backdoor_.metadata_manager = manager.get(); switch (options.propagate_locale) { case PropagateLocaleToMetadataManager::kDoNotPropagate: // Nothing to do; the no-propagate case allows the // PpdMetadataManager to acquire the metadata locale (or fail to // do so) by natural means. break; case PropagateLocaleToMetadataManager::kDoPropagate: default: provider_backdoor_.metadata_manager->SetLocaleForTesting( options.browser_locale); break; } auto config_cache = std::make_unique(); provider_backdoor_.config_cache = config_cache.get(); return PpdProvider::Create(base::Version("40.8.6753.09"), ppd_cache_, std::move(manager), std::move(config_cache)); } // Fills the fake Chrome OS Printing serving root with content. // Must be called after CreateProvider(). void StartFakePpdServer() { for (const auto& entry : server_contents()) { provider_backdoor_.config_cache->SetFetchResponseForTesting(entry.first, entry.second); provider_backdoor_.manager_config_cache->SetFetchResponseForTesting( entry.first, entry.second); } } // Interceptor posts a *task* during destruction that actually unregisters // things. So we have to run the message loop post-interceptor-destruction to // actually unregister the URLs, otherwise they won't *actually* be // unregistered until the next time we invoke the message loop. Which may be // in the middle of the next test. // // Note this is harmless to call if we haven't started a fake ppd server. void StopFakePpdServer() { for (const auto& entry : server_contents()) { provider_backdoor_.config_cache->Drop(entry.first); provider_backdoor_.manager_config_cache->Drop(entry.first); } task_environment_.RunUntilIdle(); } // Capture the result of a ResolveManufacturers() call. void CaptureResolveManufacturers(PpdProvider::CallbackResultCode code, const std::vector& data) { captured_resolve_manufacturers_.push_back({code, data}); } // Capture the result of a ResolvePrinters() call. void CaptureResolvePrinters(PpdProvider::CallbackResultCode code, const PpdProvider::ResolvedPrintersList& data) { captured_resolve_printers_.push_back({code, data}); } // Capture the result of a ResolvePpd() call. void CaptureResolvePpd(PpdProvider::CallbackResultCode code, const std::string& ppd_contents) { CapturedResolvePpdResults results; results.code = code; results.ppd_contents = ppd_contents; captured_resolve_ppd_.push_back(results); } // Capture the result of a ResolveUsbIds() call. void CaptureResolvePpdReference(PpdProvider::CallbackResultCode code, const Printer::PpdReference& ref, const std::string& usb_manufacturer) { captured_resolve_ppd_references_.push_back({code, ref, usb_manufacturer}); } // Capture the result of a ResolvePpdLicense() call. void CaptureResolvePpdLicense(PpdProvider::CallbackResultCode code, const std::string& license) { captured_resolve_ppd_license_.push_back({code, license}); } void CaptureReverseLookup(PpdProvider::CallbackResultCode code, const std::string& manufacturer, const std::string& model) { captured_reverse_lookup_.push_back({code, manufacturer, model}); } // Discard the result of a ResolvePpd() call. void DiscardResolvePpd(PpdProvider::CallbackResultCode code, const std::string& contents) {} // Calls the ResolveManufacturer() method of the |provider| and // waits for its completion. Ignores the returned string values and // returns whether the result code was // PpdProvider::CallbackResultCode::SUCCESS. bool SuccessfullyResolveManufacturers(PpdProvider* provider) { base::RunLoop run_loop; PpdProvider::CallbackResultCode code; provider->ResolveManufacturers(base::BindLambdaForTesting( [&run_loop, &code]( PpdProvider::CallbackResultCode result_code, const std::vector& unused_manufacturers) { code = result_code; run_loop.QuitClosure().Run(); })); run_loop.Run(); return code == PpdProvider::CallbackResultCode::SUCCESS; } protected: // List of relevant endpoint for this FakeServer std::vector> server_contents() const { // Use brace initialization to express the desired server contents as "url", // "contents" pairs. return {{"metadata_v3/locales.json", R"({ "locales": [ "de", "en", "es" ] })"}, {"metadata_v3/manufacturers-en.json", kDefaultManufacturersJson}, {"metadata_v3/manufacturer_a-en.json", R"({ "printers": [ { "name": "printer_a", "emm": "printer_a_ref" }, { "name": "printer_b", "emm": "printer_b_ref" } ] })"}, {"metadata_v3/manufacturer_b-en.json", R"({ "printers": [ { "name": "printer_c", "emm": "printer_c_ref" } ] })"}, {"metadata_v3/index-01.json", R"({ "ppdIndex": { "printer_a_ref": { "ppdMetadata": [ { "name": "printer_a.ppd", "license": "fake_license" } ] } } })"}, {"metadata_v3/index-02.json", R"({ "ppdIndex": { "printer_b_ref": { "ppdMetadata": [ { "name": "printer_b.ppd" } ] } } })"}, {"metadata_v3/index-03.json", R"({ "ppdIndex": { "printer_c_ref": { "ppdMetadata": [ { "name": "printer_c.ppd" } ] } } })"}, {"metadata_v3/index-04.json", R"({ "ppdIndex": { "printer_d_ref": { "ppdMetadata": [ { "name": "printer_d.ppd" } ] } } })"}, {"metadata_v3/index-05.json", R"({ "ppdIndex": { "printer_e_ref": { "ppdMetadata": [ { "name": "printer_e.ppd" } ] } } })"}, {"metadata_v3/index-08.json", R"({ "ppdIndex": { "Some canonical reference": { "ppdMetadata": [ { "name": "unused.ppd" } ] } } })"}, {"metadata_v3/index-10.json", R"({ "ppdIndex": { "Some other canonical reference": { "ppdMetadata": [ { "name": "unused.ppd" } ] } } })"}, {"metadata_v3/usb-031f.json", R"({ "usbIndex": { "1592": { "effectiveMakeAndModel": "Some canonical reference" }, "6535": { "effectiveMakeAndModel": "Some other canonical reference" } } })"}, {"metadata_v3/usb-03f0.json", ""}, {"metadata_v3/usb-1234.json", ""}, {"metadata_v3/usb_vendor_ids.json", R"({ "entries": [ { "vendorId": 799, "vendorName": "Seven Ninety Nine LLC" }, { "vendorId": 1008, "vendorName": "HP" } ] })"}, {"metadata_v3/reverse_index-en-01.json", R"({ "reverseIndex": { "printer_a_ref": { "manufacturer": "manufacturer_a_en", "model": "printer_a" } } })"}, {"metadata_v3/reverse_index-en-19.json", R"({ "reverseIndex": { "unused effective make and model": { "manufacturer": "unused manufacturer", "model": "unused model" } } })"}, {"ppds_for_metadata_v3/printer_a.ppd", kCupsFilterPpdContents}, {"ppds_for_metadata_v3/printer_b.ppd", kCupsFilter2PpdContents}, {"ppds_for_metadata_v3/printer_c.ppd", "c"}, {"ppds_for_metadata_v3/printer_d.ppd", "d"}, {"ppds_for_metadata_v3/printer_e.ppd", "e"}, {"user_supplied_ppd_directory/user_supplied.ppd", "u"}}; } // Environment for task schedulers. base::test::TaskEnvironment task_environment_; std::vector< std::pair>> captured_resolve_manufacturers_; std::vector> captured_resolve_printers_; struct CapturedResolvePpdResults { PpdProvider::CallbackResultCode code; std::string ppd_contents; }; std::vector captured_resolve_ppd_; struct CapturedResolvePpdReferenceResults { PpdProvider::CallbackResultCode code; Printer::PpdReference ref; std::string usb_manufacturer; }; std::vector captured_resolve_ppd_references_; struct CapturedReverseLookup { PpdProvider::CallbackResultCode code; std::string manufacturer; std::string model; }; std::vector captured_reverse_lookup_; struct CapturedResolvePpdLicense { PpdProvider::CallbackResultCode code; std::string license; }; std::vector captured_resolve_ppd_license_; base::ScopedTempDir ppd_cache_temp_dir_; base::ScopedTempDir interceptor_temp_dir_; // Reference to the underlying ppd_cache_ so we can muck with it to test // cache-dependent behavior of ppd_provider_. scoped_refptr ppd_cache_; PpdProviderComposedMembers provider_backdoor_; // Misc extra stuff needed for the test environment to function. base::SimpleTestClock clock_; }; // Tests that PpdProvider enqueues a bounded number of calls to // ResolveManufacturers() and fails the oldest call when the queue is // deemed full (implementation-specified detail). TEST_F(PpdProviderTest, FailsOldestQueuedResolveManufacturers) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoNotPropagate}); // Prevents the provider from ever getting a metadata locale. // We want it to stall out, forcing it to perpetually defer method // calls to ResolveManufacturers(). provider_backdoor_.manager_config_cache->DiscardFetchRequestFor( "metadata_v3/locales.json"); for (int i = kMethodDeferralLimitForTesting; i >= 0; i--) { provider->ResolveManufacturers(base::BindOnce( &PpdProviderTest::CaptureResolveManufacturers, base::Unretained(this))); } // The for loop above should have overflowed the deferral queue by // a factor of one: the oldest call to ResolveManufacturers() should // have been forced out and asked to fail, and we expect it to be // sitting on the sequence right now. ASSERT_EQ(1UL, task_environment_.GetPendingMainThreadTaskCount()); task_environment_.FastForwardUntilNoTasksRemain(); ASSERT_EQ(1UL, captured_resolve_manufacturers_.size()); EXPECT_EQ(PpdProvider::CallbackResultCode::SERVER_ERROR, captured_resolve_manufacturers_[0].first); } // Tests that PpdProvider enqueues a bounded number of calls to // ReverseLookup() and fails the oldest call when the queue is deemed // full (implementation-specified detail). TEST_F(PpdProviderTest, FailsOldestQueuedReverseLookup) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoNotPropagate}); // Prevents the provider from ever getting a metadata locale. // We want it to stall out, forcing it to perpetually defer method // calls to ReverseLookup(). provider_backdoor_.manager_config_cache->DiscardFetchRequestFor( "metadata_v3/locales.json"); for (int i = kMethodDeferralLimitForTesting; i >= 0; i--) { provider->ReverseLookup( "some effective-make-and-model string", base::BindOnce(&PpdProviderTest::CaptureReverseLookup, base::Unretained(this))); } // The for loop above should have overflowed the deferral queue by // a factor of one: the oldest call to ReverseLookup() should have // been forced out and asked to fail, and we expect it to be sitting // on the sequence right now. ASSERT_EQ(1UL, task_environment_.GetPendingMainThreadTaskCount()); task_environment_.FastForwardUntilNoTasksRemain(); ASSERT_EQ(1UL, captured_reverse_lookup_.size()); EXPECT_EQ(PpdProvider::CallbackResultCode::SERVER_ERROR, captured_reverse_lookup_[0].code); } // Test that we get back manufacturer maps as expected. TEST_F(PpdProviderTest, ManufacturersFetch) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoNotPropagate}); StartFakePpdServer(); // Issue two requests at the same time, both should be resolved properly. provider->ResolveManufacturers(base::BindOnce( &PpdProviderTest::CaptureResolveManufacturers, base::Unretained(this))); provider->ResolveManufacturers(base::BindOnce( &PpdProviderTest::CaptureResolveManufacturers, base::Unretained(this))); task_environment_.FastForwardUntilNoTasksRemain(); ASSERT_EQ(2UL, captured_resolve_manufacturers_.size()); std::vector expected_result( {"Manufacturer A", "Manufacturer B"}); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_manufacturers_[0].first); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_manufacturers_[1].first); EXPECT_TRUE(captured_resolve_manufacturers_[0].second == expected_result); EXPECT_TRUE(captured_resolve_manufacturers_[1].second == expected_result); } // Test that we get a reasonable error when we have no server to contact. Tis // is almost exactly the same as the above test, we just don't bring up the fake // server first. TEST_F(PpdProviderTest, ManufacturersFetchNoServer) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoNotPropagate}); // Issue two requests at the same time, both should resolve properly // (though they will fail). provider->ResolveManufacturers(base::BindOnce( &PpdProviderTest::CaptureResolveManufacturers, base::Unretained(this))); provider->ResolveManufacturers(base::BindOnce( &PpdProviderTest::CaptureResolveManufacturers, base::Unretained(this))); task_environment_.FastForwardUntilNoTasksRemain(); ASSERT_EQ(2UL, captured_resolve_manufacturers_.size()); EXPECT_EQ(PpdProvider::SERVER_ERROR, captured_resolve_manufacturers_[0].first); EXPECT_EQ(PpdProvider::SERVER_ERROR, captured_resolve_manufacturers_[1].first); EXPECT_TRUE(captured_resolve_manufacturers_[0].second.empty()); EXPECT_TRUE(captured_resolve_manufacturers_[1].second.empty()); } // Tests that mutiples requests for make-and-model resolution can be fulfilled // simultaneously. TEST_F(PpdProviderTest, RepeatedMakeModel) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); StartFakePpdServer(); PrinterSearchData unrecognized_printer; unrecognized_printer.discovery_type = PrinterDiscoveryType::kManual; unrecognized_printer.make_and_model = {"Printer Printer"}; PrinterSearchData recognized_printer; recognized_printer.discovery_type = PrinterDiscoveryType::kManual; recognized_printer.make_and_model = {"printer_a_ref"}; PrinterSearchData mixed; mixed.discovery_type = PrinterDiscoveryType::kManual; mixed.make_and_model = {"printer_a_ref", "Printer Printer"}; // Resolve the same thing repeatedly. provider->ResolvePpdReference( unrecognized_printer, base::BindOnce(&PpdProviderTest::CaptureResolvePpdReference, base::Unretained(this))); provider->ResolvePpdReference( mixed, base::BindOnce(&PpdProviderTest::CaptureResolvePpdReference, base::Unretained(this))); provider->ResolvePpdReference( recognized_printer, base::BindOnce(&PpdProviderTest::CaptureResolvePpdReference, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(static_cast(3), captured_resolve_ppd_references_.size()); EXPECT_EQ(PpdProvider::NOT_FOUND, captured_resolve_ppd_references_[0].code); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_references_[1].code); EXPECT_EQ("printer_a_ref", captured_resolve_ppd_references_[1].ref.effective_make_and_model); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_references_[2].code); EXPECT_EQ("printer_a_ref", captured_resolve_ppd_references_[2].ref.effective_make_and_model); } // Test successful and unsuccessful usb resolutions. TEST_F(PpdProviderTest, UsbResolution) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); StartFakePpdServer(); PrinterSearchData search_data; search_data.discovery_type = PrinterDiscoveryType::kUsb; // Should get back "Some canonical reference" search_data.usb_vendor_id = 0x031f; search_data.usb_product_id = 1592; provider->ResolvePpdReference( search_data, base::BindOnce(&PpdProviderTest::CaptureResolvePpdReference, base::Unretained(this))); // Should get back "Some other canonical reference" search_data.usb_vendor_id = 0x031f; search_data.usb_product_id = 6535; provider->ResolvePpdReference( search_data, base::BindOnce(&PpdProviderTest::CaptureResolvePpdReference, base::Unretained(this))); // Vendor id that exists, nonexistent device id, should get a NOT_FOUND. // In our fake serving root, the manufacturer with vendor ID 0x031f // (== 799) is named "Seven Ninety Nine LLC." search_data.usb_vendor_id = 0x031f; search_data.usb_product_id = 8162; provider->ResolvePpdReference( search_data, base::BindOnce(&PpdProviderTest::CaptureResolvePpdReference, base::Unretained(this))); // Nonexistent vendor id, should get a NOT_FOUND in the real world, but // the URL interceptor we're using considers all nonexistent files to // be effectively CONNECTION REFUSED, so we just check for non-success // on this one. search_data.usb_vendor_id = 0x1234; search_data.usb_product_id = 1782; provider->ResolvePpdReference( search_data, base::BindOnce(&PpdProviderTest::CaptureResolvePpdReference, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(captured_resolve_ppd_references_.size(), static_cast(4)); // ResolvePpdReference() takes place in several asynchronous steps, so // order is not guaranteed. EXPECT_THAT( captured_resolve_ppd_references_, UnorderedElementsAre( AllOf(Field(&CapturedResolvePpdReferenceResults::code, Eq(PpdProvider::SUCCESS)), Field(&CapturedResolvePpdReferenceResults::ref, Field(&Printer::PpdReference::effective_make_and_model, StrEq("Some canonical reference")))), AllOf(Field(&CapturedResolvePpdReferenceResults::code, Eq(PpdProvider::SUCCESS)), Field(&CapturedResolvePpdReferenceResults::ref, Field(&Printer::PpdReference::effective_make_and_model, StrEq("Some other canonical reference")))), AllOf(Field(&CapturedResolvePpdReferenceResults::code, Eq(PpdProvider::NOT_FOUND)), Field(&CapturedResolvePpdReferenceResults::usb_manufacturer, StrEq("Seven Ninety Nine LLC"))), Field(&CapturedResolvePpdReferenceResults::code, Eq(PpdProvider::NOT_FOUND)))); } // Test basic ResolvePrinters() functionality. At the same time, make // sure we can get the PpdReference for each of the resolved printers. TEST_F(PpdProviderTest, ResolvePrinters) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); StartFakePpdServer(); // Required setup calls to advance past PpdProvider's method deferral. ASSERT_TRUE(provider_backdoor_.metadata_manager->SetManufacturersForTesting( kDefaultManufacturersJson)); ASSERT_TRUE(SuccessfullyResolveManufacturers(provider.get())); provider->ResolvePrinters( "Manufacturer A", base::BindOnce(&PpdProviderTest::CaptureResolvePrinters, base::Unretained(this))); provider->ResolvePrinters( "Manufacturer B", base::BindOnce(&PpdProviderTest::CaptureResolvePrinters, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(2UL, captured_resolve_printers_.size()); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_printers_[0].first); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_printers_[1].first); EXPECT_EQ(2UL, captured_resolve_printers_[0].second.size()); // First capture should get back printer_a, and printer_b, with ppd // reference effective make and models of printer_a_ref and printer_b_ref. const auto& capture0 = captured_resolve_printers_[0].second; ASSERT_EQ(2UL, capture0.size()); EXPECT_EQ("printer_a", capture0[0].name); EXPECT_EQ("printer_a_ref", capture0[0].ppd_ref.effective_make_and_model); EXPECT_EQ("printer_b", capture0[1].name); EXPECT_EQ("printer_b_ref", capture0[1].ppd_ref.effective_make_and_model); // Second capture should get back printer_c with effective make and model of // printer_c_ref const auto& capture1 = captured_resolve_printers_[1].second; ASSERT_EQ(1UL, capture1.size()); EXPECT_EQ("printer_c", capture1[0].name); EXPECT_EQ("printer_c_ref", capture1[0].ppd_ref.effective_make_and_model); } // Test that if we give a bad reference to ResolvePrinters(), we get a // SERVER_ERROR. There's currently no feedback that indicates // specifically to the caller that they asked for the printers of // a manufacturer we didn't previously advertise. TEST_F(PpdProviderTest, ResolvePrintersBadReference) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); StartFakePpdServer(); // Required setup calls to advance past PpdProvider's method deferral. ASSERT_TRUE(provider_backdoor_.metadata_manager->SetManufacturersForTesting( kDefaultManufacturersJson)); ASSERT_TRUE(SuccessfullyResolveManufacturers(provider.get())); provider->ResolvePrinters( "bogus_doesnt_exist", base::BindOnce(&PpdProviderTest::CaptureResolvePrinters, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(1UL, captured_resolve_printers_.size()); EXPECT_EQ(PpdProvider::SERVER_ERROR, captured_resolve_printers_[0].first); } // Test that if the server is unavailable, we get SERVER_ERRORs back out. TEST_F(PpdProviderTest, ResolvePrintersNoServer) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); // Required setup calls to advance past PpdProvider's method deferral. ASSERT_TRUE(provider_backdoor_.metadata_manager->SetManufacturersForTesting( kDefaultManufacturersJson)); ASSERT_TRUE(SuccessfullyResolveManufacturers(provider.get())); provider->ResolvePrinters( "Manufacturer A", base::BindOnce(&PpdProviderTest::CaptureResolvePrinters, base::Unretained(this))); provider->ResolvePrinters( "Manufacturer B", base::BindOnce(&PpdProviderTest::CaptureResolvePrinters, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(2UL, captured_resolve_printers_.size()); EXPECT_EQ(PpdProvider::SERVER_ERROR, captured_resolve_printers_[0].first); EXPECT_EQ(PpdProvider::SERVER_ERROR, captured_resolve_printers_[1].first); } // Test a successful ppd resolution from an effective_make_and_model reference. TEST_F(PpdProviderTest, ResolveServerKeyPpd) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); StartFakePpdServer(); Printer::PpdReference ref; ref.effective_make_and_model = "printer_b_ref"; provider->ResolvePpd(ref, base::BindOnce(&PpdProviderTest::CaptureResolvePpd, base::Unretained(this))); ref.effective_make_and_model = "printer_c_ref"; provider->ResolvePpd(ref, base::BindOnce(&PpdProviderTest::CaptureResolvePpd, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(2UL, captured_resolve_ppd_.size()); // ResolvePpd() works in several asynchronous steps, so order of // return is not guaranteed. EXPECT_THAT( captured_resolve_ppd_, UnorderedElementsAre( AllOf(Field(&CapturedResolvePpdResults::code, PpdProvider::CallbackResultCode::SUCCESS), Field(&CapturedResolvePpdResults::ppd_contents, StrEq(kCupsFilter2PpdContents))), AllOf(Field(&CapturedResolvePpdResults::code, PpdProvider::CallbackResultCode::SUCCESS), Field(&CapturedResolvePpdResults::ppd_contents, StrEq("c"))))); } // Test that we *don't* resolve a ppd URL over non-file schemes. It's not clear // whether we'll want to do this in the long term, but for now this is // disallowed because we're not sure we completely understand the security // implications. TEST_F(PpdProviderTest, ResolveUserSuppliedUrlPpdFromNetworkFails) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); StartFakePpdServer(); Printer::PpdReference ref; // PpdProvider::ResolvePpd() shall fail if a user-supplied PPD URL // does not begin with the "file://" scheme. ref.user_supplied_ppd_url = "nonfilescheme://unused"; provider->ResolvePpd(ref, base::BindOnce(&PpdProviderTest::CaptureResolvePpd, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(1UL, captured_resolve_ppd_.size()); EXPECT_EQ(PpdProvider::INTERNAL_ERROR, captured_resolve_ppd_[0].code); EXPECT_TRUE(captured_resolve_ppd_[0].ppd_contents.empty()); } // Test a successful ppd resolution from a user_supplied_url field when // reading from a file. Note we shouldn't need the server to be up // to do this successfully, as we should be able to do this offline. TEST_F(PpdProviderTest, ResolveUserSuppliedUrlPpdFromFile) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); base::FilePath filename = temp_dir.GetPath().Append("my_spiffy.ppd"); std::string user_ppd_contents = "Woohoo"; ASSERT_TRUE(base::WriteFile(filename, user_ppd_contents)); Printer::PpdReference ref; ref.user_supplied_ppd_url = base::StringPrintf("file://%s", filename.MaybeAsASCII().c_str()); provider->ResolvePpd(ref, base::BindOnce(&PpdProviderTest::CaptureResolvePpd, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(1UL, captured_resolve_ppd_.size()); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].code); EXPECT_EQ(user_ppd_contents, captured_resolve_ppd_[0].ppd_contents); } // Test that we cache ppd resolutions when we fetch them and that we can resolve // from the cache without the server available. TEST_F(PpdProviderTest, ResolvedPpdsGetCached) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); std::string user_ppd_contents = "Woohoo"; Printer::PpdReference ref; { base::ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); base::FilePath filename = temp_dir.GetPath().Append("my_spiffy.ppd"); ASSERT_TRUE(base::WriteFile(filename, user_ppd_contents)); ref.user_supplied_ppd_url = base::StringPrintf("file://%s", filename.MaybeAsASCII().c_str()); provider->ResolvePpd(ref, base::BindOnce(&PpdProviderTest::CaptureResolvePpd, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(1UL, captured_resolve_ppd_.size()); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].code); EXPECT_EQ(user_ppd_contents, captured_resolve_ppd_[0].ppd_contents); } // ScopedTempDir goes out of scope, so the source file should now be // deleted. But if we resolve again, we should hit the cache and // still be successful. captured_resolve_ppd_.clear(); // Recreate the provider to make sure we don't have any memory caches which // would mask problems with disk persistence. provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); // Re-resolve. provider->ResolvePpd(ref, base::BindOnce(&PpdProviderTest::CaptureResolvePpd, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(1UL, captured_resolve_ppd_.size()); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].code); EXPECT_EQ(user_ppd_contents, captured_resolve_ppd_[0].ppd_contents); } // Test that all entrypoints will correctly work with case-insensitve // effective-make-and-model strings. TEST_F(PpdProviderTest, CaseInsensitiveMakeAndModel) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); StartFakePpdServer(); std::string ref = "pRiNteR_A_reF"; Printer::PpdReference ppd_ref; ppd_ref.effective_make_and_model = ref; provider->ResolvePpd(ppd_ref, base::BindOnce(&PpdProviderTest::CaptureResolvePpd, base::Unretained(this))); provider->ReverseLookup(ref, base::BindOnce(&PpdProviderTest::CaptureReverseLookup, base::Unretained(this))); PrinterSearchData printer_info; printer_info.make_and_model = {ref}; provider->ResolvePpdReference( printer_info, base::BindOnce(&PpdProviderTest::CaptureResolvePpdReference, base::Unretained(this))); task_environment_.RunUntilIdle(); // Check PpdProvider::ResolvePpd ASSERT_EQ(1UL, captured_resolve_ppd_.size()); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].code); EXPECT_EQ(kCupsFilterPpdContents, captured_resolve_ppd_[0].ppd_contents); // Check PpdProvider::ReverseLookup ASSERT_EQ(1UL, captured_reverse_lookup_.size()); EXPECT_EQ(PpdProvider::SUCCESS, captured_reverse_lookup_[0].code); EXPECT_EQ("manufacturer_a_en", captured_reverse_lookup_[0].manufacturer); EXPECT_EQ("printer_a", captured_reverse_lookup_[0].model); // Check PpdProvider::ResolvePpdReference ASSERT_EQ(1UL, captured_resolve_ppd_references_.size()); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_references_[0].code); EXPECT_EQ("printer_a_ref", captured_resolve_ppd_references_[0].ref.effective_make_and_model); } // Tests that ResolvePpdLicense is able to correctly source the index and // determine the name of the PPD license associated with the given effecive make // and model (if any). TEST_F(PpdProviderTest, ResolvePpdLicense) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoNotPropagate}); StartFakePpdServer(); // For this effective_make_and_model, we expect that there is associated // license. const char kEmm1[] = "printer_a_ref"; provider->ResolvePpdLicense( kEmm1, base::BindOnce(&PpdProviderTest::CaptureResolvePpdLicense, base::Unretained(this))); // We do not expect there to be any license associated with this // effective_make_and_model, so the response should be empty. const char kEmm2[] = "printer_b_ref"; provider->ResolvePpdLicense( kEmm2, base::BindOnce(&PpdProviderTest::CaptureResolvePpdLicense, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(2UL, captured_resolve_ppd_license_.size()); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_license_[0].code); EXPECT_EQ("fake_license", captured_resolve_ppd_license_[0].license); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_license_[1].code); EXPECT_EQ("", captured_resolve_ppd_license_[1].license); } // Verifies that we can extract the Manufacturer and Model selection for a // given effective make and model. TEST_F(PpdProviderTest, ReverseLookup) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); StartFakePpdServer(); std::string ref = "printer_a_ref"; provider->ReverseLookup(ref, base::BindOnce(&PpdProviderTest::CaptureReverseLookup, base::Unretained(this))); // TODO(skau): PpdProvider has a race condition that prevents running these // requests in parallel. task_environment_.RunUntilIdle(); std::string ref_fail = "printer_does_not_exist"; provider->ReverseLookup(ref_fail, base::BindOnce(&PpdProviderTest::CaptureReverseLookup, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(2U, captured_reverse_lookup_.size()); CapturedReverseLookup success_capture = captured_reverse_lookup_[0]; EXPECT_EQ(PpdProvider::SUCCESS, success_capture.code); EXPECT_EQ("manufacturer_a_en", success_capture.manufacturer); EXPECT_EQ("printer_a", success_capture.model); CapturedReverseLookup failed_capture = captured_reverse_lookup_[1]; EXPECT_EQ(PpdProvider::NOT_FOUND, failed_capture.code); } // Verifies that we never attempt to re-download a PPD that we // previously retrieved from the serving root. The Chrome OS Printing // Team plans to keep PPDs immutable inside the serving root, so // PpdProvider should always prefer to retrieve a PPD from the PpdCache // when it's possible to do so. TEST_F(PpdProviderTest, PreferToResolvePpdFromPpdCacheOverServingRoot) { // Explicitly *not* starting a fake server. std::string cached_ppd_contents = "These cached contents are different from what's being served"; auto provider = CreateProvider({"en", PpdCacheRunLocation::kOnTestThread, PropagateLocaleToMetadataManager::kDoPropagate}); Printer::PpdReference ref; ref.effective_make_and_model = "printer_a_ref"; std::string cache_key = PpdProvider::PpdReferenceToCacheKey(ref); // Cache exists, and is just created, so should be fresh. // // PPD basename is taken from value specified in forward index shard // defined in server_contents(). const std::string ppd_basename = "printer_a.ppd"; ppd_cache_->StoreForTesting(PpdProvider::PpdBasenameToCacheKey(ppd_basename), cached_ppd_contents, base::TimeDelta()); ppd_cache_->StoreForTesting(PpdProvider::PpdReferenceToCacheKey(ref), ppd_basename, base::TimeDelta()); task_environment_.RunUntilIdle(); provider->ResolvePpd(ref, base::BindOnce(&PpdProviderTest::CaptureResolvePpd, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(1UL, captured_resolve_ppd_.size()); // Should get the cached (not served) results back, and not have hit the // network. EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].code); EXPECT_EQ(cached_ppd_contents, captured_resolve_ppd_[0].ppd_contents); } // For user-provided ppds, we should always use the latest version on // disk if it still exists there. TEST_F(PpdProviderTest, UserPpdAlwaysRefreshedIfAvailable) { base::ScopedTempDir temp_dir; std::string cached_ppd_contents = "Cached Ppd Contents"; std::string disk_ppd_contents = "Updated Ppd Contents"; auto provider = CreateProvider({"en", PpdCacheRunLocation::kOnTestThread, PropagateLocaleToMetadataManager::kDoPropagate}); StartFakePpdServer(); ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); base::FilePath filename = temp_dir.GetPath().Append("my_spiffy.ppd"); Printer::PpdReference ref; ref.user_supplied_ppd_url = base::StringPrintf("file://%s", filename.MaybeAsASCII().c_str()); // Put cached_ppd_contents into the cache. ppd_cache_->StoreForTesting(PpdProvider::PpdReferenceToCacheKey(ref), cached_ppd_contents, base::TimeDelta()); task_environment_.RunUntilIdle(); // Write different contents to disk, so that the cached contents are // now stale. ASSERT_TRUE(base::WriteFile(filename, disk_ppd_contents)); provider->ResolvePpd(ref, base::BindOnce(&PpdProviderTest::CaptureResolvePpd, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(1UL, captured_resolve_ppd_.size()); EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].code); EXPECT_EQ(disk_ppd_contents, captured_resolve_ppd_[0].ppd_contents); // Check that the cache was also updated with the new contents. PpdCache::FindResult captured_find_result; ppd_cache_->Find(PpdProvider::PpdReferenceToCacheKey(ref), base::BindOnce( [](PpdCache::FindResult* captured_find_result, const PpdCache::FindResult& find_result) { *captured_find_result = find_result; }, &captured_find_result)); task_environment_.RunUntilIdle(); EXPECT_EQ(captured_find_result.success, true); EXPECT_EQ(captured_find_result.contents, disk_ppd_contents); } // Test resolving usb manufacturer when failed to resolve PpdReference. TEST_F(PpdProviderTest, ResolveUsbManufacturer) { auto provider = CreateProvider({"en", PpdCacheRunLocation::kInBackgroundThreads, PropagateLocaleToMetadataManager::kDoPropagate}); StartFakePpdServer(); PrinterSearchData search_data; search_data.discovery_type = PrinterDiscoveryType::kUsb; // Vendor id that exists, nonexistent device id, should get a NOT_FOUND. // Although this is an unsupported printer model, we can still expect to get // the manufacturer name. search_data.usb_vendor_id = 0x03f0; search_data.usb_product_id = 9999; provider->ResolvePpdReference( search_data, base::BindOnce(&PpdProviderTest::CaptureResolvePpdReference, base::Unretained(this))); // Nonexistent vendor id, should get a NOT_FOUND in the real world, but // the URL interceptor we're using considers all nonexistent files to // be effectively CONNECTION REFUSED, so we just check for non-success // on this one. We should also not be able to get a manufacturer name from a // nonexistent vendor id. search_data.usb_vendor_id = 0x1234; search_data.usb_product_id = 1782; provider->ResolvePpdReference( search_data, base::BindOnce(&PpdProviderTest::CaptureResolvePpdReference, base::Unretained(this))); task_environment_.RunUntilIdle(); ASSERT_EQ(static_cast(2), captured_resolve_ppd_references_.size()); // Was able to find the printer manufactuer but unable to find the printer // model should result in a NOT_FOUND. EXPECT_EQ(PpdProvider::NOT_FOUND, captured_resolve_ppd_references_[0].code); // Failed to grab the PPD for a USB printer, but should still be able to grab // its manufacturer name. EXPECT_EQ("HP", captured_resolve_ppd_references_[0].usb_manufacturer); // Unable to find the PPD file should result in a failure. EXPECT_FALSE(captured_resolve_ppd_references_[1].code == PpdProvider::SUCCESS); // Unknown vendor id should result in an empty manufacturer string. EXPECT_TRUE(captured_resolve_ppd_references_[1].usb_manufacturer.empty()); } } // namespace } // namespace chromeos