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