1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h"
6 
7 #include <memory>
8 
9 #include "base/bind.h"
10 #include "base/callback_helpers.h"
11 #include "base/files/file_path.h"
12 #include "base/json/json_string_value_serializer.h"
13 #include "base/test/metrics/histogram_tester.h"
14 #include "base/test/scoped_feature_list.h"
15 #include "base/values.h"
16 #include "chrome/browser/chromeos/printing/print_management/print_management_uma.h"
17 #include "chrome/browser/chromeos/printing/printing_stubs.h"
18 #include "chrome/browser/download/chrome_download_manager_delegate.h"
19 #include "chrome/browser/download/download_core_service_factory.h"
20 #include "chrome/browser/download/download_core_service_impl.h"
21 #include "chrome/browser/ui/chrome_select_file_policy.h"
22 #include "chrome/test/base/testing_profile.h"
23 #include "chromeos/constants/chromeos_features.h"
24 #include "chromeos/dbus/dbus_thread_manager.h"
25 #include "chromeos/dbus/debug_daemon/debug_daemon_client.h"
26 #include "content/public/test/browser_task_environment.h"
27 #include "content/public/test/test_web_ui.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29 #include "ui/shell_dialogs/select_file_dialog.h"
30 #include "ui/shell_dialogs/select_file_dialog_factory.h"
31 
32 namespace chromeos {
33 namespace settings {
34 
35 class CupsPrintersHandlerTest;
36 
37 namespace {
38 
39 // Converts JSON string to base::ListValue object.
40 // On failure, returns NULL and fills |*error| string.
GetJSONAsListValue(const std::string & json,std::string * error)41 std::unique_ptr<base::ListValue> GetJSONAsListValue(const std::string& json,
42                                                     std::string* error) {
43   auto ret = base::ListValue::From(
44       JSONStringValueDeserializer(json).Deserialize(nullptr, error));
45   if (!ret)
46     *error = "Value is not a list.";
47   return ret;
48 }
49 
50 }  // namespace
51 
52 // Callback used for testing CupsAddAutoConfiguredPrinter().
AddedPrinter(int32_t status)53 void AddedPrinter(int32_t status) {
54   ASSERT_EQ(status, 0);
55 }
56 
57 // Callback used for testing CupsRemovePrinter().
RemovedPrinter(base::OnceClosure quit_closure,bool * expected,bool result)58 void RemovedPrinter(base::OnceClosure quit_closure,
59                     bool* expected,
60                     bool result) {
61   *expected = result;
62   std::move(quit_closure).Run();
63 }
64 
65 class TestCupsPrintersManager : public StubCupsPrintersManager {
66  public:
GetPrinter(const std::string & id) const67   base::Optional<Printer> GetPrinter(const std::string& id) const override {
68     return Printer();
69   }
70 };
71 
72 class FakePpdProvider : public PpdProvider {
73  public:
74   FakePpdProvider() = default;
75 
ResolveManufacturers(ResolveManufacturersCallback cb)76   void ResolveManufacturers(ResolveManufacturersCallback cb) override {}
ResolvePrinters(const std::string & manufacturer,ResolvePrintersCallback cb)77   void ResolvePrinters(const std::string& manufacturer,
78                        ResolvePrintersCallback cb) override {}
ResolvePpdReference(const PrinterSearchData & search_data,ResolvePpdReferenceCallback cb)79   void ResolvePpdReference(const PrinterSearchData& search_data,
80                            ResolvePpdReferenceCallback cb) override {}
ResolvePpd(const Printer::PpdReference & reference,ResolvePpdCallback cb)81   void ResolvePpd(const Printer::PpdReference& reference,
82                   ResolvePpdCallback cb) override {}
ResolvePpdLicense(base::StringPiece effective_make_and_model,ResolvePpdLicenseCallback cb)83   void ResolvePpdLicense(base::StringPiece effective_make_and_model,
84                          ResolvePpdLicenseCallback cb) override {}
ReverseLookup(const std::string & effective_make_and_model,ReverseLookupCallback cb)85   void ReverseLookup(const std::string& effective_make_and_model,
86                      ReverseLookupCallback cb) override {}
87 
88  private:
~FakePpdProvider()89   ~FakePpdProvider() override {}
90 };
91 
92 class TestSelectFilePolicy : public ui::SelectFilePolicy {
93  public:
94   TestSelectFilePolicy& operator=(const TestSelectFilePolicy&) = delete;
95 
CanOpenSelectFileDialog()96   bool CanOpenSelectFileDialog() override { return true; }
SelectFileDenied()97   void SelectFileDenied() override {}
98 };
99 
100 // A fake ui::SelectFileDialog, which will cancel the file selection instead of
101 // selecting a file and verify that the extensions are correctly set.
102 class FakeSelectFileDialog : public ui::SelectFileDialog {
103  public:
FakeSelectFileDialog(Listener * listener,std::unique_ptr<ui::SelectFilePolicy> policy,FileTypeInfo * file_type)104   FakeSelectFileDialog(Listener* listener,
105                        std::unique_ptr<ui::SelectFilePolicy> policy,
106                        FileTypeInfo* file_type)
107       : ui::SelectFileDialog(listener, std::move(policy)),
108         expected_file_type_info_(file_type) {}
109 
110   FakeSelectFileDialog(const FakeSelectFileDialog&) = delete;
111   FakeSelectFileDialog& operator=(const FakeSelectFileDialog&) = delete;
112 
113  protected:
SelectFileImpl(Type type,const base::string16 & title,const base::FilePath & default_path,const FileTypeInfo * file_types,int file_type_index,const base::FilePath::StringType & default_extension,gfx::NativeWindow owning_window,void * params)114   void SelectFileImpl(Type type,
115                       const base::string16& title,
116                       const base::FilePath& default_path,
117                       const FileTypeInfo* file_types,
118                       int file_type_index,
119                       const base::FilePath::StringType& default_extension,
120                       gfx::NativeWindow owning_window,
121                       void* params) override {
122     // Check that the extensions we expect match the actual extensions passed
123     // from the CupsPrintersHandler.
124     VerifyExtensions(file_types);
125     // Close the file select dialog.
126     listener_->FileSelectionCanceled(params);
127   }
128 
IsRunning(gfx::NativeWindow owning_window) const129   bool IsRunning(gfx::NativeWindow owning_window) const override {
130     return true;
131   }
ListenerDestroyed()132   void ListenerDestroyed() override {}
HasMultipleFileTypeChoicesImpl()133   bool HasMultipleFileTypeChoicesImpl() override { return false; }
134 
VerifyExtensions(const FileTypeInfo * file_types)135   void VerifyExtensions(const FileTypeInfo* file_types) {
136     const std::vector<std::vector<base::FilePath::StringType>>& actual_exts =
137         file_types->extensions;
138     std::vector<std::vector<base::FilePath::StringType>> expected_exts =
139         expected_file_type_info_->extensions;
140 
141     for (std::vector<base::FilePath::StringType> actual : actual_exts) {
142       bool is_equal = false;
143       std::sort(actual.begin(), actual.end());
144       for (auto expected_it = expected_exts.begin();
145            expected_it != expected_exts.end(); ++expected_it) {
146         std::vector<base::FilePath::StringType>& expected = *expected_it;
147         std::sort(expected.begin(), expected.end());
148         if (expected == actual) {
149           is_equal = true;
150           expected_exts.erase(expected_it);
151           break;
152         }
153       }
154       ASSERT_TRUE(is_equal);
155     }
156   }
157 
158  private:
159   ~FakeSelectFileDialog() override = default;
160 
161   ui::SelectFileDialog::FileTypeInfo* expected_file_type_info_;
162 };
163 
164 // A factory associated with the artificial file picker.
165 class TestSelectFileDialogFactory : public ui::SelectFileDialogFactory {
166  public:
TestSelectFileDialogFactory(ui::SelectFileDialog::FileTypeInfo * expected_file_type_info)167   explicit TestSelectFileDialogFactory(
168       ui::SelectFileDialog::FileTypeInfo* expected_file_type_info)
169       : expected_file_type_info_(expected_file_type_info) {}
170 
Create(ui::SelectFileDialog::Listener * listener,std::unique_ptr<ui::SelectFilePolicy> policy)171   ui::SelectFileDialog* Create(
172       ui::SelectFileDialog::Listener* listener,
173       std::unique_ptr<ui::SelectFilePolicy> policy) override {
174     // TODO(jimmyxgong): Investigate why using |policy| created by
175     // CupsPrintersHandler crashes the test.
176     return new FakeSelectFileDialog(listener,
177                                     std::make_unique<TestSelectFilePolicy>(),
178                                     expected_file_type_info_);
179   }
180 
181   TestSelectFileDialogFactory(const TestSelectFileDialogFactory&) = delete;
182   TestSelectFileDialogFactory& operator=(const TestSelectFileDialogFactory&) =
183       delete;
184 
185  private:
186   ui::SelectFileDialog::FileTypeInfo* expected_file_type_info_;
187 };
188 
189 class CupsPrintersHandlerTest : public testing::Test {
190  public:
CupsPrintersHandlerTest()191   CupsPrintersHandlerTest()
192       : task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD),
193         profile_(),
194         web_ui_(),
195         printers_handler_() {}
196   ~CupsPrintersHandlerTest() override = default;
197 
SetUp()198   void SetUp() override {
199     scoped_feature_list_.InitWithFeatures(
200         {chromeos::features::kPrintJobManagementApp}, {});
201     printers_handler_ = CupsPrintersHandler::CreateForTesting(
202         &profile_, base::MakeRefCounted<FakePpdProvider>(),
203         std::make_unique<StubPrinterConfigurer>(), &printers_manager_);
204     printers_handler_->SetWebUIForTest(&web_ui_);
205     printers_handler_->RegisterMessages();
206   }
207 
208  protected:
209   // Must outlive |profile_|.
210   base::HistogramTester histogram_tester_;
211   content::BrowserTaskEnvironment task_environment_;
212   TestingProfile profile_;
213   content::TestWebUI web_ui_;
214   std::unique_ptr<CupsPrintersHandler> printers_handler_;
215   TestCupsPrintersManager printers_manager_;
216   base::test::ScopedFeatureList scoped_feature_list_;
217 };
218 
TEST_F(CupsPrintersHandlerTest,RemoveCorrectPrinter)219 TEST_F(CupsPrintersHandlerTest, RemoveCorrectPrinter) {
220   DBusThreadManager::Initialize();
221   DebugDaemonClient* client = DBusThreadManager::Get()->GetDebugDaemonClient();
222   client->CupsAddAutoConfiguredPrinter("testprinter1", "fakeuri",
223                                        base::BindOnce(&AddedPrinter));
224 
225   const std::string remove_list = R"(
226     ["testprinter1", "Test Printer 1"]
227   )";
228   std::string error;
229   std::unique_ptr<base::ListValue> remove_printers(
230       GetJSONAsListValue(remove_list, &error));
231   ASSERT_TRUE(remove_printers) << "Error deserializing list: " << error;
232 
233   web_ui_.HandleReceivedMessage("removeCupsPrinter", remove_printers.get());
234 
235   // We expect this printer removal to fail since the printer should have
236   // already been removed by the previous call to 'removeCupsPrinter'.
237   base::RunLoop run_loop;
238   bool expected = true;
239   client->CupsRemovePrinter(
240       "testprinter1",
241       base::BindOnce(&RemovedPrinter, run_loop.QuitClosure(), &expected),
242       base::DoNothing());
243   run_loop.Run();
244   EXPECT_FALSE(expected);
245 }
246 
TEST_F(CupsPrintersHandlerTest,VerifyOnlyPpdFilesAllowed)247 TEST_F(CupsPrintersHandlerTest, VerifyOnlyPpdFilesAllowed) {
248   DownloadCoreServiceFactory::GetForBrowserContext(&profile_)
249       ->SetDownloadManagerDelegateForTesting(
250           std::make_unique<ChromeDownloadManagerDelegate>(&profile_));
251 
252   ui::SelectFileDialog::FileTypeInfo expected_file_type_info;
253   // We only allow .ppd and .ppd.gz file extensions for our file select dialog.
254   expected_file_type_info.extensions.push_back({"ppd"});
255   expected_file_type_info.extensions.push_back({"ppd.gz"});
256   ui::SelectFileDialog::SetFactory(
257       new TestSelectFileDialogFactory(&expected_file_type_info));
258 
259   base::Value args(base::Value::Type::LIST);
260   args.Append("handleFunctionName");
261   web_ui_.HandleReceivedMessage("selectPPDFile",
262                                 &base::Value::AsListValue(args));
263 }
264 
TEST_F(CupsPrintersHandlerTest,VerifyPrintManagementAppEntryPointHistogram)265 TEST_F(CupsPrintersHandlerTest, VerifyPrintManagementAppEntryPointHistogram) {
266   base::Value args(base::Value::Type::LIST);
267   web_ui_.HandleReceivedMessage("openPrintManagementApp",
268                                 &base::Value::AsListValue(args));
269   histogram_tester_.ExpectBucketCount(
270       "Printing.CUPS.PrintManagementAppEntryPoint",
271       PrintManagementAppEntryPoint::kSettings, 1);
272   histogram_tester_.ExpectBucketCount(
273       "Printing.CUPS.PrintManagementAppEntryPoint",
274       PrintManagementAppEntryPoint::kNotification, 0);
275   histogram_tester_.ExpectBucketCount(
276       "Printing.CUPS.PrintManagementAppEntryPoint",
277       PrintManagementAppEntryPoint::kLauncher, 0);
278   histogram_tester_.ExpectBucketCount(
279       "Printing.CUPS.PrintManagementAppEntryPoint",
280       PrintManagementAppEntryPoint::kBrowser, 0);
281 }
282 
283 }  // namespace settings.
284 }  // namespace chromeos.
285