| // Copyright 2016 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "chrome/browser/devtools/devtools_ui_bindings.h" | 
 |  | 
 | #include "base/feature_list.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "base/test/bind.h" | 
 | #include "chrome/browser/devtools/devtools_dispatch_http_request_params.h" | 
 | #include "chrome/browser/devtools/devtools_http_service_handler.h" | 
 | #include "chrome/browser/devtools/devtools_http_service_registry.h" | 
 | #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h" | 
 | #include "chrome/browser/sync/sync_service_factory.h" | 
 | #include "chrome/test/base/testing_profile.h" | 
 | #include "components/signin/public/base/signin_switches.h" | 
 | #include "components/signin/public/identity_manager/identity_test_environment.h" | 
 | #include "components/sync/test/test_sync_service.h" | 
 | #include "content/public/test/browser_task_environment.h" | 
 | #include "content/public/test/test_web_contents_factory.h" | 
 | #include "content/public/test/url_loader_interceptor.h" | 
 | #include "google_apis/gaia/google_service_auth_error.h" | 
 | #include "net/base/net_errors.h" | 
 | #include "net/http/http_status_code.h" | 
 | #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" | 
 | #include "testing/gmock/include/gmock/gmock.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | using testing::_; | 
 |  | 
 | class DevToolsUIBindingsTest : public testing::Test {}; | 
 |  | 
 | TEST_F(DevToolsUIBindingsTest, SanitizeFrontendURL) { | 
 |   std::vector<std::pair<std::string, std::string>> tests = { | 
 |       {"random-string", "devtools://devtools/"}, | 
 |       {"http://valid.url/but/wrong", "devtools://devtools/but/wrong"}, | 
 |       {"devtools://wrong-domain/", "devtools://devtools/"}, | 
 |       {"devtools://devtools/bundled/devtools.html", | 
 |        "devtools://devtools/bundled/devtools.html"}, | 
 |       {"devtools://devtools:1234/bundled/devtools.html#hash", | 
 |        "devtools://devtools/bundled/devtools.html#hash"}, | 
 |       {"devtools://devtools/some/random/path", | 
 |        "devtools://devtools/some/random/path"}, | 
 |       {"devtools://devtools/bundled/devtools.html?debugFrontend=true", | 
 |        "devtools://devtools/bundled/devtools.html?debugFrontend=true"}, | 
 |       {"devtools://devtools/bundled/devtools.html" | 
 |        "?some-flag=flag&v8only=true&debugFrontend=a" | 
 |        "&another-flag=another-flag&can_dock=false&isSharedWorker=notreally" | 
 |        "&remoteFrontend=sure", | 
 |        "devtools://devtools/bundled/devtools.html" | 
 |        "?v8only=true&debugFrontend=true" | 
 |        "&can_dock=true&isSharedWorker=true&remoteFrontend=true"}, | 
 |       {"devtools://devtools/?ws=any-value-is-fine", | 
 |        "devtools://devtools/?ws=any-value-is-fine"}, | 
 |       {"devtools://devtools/" | 
 |        "?service-backend=ws://localhost:9222/services", | 
 |        "devtools://devtools/" | 
 |        "?service-backend=ws://localhost:9222/services"}, | 
 |       {"devtools://devtools/?remoteBase=" | 
 |        "http://example.com:1234/remote-base#hash", | 
 |        "devtools://devtools/?remoteBase=" | 
 |        "https://chrome-devtools-frontend.appspot.com/" | 
 |        "serve_file//#hash"}, | 
 |       {"devtools://devtools/?ws=1%26evil%3dtrue", | 
 |        "devtools://devtools/?ws=1%26evil%3dtrue"}, | 
 |       {"devtools://devtools/?ws=encoded-ok'", | 
 |        "devtools://devtools/?ws=encoded-ok%27"}, | 
 |       {"devtools://devtools/?remoteBase=" | 
 |        "https://chrome-devtools-frontend.appspot.com/some/path/" | 
 |        "@123719741873/more/path.html", | 
 |        "devtools://devtools/?remoteBase=" | 
 |        "https://chrome-devtools-frontend.appspot.com/serve_file/path/"}, | 
 |       {"devtools://devtools/?remoteBase=" | 
 |        "https://chrome-devtools-frontend.appspot.com/serve_file/" | 
 |        "@123719741873/inspector.html%3FdebugFrontend%3Dfalse", | 
 |        "devtools://devtools/?remoteBase=" | 
 |        "https://chrome-devtools-frontend.appspot.com/serve_file/" | 
 |        "@123719741873/"}, | 
 |       {"devtools://devtools/bundled/inspector.html?" | 
 |        "&remoteBase=https://chrome-devtools-frontend.appspot.com/serve_file/" | 
 |        "@b4907cc5d602ff470740b2eb6344b517edecb7b9/&can_dock=true", | 
 |        "devtools://devtools/bundled/inspector.html?" | 
 |        "remoteBase=https://chrome-devtools-frontend.appspot.com/serve_file/" | 
 |        "@b4907cc5d602ff470740b2eb6344b517edecb7b9/&can_dock=true"}, | 
 |       {"devtools://devtools/?remoteFrontendUrl=" | 
 |        "https://chrome-devtools-frontend.appspot.com/serve_rev/" | 
 |        "@12345/inspector.html%3FdebugFrontend%3Dfalse", | 
 |        "devtools://devtools/?remoteFrontendUrl=" | 
 |        "https%3A%2F%2Fchrome-devtools-frontend.appspot.com%2Fserve_rev" | 
 |        "%2F%4012345%2Finspector.html%3FdebugFrontend%3Dtrue"}, | 
 |       {"devtools://devtools/?remoteFrontendUrl=" | 
 |        "https://chrome-devtools-frontend.appspot.com/serve_rev/" | 
 |        "@12345/inspector.html%22></iframe>something", | 
 |        "devtools://devtools/?remoteFrontendUrl=" | 
 |        "https%3A%2F%2Fchrome-devtools-frontend.appspot.com%2Fserve_rev" | 
 |        "%2F%4012345%2Finspector.html"}, | 
 |       {"devtools://devtools/?remoteFrontendUrl=" | 
 |        "http://domain:1234/path/rev/a/filename.html%3Fparam%3Dvalue#hash", | 
 |        "devtools://devtools/?remoteFrontendUrl=" | 
 |        "https%3A%2F%2Fchrome-devtools-frontend.appspot.com%2Fserve_rev" | 
 |        "%2Frev%2Finspector.html#hash"}, | 
 |       {"devtools://devtools/?remoteFrontendUrl=" | 
 |        "https://chrome-devtools-frontend.appspot.com/serve_rev/" | 
 |        "@12345/devtools.html%3Fws%3Danyvalue" | 
 |        "&unencoded=value&debugFrontend=true", | 
 |        "devtools://devtools/?remoteFrontendUrl=" | 
 |        "https%3A%2F%2Fchrome-devtools-frontend.appspot.com%2Fserve_rev" | 
 |        "%2F%4012345%2Fdevtools.html%3Fws%3Danyvalue" | 
 |        "&debugFrontend=true"}, | 
 |       {"devtools://devtools/?remoteFrontendUrl=" | 
 |        "https://chrome-devtools-frontend.appspot.com/serve_rev/" | 
 |        "@12345/inspector.html%23%27", | 
 |        "devtools://devtools/?remoteFrontendUrl=" | 
 |        "https%3A%2F%2Fchrome-devtools-frontend.appspot.com%2Fserve_rev" | 
 |        "%2F%4012345%2Finspector.html"}, | 
 |       {"devtools://devtools/" | 
 |        "?enabledExperiments=explosionsWhileTyping;newA11yTool", | 
 |        "devtools://devtools/" | 
 |        "?enabledExperiments=explosionsWhileTyping;newA11yTool"}, | 
 |       {"devtools://devtools/?enabledExperiments=invalidExperiment$", | 
 |        "devtools://devtools/"}, | 
 |   }; | 
 |  | 
 |   for (const auto& pair : tests) { | 
 |     GURL url = GURL(pair.first); | 
 |     url = DevToolsUIBindings::SanitizeFrontendURL(url); | 
 |     EXPECT_EQ(pair.second, url.spec()); | 
 |   } | 
 | } | 
 |  | 
 | class DevToolsUIBindingsSyncInfoTest : public testing::Test { | 
 |  public: | 
 |   void SetUp() override { | 
 |     SyncServiceFactory::GetInstance()->SetTestingFactory( | 
 |         &profile_, base::BindRepeating([](content::BrowserContext*) { | 
 |           return static_cast<std::unique_ptr<KeyedService>>( | 
 |               std::make_unique<syncer::TestSyncService>()); | 
 |         })); | 
 |     sync_service_ = static_cast<syncer::TestSyncService*>( | 
 |         SyncServiceFactory::GetForProfile(&profile_)); | 
 |   } | 
 |  | 
 |  protected: | 
 |   content::BrowserTaskEnvironment browser_task_environment_; | 
 |   signin::IdentityTestEnvironment identity_test_env_; | 
 |  | 
 |   TestingProfile profile_; | 
 |   raw_ptr<syncer::TestSyncService> sync_service_; | 
 | }; | 
 |  | 
 | TEST_F(DevToolsUIBindingsSyncInfoTest, SyncDisabled) { | 
 |   sync_service_->SetSignedOut(); | 
 |  | 
 |   base::Value::Dict info = | 
 |       DevToolsUIBindings::GetSyncInformationForProfile(&profile_); | 
 |  | 
 |   EXPECT_EQ( | 
 |       base::FeatureList::IsEnabled(switches::kEnablePreferencesAccountStorage), | 
 |       info.FindBool("isSyncActive").value()); | 
 | } | 
 |  | 
 | TEST_F(DevToolsUIBindingsSyncInfoTest, PreferencesNotSynced) { | 
 |   sync_service_->GetUserSettings()->SetSelectedTypes( | 
 |       /*sync_everything=*/false, | 
 |       /*types=*/{syncer::UserSelectableType::kBookmarks}); | 
 |  | 
 |   base::Value::Dict info = | 
 |       DevToolsUIBindings::GetSyncInformationForProfile(&profile_); | 
 |  | 
 |   EXPECT_THAT(info.FindBool("isSyncActive"), testing::Optional(true)); | 
 |   EXPECT_THAT(info.FindBool("arePreferencesSynced"), testing::Optional(false)); | 
 | } | 
 |  | 
 | TEST_F(DevToolsUIBindingsSyncInfoTest, ImageAlwaysProvided) { | 
 |   AccountInfo account_info = identity_test_env_.MakePrimaryAccountAvailable( | 
 |       "sync@devtools.dev", signin::ConsentLevel::kSync); | 
 |   sync_service_->SetSignedIn(signin::ConsentLevel::kSync, account_info); | 
 |  | 
 |   EXPECT_TRUE(account_info.account_image.IsEmpty()); | 
 |  | 
 |   base::Value::Dict info = | 
 |       DevToolsUIBindings::GetSyncInformationForProfile(&profile_); | 
 |  | 
 |   EXPECT_EQ(*info.FindString("accountEmail"), "sync@devtools.dev"); | 
 |   EXPECT_NE(info.FindString("accountImage"), nullptr); | 
 | } | 
 |  | 
 | class MockServiceHandler : public DevToolsHttpServiceHandler { | 
 |  public: | 
 |   MockServiceHandler() = default; | 
 |   ~MockServiceHandler() override = default; | 
 |  | 
 |   GURL BaseURL() const override { return GURL("http://localhost:8000"); } | 
 |   signin::ScopeSet OAuthScopes() const override { return {}; } | 
 |   net::NetworkTrafficAnnotationTag NetworkTrafficAnnotationTag() | 
 |       const override { | 
 |     return TRAFFIC_ANNOTATION_FOR_TESTS; | 
 |   } | 
 |  | 
 |   MOCK_METHOD(void, | 
 |               CanMakeRequest, | 
 |               (Profile * profile, base::OnceCallback<void(bool)> callback), | 
 |               (override)); | 
 | }; | 
 |  | 
 | class DevToolsUIBindingsDispatchHttpRequestTest : public testing::Test { | 
 |  public: | 
 |   DevToolsUIBindingsDispatchHttpRequestTest() | 
 |       : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {} | 
 |  | 
 |   void SetUp() override { | 
 |     profile_ = IdentityTestEnvironmentProfileAdaptor:: | 
 |         CreateProfileForIdentityTestEnvironment(); | 
 |     identity_test_env_adaptor_ = | 
 |         std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile_.get()); | 
 |  | 
 |     // Initialize the interceptor with a callback to our custom handler. | 
 |     interceptor_ = | 
 |         std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating( | 
 |             &DevToolsUIBindingsDispatchHttpRequestTest::InterceptRequest, | 
 |             base::Unretained(this))); | 
 |  | 
 |     web_contents_ = web_contents_factory_.CreateWebContents(profile_.get()); | 
 |     bindings_ = std::make_unique<DevToolsUIBindings>(web_contents_); | 
 |  | 
 |     auto registry = std::make_unique<DevToolsHttpServiceRegistry>(); | 
 |     auto mock_handler = base::WrapUnique(new MockServiceHandler()); | 
 |     mock_handler_ptr_ = mock_handler.get(); | 
 |     registry->AddForTesting(DevToolsHttpServiceRegistry::Service( | 
 |         "mockService", {{"/getFoo", "GET"}, {"/postBar", "POST"}}, | 
 |         std::move(mock_handler))); | 
 |     bindings_->SetHttpServiceRegistryForTesting(std::move(registry)); | 
 |   } | 
 |  | 
 |  protected: | 
 |   // A struct to hold response data for our interception map. | 
 |   struct TestResponse { | 
 |     std::string headers; | 
 |     std::string body; | 
 |     net::Error net_error; | 
 |   }; | 
 |  | 
 |   void DispatchHttpRequest(DevToolsUIBindings::DispatchCallback callback, | 
 |                            const DevToolsDispatchHttpRequestParams& params) { | 
 |     bindings_->DispatchHttpRequest(std::move(callback), params); | 
 |   } | 
 |  | 
 |   void ExpectCanMakeRequest(bool can_make_request) { | 
 |     EXPECT_CALL(*mock_handler_ptr_, CanMakeRequest(_, _)) | 
 |         .WillOnce([=](Profile*, base::OnceCallback<void(bool)> callback) { | 
 |           std::move(callback).Run(can_make_request); | 
 |         }); | 
 |   } | 
 |  | 
 |   // Helper to configure a response for a specific URL. | 
 |   void SetResponse(const GURL& url, | 
 |                    const std::string& headers, | 
 |                    const std::string& body, | 
 |                    net::Error net_error = net::OK) { | 
 |     response_map_[url] = {headers, body, net_error}; | 
 |   } | 
 |  | 
 |   IdentityTestEnvironmentProfileAdaptor* identity_test_env_adaptor() { | 
 |     return identity_test_env_adaptor_.get(); | 
 |   } | 
 |  | 
 |   content::URLLoaderInterceptor* interceptor() { return interceptor_.get(); } | 
 |  | 
 |   const std::optional<network::ResourceRequest>& last_request() const { | 
 |     return last_request_; | 
 |   } | 
 |  | 
 |  private: | 
 |   bool InterceptRequest(content::URLLoaderInterceptor::RequestParams* params) { | 
 |     last_request_ = params->url_request; | 
 |     const GURL& url = params->url_request.url; | 
 |     auto it = response_map_.find(url); | 
 |  | 
 |     // If a specific response is not configured, use the default response. | 
 |     if (it == response_map_.end()) { | 
 |       content::URLLoaderInterceptor::WriteResponse("HTTP/1.1 200 OK\n", "body", | 
 |                                                    params->client.get()); | 
 |       return true; | 
 |     } | 
 |  | 
 |     const TestResponse& response = it->second; | 
 |     if (response.net_error != net::OK) { | 
 |       // Handle network errors. | 
 |       params->client->OnComplete( | 
 |           network::URLLoaderCompletionStatus(response.net_error)); | 
 |       return true; | 
 |     } | 
 |  | 
 |     // Handle HTTP success or error responses. | 
 |     content::URLLoaderInterceptor::WriteResponse( | 
 |         response.headers, response.body, params->client.get()); | 
 |     return true; | 
 |   } | 
 |  | 
 |   content::BrowserTaskEnvironment task_environment_; | 
 |   std::unique_ptr<IdentityTestEnvironmentProfileAdaptor> | 
 |       identity_test_env_adaptor_; | 
 |   std::unique_ptr<content::URLLoaderInterceptor> interceptor_; | 
 |   std::optional<network::ResourceRequest> last_request_; | 
 |  | 
 |   std::unique_ptr<TestingProfile> profile_; | 
 |   content::TestWebContentsFactory web_contents_factory_; | 
 |  | 
 |   raw_ptr<content::WebContents> web_contents_; | 
 |   std::unique_ptr<DevToolsUIBindings> bindings_; | 
 |   raw_ptr<MockServiceHandler> mock_handler_ptr_; | 
 |  | 
 |   std::map<GURL, TestResponse> response_map_; | 
 | }; | 
 |  | 
 | TEST_F(DevToolsUIBindingsDispatchHttpRequestTest, | 
 |        DispatchHttpRequestUnknownService) { | 
 |   base::Value::Dict result; | 
 |   DevToolsDispatchHttpRequestParams params; | 
 |   params.service = "unknownService"; | 
 |   params.path = "/path"; | 
 |   params.method = "GET"; | 
 |   DispatchHttpRequest(base::BindLambdaForTesting([&](const base::Value* value) { | 
 |                         result = value->GetDict().Clone(); | 
 |                       }), | 
 |                       params); | 
 |  | 
 |   EXPECT_EQ(*result.FindString("error"), "Service not found"); | 
 | } | 
 |  | 
 | TEST_F(DevToolsUIBindingsDispatchHttpRequestTest, | 
 |        DispatchHttpRequestDisallowedPath) { | 
 |   base::Value::Dict result; | 
 |   base::RunLoop run_loop; | 
 |   DevToolsDispatchHttpRequestParams params; | 
 |   params.service = "mockService"; | 
 |   params.path = "/disallowedPath"; | 
 |   params.method = "GET"; | 
 |   DispatchHttpRequest(base::BindLambdaForTesting([&](const base::Value* value) { | 
 |                         result = value->GetDict().Clone(); | 
 |                         run_loop.Quit(); | 
 |                       }), | 
 |                       params); | 
 |   run_loop.Run(); | 
 |   EXPECT_EQ(*result.FindString("error"), "Disallowed path or method"); | 
 | } | 
 |  | 
 | TEST_F(DevToolsUIBindingsDispatchHttpRequestTest, | 
 |        DispatchHttpRequestValidationFailure) { | 
 |   ExpectCanMakeRequest(false); | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   base::Value::Dict result; | 
 |   DevToolsDispatchHttpRequestParams params; | 
 |   params.service = "mockService"; | 
 |   params.path = "/getFoo"; | 
 |   params.method = "GET"; | 
 |   DispatchHttpRequest(base::BindLambdaForTesting([&](const base::Value* value) { | 
 |                         result = value->GetDict().Clone(); | 
 |                         run_loop.Quit(); | 
 |                       }), | 
 |                       params); | 
 |   run_loop.Run(); | 
 |  | 
 |   EXPECT_EQ(*result.FindString("error"), "Request validation failed"); | 
 | } | 
 |  | 
 | TEST_F(DevToolsUIBindingsDispatchHttpRequestTest, | 
 |        DispatchHttpRequestTokenFetchFailure) { | 
 |   ExpectCanMakeRequest(true); | 
 |   identity_test_env_adaptor()->identity_test_env()->MakePrimaryAccountAvailable( | 
 |       "test@google.com", signin::ConsentLevel::kSignin); | 
 |   base::RunLoop run_loop; | 
 |   base::Value::Dict result; | 
 |   DevToolsDispatchHttpRequestParams params; | 
 |   params.service = "mockService"; | 
 |   params.path = "/getFoo"; | 
 |   params.method = "GET"; | 
 |   DispatchHttpRequest(base::BindLambdaForTesting([&](const base::Value* value) { | 
 |                         result = value->GetDict().Clone(); | 
 |                         run_loop.Quit(); | 
 |                       }), | 
 |                       params); | 
 |  | 
 |   identity_test_env_adaptor() | 
 |       ->identity_test_env() | 
 |       ->WaitForAccessTokenRequestIfNecessaryAndRespondWithError( | 
 |           GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); | 
 |   run_loop.Run(); | 
 |  | 
 |   EXPECT_EQ(*result.FindString("error"), "Token fetch error"); | 
 |   EXPECT_EQ(*result.FindString("detail"), | 
 |             "Service unavailable; try again later."); | 
 | } | 
 |  | 
 | TEST_F(DevToolsUIBindingsDispatchHttpRequestTest, | 
 |        DispatchHttpRequestSuccessful) { | 
 |   ExpectCanMakeRequest(true); | 
 |   identity_test_env_adaptor()->identity_test_env()->MakePrimaryAccountAvailable( | 
 |       "test@google.com", signin::ConsentLevel::kSignin); | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   base::Value::Dict result; | 
 |   DevToolsDispatchHttpRequestParams params; | 
 |   params.service = "mockService"; | 
 |   params.path = "/getFoo"; | 
 |   params.method = "GET"; | 
 |   DispatchHttpRequest(base::BindLambdaForTesting([&](const base::Value* value) { | 
 |                         result = value->GetDict().Clone(); | 
 |                         run_loop.Quit(); | 
 |                       }), | 
 |                       params); | 
 |  | 
 |   identity_test_env_adaptor() | 
 |       ->identity_test_env() | 
 |       ->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( | 
 |           "test_token", base::Time::Max()); | 
 |   run_loop.Run(); | 
 |  | 
 |   net::HttpRequestHeaders headers = last_request()->headers; | 
 |   EXPECT_EQ(last_request()->url, GURL("http://localhost:8000/getFoo")); | 
 |   EXPECT_EQ(headers.GetHeader("Authorization"), "Bearer test_token"); | 
 |   EXPECT_EQ(*result.FindString("response"), "body"); | 
 |   EXPECT_EQ(*result.FindInt("statusCode"), net::HTTP_OK); | 
 | } | 
 |  | 
 | TEST_F(DevToolsUIBindingsDispatchHttpRequestTest, | 
 |        DispatchHttpRequestNetworkError) { | 
 |   SetResponse(GURL("http://localhost:8000/postBar"), "", "", net::ERR_FAILED); | 
 |   ExpectCanMakeRequest(true); | 
 |   identity_test_env_adaptor()->identity_test_env()->MakePrimaryAccountAvailable( | 
 |       "test@google.com", signin::ConsentLevel::kSignin); | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   base::Value::Dict result; | 
 |   DevToolsDispatchHttpRequestParams params; | 
 |   params.service = "mockService"; | 
 |   params.path = "/postBar"; | 
 |   params.method = "POST"; | 
 |   params.body = "{\"foo\": \"bar\"}"; | 
 |   DispatchHttpRequest(base::BindLambdaForTesting([&](const base::Value* value) { | 
 |                         result = value->GetDict().Clone(); | 
 |                         run_loop.Quit(); | 
 |                       }), | 
 |                       params); | 
 |  | 
 |   identity_test_env_adaptor() | 
 |       ->identity_test_env() | 
 |       ->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( | 
 |           "test_token", base::Time::Max()); | 
 |   run_loop.Run(); | 
 |  | 
 |   EXPECT_EQ(*result.FindString("error"), "Request failed"); | 
 |   EXPECT_EQ(*result.FindInt("netError"), net::ERR_FAILED); | 
 |   EXPECT_EQ(*result.FindInt("statusCode"), -1); | 
 | } | 
 |  | 
 | TEST_F(DevToolsUIBindingsDispatchHttpRequestTest, | 
 |        DispatchHttpRequestHttpError) { | 
 |   SetResponse(GURL("http://localhost:8000/postBar"), | 
 |               "HTTP/1.1 400 Internal Server Error\n\n", "Bad request detail"); | 
 |  | 
 |   ExpectCanMakeRequest(true); | 
 |   identity_test_env_adaptor()->identity_test_env()->MakePrimaryAccountAvailable( | 
 |       "test@google.com", signin::ConsentLevel::kSignin); | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   base::Value::Dict result; | 
 |   DevToolsDispatchHttpRequestParams params; | 
 |   params.service = "mockService"; | 
 |   params.path = "/postBar"; | 
 |   params.method = "POST"; | 
 |   params.body = "{\"foo\": \"bar\"}"; | 
 |   DispatchHttpRequest(base::BindLambdaForTesting([&](const base::Value* value) { | 
 |                         result = value->GetDict().Clone(); | 
 |                         run_loop.Quit(); | 
 |                       }), | 
 |                       params); | 
 |  | 
 |   identity_test_env_adaptor() | 
 |       ->identity_test_env() | 
 |       ->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( | 
 |           "test_token", base::Time::Max()); | 
 |   run_loop.Run(); | 
 |  | 
 |   EXPECT_EQ(*result.FindString("error"), "Request failed"); | 
 |   EXPECT_EQ(*result.FindString("detail"), "Bad request detail"); | 
 |   EXPECT_EQ(*result.FindInt("statusCode"), net::HTTP_BAD_REQUEST); | 
 |   EXPECT_EQ(*result.FindInt("netError"), net::OK); | 
 | } | 
 |  | 
 | TEST_F(DevToolsUIBindingsDispatchHttpRequestTest, DispatchHttpRequestWithBody) { | 
 |   ExpectCanMakeRequest(true); | 
 |   identity_test_env_adaptor()->identity_test_env()->MakePrimaryAccountAvailable( | 
 |       "test@google.com", signin::ConsentLevel::kSignin); | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   base::Value::Dict result; | 
 |   DevToolsDispatchHttpRequestParams params; | 
 |   params.service = "mockService"; | 
 |   params.path = "/postBar"; | 
 |   params.method = "POST"; | 
 |   params.body = "{\"foo\": \"bar\"}"; | 
 |   DispatchHttpRequest(base::BindLambdaForTesting([&](const base::Value* value) { | 
 |                         result = value->GetDict().Clone(); | 
 |                         run_loop.Quit(); | 
 |                       }), | 
 |                       params); | 
 |  | 
 |   identity_test_env_adaptor() | 
 |       ->identity_test_env() | 
 |       ->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( | 
 |           "test_token", base::Time::Max()); | 
 |   run_loop.Run(); | 
 |  | 
 |   ASSERT_TRUE(last_request().has_value()); | 
 |   ASSERT_TRUE(last_request()->request_body); | 
 |   ASSERT_EQ(last_request()->request_body->elements()->size(), 1u); | 
 |   const network::DataElement& element = | 
 |       last_request()->request_body->elements()->at(0); | 
 |   ASSERT_EQ(element.type(), network::DataElement::Tag::kBytes); | 
 |   EXPECT_EQ(element.As<network::DataElementBytes>().AsStringPiece(), | 
 |             "{\"foo\": \"bar\"}"); | 
 | } | 
 |  | 
 | TEST_F(DevToolsUIBindingsDispatchHttpRequestTest, | 
 |        DispatchHttpRequestWithoutBody) { | 
 |   ExpectCanMakeRequest(true); | 
 |   identity_test_env_adaptor()->identity_test_env()->MakePrimaryAccountAvailable( | 
 |       "test@google.com", signin::ConsentLevel::kSignin); | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   base::Value::Dict result; | 
 |   DevToolsDispatchHttpRequestParams params; | 
 |   params.service = "mockService"; | 
 |   params.path = "/getFoo"; | 
 |   params.method = "GET"; | 
 |   DispatchHttpRequest(base::BindLambdaForTesting([&](const base::Value* value) { | 
 |                         result = value->GetDict().Clone(); | 
 |                         run_loop.Quit(); | 
 |                       }), | 
 |                       params); | 
 |  | 
 |   identity_test_env_adaptor() | 
 |       ->identity_test_env() | 
 |       ->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( | 
 |           "test_token", base::Time::Max()); | 
 |   run_loop.Run(); | 
 |  | 
 |   ASSERT_TRUE(last_request().has_value()); | 
 |   EXPECT_FALSE(last_request()->request_body); | 
 | } | 
 |  | 
 | TEST_F(DevToolsUIBindingsDispatchHttpRequestTest, | 
 |        DispatchHttpRequestWithQueryParamsSuccessful) { | 
 |   ExpectCanMakeRequest(true); | 
 |   identity_test_env_adaptor()->identity_test_env()->MakePrimaryAccountAvailable( | 
 |       "test@google.com", signin::ConsentLevel::kSignin); | 
 |  | 
 |   base::RunLoop run_loop; | 
 |   base::Value::Dict result; | 
 |   DevToolsDispatchHttpRequestParams params; | 
 |   params.service = "mockService"; | 
 |   params.path = "/getFoo"; | 
 |   params.method = "GET"; | 
 |   params.query_params["q"].push_back("test/toescape"); | 
 |   params.query_params["q"].push_back("test2"); | 
 |  | 
 |   DispatchHttpRequest(base::BindLambdaForTesting([&](const base::Value* value) { | 
 |                         result = value->GetDict().Clone(); | 
 |                         run_loop.Quit(); | 
 |                       }), | 
 |                       params); | 
 |  | 
 |   identity_test_env_adaptor() | 
 |       ->identity_test_env() | 
 |       ->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( | 
 |           "test_token", base::Time::Max()); | 
 |   run_loop.Run(); | 
 |  | 
 |   EXPECT_EQ(last_request()->url, | 
 |             GURL("http://localhost:8000/getFoo?q=test%2Ftoescape&q=test2")); | 
 |   EXPECT_EQ(*result.FindString("response"), "body"); | 
 |   EXPECT_EQ(*result.FindInt("statusCode"), net::HTTP_OK); | 
 | } |