1 // Copyright 2013 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 "base/basictypes.h" 6 #include "base/bind.h" 7 #include "base/callback.h" 8 #include "base/command_line.h" 9 #include "base/compiler_specific.h" 10 #include "base/memory/scoped_ptr.h" 11 #include "base/message_loop/message_loop.h" 12 #include "chrome/browser/local_discovery/test_service_discovery_client.h" 13 #include "chrome/browser/profiles/profile.h" 14 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 15 #include "chrome/browser/signin/signin_manager_factory.h" 16 #include "chrome/browser/ui/browser.h" 17 #include "chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h" 18 #include "chrome/common/chrome_switches.h" 19 #include "chrome/common/url_constants.h" 20 #include "chrome/test/base/ui_test_utils.h" 21 #include "chrome/test/base/web_ui_browser_test.h" 22 #include "components/signin/core/browser/profile_oauth2_token_service.h" 23 #include "components/signin/core/browser/signin_manager.h" 24 #include "components/signin/core/browser/signin_manager_base.h" 25 #include "google_apis/gaia/gaia_urls.h" 26 #include "net/http/http_status_code.h" 27 #include "net/url_request/test_url_fetcher_factory.h" 28 #include "net/url_request/url_request_status.h" 29 #include "net/url_request/url_request_test_util.h" 30 31 #if defined(OS_CHROMEOS) 32 #include "base/prefs/pref_service.h" 33 #include "chrome/common/pref_names.h" 34 #endif 35 36 using testing::InvokeWithoutArgs; 37 using testing::Return; 38 using testing::AtLeast; 39 using testing::DoDefault; 40 using testing::DoAll; 41 using testing::InSequence; 42 using testing::StrictMock; 43 using testing::AnyNumber; 44 45 using testing::InvokeWithoutArgs; 46 using testing::Return; 47 using testing::AtLeast; 48 49 namespace local_discovery { 50 51 namespace { 52 53 const uint8 kQueryData[] = { 54 // Header 55 0x00, 0x00, 56 0x00, 0x00, // Flags not set. 57 0x00, 0x01, // Set QDCOUNT (question count) to 1, all the 58 // rest are 0 for a query. 59 0x00, 0x00, 60 0x00, 0x00, 61 0x00, 0x00, 62 63 // Question 64 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't', 65 0x04, '_', 't', 'c', 'p', 66 0x05, 'l', 'o', 'c', 'a', 'l', 67 0x00, 68 69 0x00, 0x0c, // QTYPE: A query. 70 0x00, 0x01, // QCLASS: IN class. Unicast bit not set. 71 }; 72 73 const uint8 kAnnouncePacket[] = { 74 // Header 75 0x00, 0x00, // ID is zeroed out 76 0x80, 0x00, // Standard query response, no error 77 0x00, 0x00, // No questions (for simplicity) 78 0x00, 0x05, // 5 RR (answers) 79 0x00, 0x00, // 0 authority RRs 80 0x00, 0x00, // 0 additional RRs 81 82 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't', 83 0x04, '_', 't', 'c', 'p', 84 0x05, 'l', 'o', 'c', 'a', 'l', 85 0x00, 86 0x00, 0x0c, // TYPE is PTR. 87 0x00, 0x01, // CLASS is IN. 88 0x00, 0x00, // TTL (4 bytes) is 32768 second. 89 0x10, 0x00, 90 0x00, 0x0c, // RDLENGTH is 12 bytes. 91 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 92 0xc0, 0x0c, 93 94 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 95 0xc0, 0x0c, 96 0x00, 0x10, // TYPE is TXT. 97 0x00, 0x01, // CLASS is IN. 98 0x00, 0x00, // TTL (4 bytes) is 32768 seconds. 99 0x01, 0x00, 100 0x00, 0x41, // RDLENGTH is 69 bytes. 101 0x03, 'i', 'd', '=', 102 0x10, 't', 'y', '=', 'S', 'a', 'm', 'p', 'l', 'e', ' ', 103 'd', 'e', 'v', 'i', 'c', 'e', 104 0x1e, 'n', 'o', 't', 'e', '=', 105 'S', 'a', 'm', 'p', 'l', 'e', ' ', 'd', 'e', 'v', 'i', 'c', 'e', ' ', 106 'd', 'e', 's', 'c', 'r', 'i', 'p', 't', 'i', 'o', 'n', 107 0x0c, 't', 'y', 'p', 'e', '=', 'p', 'r', 'i', 'n', 't', 'e', 'r', 108 109 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 110 0xc0, 0x0c, 111 0x00, 0x21, // Type is SRV 112 0x00, 0x01, // CLASS is IN 113 0x00, 0x00, // TTL (4 bytes) is 32768 second. 114 0x10, 0x00, 115 0x00, 0x17, // RDLENGTH is 23 116 0x00, 0x00, 117 0x00, 0x00, 118 0x22, 0xb8, // port 8888 119 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 120 0x05, 'l', 'o', 'c', 'a', 'l', 121 0x00, 122 123 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 124 0x05, 'l', 'o', 'c', 'a', 'l', 125 0x00, 126 0x00, 0x01, // Type is A 127 0x00, 0x01, // CLASS is IN 128 0x00, 0x00, // TTL (4 bytes) is 32768 second. 129 0x10, 0x00, 130 0x00, 0x04, // RDLENGTH is 4 131 0x01, 0x02, 0x03, 0x04, // 1.2.3.4 132 133 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 134 0x05, 'l', 'o', 'c', 'a', 'l', 135 0x00, 136 0x00, 0x1C, // Type is AAAA 137 0x00, 0x01, // CLASS is IN 138 0x00, 0x00, // TTL (4 bytes) is 32768 second. 139 0x10, 0x00, 140 0x00, 0x10, // RDLENGTH is 16 141 0x01, 0x02, 0x03, 0x04, // 1.2.3.4 142 0x01, 0x02, 0x03, 0x04, 143 0x01, 0x02, 0x03, 0x04, 144 0x01, 0x02, 0x03, 0x04, 145 }; 146 147 148 const uint8 kGoodbyePacket[] = { 149 // Header 150 0x00, 0x00, // ID is zeroed out 151 0x80, 0x00, // Standard query response, RA, no error 152 0x00, 0x00, // No questions (for simplicity) 153 0x00, 0x02, // 1 RR (answers) 154 0x00, 0x00, // 0 authority RRs 155 0x00, 0x00, // 0 additional RRs 156 157 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't', 158 0x04, '_', 't', 'c', 'p', 159 0x05, 'l', 'o', 'c', 'a', 'l', 160 0x00, 161 0x00, 0x0c, // TYPE is PTR. 162 0x00, 0x01, // CLASS is IN. 163 0x00, 0x00, // TTL (4 bytes) is 0 seconds. 164 0x00, 0x00, 165 0x00, 0x0c, // RDLENGTH is 12 bytes. 166 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 167 0xc0, 0x0c, 168 169 170 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 171 0xc0, 0x0c, 172 0x00, 0x21, // Type is SRV 173 0x00, 0x01, // CLASS is IN 174 0x00, 0x00, // TTL (4 bytes) is 0 seconds. 175 0x00, 0x00, 176 0x00, 0x17, // RDLENGTH is 23 177 0x00, 0x00, 178 0x00, 0x00, 179 0x22, 0xb8, // port 8888 180 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 181 0x05, 'l', 'o', 'c', 'a', 'l', 182 0x00, 183 }; 184 185 const uint8 kAnnouncePacketRegistered[] = { 186 // Header 187 0x00, 0x00, // ID is zeroed out 188 0x80, 0x00, // Standard query response, RA, no error 189 0x00, 0x00, // No questions (for simplicity) 190 0x00, 0x01, // 1 RR (answers) 191 0x00, 0x00, // 0 authority RRs 192 0x00, 0x00, // 0 additional RRs 193 194 0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e', 195 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't', 196 0x04, '_', 't', 'c', 'p', 197 0x05, 'l', 'o', 'c', 'a', 'l', 198 0x00, 199 0x00, 0x10, // TYPE is TXT. 200 0x00, 0x01, // CLASS is IN. 201 0x00, 0x00, // TTL (4 bytes) is 32768 seconds. 202 0x01, 0x00, 203 0x00, 0x3b, // RDLENGTH is 76 bytes. 204 0x0a, 'i', 'd', '=', 's', 'o', 'm', 'e', '_', 'i', 'd', 205 0x10, 't', 'y', '=', 'S', 'a', 'm', 'p', 'l', 'e', ' ', 206 'd', 'e', 'v', 'i', 'c', 'e', 207 0x1e, 'n', 'o', 't', 'e', '=', 208 'S', 'a', 'm', 'p', 'l', 'e', ' ', 'd', 'e', 'v', 'i', 'c', 'e', ' ', 209 'd', 'e', 's', 'c', 'r', 'i', 'p', 't', 'i', 'o', 'n', 210 }; 211 212 const char kResponseInfo[] = "{" 213 " \"x-privet-token\" : \"MyPrivetToken\"" 214 "}"; 215 216 const char kResponseInfoWithID[] = "{" 217 " \"x-privet-token\" : \"MyPrivetToken\"," 218 " \"id\" : \"my_id\"" 219 "}"; 220 221 const char kResponseRegisterStart[] = "{" 222 " \"action\": \"start\"," 223 " \"user\": \"user (at) host.com\"" 224 "}"; 225 226 const char kResponseRegisterClaimTokenNoConfirm[] = "{" 227 " \"action\": \"getClaimToken\"," 228 " \"user\": \"user (at) host.com\"," 229 " \"error\": \"pending_user_action\"," 230 " \"timeout\": 1" 231 "}"; 232 233 const char kResponseRegisterClaimTokenConfirm[] = "{" 234 " \"action\": \"getClaimToken\"," 235 " \"user\": \"user (at) host.com\"," 236 " \"token\": \"MySampleToken\"," 237 " \"claim_url\": \"http://someurl.com/\"" 238 "}"; 239 240 const char kResponseCloudPrintConfirm[] = "{ \"success\": true }"; 241 242 const char kResponseRegisterComplete[] = "{" 243 " \"action\": \"complete\"," 244 " \"user\": \"user (at) host.com\"," 245 " \"device_id\": \"my_id\"" 246 "}"; 247 248 const char kResponseGaiaToken[] = "{" 249 " \"access_token\": \"at1\"," 250 " \"expires_in\": 3600," 251 " \"token_type\": \"Bearer\"" 252 "}"; 253 254 const char kResponseGaiaId[] = "{" 255 " \"id\": \"12345\"" 256 "}"; 257 258 const char kURLInfo[] = "http://1.2.3.4:8888/privet/info"; 259 260 const char kURLRegisterStart[] = 261 "http://1.2.3.4:8888/privet/register?action=start&user=user%40host.com"; 262 263 const char kURLRegisterClaimToken[] = 264 "http://1.2.3.4:8888/privet/register?action=getClaimToken&" 265 "user=user%40host.com"; 266 267 const char kURLCloudPrintConfirm[] = 268 "https://www.google.com/cloudprint/confirm?token=MySampleToken"; 269 270 const char kURLRegisterComplete[] = 271 "http://1.2.3.4:8888/privet/register?action=complete&user=user%40host.com"; 272 273 const char kURLGaiaToken[] = 274 "https://accounts.google.com/o/oauth2/token"; 275 276 const char kSampleUser[] = "user (at) host.com"; 277 278 class TestMessageLoopCondition { 279 public: 280 TestMessageLoopCondition() : signaled_(false), 281 waiting_(false) { 282 } 283 284 ~TestMessageLoopCondition() { 285 } 286 287 // Signal a waiting method that it can continue executing. 288 void Signal() { 289 signaled_ = true; 290 if (waiting_) 291 base::MessageLoop::current()->Quit(); 292 } 293 294 // Pause execution and recursively run the message loop until |Signal()| is 295 // called. Do not pause if |Signal()| has already been called. 296 void Wait() { 297 while (!signaled_) { 298 waiting_ = true; 299 base::MessageLoop::current()->Run(); 300 waiting_ = false; 301 } 302 signaled_ = false; 303 } 304 305 private: 306 bool signaled_; 307 bool waiting_; 308 309 DISALLOW_COPY_AND_ASSIGN(TestMessageLoopCondition); 310 }; 311 312 class MockableFakeURLFetcherCreator { 313 public: 314 MockableFakeURLFetcherCreator() { 315 } 316 317 ~MockableFakeURLFetcherCreator() { 318 } 319 320 MOCK_METHOD1(OnCreateFakeURLFetcher, void(const std::string& url)); 321 322 scoped_ptr<net::FakeURLFetcher> CreateFakeURLFetcher( 323 const GURL& url, 324 net::URLFetcherDelegate* delegate, 325 const std::string& response_data, 326 net::HttpStatusCode response_code, 327 net::URLRequestStatus::Status status) { 328 OnCreateFakeURLFetcher(url.spec()); 329 return scoped_ptr<net::FakeURLFetcher>(new net::FakeURLFetcher( 330 url, delegate, response_data, response_code, status)); 331 } 332 333 net::FakeURLFetcherFactory::FakeURLFetcherCreator callback() { 334 return base::Bind(&MockableFakeURLFetcherCreator::CreateFakeURLFetcher, 335 base::Unretained(this)); 336 } 337 }; 338 339 class LocalDiscoveryUITest : public WebUIBrowserTest { 340 public: 341 LocalDiscoveryUITest() : fake_fetcher_factory_( 342 &fetcher_impl_factory_, 343 fake_url_fetcher_creator_.callback()) { 344 } 345 virtual ~LocalDiscoveryUITest() { 346 } 347 348 virtual void SetUpOnMainThread() OVERRIDE { 349 WebUIBrowserTest::SetUpOnMainThread(); 350 351 test_service_discovery_client_ = new TestServiceDiscoveryClient(); 352 test_service_discovery_client_->Start(); 353 EXPECT_CALL(*test_service_discovery_client_, OnSendTo( 354 std::string((const char*)kQueryData, 355 sizeof(kQueryData)))) 356 .Times(AtLeast(2)) 357 .WillOnce(InvokeWithoutArgs(&condition_devices_listed_, 358 &TestMessageLoopCondition::Signal)) 359 .WillRepeatedly(Return()); 360 361 SigninManagerBase* signin_manager = 362 SigninManagerFactory::GetForProfile(browser()->profile()); 363 364 #if defined(OS_CHROMEOS) 365 // Chrome OS initializes prefs::kGoogleServicesUsername to "stub user" so 366 // we need to override it as well. 367 browser()->profile()->GetPrefs()-> 368 SetString(prefs::kGoogleServicesUsername, kSampleUser); 369 #endif 370 DCHECK(signin_manager); 371 signin_manager->SetAuthenticatedUsername(kSampleUser); 372 373 fake_fetcher_factory().SetFakeResponse( 374 GURL(kURLInfo), 375 kResponseInfo, 376 net::HTTP_OK, 377 net::URLRequestStatus::SUCCESS); 378 379 fake_fetcher_factory().SetFakeResponse( 380 GURL(kURLRegisterStart), 381 kResponseRegisterStart, 382 net::HTTP_OK, 383 net::URLRequestStatus::SUCCESS); 384 385 fake_fetcher_factory().SetFakeResponse( 386 GURL(kURLRegisterClaimToken), 387 kResponseRegisterClaimTokenNoConfirm, 388 net::HTTP_OK, 389 net::URLRequestStatus::SUCCESS); 390 391 fake_fetcher_factory().SetFakeResponse( 392 GURL(kURLCloudPrintConfirm), 393 kResponseCloudPrintConfirm, 394 net::HTTP_OK, 395 net::URLRequestStatus::SUCCESS); 396 397 fake_fetcher_factory().SetFakeResponse( 398 GURL(kURLRegisterComplete), 399 kResponseRegisterComplete, 400 net::HTTP_OK, 401 net::URLRequestStatus::SUCCESS); 402 403 fake_fetcher_factory().SetFakeResponse( 404 GURL(kURLGaiaToken), 405 kResponseGaiaToken, 406 net::HTTP_OK, 407 net::URLRequestStatus::SUCCESS); 408 409 EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher( 410 kURLGaiaToken)) 411 .Times(AnyNumber()); 412 413 fake_fetcher_factory().SetFakeResponse( 414 GaiaUrls::GetInstance()->oauth_user_info_url(), 415 kResponseGaiaId, 416 net::HTTP_OK, 417 net::URLRequestStatus::SUCCESS); 418 419 EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher( 420 GaiaUrls::GetInstance()->oauth_user_info_url().spec())) 421 .Times(AnyNumber()); 422 423 ProfileOAuth2TokenService* token_service = 424 ProfileOAuth2TokenServiceFactory::GetForProfile(browser()->profile()); 425 426 token_service->UpdateCredentials("user (at) host.com", "MyFakeToken"); 427 428 AddLibrary(base::FilePath(FILE_PATH_LITERAL("local_discovery_ui_test.js"))); 429 } 430 431 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 432 WebUIBrowserTest::SetUpCommandLine(command_line); 433 } 434 435 void RunFor(base::TimeDelta time_period) { 436 base::CancelableCallback<void()> callback(base::Bind( 437 &base::MessageLoop::Quit, base::Unretained( 438 base::MessageLoop::current()))); 439 base::MessageLoop::current()->PostDelayedTask( 440 FROM_HERE, callback.callback(), time_period); 441 442 base::MessageLoop::current()->Run(); 443 callback.Cancel(); 444 } 445 446 TestServiceDiscoveryClient* test_service_discovery_client() { 447 return test_service_discovery_client_.get(); 448 } 449 450 TestMessageLoopCondition& condition_devices_listed() { 451 return condition_devices_listed_; 452 } 453 454 net::FakeURLFetcherFactory& fake_fetcher_factory() { 455 return fake_fetcher_factory_; 456 } 457 458 MockableFakeURLFetcherCreator& fake_url_fetcher_creator() { 459 return fake_url_fetcher_creator_; 460 } 461 462 private: 463 scoped_refptr<TestServiceDiscoveryClient> test_service_discovery_client_; 464 TestMessageLoopCondition condition_devices_listed_; 465 466 net::URLFetcherImplFactory fetcher_impl_factory_; 467 StrictMock<MockableFakeURLFetcherCreator> fake_url_fetcher_creator_; 468 net::FakeURLFetcherFactory fake_fetcher_factory_; 469 470 DISALLOW_COPY_AND_ASSIGN(LocalDiscoveryUITest); 471 }; 472 473 IN_PROC_BROWSER_TEST_F(LocalDiscoveryUITest, EmptyTest) { 474 ui_test_utils::NavigateToURL(browser(), GURL( 475 chrome::kChromeUIDevicesURL)); 476 condition_devices_listed().Wait(); 477 EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("checkNoDevices")); 478 } 479 480 IN_PROC_BROWSER_TEST_F(LocalDiscoveryUITest, AddRowTest) { 481 ui_test_utils::NavigateToURL(browser(), GURL( 482 chrome::kChromeUIDevicesURL)); 483 condition_devices_listed().Wait(); 484 485 test_service_discovery_client()->SimulateReceive( 486 kAnnouncePacket, sizeof(kAnnouncePacket)); 487 488 base::MessageLoop::current()->RunUntilIdle(); 489 490 EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("checkOneDevice")); 491 492 test_service_discovery_client()->SimulateReceive( 493 kGoodbyePacket, sizeof(kGoodbyePacket)); 494 495 RunFor(base::TimeDelta::FromMilliseconds(1100)); 496 497 EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("checkNoDevices")); 498 } 499 500 501 IN_PROC_BROWSER_TEST_F(LocalDiscoveryUITest, RegisterTest) { 502 TestMessageLoopCondition condition_token_claimed; 503 504 ui_test_utils::NavigateToURL(browser(), GURL( 505 chrome::kChromeUIDevicesURL)); 506 condition_devices_listed().Wait(); 507 508 test_service_discovery_client()->SimulateReceive( 509 kAnnouncePacket, sizeof(kAnnouncePacket)); 510 511 base::MessageLoop::current()->RunUntilIdle(); 512 513 EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("checkOneDevice")); 514 515 EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("registerShowOverlay")); 516 517 { 518 InSequence s; 519 EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher(kURLInfo)); 520 EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher( 521 kURLRegisterStart)); 522 EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher( 523 kURLRegisterClaimToken)) 524 .WillOnce(InvokeWithoutArgs(&condition_token_claimed, 525 &TestMessageLoopCondition::Signal)); 526 } 527 528 EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("registerBegin")); 529 530 condition_token_claimed.Wait(); 531 532 EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("expectPageAdding1")); 533 534 fake_fetcher_factory().SetFakeResponse( 535 GURL(kURLRegisterClaimToken), 536 kResponseRegisterClaimTokenConfirm, 537 net::HTTP_OK, 538 net::URLRequestStatus::SUCCESS); 539 540 fake_fetcher_factory().SetFakeResponse( 541 GURL(kURLInfo), 542 kResponseInfoWithID, 543 net::HTTP_OK, 544 net::URLRequestStatus::SUCCESS); 545 546 { 547 InSequence s; 548 EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher( 549 kURLRegisterClaimToken)); 550 EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher( 551 kURLCloudPrintConfirm)); 552 EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher( 553 kURLRegisterComplete)); 554 EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher(kURLInfo)) 555 .WillOnce(InvokeWithoutArgs(&condition_token_claimed, 556 &TestMessageLoopCondition::Signal)); 557 } 558 559 condition_token_claimed.Wait(); 560 561 test_service_discovery_client()->SimulateReceive( 562 kAnnouncePacketRegistered, sizeof(kAnnouncePacketRegistered)); 563 564 base::MessageLoop::current()->RunUntilIdle(); 565 566 EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("expectRegisterDone")); 567 } 568 569 } // namespace 570 571 } // namespace local_discovery 572