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
{
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
)));
53 class DeveloperPrivateApiUnitTest
: public ExtensionServiceTestBase
{
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
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
,
82 Browser
* browser() { return browser_
.get(); }
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(
103 make_scoped_ptr(args
.DeepCopy()),
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\": [\"*://*/*\"]"
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
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;
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(),
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
)
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
);
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(),
234 DeveloperPrivateAPI::GetFactoryInstance()->SetTestingFactory(
235 profile(), &BuildAPI
);
238 void DeveloperPrivateApiUnitTest::TearDown() {
239 test_extension_dirs_
.clear();
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 TEST_F(DeveloperPrivateApiUnitTest
, DeveloperPrivatePackFunction
) {
281 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT
);
283 base::FilePath root_path
= data_dir().AppendASCII("good_unpacked");
284 base::FilePath crx_path
= data_dir().AppendASCII("good_unpacked.crx");
285 base::FilePath pem_path
= data_dir().AppendASCII("good_unpacked.pem");
287 // First, test a directory that should pack properly.
288 base::ListValue pack_args
;
289 pack_args
.AppendString(root_path
.AsUTF8Unsafe());
290 EXPECT_TRUE(TestPackExtensionFunction(
291 pack_args
, api::developer_private::PACK_STATUS_SUCCESS
, 0));
293 // Should have created crx file and pem file.
294 EXPECT_TRUE(base::PathExists(crx_path
));
295 EXPECT_TRUE(base::PathExists(pem_path
));
297 // Deliberately don't cleanup the files, and append the pem path.
298 pack_args
.AppendString(pem_path
.AsUTF8Unsafe());
300 // Try to pack again - we should get a warning abot overwriting the crx.
301 EXPECT_TRUE(TestPackExtensionFunction(
303 api::developer_private::PACK_STATUS_WARNING
,
304 ExtensionCreator::kOverwriteCRX
));
306 // Try to pack again, with the overwrite flag; this should succeed.
307 pack_args
.AppendInteger(ExtensionCreator::kOverwriteCRX
);
308 EXPECT_TRUE(TestPackExtensionFunction(
309 pack_args
, api::developer_private::PACK_STATUS_SUCCESS
, 0));
311 // Try to pack a final time when omitting (an existing) pem file. We should
313 base::DeleteFile(crx_path
, false);
314 EXPECT_TRUE(pack_args
.Remove(1u, nullptr)); // Remove the pem key argument.
315 EXPECT_TRUE(pack_args
.Remove(1u, nullptr)); // Remove the flags argument.
316 EXPECT_TRUE(TestPackExtensionFunction(
317 pack_args
, api::developer_private::PACK_STATUS_ERROR
, 0));
319 base::DeleteFile(crx_path
, false);
320 base::DeleteFile(pem_path
, false);
323 // Test developerPrivate.choosePath.
324 TEST_F(DeveloperPrivateApiUnitTest
, DeveloperPrivateChoosePath
) {
325 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT
);
326 content::TestWebContentsFactory web_contents_factory
;
327 content::WebContents
* web_contents
=
328 web_contents_factory
.CreateWebContents(profile());
330 base::FilePath expected_dir_path
= data_dir().AppendASCII("good_unpacked");
331 api::EntryPicker::SkipPickerAndAlwaysSelectPathForTest(&expected_dir_path
);
333 // Try selecting a directory.
334 base::ListValue choose_args
;
335 choose_args
.AppendString("FOLDER");
336 choose_args
.AppendString("LOAD");
337 scoped_refptr
<UIThreadExtensionFunction
> function(
338 new api::DeveloperPrivateChoosePathFunction());
339 function
->SetRenderFrameHost(web_contents
->GetMainFrame());
340 EXPECT_TRUE(RunFunction(function
, choose_args
)) << function
->GetError();
342 EXPECT_TRUE(function
->GetResultList() &&
343 function
->GetResultList()->GetString(0, &path
));
344 EXPECT_EQ(path
, expected_dir_path
.AsUTF8Unsafe());
346 // Try selecting a pem file.
347 base::FilePath expected_file_path
=
348 data_dir().AppendASCII("good_unpacked.pem");
349 api::EntryPicker::SkipPickerAndAlwaysSelectPathForTest(&expected_file_path
);
351 choose_args
.AppendString("FILE");
352 choose_args
.AppendString("PEM");
353 function
= new api::DeveloperPrivateChoosePathFunction();
354 function
->SetRenderFrameHost(web_contents
->GetMainFrame());
355 EXPECT_TRUE(RunFunction(function
, choose_args
)) << function
->GetError();
356 EXPECT_TRUE(function
->GetResultList() &&
357 function
->GetResultList()->GetString(0, &path
));
358 EXPECT_EQ(path
, expected_file_path
.AsUTF8Unsafe());
360 // Try canceling the file dialog.
361 api::EntryPicker::SkipPickerAndAlwaysCancelForTest();
362 function
= new api::DeveloperPrivateChoosePathFunction();
363 function
->SetRenderFrameHost(web_contents
->GetMainFrame());
364 EXPECT_FALSE(RunFunction(function
, choose_args
));
365 EXPECT_EQ(std::string("File selection was canceled."), function
->GetError());
368 // Test developerPrivate.loadUnpacked.
369 TEST_F(DeveloperPrivateApiUnitTest
, DeveloperPrivateLoadUnpacked
) {
370 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT
);
371 content::TestWebContentsFactory web_contents_factory
;
372 content::WebContents
* web_contents
=
373 web_contents_factory
.CreateWebContents(profile());
375 base::FilePath path
= data_dir().AppendASCII("good_unpacked");
376 api::EntryPicker::SkipPickerAndAlwaysSelectPathForTest(&path
);
378 // Try loading a good extension (it should succeed, and the extension should
380 scoped_refptr
<UIThreadExtensionFunction
> function(
381 new api::DeveloperPrivateLoadUnpackedFunction());
382 function
->SetRenderFrameHost(web_contents
->GetMainFrame());
383 ExtensionIdSet current_ids
= registry()->enabled_extensions().GetIDs();
384 EXPECT_TRUE(RunFunction(function
, base::ListValue())) << function
->GetError();
385 // We should have added one new extension.
386 ExtensionIdSet id_difference
= base::STLSetDifference
<ExtensionIdSet
>(
387 registry()->enabled_extensions().GetIDs(), current_ids
);
388 ASSERT_EQ(1u, id_difference
.size());
389 // The new extension should have the same path.
392 registry()->enabled_extensions().GetByID(*id_difference
.begin())->path());
394 path
= data_dir().AppendASCII("empty_manifest");
395 api::EntryPicker::SkipPickerAndAlwaysSelectPathForTest(&path
);
397 // Try loading a bad extension (it should fail, and we should get an error).
398 function
= new api::DeveloperPrivateLoadUnpackedFunction();
399 function
->SetRenderFrameHost(web_contents
->GetMainFrame());
400 base::ListValue unpacked_args
;
401 scoped_ptr
<base::DictionaryValue
> options(new base::DictionaryValue());
402 options
->SetBoolean("failQuietly", true);
403 unpacked_args
.Append(options
.release());
404 current_ids
= registry()->enabled_extensions().GetIDs();
405 EXPECT_FALSE(RunFunction(function
, unpacked_args
));
406 EXPECT_EQ(manifest_errors::kManifestUnreadable
, function
->GetError());
407 // We should have no new extensions installed.
408 EXPECT_EQ(0u, base::STLSetDifference
<ExtensionIdSet
>(
409 registry()->enabled_extensions().GetIDs(),
410 current_ids
).size());
413 // Test developerPrivate.requestFileSource.
414 TEST_F(DeveloperPrivateApiUnitTest
, DeveloperPrivateRequestFileSource
) {
415 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT
);
416 // Testing of this function seems light, but that's because it basically just
417 // forwards to reading a file to a string, and highlighting it - both of which
418 // are already tested separately.
419 const Extension
* extension
= LoadUnpackedExtension();
420 const char kErrorMessage
[] = "Something went wrong";
421 api::developer_private::RequestFileSourceProperties properties
;
422 properties
.extension_id
= extension
->id();
423 properties
.path_suffix
= "manifest.json";
424 properties
.message
= kErrorMessage
;
425 properties
.manifest_key
.reset(new std::string("name"));
427 scoped_refptr
<UIThreadExtensionFunction
> function(
428 new api::DeveloperPrivateRequestFileSourceFunction());
429 base::ListValue file_source_args
;
430 file_source_args
.Append(properties
.ToValue().release());
431 EXPECT_TRUE(RunFunction(function
, file_source_args
)) << function
->GetError();
433 const base::Value
* response_value
= nullptr;
434 ASSERT_TRUE(function
->GetResultList()->Get(0u, &response_value
));
435 scoped_ptr
<api::developer_private::RequestFileSourceResponse
> response
=
436 api::developer_private::RequestFileSourceResponse::FromValue(
438 EXPECT_FALSE(response
->before_highlight
.empty());
439 EXPECT_EQ("\"name\": \"foo\"", response
->highlight
);
440 EXPECT_FALSE(response
->after_highlight
.empty());
441 EXPECT_EQ("foo: manifest.json", response
->title
);
442 EXPECT_EQ(kErrorMessage
, response
->message
);
445 // Test developerPrivate.getExtensionsInfo.
446 TEST_F(DeveloperPrivateApiUnitTest
, DeveloperPrivateGetExtensionsInfo
) {
447 ResetThreadBundle(content::TestBrowserThreadBundle::DEFAULT
);
448 LoadSimpleExtension();
450 // The test here isn't so much about the generated value (that's tested in
451 // ExtensionInfoGenerator's unittest), but rather just to make sure we can
452 // serialize/deserialize the result - which implicity tests that everything
454 scoped_refptr
<UIThreadExtensionFunction
> function(
455 new api::DeveloperPrivateGetExtensionsInfoFunction());
456 EXPECT_TRUE(RunFunction(function
, base::ListValue())) << function
->GetError();
457 const base::ListValue
* results
= function
->GetResultList();
458 ASSERT_EQ(1u, results
->GetSize());
459 const base::ListValue
* list
= nullptr;
460 ASSERT_TRUE(results
->GetList(0u, &list
));
461 ASSERT_EQ(1u, list
->GetSize());
462 const base::Value
* value
= nullptr;
463 ASSERT_TRUE(list
->Get(0u, &value
));
464 scoped_ptr
<api::developer_private::ExtensionInfo
> info
=
465 api::developer_private::ExtensionInfo::FromValue(*value
);
468 // As a sanity check, also run the GetItemsInfo and make sure it returns a
470 function
= new api::DeveloperPrivateGetItemsInfoFunction();
471 base::ListValue args
;
472 args
.AppendBoolean(false);
473 args
.AppendBoolean(false);
474 EXPECT_TRUE(RunFunction(function
, args
)) << function
->GetError();
475 results
= function
->GetResultList();
476 ASSERT_EQ(1u, results
->GetSize());
477 ASSERT_TRUE(results
->GetList(0u, &list
));
478 ASSERT_EQ(1u, list
->GetSize());
479 ASSERT_TRUE(list
->Get(0u, &value
));
480 scoped_ptr
<api::developer_private::ItemInfo
> item_info
=
481 api::developer_private::ItemInfo::FromValue(*value
);
482 ASSERT_TRUE(item_info
);
485 // Test developerPrivate.deleteExtensionErrors.
486 TEST_F(DeveloperPrivateApiUnitTest
, DeveloperPrivateDeleteExtensionErrors
) {
487 FeatureSwitch::ScopedOverride
error_console_override(
488 FeatureSwitch::error_console(), true);
489 profile()->GetPrefs()->SetBoolean(prefs::kExtensionsUIDeveloperMode
, true);
490 const Extension
* extension
= LoadSimpleExtension();
492 // Report some errors.
493 ErrorConsole
* error_console
= ErrorConsole::Get(profile());
494 error_console
->SetReportingAllForExtension(extension
->id(), true);
495 error_console
->ReportError(
496 error_test_util::CreateNewRuntimeError(extension
->id(), "foo"));
497 error_console
->ReportError(
498 error_test_util::CreateNewRuntimeError(extension
->id(), "bar"));
499 error_console
->ReportError(
500 error_test_util::CreateNewManifestError(extension
->id(), "baz"));
501 EXPECT_EQ(3u, error_console
->GetErrorsForExtension(extension
->id()).size());
503 // Start by removing all errors for the extension of a given type (manifest).
504 std::string type_string
= api::developer_private::ToString(
505 api::developer_private::ERROR_TYPE_MANIFEST
);
506 scoped_ptr
<base::ListValue
> args
=
508 .Append(DictionaryBuilder()
509 .Set("extensionId", extension
->id())
510 .Set("type", type_string
))
512 scoped_refptr
<UIThreadExtensionFunction
> function
=
513 new api::DeveloperPrivateDeleteExtensionErrorsFunction();
514 EXPECT_TRUE(RunFunction(function
, *args
)) << function
->GetError();
515 // Two errors should remain.
516 const ErrorList
& error_list
=
517 error_console
->GetErrorsForExtension(extension
->id());
518 ASSERT_EQ(2u, error_list
.size());
520 // Next remove errors by id.
521 int error_id
= error_list
[0]->id();
523 .Append(DictionaryBuilder()
524 .Set("extensionId", extension
->id())
525 .Set("errorIds", ListBuilder().Append(error_id
)))
527 function
= new api::DeveloperPrivateDeleteExtensionErrorsFunction();
528 EXPECT_TRUE(RunFunction(function
, *args
)) << function
->GetError();
529 // And then there was one.
530 EXPECT_EQ(1u, error_console
->GetErrorsForExtension(extension
->id()).size());
532 // Finally remove all errors for the extension.
534 .Append(DictionaryBuilder().Set("extensionId", extension
->id()))
536 function
= new api::DeveloperPrivateDeleteExtensionErrorsFunction();
537 EXPECT_TRUE(RunFunction(function
, *args
)) << function
->GetError();
539 EXPECT_TRUE(error_console
->GetErrorsForExtension(extension
->id()).empty());
542 } // namespace extensions