1 // Copyright 2020 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.
4
5 #include "chrome/browser/media/feeds/media_feeds_fetcher.h"
6
7 #include <memory>
8
9 #include "base/bind.h"
10 #include "base/test/bind.h"
11 #include "base/test/mock_callback.h"
12 #include "base/test/task_environment.h"
13 #include "chrome/browser/media/history/media_history_keyed_service.h"
14 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
15 #include "components/schema_org/common/metadata.mojom.h"
16 #include "components/schema_org/schema_org_entity_names.h"
17 #include "content/public/browser/storage_partition.h"
18 #include "net/cookies/cookie_access_result.h"
19 #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
20 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
21 #include "services/network/test/test_url_loader_factory.h"
22 #include "services/network/test/test_utils.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 #include "url/gurl.h"
25
26 namespace media_feeds {
27
28 using testing::_;
29
30 namespace {
31
32 const char kTestUrl[] = "https://www.google.com";
33
34 const char kTestData[] = R"END({
35 "@context": "https://schema.org",
36 "@type": "CompleteDataFeed",
37 "dataFeedElement": [],
38 "provider": {
39 "@type": "Organization",
40 "name": "Media Feeds Developers",
41 "logo": [{
42 "@type": "ImageObject",
43 "width": 1113,
44 "height": 245,
45 "url": "https://wicg.github.io/media-feeds/data/logo_white.png",
46 "additionalProperty": {
47 "@type": "PropertyValue",
48 "name": "contentAttributes",
49 "value": ["forDarkBackground", "hasTitle", "transparentBackground"]
50 }
51 }]
52 }
53 })END";
54
55 const char kTestDataWithAssociatedOrigins[] = R"END({
56 "@context": "https://schema.org",
57 "@type": "CompleteDataFeed",
58 "dataFeedElement": [],
59 "additionalProperty": {
60 "@type": "PropertyValue",
61 "name": "associatedOrigin",
62 "value": ["https://login.example.org", "https://login.example.com"]
63 },
64 "provider": {
65 "@type": "Organization",
66 "name": "Media Feeds Developers",
67 "logo": [{
68 "@type": "ImageObject",
69 "width": 1113,
70 "height": 245,
71 "url": "https://wicg.github.io/media-feeds/data/logo_white.png",
72 "additionalProperty": {
73 "@type": "PropertyValue",
74 "name": "contentAttributes",
75 "value": ["forDarkBackground", "hasTitle", "transparentBackground"]
76 }
77 }]
78 }
79 })END";
80
81 const char kTestFeedName[] = "Media Feeds Developers";
82
83 } // namespace
84
85 class MediaFeedsFetcherTest : public ChromeRenderViewHostTestHarness {
86 public:
87 MediaFeedsFetcherTest() = default;
88 ~MediaFeedsFetcherTest() override = default;
89 MediaFeedsFetcherTest(const MediaFeedsFetcherTest& t) = delete;
90 MediaFeedsFetcherTest& operator=(const MediaFeedsFetcherTest&) = delete;
91
SetUp()92 void SetUp() override {
93 ChromeRenderViewHostTestHarness::SetUp();
94
95 fetcher_ = std::make_unique<MediaFeedsFetcher>(
96 base::MakeRefCounted<::network::WeakWrapperSharedURLLoaderFactory>(
97 url_loader_factory()));
98 }
99
100 MediaFeedsFetcher* fetcher() { return fetcher_.get(); }
101 ::network::TestURLLoaderFactory* url_loader_factory() {
102 return &url_loader_factory_;
103 }
104
105 bool RespondToFetch(
106 const std::string& response_body,
107 net::HttpStatusCode response_code = net::HttpStatusCode::HTTP_OK,
108 int net_error = net::OK,
109 bool was_fetched_via_cache = false) {
110 auto response_head = ::network::CreateURLResponseHead(response_code);
111 response_head->was_fetched_via_cache = was_fetched_via_cache;
112
113 bool rv = url_loader_factory()->SimulateResponseForPendingRequest(
114 GURL(kTestUrl), ::network::URLLoaderCompletionStatus(net_error),
115 std::move(response_head), response_body);
116 task_environment()->RunUntilIdle();
117 return rv;
118 }
119
120 void WaitForRequest() {
121 task_environment()->RunUntilIdle();
122
123 ASSERT_TRUE(GetCurrentRequest().url.is_valid());
124 EXPECT_TRUE(GetCurrentRequest().site_for_cookies.IsEquivalent(
125 net::SiteForCookies::FromUrl(GURL(kTestUrl))));
126 EXPECT_EQ(GetCurrentlyQueriedHeaderValue(net::HttpRequestHeaders::kAccept),
127 "application/ld+json");
128 EXPECT_EQ(GetCurrentRequest().redirect_mode,
129 ::network::mojom::RedirectMode::kError);
130 EXPECT_EQ(net::HttpRequestHeaders::kGetMethod, GetCurrentRequest().method);
131 }
132
133 bool SetCookie(content::BrowserContext* browser_context,
134 const GURL& url,
135 const std::string& value) {
136 bool result = false;
137 base::RunLoop run_loop;
138 mojo::Remote<network::mojom::CookieManager> cookie_manager;
139 content::BrowserContext::GetDefaultStoragePartition(browser_context)
140 ->GetNetworkContext()
141 ->GetCookieManager(cookie_manager.BindNewPipeAndPassReceiver());
142 std::unique_ptr<net::CanonicalCookie> cc(net::CanonicalCookie::Create(
143 url, value, base::Time::Now(), base::nullopt /* server_time */));
144 EXPECT_TRUE(cc.get());
145
146 cookie_manager->SetCanonicalCookie(
147 *cc.get(), url, net::CookieOptions::MakeAllInclusive(),
148 base::BindOnce(
149 [](bool* result, base::RunLoop* run_loop,
150 net::CookieAccessResult set_cookie_access_result) {
151 *result = set_cookie_access_result.status.IsInclude();
152 run_loop->Quit();
153 },
154 &result, &run_loop));
155 run_loop.Run();
156 return result;
157 }
158
159 private:
160 std::string GetCurrentlyQueriedHeaderValue(const base::StringPiece& key) {
161 std::string out;
162 GetCurrentRequest().headers.GetHeader(key, &out);
163 return out;
164 }
165
166 const ::network::ResourceRequest& GetCurrentRequest() {
167 return url_loader_factory()->pending_requests()->front().request;
168 }
169
170 ::network::TestURLLoaderFactory url_loader_factory_;
171 std::unique_ptr<MediaFeedsFetcher> fetcher_;
172 data_decoder::test::InProcessDataDecoder data_decoder_;
173 };
174
175 TEST_F(MediaFeedsFetcherTest, SucceedsOnBasicFetch) {
176 GURL site_with_cookies(kTestUrl);
177 ASSERT_TRUE(SetCookie(profile(), site_with_cookies, "testing"));
178
179 fetcher()->FetchFeed(
180 GURL("https://www.google.com"), false,
181 base::BindLambdaForTesting(
182 [&](media_history::MediaHistoryKeyedService::MediaFeedFetchResult
183 result) {
184 EXPECT_EQ(result.status, mojom::FetchResult::kSuccess);
185 EXPECT_FALSE(result.was_fetched_from_cache);
186 EXPECT_FALSE(result.gone);
187 EXPECT_EQ(kTestFeedName, result.display_name);
188 }));
189
190 WaitForRequest();
191 ASSERT_TRUE(RespondToFetch(kTestData));
192 }
193
TEST_F(MediaFeedsFetcherTest,SucceedsOnBasicFetch_ForceCache)194 TEST_F(MediaFeedsFetcherTest, SucceedsOnBasicFetch_ForceCache) {
195 GURL site_with_cookies(kTestUrl);
196 ASSERT_TRUE(SetCookie(profile(), site_with_cookies, "testing"));
197
198 fetcher()->FetchFeed(
199 GURL("https://www.google.com"), true,
200 base::BindLambdaForTesting(
201 [&](media_history::MediaHistoryKeyedService::MediaFeedFetchResult
202 result) {
203 EXPECT_EQ(result.status, mojom::FetchResult::kSuccess);
204 EXPECT_FALSE(result.was_fetched_from_cache);
205 EXPECT_FALSE(result.gone);
206 EXPECT_EQ(kTestFeedName, result.display_name);
207 }));
208
209 WaitForRequest();
210 ASSERT_TRUE(RespondToFetch(kTestData));
211 }
212
TEST_F(MediaFeedsFetcherTest,SucceedsFetchFromCache)213 TEST_F(MediaFeedsFetcherTest, SucceedsFetchFromCache) {
214 fetcher()->FetchFeed(
215 GURL("https://www.google.com"), false,
216 base::BindLambdaForTesting(
217 [&](media_history::MediaHistoryKeyedService::MediaFeedFetchResult
218 result) {
219 EXPECT_EQ(result.status, mojom::FetchResult::kSuccess);
220 EXPECT_TRUE(result.was_fetched_from_cache);
221 EXPECT_FALSE(result.gone);
222 EXPECT_EQ(kTestFeedName, result.display_name);
223 }));
224
225 WaitForRequest();
226 ASSERT_TRUE(
227 RespondToFetch(kTestData, net::HttpStatusCode::HTTP_OK, net::OK, true));
228 }
229
TEST_F(MediaFeedsFetcherTest,ReturnsFailedResponseCode)230 TEST_F(MediaFeedsFetcherTest, ReturnsFailedResponseCode) {
231 fetcher()->FetchFeed(
232 GURL("https://www.google.com"), false,
233 base::BindLambdaForTesting(
234 [&](media_history::MediaHistoryKeyedService::MediaFeedFetchResult
235 result) {
236 EXPECT_EQ(result.status, mojom::FetchResult::kFailedBackendError);
237 EXPECT_FALSE(result.was_fetched_from_cache);
238 EXPECT_FALSE(result.gone);
239 }));
240
241 WaitForRequest();
242 ASSERT_TRUE(RespondToFetch("", net::HTTP_BAD_REQUEST));
243 }
244
TEST_F(MediaFeedsFetcherTest,ReturnsGone)245 TEST_F(MediaFeedsFetcherTest, ReturnsGone) {
246 base::MockCallback<MediaFeedsFetcher::MediaFeedCallback> callback;
247
248 fetcher()->FetchFeed(
249 GURL("https://www.google.com"), false,
250 base::BindLambdaForTesting(
251 [&](media_history::MediaHistoryKeyedService::MediaFeedFetchResult
252 result) {
253 EXPECT_EQ(result.status, mojom::FetchResult::kNone);
254 EXPECT_FALSE(result.was_fetched_from_cache);
255 EXPECT_TRUE(result.gone);
256 }));
257
258 WaitForRequest();
259 ASSERT_TRUE(RespondToFetch("", net::HTTP_GONE));
260 }
261
TEST_F(MediaFeedsFetcherTest,ReturnsNetError)262 TEST_F(MediaFeedsFetcherTest, ReturnsNetError) {
263 fetcher()->FetchFeed(
264 GURL("https://www.google.com"), false,
265 base::BindLambdaForTesting(
266 [&](media_history::MediaHistoryKeyedService::MediaFeedFetchResult
267 result) {
268 EXPECT_EQ(result.status, mojom::FetchResult::kFailedBackendError);
269 EXPECT_FALSE(result.was_fetched_from_cache);
270 EXPECT_FALSE(result.gone);
271 }));
272
273 WaitForRequest();
274 ASSERT_TRUE(RespondToFetch("", net::HTTP_OK, net::ERR_UNEXPECTED));
275 }
276
TEST_F(MediaFeedsFetcherTest,ReturnsErrFileNotFoundForEmptyFeedData)277 TEST_F(MediaFeedsFetcherTest, ReturnsErrFileNotFoundForEmptyFeedData) {
278 fetcher()->FetchFeed(
279 GURL("https://www.google.com"), false,
280 base::BindLambdaForTesting(
281 [&](media_history::MediaHistoryKeyedService::MediaFeedFetchResult
282 result) {
283 EXPECT_EQ(result.status, mojom::FetchResult::kFailedNetworkError);
284 EXPECT_FALSE(result.was_fetched_from_cache);
285 EXPECT_FALSE(result.gone);
286 }));
287
288 WaitForRequest();
289 ASSERT_TRUE(RespondToFetch(""));
290 }
291
TEST_F(MediaFeedsFetcherTest,ReturnsErrFailedForBadEntityData)292 TEST_F(MediaFeedsFetcherTest, ReturnsErrFailedForBadEntityData) {
293 fetcher()->FetchFeed(
294 GURL("https://www.google.com"), false,
295 base::BindLambdaForTesting(
296 [&](media_history::MediaHistoryKeyedService::MediaFeedFetchResult
297 result) {
298 EXPECT_EQ(result.status, mojom::FetchResult::kFailedBackendError);
299 EXPECT_FALSE(result.was_fetched_from_cache);
300 EXPECT_FALSE(result.gone);
301 }));
302
303 WaitForRequest();
304 ASSERT_TRUE(RespondToFetch(
305 "{\"@type\":\"CompleteDataFeed\"\"name\":\"Bad json missing a comma\"}"));
306 }
307
TEST_F(MediaFeedsFetcherTest,Success_AssociatedOrigins_BothWork)308 TEST_F(MediaFeedsFetcherTest, Success_AssociatedOrigins_BothWork) {
309 GURL site_with_cookies(kTestUrl);
310 ASSERT_TRUE(SetCookie(profile(), site_with_cookies, "testing"));
311
312 fetcher()->FetchFeed(
313 GURL("https://www.google.com"), false,
314 base::BindLambdaForTesting(
315 [&](media_history::MediaHistoryKeyedService::MediaFeedFetchResult
316 result) {
317 EXPECT_EQ(result.status, mojom::FetchResult::kSuccess);
318 EXPECT_FALSE(result.was_fetched_from_cache);
319 EXPECT_FALSE(result.gone);
320 EXPECT_EQ(kTestFeedName, result.display_name);
321 }));
322
323 WaitForRequest();
324 ASSERT_TRUE(RespondToFetch(kTestDataWithAssociatedOrigins));
325 }
326
327 } // namespace media_feeds
328