[go: nahoru, domu]

blob: c4fec31a690a4b5b6d42c2301ca5ecd9d5dbc3f7 [file] [log] [blame]
Danil Somsikovda8c5702023-08-29 11:36:231// Copyright 2023 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/devtools/aida_client.h"
6
7#include <memory>
8#include <utility>
9
10#include "base/functional/bind.h"
Danil Somsikov4b8fdc12024-02-07 10:01:4111#include "base/test/metrics/histogram_tester.h"
Danil Somsikovda8c5702023-08-29 11:36:2312#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
13#include "chrome/test/base/testing_profile.h"
14#include "content/public/browser/network_service_instance.h"
15#include "content/public/test/browser_task_environment.h"
16#include "content/public/test/test_utils.h"
17#include "net/test/embedded_test_server/embedded_test_server.h"
18#include "services/network/network_service.h"
John Abd-El-Malekf2592db2024-02-12 07:20:2319#include "services/network/public/mojom/network_context_client.mojom.h"
Danil Somsikovda8c5702023-08-29 11:36:2320#include "services/network/test/test_shared_url_loader_factory.h"
21#include "testing/gtest/include/gtest/gtest.h"
22
23namespace {
24using net::test_server::BasicHttpResponse;
25using net::test_server::HttpRequest;
26using net::test_server::HttpResponse;
27} // namespace
28
29const char kEmail[] = "alice@example.com";
30const char kEndpointPath[] = "/foo";
31const char kScope[] = "bar";
32const char kRequest[] =
33 R"({"input": "What does this code do: 1+1", "client": "GENERAL"})";
34const char kResponse[] =
35 R"([{"textChunk":{"text":"The function `foo()` takes no arguments and returns nothing."}}])";
36
37class AidaClientTest : public testing::Test {
38 public:
39 AidaClientTest()
40 : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
41 profile_(IdentityTestEnvironmentProfileAdaptor::
42 CreateProfileForIdentityTestEnvironment()),
43 identity_test_env_adaptor_(
44 std::make_unique<IdentityTestEnvironmentProfileAdaptor>(
45 profile_.get())),
46 identity_test_env_(identity_test_env_adaptor_->identity_test_env()) {
47 content::GetNetworkService();
48 content::RunAllPendingInMessageLoop(content::BrowserThread::IO);
49
50 shared_url_loader_factory_ =
51 base::MakeRefCounted<network::TestSharedURLLoaderFactory>(
52 network::NetworkService::GetNetworkServiceForTesting());
53
54 identity_test_env_->MakePrimaryAccountAvailable(
55 kEmail, signin::ConsentLevel::kSync);
56 }
57
58 protected:
59 content::BrowserTaskEnvironment task_environment_;
60 std::unique_ptr<network::mojom::NetworkContextClient> network_context_client_;
61 scoped_refptr<network::TestSharedURLLoaderFactory> shared_url_loader_factory_;
62 net::EmbeddedTestServer test_server_;
63 std::unique_ptr<TestingProfile> profile_;
64 std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
65 identity_test_env_adaptor_;
66 signin::IdentityTestEnvironment* identity_test_env_;
Danil Somsikov4b8fdc12024-02-07 10:01:4167 base::HistogramTester histogram_tester_;
Danil Somsikovda8c5702023-08-29 11:36:2368};
69
70class Delegate {
71 public:
72 Delegate() = default;
73
74 std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
75 request_ = request.content;
76 authorization_header_ =
77 request.headers.at(net::HttpRequestHeaders::kAuthorization);
78
79 auto http_response = std::make_unique<BasicHttpResponse>();
80 http_response->set_code(api_response_code_);
81 http_response->set_content(api_response_);
82 http_response->set_content_type("application/json");
83 return http_response;
84 }
85
86 void FinishCallback(base::RunLoop* run_loop, const std::string& response) {
87 response_ = response;
88 if (run_loop) {
89 run_loop->Quit();
90 }
91 }
92
93 std::string request_;
94 std::string api_response_ = kResponse;
95 net::HttpStatusCode api_response_code_ = net::HTTP_OK;
96 std::string response_;
97 std::string authorization_header_;
98};
99
100constexpr char kOAuthToken[] = "5678";
101
102TEST_F(AidaClientTest, DoesNothingIfNoScope) {
103 Delegate delegate;
104 test_server_.RegisterRequestHandler(base::BindRepeating(
105 &Delegate::HandleRequest, base::Unretained(&delegate)));
106
107 AidaClient aida_client(profile_.get(), shared_url_loader_factory_);
108 aida_client.OverrideAidaEndpointAndScopeForTesting("", "");
109 aida_client.DoConversation(
110 kRequest, base::BindOnce(&Delegate::FinishCallback,
111 base::Unretained(&delegate), nullptr));
112 EXPECT_EQ("", delegate.request_);
113 EXPECT_EQ(R"([{"error": "AIDA scope is not configured"}])",
114 delegate.response_);
115}
116
117TEST_F(AidaClientTest, FailsIfNotAuthorized) {
118 base::RunLoop run_loop;
119 Delegate delegate;
120
121 AidaClient aida_client(profile_.get(), shared_url_loader_factory_);
122 aida_client.OverrideAidaEndpointAndScopeForTesting("https://example.com/foo",
123 kScope);
124 aida_client.DoConversation(
125 kRequest, base::BindOnce(&Delegate::FinishCallback,
126 base::Unretained(&delegate), &run_loop));
127 identity_test_env_->WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
128 GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED));
129
130 EXPECT_EQ("", delegate.request_);
131 EXPECT_EQ(
132 R"([{"error": "Cannot get OAuth credentials", "detail": "Request canceled."}])",
133 delegate.response_);
134}
135
136TEST_F(AidaClientTest, Succeeds) {
137 base::RunLoop run_loop;
138 Delegate delegate;
139 test_server_.RegisterRequestHandler(base::BindRepeating(
140 &Delegate::HandleRequest, base::Unretained(&delegate)));
141
142 ASSERT_TRUE(test_server_.Start());
143
144 GURL endpoint_url = test_server_.GetURL(kEndpointPath);
145 AidaClient aida_client(profile_.get(), shared_url_loader_factory_);
146 aida_client.OverrideAidaEndpointAndScopeForTesting(endpoint_url.spec(),
147 kScope);
148 aida_client.DoConversation(
149 kRequest, base::BindOnce(&Delegate::FinishCallback,
150 base::Unretained(&delegate), &run_loop));
151 identity_test_env_
152 ->WaitForAccessTokenRequestIfNecessaryAndRespondWithTokenForScopes(
153 kOAuthToken, base::Time::Now() + base::Seconds(10),
154 std::string() /*id_token*/, signin::ScopeSet{kScope});
155 run_loop.Run();
156
157 EXPECT_EQ(kRequest, delegate.request_);
158 EXPECT_EQ(kResponse, delegate.response_);
Danil Somsikov4b8fdc12024-02-07 10:01:41159 histogram_tester_.ExpectTotalCount("DevTools.AidaResponseTime", 1);
Danil Somsikovda8c5702023-08-29 11:36:23160}
161
162TEST_F(AidaClientTest, ReusesOAuthToken) {
163 base::RunLoop run_loop;
164 Delegate delegate;
165 test_server_.RegisterRequestHandler(base::BindRepeating(
166 &Delegate::HandleRequest, base::Unretained(&delegate)));
167
168 ASSERT_TRUE(test_server_.Start());
169
170 GURL endpoint_url = test_server_.GetURL(kEndpointPath);
171 AidaClient aida_client(profile_.get(), shared_url_loader_factory_);
172 aida_client.OverrideAidaEndpointAndScopeForTesting(endpoint_url.spec(),
173 kScope);
174 aida_client.DoConversation(
175 kRequest, base::BindOnce(&Delegate::FinishCallback,
176 base::Unretained(&delegate), &run_loop));
177 identity_test_env_
178 ->WaitForAccessTokenRequestIfNecessaryAndRespondWithTokenForScopes(
179 kOAuthToken, base::Time::Now() + base::Seconds(10),
180 std::string() /*id_token*/, signin::ScopeSet{kScope});
181 run_loop.Run();
182
183 EXPECT_EQ(kRequest, delegate.request_);
184 EXPECT_EQ(kResponse, delegate.response_);
185 std::string authorization_header = delegate.authorization_header_;
186
187 const char kAnotherRequest[] = "another request";
188 const char kAnotherResponse[] = "another response";
189 delegate.api_response_ = kAnotherResponse;
190 base::RunLoop run_loop2;
191 aida_client.DoConversation(
192 kAnotherRequest, base::BindOnce(&Delegate::FinishCallback,
193 base::Unretained(&delegate), &run_loop2));
194 run_loop2.Run();
195 EXPECT_EQ(kAnotherRequest, delegate.request_);
196 EXPECT_EQ(kAnotherResponse, delegate.response_);
197 EXPECT_EQ(authorization_header, delegate.authorization_header_);
198}
199
200TEST_F(AidaClientTest, RefetchesTokenIfUnauthorized) {
201 base::RunLoop run_loop;
202 Delegate delegate;
203 test_server_.RegisterRequestHandler(base::BindRepeating(
204 &Delegate::HandleRequest, base::Unretained(&delegate)));
205
206 ASSERT_TRUE(test_server_.Start());
207
208 GURL endpoint_url = test_server_.GetURL(kEndpointPath);
209 AidaClient aida_client(profile_.get(), shared_url_loader_factory_);
210 aida_client.OverrideAidaEndpointAndScopeForTesting(endpoint_url.spec(),
211 kScope);
212 aida_client.DoConversation(
213 kRequest, base::BindOnce(&Delegate::FinishCallback,
214 base::Unretained(&delegate), &run_loop));
215 identity_test_env_
216 ->WaitForAccessTokenRequestIfNecessaryAndRespondWithTokenForScopes(
217 kOAuthToken, base::Time::Now() + base::Seconds(10),
218 std::string() /*id_token*/, signin::ScopeSet{kScope});
219 run_loop.Run();
220
221 EXPECT_EQ(kRequest, delegate.request_);
222 EXPECT_EQ(kResponse, delegate.response_);
223 std::string authorization_header = delegate.authorization_header_;
224
225 delegate.api_response_code_ = net::HTTP_UNAUTHORIZED;
226 base::RunLoop run_loop2;
227 const char kAnotherRequest[] = "another request";
228 const char kAnotherResponse[] = "another response";
229 const char kAnotherOAuthToken[] = "another token";
230
231 aida_client.DoConversation(
232 kAnotherRequest, base::BindOnce(&Delegate::FinishCallback,
233 base::Unretained(&delegate), &run_loop2));
234 identity_test_env_
235 ->WaitForAccessTokenRequestIfNecessaryAndRespondWithTokenForScopes(
236 kAnotherOAuthToken, base::Time::Now() + base::Seconds(10),
237 std::string() /*id_token*/, signin::ScopeSet{kScope});
238 delegate.api_response_code_ = net::HTTP_OK;
239 delegate.api_response_ = kAnotherResponse;
240
241 run_loop2.Run();
242 EXPECT_EQ(kAnotherRequest, delegate.request_);
243 EXPECT_EQ(kAnotherResponse, delegate.response_);
244 EXPECT_NE(authorization_header, delegate.authorization_header_);
245}