Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / extensions / api / developer_private / developer_private_api_unittest.cc
blobc64fc63ad517cc02b24e7a96bcd4844cf6cade31
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/files/file_util.h"
6 #include "base/memory/scoped_ptr.h"
7 #include "base/strings/utf_string_conversions.h"
8 #include "chrome/browser/extensions/api/developer_private/developer_private_api.h"
9 #include "chrome/browser/extensions/error_console/error_console.h"
10 #include "chrome/browser/extensions/extension_function_test_utils.h"
11 #include "chrome/browser/extensions/extension_service.h"
12 #include "chrome/browser/extensions/extension_service_test_base.h"
13 #include "chrome/browser/extensions/extension_util.h"
14 #include "chrome/browser/extensions/test_extension_dir.h"
15 #include "chrome/browser/extensions/test_extension_system.h"
16 #include "chrome/browser/extensions/unpacked_installer.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/host_desktop.h"
19 #include "chrome/common/extensions/api/developer_private.h"
20 #include "chrome/common/pref_names.h"
21 #include "chrome/test/base/test_browser_window.h"
22 #include "components/crx_file/id_util.h"
23 #include "content/public/test/test_web_contents_factory.h"
24 #include "extensions/browser/event_router_factory.h"
25 #include "extensions/browser/extension_error_test_util.h"
26 #include "extensions/browser/extension_prefs.h"
27 #include "extensions/browser/extension_registry.h"
28 #include "extensions/browser/extension_system.h"
29 #include "extensions/browser/test_extension_registry_observer.h"
30 #include "extensions/common/extension.h"
31 #include "extensions/common/extension_builder.h"
32 #include "extensions/common/extension_set.h"
33 #include "extensions/common/feature_switch.h"
34 #include "extensions/common/manifest_constants.h"
35 #include "extensions/common/test_util.h"
36 #include "extensions/common/value_builder.h"
38 namespace extensions {
40 namespace {
42 scoped_ptr<KeyedService> BuildAPI(content::BrowserContext* context) {
43 return make_scoped_ptr(new DeveloperPrivateAPI(context));
46 scoped_ptr<KeyedService> BuildEventRouter(content::BrowserContext* profile) {
47 return make_scoped_ptr(
48 new EventRouter(profile, ExtensionPrefs::Get(profile)));
51 } // namespace
53 class DeveloperPrivateApiUnitTest : public ExtensionServiceTestBase {
54 protected:
55 DeveloperPrivateApiUnitTest() {}
56 ~DeveloperPrivateApiUnitTest() override {}
58 // A wrapper around extension_function_test_utils::RunFunction that runs with
59 // the associated browser, no flags, and can take stack-allocated arguments.
60 bool RunFunction(const scoped_refptr<UIThreadExtensionFunction>& function,
61 const base::ListValue& args);
63 // Loads an unpacked extension that is backed by a real directory, allowing
64 // it to be reloaded.
65 const Extension* LoadUnpackedExtension();
67 // Loads an extension with no real directory; this is faster, but means the
68 // extension can't be reloaded.
69 const Extension* LoadSimpleExtension();
71 // Tests modifying the extension's configuration.
72 void TestExtensionPrefSetting(
73 bool (*has_pref)(const std::string&, content::BrowserContext*),
74 const std::string& key,
75 const std::string& extension_id);
77 testing::AssertionResult TestPackExtensionFunction(
78 const base::ListValue& args,
79 api::developer_private::PackStatus expected_status,
80 int expected_flags);
82 Browser* browser() { return browser_.get(); }
84 private:
85 // ExtensionServiceTestBase:
86 void SetUp() override;
87 void TearDown() override;
89 // The browser (and accompanying window).
90 scoped_ptr<TestBrowserWindow> browser_window_;
91 scoped_ptr<Browser> browser_;
93 ScopedVector<TestExtensionDir> test_extension_dirs_;
95 DISALLOW_COPY_AND_ASSIGN(DeveloperPrivateApiUnitTest);
98 bool DeveloperPrivateApiUnitTest::RunFunction(
99 const scoped_refptr<UIThreadExtensionFunction>& function,
100 const base::ListValue& args) {
101 return extension_function_test_utils::RunFunction(
102 function.get(),
103 make_scoped_ptr(args.DeepCopy()),
104 browser(),
105 extension_function_test_utils::NONE);
108 const Extension* DeveloperPrivateApiUnitTest::LoadUnpackedExtension() {
109 const char kManifest[] =
111 " \"name\": \"foo\","
112 " \"version\": \"1.0\","
113 " \"manifest_version\": 2,"
114 " \"permissions\": [\"*://*/*\"]"
115 "}";
117 test_extension_dirs_.push_back(new TestExtensionDir);
118 TestExtensionDir* dir = test_extension_dirs_.back();
119 dir->WriteManifest(kManifest);
121 // TODO(devlin): We should extract out methods to load an unpacked extension
122 // synchronously. We do it in ExtensionBrowserTest, but that's not helpful
123 // for unittests.
124 TestExtensionRegistryObserver registry_observer(registry());
125 scoped_refptr<UnpackedInstaller> installer(
126 UnpackedInstaller::Create(service()));
127 installer->Load(dir->unpacked_path());
128 base::FilePath extension_path =
129 base::MakeAbsoluteFilePath(dir->unpacked_path());
130 const Extension* extension = nullptr;
131 do {
132 extension = registry_observer.WaitForExtensionLoaded();
133 } while (extension->path() != extension_path);
134 // The fact that unpacked extensions get file access by default is an
135 // irrelevant detail to these tests. Disable it.
136 ExtensionPrefs::Get(browser_context())->SetAllowFileAccess(extension->id(),
137 false);
138 return extension;
141 const Extension* DeveloperPrivateApiUnitTest::LoadSimpleExtension() {
142 const char kName[] = "extension name";
143 const char kVersion[] = "1.0.0.1";
144 std::string id = crx_file::id_util::GenerateId(kName);
145 DictionaryBuilder manifest;
146 manifest.Set("name", kName)
147 .Set("version", kVersion)
148 .Set("manifest_version", 2)
149 .Set("description", "an extension");
150 scoped_refptr<const Extension> extension =
151 ExtensionBuilder().SetManifest(manifest)
152 .SetLocation(Manifest::INTERNAL)
153 .SetID(id)
154 .Build();
155 service()->AddExtension(extension.get());
156 return extension.get();
159 void DeveloperPrivateApiUnitTest::TestExtensionPrefSetting(
160 bool (*has_pref)(const std::string&, content::BrowserContext*),
161 const std::string& key,
162 const std::string& extension_id) {
163 scoped_refptr<UIThreadExtensionFunction> function(
164 new api::DeveloperPrivateUpdateExtensionConfigurationFunction());
166 base::ListValue args;
167 base::DictionaryValue* parameters = new base::DictionaryValue();
168 parameters->SetString("extensionId", extension_id);
169 parameters->SetBoolean(key, true);
170 args.Append(parameters);
172 EXPECT_FALSE(has_pref(extension_id, profile())) << key;
174 EXPECT_FALSE(RunFunction(function, args)) << key;
175 EXPECT_EQ(std::string("This action requires a user gesture."),
176 function->GetError());
178 ExtensionFunction::ScopedUserGestureForTests scoped_user_gesture;
179 function = new api::DeveloperPrivateUpdateExtensionConfigurationFunction();
180 EXPECT_TRUE(RunFunction(function, args)) << key;
181 EXPECT_TRUE(has_pref(extension_id, profile())) << key;
183 parameters->SetBoolean(key, false);
184 function = new api::DeveloperPrivateUpdateExtensionConfigurationFunction();
185 EXPECT_TRUE(RunFunction(function, args)) << key;
186 EXPECT_FALSE(has_pref(extension_id, profile())) << key;
189 testing::AssertionResult DeveloperPrivateApiUnitTest::TestPackExtensionFunction(
190 const base::ListValue& args,
191 api::developer_private::PackStatus expected_status,
192 int expected_flags) {
193 scoped_refptr<UIThreadExtensionFunction> function(
194 new api::DeveloperPrivatePackDirectoryFunction());
195 if (!RunFunction(function, args))
196 return testing::AssertionFailure() << "Could not run function.";
198 // Extract the result. We don't have to test this here, since it's verified as
199 // part of the general extension api system.
200 const base::Value* response_value = nullptr;
201 CHECK(function->GetResultList()->Get(0u, &response_value));
202 scoped_ptr<api::developer_private::PackDirectoryResponse> response =
203 api::developer_private::PackDirectoryResponse::FromValue(*response_value);
204 CHECK(response);
206 if (response->status != expected_status) {
207 return testing::AssertionFailure() << "Expected status: " <<
208 expected_status << ", found status: " << response->status <<
209 ", message: " << response->message;
212 if (response->override_flags != expected_flags) {
213 return testing::AssertionFailure() << "Expected flags: " <<
214 expected_flags << ", found flags: " << response->override_flags;
217 return testing::AssertionSuccess();
220 void DeveloperPrivateApiUnitTest::SetUp() {
221 ExtensionServiceTestBase::SetUp();
222 InitializeEmptyExtensionService();
224 browser_window_.reset(new TestBrowserWindow());
225 Browser::CreateParams params(profile(), chrome::HOST_DESKTOP_TYPE_NATIVE);
226 params.type = Browser::TYPE_TABBED;
227 params.window = browser_window_.get();
228 browser_.reset(new Browser(params));
230 // Allow the API to be created.
231 EventRouterFactory::GetInstance()->SetTestingFactory(profile(),
232 &BuildEventRouter);
234 DeveloperPrivateAPI::GetFactoryInstance()->SetTestingFactory(
235 profile(), &BuildAPI);
238 void DeveloperPrivateApiUnitTest::TearDown() {
239 test_extension_dirs_.clear();
240 browser_.reset();
241 browser_window_.reset();
242 ExtensionServiceTestBase::TearDown();
245 // Test developerPrivate.updateExtensionConfiguration.
246 TEST_F(DeveloperPrivateApiUnitTest,
247 DeveloperPrivateUpdateExtensionConfiguration) {
248 FeatureSwitch::ScopedOverride scripts_require_action(
249 FeatureSwitch::scripts_require_action(), true);
250 // Sadly, we need a "real" directory here, because toggling prefs causes
251 // a reload (which needs a path).
252 std::string id = LoadUnpackedExtension()->id();
254 TestExtensionPrefSetting(&util::IsIncognitoEnabled, "incognitoAccess", id);
255 TestExtensionPrefSetting(&util::AllowFileAccess, "fileAccess", id);
256 TestExtensionPrefSetting(
257 &util::AllowedScriptingOnAllUrls, "runOnAllUrls", id);
260 // Test developerPrivate.reload.
261 TEST_F(DeveloperPrivateApiUnitTest, DeveloperPrivateReload) {
262 const Extension* extension = LoadUnpackedExtension();
263 std::string extension_id = extension->id();
264 scoped_refptr<UIThreadExtensionFunction> function(
265 new api::DeveloperPrivateReloadFunction());
266 base::ListValue reload_args;
267 reload_args.AppendString(extension_id);
269 TestExtensionRegistryObserver registry_observer(registry());
270 EXPECT_TRUE(RunFunction(function, reload_args));
271 const Extension* unloaded_extension =
272 registry_observer.WaitForExtensionUnloaded();
273 EXPECT_EQ(extension, unloaded_extension);
274 const Extension* reloaded_extension =
275 registry_observer.WaitForExtensionLoaded();
276 EXPECT_EQ(extension_id, reloaded_extension->id());
279 // Test developerPrivate.packDirectory.
280 // http://crbug.com/527228 flaky
281 TEST_F(DeveloperPrivateApiUnitTest, DISABLED_DeveloperPrivatePackFunction) {
282 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT);
284 base::FilePath root_path = data_dir().AppendASCII("good_unpacked");
285 base::FilePath crx_path = data_dir().AppendASCII("good_unpacked.crx");
286 base::FilePath pem_path = data_dir().AppendASCII("good_unpacked.pem");
288 // First, test a directory that should pack properly.
289 base::ListValue pack_args;
290 pack_args.AppendString(root_path.AsUTF8Unsafe());
291 EXPECT_TRUE(TestPackExtensionFunction(
292 pack_args, api::developer_private::PACK_STATUS_SUCCESS, 0));
294 // Should have created crx file and pem file.
295 EXPECT_TRUE(base::PathExists(crx_path));
296 EXPECT_TRUE(base::PathExists(pem_path));
298 // Deliberately don't cleanup the files, and append the pem path.
299 pack_args.AppendString(pem_path.AsUTF8Unsafe());
301 // Try to pack again - we should get a warning abot overwriting the crx.
302 EXPECT_TRUE(TestPackExtensionFunction(
303 pack_args,
304 api::developer_private::PACK_STATUS_WARNING,
305 ExtensionCreator::kOverwriteCRX));
307 // Try to pack again, with the overwrite flag; this should succeed.
308 pack_args.AppendInteger(ExtensionCreator::kOverwriteCRX);
309 EXPECT_TRUE(TestPackExtensionFunction(
310 pack_args, api::developer_private::PACK_STATUS_SUCCESS, 0));
312 // Try to pack a final time when omitting (an existing) pem file. We should
313 // get an error.
314 base::DeleteFile(crx_path, false);
315 EXPECT_TRUE(pack_args.Remove(1u, nullptr)); // Remove the pem key argument.
316 EXPECT_TRUE(pack_args.Remove(1u, nullptr)); // Remove the flags argument.
317 EXPECT_TRUE(TestPackExtensionFunction(
318 pack_args, api::developer_private::PACK_STATUS_ERROR, 0));
320 base::DeleteFile(crx_path, false);
321 base::DeleteFile(pem_path, false);
324 // Test developerPrivate.choosePath.
325 // http://crbug.com/527228 flaky
326 TEST_F(DeveloperPrivateApiUnitTest, DISABLED_DeveloperPrivateChoosePath) {
327 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT);
328 content::TestWebContentsFactory web_contents_factory;
329 content::WebContents* web_contents =
330 web_contents_factory.CreateWebContents(profile());
332 base::FilePath expected_dir_path = data_dir().AppendASCII("good_unpacked");
333 api::EntryPicker::SkipPickerAndAlwaysSelectPathForTest(&expected_dir_path);
335 // Try selecting a directory.
336 base::ListValue choose_args;
337 choose_args.AppendString("FOLDER");
338 choose_args.AppendString("LOAD");
339 scoped_refptr<UIThreadExtensionFunction> function(
340 new api::DeveloperPrivateChoosePathFunction());
341 function->SetRenderFrameHost(web_contents->GetMainFrame());
342 EXPECT_TRUE(RunFunction(function, choose_args)) << function->GetError();
343 std::string path;
344 EXPECT_TRUE(function->GetResultList() &&
345 function->GetResultList()->GetString(0, &path));
346 EXPECT_EQ(path, expected_dir_path.AsUTF8Unsafe());
348 // Try selecting a pem file.
349 base::FilePath expected_file_path =
350 data_dir().AppendASCII("good_unpacked.pem");
351 api::EntryPicker::SkipPickerAndAlwaysSelectPathForTest(&expected_file_path);
352 choose_args.Clear();
353 choose_args.AppendString("FILE");
354 choose_args.AppendString("PEM");
355 function = new api::DeveloperPrivateChoosePathFunction();
356 function->SetRenderFrameHost(web_contents->GetMainFrame());
357 EXPECT_TRUE(RunFunction(function, choose_args)) << function->GetError();
358 EXPECT_TRUE(function->GetResultList() &&
359 function->GetResultList()->GetString(0, &path));
360 EXPECT_EQ(path, expected_file_path.AsUTF8Unsafe());
362 // Try canceling the file dialog.
363 api::EntryPicker::SkipPickerAndAlwaysCancelForTest();
364 function = new api::DeveloperPrivateChoosePathFunction();
365 function->SetRenderFrameHost(web_contents->GetMainFrame());
366 EXPECT_FALSE(RunFunction(function, choose_args));
367 EXPECT_EQ(std::string("File selection was canceled."), function->GetError());
370 // Test developerPrivate.loadUnpacked.
371 // http://crbug.com/527228 flaky
372 TEST_F(DeveloperPrivateApiUnitTest, DISABLED_DeveloperPrivateLoadUnpacked) {
373 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT);
374 content::TestWebContentsFactory web_contents_factory;
375 content::WebContents* web_contents =
376 web_contents_factory.CreateWebContents(profile());
378 base::FilePath path = data_dir().AppendASCII("good_unpacked");
379 api::EntryPicker::SkipPickerAndAlwaysSelectPathForTest(&path);
381 // Try loading a good extension (it should succeed, and the extension should
382 // be added).
383 scoped_refptr<UIThreadExtensionFunction> function(
384 new api::DeveloperPrivateLoadUnpackedFunction());
385 function->SetRenderFrameHost(web_contents->GetMainFrame());
386 ExtensionIdSet current_ids = registry()->enabled_extensions().GetIDs();
387 EXPECT_TRUE(RunFunction(function, base::ListValue())) << function->GetError();
388 // We should have added one new extension.
389 ExtensionIdSet id_difference = base::STLSetDifference<ExtensionIdSet>(
390 registry()->enabled_extensions().GetIDs(), current_ids);
391 ASSERT_EQ(1u, id_difference.size());
392 // The new extension should have the same path.
393 EXPECT_EQ(
394 path,
395 registry()->enabled_extensions().GetByID(*id_difference.begin())->path());
397 path = data_dir().AppendASCII("empty_manifest");
398 api::EntryPicker::SkipPickerAndAlwaysSelectPathForTest(&path);
400 // Try loading a bad extension (it should fail, and we should get an error).
401 function = new api::DeveloperPrivateLoadUnpackedFunction();
402 function->SetRenderFrameHost(web_contents->GetMainFrame());
403 base::ListValue unpacked_args;
404 scoped_ptr<base::DictionaryValue> options(new base::DictionaryValue());
405 options->SetBoolean("failQuietly", true);
406 unpacked_args.Append(options.release());
407 current_ids = registry()->enabled_extensions().GetIDs();
408 EXPECT_FALSE(RunFunction(function, unpacked_args));
409 EXPECT_EQ(manifest_errors::kManifestUnreadable, function->GetError());
410 // We should have no new extensions installed.
411 EXPECT_EQ(0u, base::STLSetDifference<ExtensionIdSet>(
412 registry()->enabled_extensions().GetIDs(),
413 current_ids).size());
416 // Test developerPrivate.requestFileSource.
417 // http://crbug.com/527228 flaky
418 TEST_F(DeveloperPrivateApiUnitTest,
419 DISABLED_DeveloperPrivateRequestFileSource) {
420 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT);
421 // Testing of this function seems light, but that's because it basically just
422 // forwards to reading a file to a string, and highlighting it - both of which
423 // are already tested separately.
424 const Extension* extension = LoadUnpackedExtension();
425 const char kErrorMessage[] = "Something went wrong";
426 api::developer_private::RequestFileSourceProperties properties;
427 properties.extension_id = extension->id();
428 properties.path_suffix = "manifest.json";
429 properties.message = kErrorMessage;
430 properties.manifest_key.reset(new std::string("name"));
432 scoped_refptr<UIThreadExtensionFunction> function(
433 new api::DeveloperPrivateRequestFileSourceFunction());
434 base::ListValue file_source_args;
435 file_source_args.Append(properties.ToValue().release());
436 EXPECT_TRUE(RunFunction(function, file_source_args)) << function->GetError();
438 const base::Value* response_value = nullptr;
439 ASSERT_TRUE(function->GetResultList()->Get(0u, &response_value));
440 scoped_ptr<api::developer_private::RequestFileSourceResponse> response =
441 api::developer_private::RequestFileSourceResponse::FromValue(
442 *response_value);
443 EXPECT_FALSE(response->before_highlight.empty());
444 EXPECT_EQ("\"name\": \"foo\"", response->highlight);
445 EXPECT_FALSE(response->after_highlight.empty());
446 EXPECT_EQ("foo: manifest.json", response->title);
447 EXPECT_EQ(kErrorMessage, response->message);
450 // Test developerPrivate.getExtensionsInfo.
451 // http://crbug.com/527228 flaky
452 TEST_F(DeveloperPrivateApiUnitTest,
453 DISABLED_DeveloperPrivateGetExtensionsInfo) {
454 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT);
455 LoadSimpleExtension();
457 // The test here isn't so much about the generated value (that's tested in
458 // ExtensionInfoGenerator's unittest), but rather just to make sure we can
459 // serialize/deserialize the result - which implicity tests that everything
460 // has a sane value.
461 scoped_refptr<UIThreadExtensionFunction> function(
462 new api::DeveloperPrivateGetExtensionsInfoFunction());
463 EXPECT_TRUE(RunFunction(function, base::ListValue())) << function->GetError();
464 const base::ListValue* results = function->GetResultList();
465 ASSERT_EQ(1u, results->GetSize());
466 const base::ListValue* list = nullptr;
467 ASSERT_TRUE(results->GetList(0u, &list));
468 ASSERT_EQ(1u, list->GetSize());
469 const base::Value* value = nullptr;
470 ASSERT_TRUE(list->Get(0u, &value));
471 scoped_ptr<api::developer_private::ExtensionInfo> info =
472 api::developer_private::ExtensionInfo::FromValue(*value);
473 ASSERT_TRUE(info);
475 // As a sanity check, also run the GetItemsInfo and make sure it returns a
476 // sane value.
477 function = new api::DeveloperPrivateGetItemsInfoFunction();
478 base::ListValue args;
479 args.AppendBoolean(false);
480 args.AppendBoolean(false);
481 EXPECT_TRUE(RunFunction(function, args)) << function->GetError();
482 results = function->GetResultList();
483 ASSERT_EQ(1u, results->GetSize());
484 ASSERT_TRUE(results->GetList(0u, &list));
485 ASSERT_EQ(1u, list->GetSize());
486 ASSERT_TRUE(list->Get(0u, &value));
487 scoped_ptr<api::developer_private::ItemInfo> item_info =
488 api::developer_private::ItemInfo::FromValue(*value);
489 ASSERT_TRUE(item_info);
492 // Test developerPrivate.deleteExtensionErrors.
493 TEST_F(DeveloperPrivateApiUnitTest, DeveloperPrivateDeleteExtensionErrors) {
494 FeatureSwitch::ScopedOverride error_console_override(
495 FeatureSwitch::error_console(), true);
496 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode, true);
497 const Extension* extension = LoadSimpleExtension();
499 // Report some errors.
500 ErrorConsole* error_console = ErrorConsole::Get(profile());
501 error_console->SetReportingAllForExtension(extension->id(), true);
502 error_console->ReportError(
503 error_test_util::CreateNewRuntimeError(extension->id(), "foo"));
504 error_console->ReportError(
505 error_test_util::CreateNewRuntimeError(extension->id(), "bar"));
506 error_console->ReportError(
507 error_test_util::CreateNewManifestError(extension->id(), "baz"));
508 EXPECT_EQ(3u, error_console->GetErrorsForExtension(extension->id()).size());
510 // Start by removing all errors for the extension of a given type (manifest).
511 std::string type_string = api::developer_private::ToString(
512 api::developer_private::ERROR_TYPE_MANIFEST);
513 scoped_ptr<base::ListValue> args =
514 ListBuilder()
515 .Append(DictionaryBuilder()
516 .Set("extensionId", extension->id())
517 .Set("type", type_string))
518 .Build();
519 scoped_refptr<UIThreadExtensionFunction> function =
520 new api::DeveloperPrivateDeleteExtensionErrorsFunction();
521 EXPECT_TRUE(RunFunction(function, *args)) << function->GetError();
522 // Two errors should remain.
523 const ErrorList& error_list =
524 error_console->GetErrorsForExtension(extension->id());
525 ASSERT_EQ(2u, error_list.size());
527 // Next remove errors by id.
528 int error_id = error_list[0]->id();
529 args = ListBuilder()
530 .Append(DictionaryBuilder()
531 .Set("extensionId", extension->id())
532 .Set("errorIds", ListBuilder().Append(error_id)))
533 .Build();
534 function = new api::DeveloperPrivateDeleteExtensionErrorsFunction();
535 EXPECT_TRUE(RunFunction(function, *args)) << function->GetError();
536 // And then there was one.
537 EXPECT_EQ(1u, error_console->GetErrorsForExtension(extension->id()).size());
539 // Finally remove all errors for the extension.
540 args = ListBuilder()
541 .Append(DictionaryBuilder().Set("extensionId", extension->id()))
542 .Build();
543 function = new api::DeveloperPrivateDeleteExtensionErrorsFunction();
544 EXPECT_TRUE(RunFunction(function, *args)) << function->GetError();
545 // No more errors!
546 EXPECT_TRUE(error_console->GetErrorsForExtension(extension->id()).empty());
549 } // namespace extensions