Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / extensions / api / developer_private / extension_info_generator_unittest.cc
blob65e3aec67ffb18e1ef4f1722b26c09d3aae6ff2b
1 // Copyright 2015 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.
5 #include "base/json/json_file_value_serializer.h"
6 #include "base/json/json_writer.h"
7 #include "base/strings/string_util.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/extensions/api/developer_private/extension_info_generator.h"
10 #include "chrome/browser/extensions/api/developer_private/inspectable_views_finder.h"
11 #include "chrome/browser/extensions/error_console/error_console.h"
12 #include "chrome/browser/extensions/extension_service.h"
13 #include "chrome/browser/extensions/extension_service_test_base.h"
14 #include "chrome/browser/extensions/extension_util.h"
15 #include "chrome/browser/extensions/permissions_updater.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/common/extensions/api/developer_private.h"
18 #include "chrome/common/pref_names.h"
19 #include "components/crx_file/id_util.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/common/constants.h"
22 #include "extensions/common/extension.h"
23 #include "extensions/common/extension_builder.h"
24 #include "extensions/common/feature_switch.h"
25 #include "extensions/common/value_builder.h"
26 #include "testing/gtest/include/gtest/gtest.h"
28 namespace extensions {
30 namespace developer = api::developer_private;
32 namespace {
34 const char kAllHostsPermission[] = "*://*/*";
36 scoped_ptr<base::DictionaryValue> DeserializeJSONTestData(
37 const base::FilePath& path,
38 std::string *error) {
39 base::Value* value = nullptr;
40 JSONFileValueDeserializer deserializer(path);
41 value = deserializer.Deserialize(nullptr, error);
42 return make_scoped_ptr(static_cast<base::DictionaryValue*>(value));
45 } // namespace
47 class ExtensionInfoGeneratorUnitTest : public ExtensionServiceTestBase {
48 public:
49 ExtensionInfoGeneratorUnitTest() {}
50 ~ExtensionInfoGeneratorUnitTest() override {}
52 protected:
53 void SetUp() override {
54 ExtensionServiceTestBase::SetUp();
55 InitializeEmptyExtensionService();
58 void OnInfosGenerated(linked_ptr<developer::ExtensionInfo>* info_out,
59 const ExtensionInfoGenerator::ExtensionInfoList& list) {
60 EXPECT_EQ(1u, list.size());
61 if (!list.empty())
62 *info_out = list[0];
63 quit_closure_.Run();
64 quit_closure_.Reset();
67 scoped_ptr<developer::ExtensionInfo> GenerateExtensionInfo(
68 const std::string& extension_id) {
69 linked_ptr<developer::ExtensionInfo> info;
70 base::RunLoop run_loop;
71 quit_closure_ = run_loop.QuitClosure();
72 scoped_ptr<ExtensionInfoGenerator> generator(
73 new ExtensionInfoGenerator(browser_context()));
74 generator->CreateExtensionInfo(
75 extension_id,
76 base::Bind(&ExtensionInfoGeneratorUnitTest::OnInfosGenerated,
77 base::Unretained(this),
78 base::Unretained(&info)));
79 run_loop.Run();
80 return make_scoped_ptr(info.release());
83 const scoped_refptr<const Extension> CreateExtension(
84 const std::string& name,
85 ListBuilder& permissions) {
86 const std::string kId = crx_file::id_util::GenerateId(name);
87 scoped_refptr<const Extension> extension =
88 ExtensionBuilder().SetManifest(
89 DictionaryBuilder()
90 .Set("name", name)
91 .Set("description", "an extension")
92 .Set("manifest_version", 2)
93 .Set("version", "1.0.0")
94 .Set("permissions", permissions))
95 .SetLocation(Manifest::INTERNAL)
96 .SetID(kId)
97 .Build();
99 ExtensionRegistry::Get(profile())->AddEnabled(extension);
100 PermissionsUpdater(profile()).InitializePermissions(extension.get());
101 return extension;
104 scoped_ptr<developer::ExtensionInfo> CreateExtensionInfoFromPath(
105 const base::FilePath& extension_path,
106 Manifest::Location location) {
107 std::string error;
109 base::FilePath manifest_path = extension_path.Append(kManifestFilename);
110 scoped_ptr<base::DictionaryValue> extension_data =
111 DeserializeJSONTestData(manifest_path, &error);
112 EXPECT_EQ(std::string(), error);
114 scoped_refptr<Extension> extension(Extension::Create(
115 extension_path, location, *extension_data, Extension::REQUIRE_KEY,
116 &error));
117 CHECK(extension.get());
118 service()->AddExtension(extension.get());
119 EXPECT_EQ(std::string(), error);
121 return GenerateExtensionInfo(extension->id());
124 void CompareExpectedAndActualOutput(
125 const base::FilePath& extension_path,
126 const InspectableViewsFinder::ViewList& views,
127 const base::FilePath& expected_output_path) {
128 std::string error;
129 scoped_ptr<base::DictionaryValue> expected_output_data(
130 DeserializeJSONTestData(expected_output_path, &error));
131 EXPECT_EQ(std::string(), error);
133 // Produce test output.
134 scoped_ptr<developer::ExtensionInfo> info =
135 CreateExtensionInfoFromPath(extension_path, Manifest::INVALID_LOCATION);
136 info->views = views;
137 scoped_ptr<base::DictionaryValue> actual_output_data = info->ToValue();
138 ASSERT_TRUE(actual_output_data);
140 // Compare the outputs.
141 // Ignore unknown fields in the actual output data.
142 std::string paths_details = " - expected (" +
143 expected_output_path.MaybeAsASCII() + ") vs. actual (" +
144 extension_path.MaybeAsASCII() + ")";
145 std::string expected_string;
146 std::string actual_string;
147 for (base::DictionaryValue::Iterator field(*expected_output_data);
148 !field.IsAtEnd(); field.Advance()) {
149 const base::Value& expected_value = field.value();
150 base::Value* actual_value = nullptr;
151 EXPECT_TRUE(actual_output_data->Get(field.key(), &actual_value)) <<
152 field.key() + " is missing" + paths_details;
153 if (!actual_value)
154 continue;
155 if (!actual_value->Equals(&expected_value)) {
156 base::JSONWriter::Write(expected_value, &expected_string);
157 base::JSONWriter::Write(*actual_value, &actual_string);
158 EXPECT_EQ(expected_string, actual_string) <<
159 field.key() << paths_details;
164 private:
165 base::Closure quit_closure_;
167 DISALLOW_COPY_AND_ASSIGN(ExtensionInfoGeneratorUnitTest);
170 // Test some of the basic fields.
171 TEST_F(ExtensionInfoGeneratorUnitTest, BasicInfoTest) {
172 // Enable error console for testing.
173 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT);
174 FeatureSwitch::ScopedOverride error_console_override(
175 FeatureSwitch::error_console(), true);
176 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
178 const char kName[] = "extension name";
179 const char kVersion[] = "1.0.0.1";
180 std::string id = crx_file::id_util::GenerateId("alpha");
181 scoped_ptr<base::DictionaryValue> manifest =
182 DictionaryBuilder().Set("name", kName)
183 .Set("version", kVersion)
184 .Set("manifest_version", 2)
185 .Set("description", "an extension")
186 .Set("permissions",
187 ListBuilder().Append("file://*/*")).Build();
188 scoped_ptr<base::DictionaryValue> manifest_copy(manifest->DeepCopy());
189 scoped_refptr<const Extension> extension =
190 ExtensionBuilder().SetManifest(manifest.Pass())
191 .SetLocation(Manifest::UNPACKED)
192 .SetPath(data_dir())
193 .SetID(id)
194 .Build();
195 service()->AddExtension(extension.get());
196 ErrorConsole* error_console = ErrorConsole::Get(profile());
197 error_console->ReportError(
198 make_scoped_ptr(new RuntimeError(
199 extension->id(),
200 false,
201 base::UTF8ToUTF16("source"),
202 base::UTF8ToUTF16("message"),
203 StackTrace(1, StackFrame(1,
205 base::UTF8ToUTF16("source"),
206 base::UTF8ToUTF16("function"))),
207 GURL("url"),
208 logging::LOG_ERROR,
210 1)));
211 error_console->ReportError(
212 make_scoped_ptr(new ManifestError(extension->id(),
213 base::UTF8ToUTF16("message"),
214 base::UTF8ToUTF16("key"),
215 base::string16())));
216 error_console->ReportError(
217 make_scoped_ptr(new RuntimeError(
218 extension->id(),
219 false,
220 base::UTF8ToUTF16("source"),
221 base::UTF8ToUTF16("message"),
222 StackTrace(1, StackFrame(1,
224 base::UTF8ToUTF16("source"),
225 base::UTF8ToUTF16("function"))),
226 GURL("url"),
227 logging::LOG_VERBOSE,
229 1)));
231 // It's not feasible to validate every field here, because that would be
232 // a duplication of the logic in the method itself. Instead, test a handful
233 // of fields for sanity.
234 scoped_ptr<api::developer_private::ExtensionInfo> info =
235 GenerateExtensionInfo(extension->id());
236 ASSERT_TRUE(info.get());
237 EXPECT_EQ(kName, info->name);
238 EXPECT_EQ(id, info->id);
239 EXPECT_EQ(kVersion, info->version);
240 EXPECT_EQ(info->location, developer::LOCATION_UNPACKED);
241 ASSERT_TRUE(info->path);
242 EXPECT_EQ(data_dir(), base::FilePath::FromUTF8Unsafe(*info->path));
243 EXPECT_EQ(api::developer_private::EXTENSION_STATE_ENABLED, info->state);
244 EXPECT_EQ(api::developer_private::EXTENSION_TYPE_EXTENSION, info->type);
245 EXPECT_TRUE(info->file_access.is_enabled);
246 EXPECT_FALSE(info->file_access.is_active);
247 EXPECT_TRUE(info->incognito_access.is_enabled);
248 EXPECT_FALSE(info->incognito_access.is_active);
249 ASSERT_EQ(2u, info->runtime_errors.size());
250 const api::developer_private::RuntimeError& runtime_error =
251 *info->runtime_errors[0];
252 EXPECT_EQ(extension->id(), runtime_error.extension_id);
253 EXPECT_EQ(api::developer_private::ERROR_TYPE_RUNTIME, runtime_error.type);
254 EXPECT_EQ(api::developer_private::ERROR_LEVEL_ERROR,
255 runtime_error.severity);
256 EXPECT_EQ(1u, runtime_error.stack_trace.size());
257 ASSERT_EQ(1u, info->manifest_errors.size());
258 const api::developer_private::RuntimeError& runtime_error_verbose =
259 *info->runtime_errors[1];
260 EXPECT_EQ(api::developer_private::ERROR_LEVEL_LOG,
261 runtime_error_verbose.severity);
262 const api::developer_private::ManifestError& manifest_error =
263 *info->manifest_errors[0];
264 EXPECT_EQ(extension->id(), manifest_error.extension_id);
266 // Test an extension that isn't unpacked.
267 manifest_copy->SetString("update_url",
268 "https://clients2.google.com/service/update2/crx");
269 id = crx_file::id_util::GenerateId("beta");
270 extension = ExtensionBuilder().SetManifest(manifest_copy.Pass())
271 .SetLocation(Manifest::EXTERNAL_PREF)
272 .SetID(id)
273 .Build();
274 service()->AddExtension(extension.get());
275 info = GenerateExtensionInfo(extension->id());
276 EXPECT_EQ(developer::LOCATION_THIRD_PARTY, info->location);
277 EXPECT_FALSE(info->path);
280 // Test three generated json outputs.
281 TEST_F(ExtensionInfoGeneratorUnitTest, GenerateExtensionsJSONData) {
282 // Test Extension1
283 base::FilePath extension_path =
284 data_dir().AppendASCII("good")
285 .AppendASCII("Extensions")
286 .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
287 .AppendASCII("1.0.0.0");
289 InspectableViewsFinder::ViewList views;
290 views.push_back(InspectableViewsFinder::ConstructView(
291 GURL("chrome-extension://behllobkkfkfnphdnhnkndlbkcpglgmj/bar.html"),
292 42, 88, false, VIEW_TYPE_TAB_CONTENTS));
293 views.push_back(InspectableViewsFinder::ConstructView(
294 GURL("chrome-extension://behllobkkfkfnphdnhnkndlbkcpglgmj/dog.html"),
295 0, 0, false, VIEW_TYPE_TAB_CONTENTS));
297 base::FilePath expected_outputs_path =
298 data_dir().AppendASCII("api_test")
299 .AppendASCII("developer")
300 .AppendASCII("generated_output");
302 CompareExpectedAndActualOutput(
303 extension_path,
304 views,
305 expected_outputs_path.AppendASCII(
306 "behllobkkfkfnphdnhnkndlbkcpglgmj.json"));
308 #if !defined(OS_CHROMEOS)
309 // Test Extension2
310 extension_path = data_dir().AppendASCII("good")
311 .AppendASCII("Extensions")
312 .AppendASCII("hpiknbiabeeppbpihjehijgoemciehgk")
313 .AppendASCII("2");
315 // It's OK to have duplicate URLs, so long as the IDs are different.
316 views[0]->url =
317 "chrome-extension://hpiknbiabeeppbpihjehijgoemciehgk/bar.html";
318 views[1]->url = views[0]->url;
320 CompareExpectedAndActualOutput(
321 extension_path,
322 views,
323 expected_outputs_path.AppendASCII(
324 "hpiknbiabeeppbpihjehijgoemciehgk.json"));
325 #endif
327 // Test Extension3
328 extension_path = data_dir().AppendASCII("good")
329 .AppendASCII("Extensions")
330 .AppendASCII("bjafgdebaacbbbecmhlhpofkepfkgcpa")
331 .AppendASCII("1.0");
332 views.clear();
334 CompareExpectedAndActualOutput(
335 extension_path,
336 views,
337 expected_outputs_path.AppendASCII(
338 "bjafgdebaacbbbecmhlhpofkepfkgcpa.json"));
341 // Test that the all_urls checkbox only shows up for extensions that want all
342 // urls, and only when the switch is on.
343 TEST_F(ExtensionInfoGeneratorUnitTest, ExtensionInfoRunOnAllUrls) {
344 // Start with the switch enabled.
345 scoped_ptr<FeatureSwitch::ScopedOverride> enable_scripts_switch(
346 new FeatureSwitch::ScopedOverride(
347 FeatureSwitch::scripts_require_action(), true));
348 // Two extensions - one with all urls, one without.
349 scoped_refptr<const Extension> all_urls_extension = CreateExtension(
350 "all_urls", ListBuilder().Append(kAllHostsPermission).Pass());
351 scoped_refptr<const Extension> no_urls_extension =
352 CreateExtension("no urls", ListBuilder().Pass());
354 scoped_ptr<developer::ExtensionInfo> info =
355 GenerateExtensionInfo(all_urls_extension->id());
357 // The extension should want all urls, but not currently have it.
358 EXPECT_TRUE(info->run_on_all_urls.is_enabled);
359 EXPECT_FALSE(info->run_on_all_urls.is_active);
361 // Give the extension all urls.
362 util::SetAllowedScriptingOnAllUrls(all_urls_extension->id(), profile(), true);
364 // Now the extension should both want and have all urls.
365 info = GenerateExtensionInfo(all_urls_extension->id());
366 EXPECT_TRUE(info->run_on_all_urls.is_enabled);
367 EXPECT_TRUE(info->run_on_all_urls.is_active);
369 // The other extension should neither want nor have all urls.
370 info = GenerateExtensionInfo(no_urls_extension->id());
371 EXPECT_FALSE(info->run_on_all_urls.is_enabled);
372 EXPECT_FALSE(info->run_on_all_urls.is_active);
374 // Revoke the first extension's permissions.
375 util::SetAllowedScriptingOnAllUrls(
376 all_urls_extension->id(), profile(), false);
378 // Turn off the switch and load another extension (so permissions are
379 // re-initialized).
380 enable_scripts_switch.reset();
382 // Since the extension doesn't have access to all urls (but normally would),
383 // the extension should have the "want" flag even with the switch off.
384 info = GenerateExtensionInfo(all_urls_extension->id());
385 EXPECT_TRUE(info->run_on_all_urls.is_enabled);
386 EXPECT_FALSE(info->run_on_all_urls.is_active);
388 // If we grant the extension all urls, then the checkbox should still be
389 // there, since it has an explicitly-set user preference.
390 util::SetAllowedScriptingOnAllUrls(all_urls_extension->id(), profile(), true);
391 info = GenerateExtensionInfo(all_urls_extension->id());
392 EXPECT_TRUE(info->run_on_all_urls.is_enabled);
393 EXPECT_TRUE(info->run_on_all_urls.is_active);
395 // Load another extension with all urls (so permissions get re-init'd).
396 all_urls_extension = CreateExtension(
397 "all_urls_II", ListBuilder().Append(kAllHostsPermission).Pass());
399 // Even though the extension has all_urls permission, the checkbox shouldn't
400 // show up without the switch.
401 info = GenerateExtensionInfo(all_urls_extension->id());
402 EXPECT_FALSE(info->run_on_all_urls.is_enabled);
403 EXPECT_TRUE(info->run_on_all_urls.is_active);
406 } // namespace extensions