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 // 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(
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
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();
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
);
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
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.
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(
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
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
);
475 // As a sanity check, also run the GetItemsInfo and make sure it returns a
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
=
515 .Append(DictionaryBuilder()
516 .Set("extensionId", extension
->id())
517 .Set("type", type_string
))
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();
530 .Append(DictionaryBuilder()
531 .Set("extensionId", extension
->id())
532 .Set("errorIds", ListBuilder().Append(error_id
)))
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.
541 .Append(DictionaryBuilder().Set("extensionId", extension
->id()))
543 function
= new api::DeveloperPrivateDeleteExtensionErrorsFunction();
544 EXPECT_TRUE(RunFunction(function
, *args
)) << function
->GetError();
546 EXPECT_TRUE(error_console
->GetErrorsForExtension(extension
->id()).empty());
549 } // namespace extensions