| // Copyright 2021 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/nearby_sharing/tachyon_ice_config_fetcher.h" |
| |
| #include "base/logging.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/task_environment.h" |
| #include "chrome/browser/nearby_sharing/proto/duration.pb.h" |
| #include "chrome/browser/nearby_sharing/proto/ice.pb.h" |
| #include "chrome/browser/nearby_sharing/proto/tachyon.pb.h" |
| #include "chrome/browser/nearby_sharing/proto/tachyon_common.pb.h" |
| #include "chrome/browser/nearby_sharing/proto/tachyon_enums.pb.h" |
| #include "components/signin/public/identity_manager/identity_test_environment.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| namespace tachyon_proto = nearbyshare::tachyon_proto; |
| |
| const char kApiUrl[] = |
| "https://instantmessaging-pa.googleapis.com/v1/peertopeer:geticeserver"; |
| const char kOAuthToken[] = "oauth_token"; |
| const char kTestAccount[] = "test@test.test"; |
| const char kIceConfigFetchedMetric[] = "Sharing.WebRtc.IceConfigFetched"; |
| const char kResultMetric[] = |
| "Nearby.Connections.InstantMessaging.TachyonIceConfigFetcher.Result"; |
| const char kFailureReasonMetric[] = |
| "Nearby.Connections.InstantMessaging.TachyonIceConfigFetcher.FailureReason"; |
| const char kCacheHitMetric[] = |
| "Nearby.Connections.InstantMessaging.TachyonIceConfigFetcher.CacheHit"; |
| const char kTokenFetchSuccessMetric[] = |
| "Nearby.Connections.InstantMessaging.TachyonIceConfigFetcher." |
| "OAuthTokenFetchResult"; |
| const int kLifetimeDurationSeconds = 86400; |
| |
| void CheckSuccessResponse( |
| const std::vector<::sharing::mojom::IceServerPtr>& ice_servers) { |
| ASSERT_EQ(2u, ice_servers.size()); |
| |
| // First response doesnt have credentials. |
| ASSERT_EQ(1u, ice_servers[0]->urls.size()); |
| ASSERT_FALSE(ice_servers[0]->username); |
| ASSERT_FALSE(ice_servers[0]->credential); |
| |
| // Second response has credentials. |
| ASSERT_EQ(2u, ice_servers[1]->urls.size()); |
| ASSERT_EQ("username", ice_servers[1]->username); |
| ASSERT_EQ("credential", ice_servers[1]->credential); |
| } |
| |
| } // namespace |
| |
| class TachyonIceConfigFetcherTest : public testing::Test { |
| public: |
| TachyonIceConfigFetcherTest() |
| : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME), |
| test_shared_loader_factory_( |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory_)), |
| ice_config_fetcher_(identity_test_environment_.identity_manager(), |
| test_shared_loader_factory_) { |
| identity_test_environment_.MakePrimaryAccountAvailable( |
| kTestAccount, signin::ConsentLevel::kSignin); |
| } |
| ~TachyonIceConfigFetcherTest() override = default; |
| |
| std::string GetSuccessResponse() { |
| tachyon_proto::GetICEServerResponse response; |
| |
| auto* config = response.mutable_ice_config(); |
| config->mutable_lifetime_duration()->set_seconds(kLifetimeDurationSeconds); |
| auto* server1 = config->add_ice_servers(); |
| server1->add_urls("stun:url1"); |
| auto* server2 = config->add_ice_servers(); |
| server2->add_urls("turn:url2?transport=udp"); |
| server2->add_urls("turn:url3?transport=tcp"); |
| server2->set_username("username"); |
| server2->set_credential("credential"); |
| |
| std::string output; |
| response.SerializeToString(&output); |
| return output; |
| } |
| |
| void SetOAuthTokenSuccessful(bool success) { |
| identity_test_environment_ |
| .WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| success ? kOAuthToken : "", base::Time::Now()); |
| } |
| |
| protected: |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| signin::IdentityTestEnvironment identity_test_environment_; |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_; |
| TachyonIceConfigFetcher ice_config_fetcher_; |
| base::HistogramTester histogram_tester_; |
| }; |
| |
| TEST_F(TachyonIceConfigFetcherTest, ResponseSuccessful) { |
| base::RunLoop run_loop; |
| ice_config_fetcher_.GetIceServers(base::BindLambdaForTesting( |
| [&](std::vector<::sharing::mojom::IceServerPtr> ice_servers) { |
| CheckSuccessResponse(ice_servers); |
| run_loop.Quit(); |
| })); |
| SetOAuthTokenSuccessful(true); |
| |
| std::string response = GetSuccessResponse(); |
| |
| ASSERT_TRUE(test_url_loader_factory_.IsPending(kApiUrl, nullptr)); |
| |
| test_url_loader_factory_.AddResponse(kApiUrl, response, net::HTTP_OK); |
| run_loop.Run(); |
| |
| histogram_tester_.ExpectTotalCount(kIceConfigFetchedMetric, 1); |
| histogram_tester_.ExpectBucketCount(kIceConfigFetchedMetric, 2, 1); |
| histogram_tester_.ExpectTotalCount(kResultMetric, 1); |
| histogram_tester_.ExpectBucketCount(kResultMetric, 1, 1); |
| histogram_tester_.ExpectTotalCount(kCacheHitMetric, 1); |
| histogram_tester_.ExpectBucketCount(kCacheHitMetric, 0, 1); |
| histogram_tester_.ExpectTotalCount(kFailureReasonMetric, 0); |
| histogram_tester_.ExpectTotalCount(kTokenFetchSuccessMetric, 1); |
| histogram_tester_.ExpectBucketCount(kTokenFetchSuccessMetric, 1, 1); |
| } |
| |
| TEST_F(TachyonIceConfigFetcherTest, ResponseError) { |
| base::RunLoop run_loop; |
| ice_config_fetcher_.GetIceServers(base::BindLambdaForTesting( |
| [&](std::vector<::sharing::mojom::IceServerPtr> ice_servers) { |
| // Makes sure that we at least return default servers in case of an |
| // error. |
| EXPECT_FALSE(ice_servers.empty()); |
| run_loop.Quit(); |
| })); |
| SetOAuthTokenSuccessful(true); |
| |
| ASSERT_TRUE(test_url_loader_factory_.IsPending(kApiUrl, nullptr)); |
| |
| test_url_loader_factory_.AddResponse(kApiUrl, "", |
| net::HTTP_INTERNAL_SERVER_ERROR); |
| run_loop.Run(); |
| |
| histogram_tester_.ExpectTotalCount(kIceConfigFetchedMetric, 1); |
| histogram_tester_.ExpectBucketCount(kIceConfigFetchedMetric, 0, 1); |
| histogram_tester_.ExpectTotalCount(kResultMetric, 1); |
| histogram_tester_.ExpectBucketCount(kResultMetric, 0, 1); |
| histogram_tester_.ExpectTotalCount(kCacheHitMetric, 1); |
| histogram_tester_.ExpectBucketCount(kCacheHitMetric, 0, 1); |
| histogram_tester_.ExpectTotalCount(kFailureReasonMetric, 1); |
| histogram_tester_.ExpectBucketCount(kFailureReasonMetric, 500, 1); |
| histogram_tester_.ExpectTotalCount(kTokenFetchSuccessMetric, 1); |
| histogram_tester_.ExpectBucketCount(kTokenFetchSuccessMetric, 1, 1); |
| } |
| |
| TEST_F(TachyonIceConfigFetcherTest, OverlappingCalls) { |
| base::RunLoop run_loop; |
| int counter = 2; |
| auto callback = [&](std::vector<::sharing::mojom::IceServerPtr> ice_servers) { |
| CheckSuccessResponse(ice_servers); |
| counter -= 1; |
| if (counter == 0) { |
| run_loop.Quit(); |
| } |
| }; |
| // First call. |
| ice_config_fetcher_.GetIceServers(base::BindLambdaForTesting(callback)); |
| SetOAuthTokenSuccessful(true); |
| |
| // Second call overlaps before any responses are processed. |
| ice_config_fetcher_.GetIceServers(base::BindLambdaForTesting(callback)); |
| SetOAuthTokenSuccessful(true); |
| |
| std::string response = GetSuccessResponse(); |
| |
| ASSERT_TRUE(test_url_loader_factory_.IsPending(kApiUrl, nullptr)); |
| |
| test_url_loader_factory_.AddResponse(kApiUrl, response, net::HTTP_OK); |
| run_loop.Run(); |
| |
| histogram_tester_.ExpectTotalCount(kIceConfigFetchedMetric, 2); |
| histogram_tester_.ExpectBucketCount(kIceConfigFetchedMetric, 2, 2); |
| histogram_tester_.ExpectTotalCount(kResultMetric, 2); |
| histogram_tester_.ExpectBucketCount(kResultMetric, 1, 2); |
| histogram_tester_.ExpectTotalCount(kCacheHitMetric, 2); |
| histogram_tester_.ExpectBucketCount(kCacheHitMetric, 0, 2); |
| histogram_tester_.ExpectTotalCount(kFailureReasonMetric, 0); |
| histogram_tester_.ExpectTotalCount(kTokenFetchSuccessMetric, 2); |
| histogram_tester_.ExpectBucketCount(kTokenFetchSuccessMetric, 1, 2); |
| } |
| |
| TEST_F(TachyonIceConfigFetcherTest, IceServersCached) { |
| auto callback = [](base::RunLoop* run_loop, |
| std::vector<::sharing::mojom::IceServerPtr> ice_servers) { |
| CheckSuccessResponse(ice_servers); |
| run_loop->Quit(); |
| }; |
| std::string response = GetSuccessResponse(); |
| |
| // First call. |
| auto run_loop = std::make_unique<base::RunLoop>(); |
| ice_config_fetcher_.GetIceServers(base::BindOnce(callback, run_loop.get())); |
| SetOAuthTokenSuccessful(true); |
| ASSERT_TRUE(test_url_loader_factory_.IsPending(kApiUrl)); |
| test_url_loader_factory_.SimulateResponseForPendingRequest(kApiUrl, response); |
| |
| // Complete first call before beginning second call |
| run_loop->Run(); |
| |
| // Second call returns cached result |
| run_loop.reset(new base::RunLoop()); |
| ice_config_fetcher_.GetIceServers(base::BindOnce(callback, run_loop.get())); |
| ASSERT_FALSE(test_url_loader_factory_.IsPending(kApiUrl)); |
| run_loop->Run(); |
| |
| // Wait until the cache has expired. |
| task_environment_.FastForwardBy(base::Seconds(kLifetimeDurationSeconds + 1)); |
| |
| // Expired cache results in fetching the servers again. |
| run_loop.reset(new base::RunLoop()); |
| ice_config_fetcher_.GetIceServers(base::BindOnce(callback, run_loop.get())); |
| SetOAuthTokenSuccessful(true); |
| ASSERT_TRUE(test_url_loader_factory_.IsPending(kApiUrl)); |
| test_url_loader_factory_.SimulateResponseForPendingRequest(kApiUrl, response); |
| run_loop->Run(); |
| |
| histogram_tester_.ExpectTotalCount(kIceConfigFetchedMetric, 2); |
| histogram_tester_.ExpectBucketCount(kIceConfigFetchedMetric, 2, 2); |
| histogram_tester_.ExpectTotalCount(kResultMetric, 2); |
| histogram_tester_.ExpectBucketCount(kResultMetric, 1, 2); |
| histogram_tester_.ExpectTotalCount(kCacheHitMetric, 3); |
| histogram_tester_.ExpectBucketCount(kCacheHitMetric, 0, 2); |
| histogram_tester_.ExpectTotalCount(kFailureReasonMetric, 0); |
| histogram_tester_.ExpectTotalCount(kTokenFetchSuccessMetric, 2); |
| histogram_tester_.ExpectBucketCount(kTokenFetchSuccessMetric, 1, 2); |
| } |
| |
| TEST_F(TachyonIceConfigFetcherTest, OAuthTokenFailed) { |
| base::RunLoop run_loop; |
| ice_config_fetcher_.GetIceServers(base::BindLambdaForTesting( |
| [&](std::vector<::sharing::mojom::IceServerPtr> ice_servers) { |
| // Makes sure that we at least return default servers in case of an |
| // error. |
| EXPECT_FALSE(ice_servers.empty()); |
| run_loop.Quit(); |
| })); |
| SetOAuthTokenSuccessful(false); |
| ASSERT_EQ(0, test_url_loader_factory_.NumPending()); |
| run_loop.Run(); |
| |
| histogram_tester_.ExpectTotalCount(kIceConfigFetchedMetric, 0); |
| histogram_tester_.ExpectTotalCount(kResultMetric, 0); |
| histogram_tester_.ExpectTotalCount(kCacheHitMetric, 1); |
| histogram_tester_.ExpectBucketCount(kCacheHitMetric, 0, 1); |
| histogram_tester_.ExpectTotalCount(kFailureReasonMetric, 0); |
| histogram_tester_.ExpectTotalCount(kTokenFetchSuccessMetric, 1); |
| histogram_tester_.ExpectBucketCount(kTokenFetchSuccessMetric, 0, 1); |
| } |
| |
| TEST_F(TachyonIceConfigFetcherTest, OverlappingTokenFetch) { |
| base::RunLoop run_loop; |
| int counter = 2; |
| auto callback = [&](std::vector<::sharing::mojom::IceServerPtr> ice_servers) { |
| CheckSuccessResponse(ice_servers); |
| counter -= 1; |
| if (counter == 0) { |
| run_loop.Quit(); |
| } |
| }; |
| // First call. |
| ice_config_fetcher_.GetIceServers(base::BindLambdaForTesting(callback)); |
| |
| // Second call overlaps before the first has an OAuth token. |
| ice_config_fetcher_.GetIceServers(base::BindLambdaForTesting(callback)); |
| |
| // Return an OAuth token for both requests. |
| SetOAuthTokenSuccessful(true); |
| |
| std::string response = GetSuccessResponse(); |
| |
| ASSERT_TRUE(test_url_loader_factory_.IsPending(kApiUrl, nullptr)); |
| |
| test_url_loader_factory_.AddResponse(kApiUrl, response, net::HTTP_OK); |
| run_loop.Run(); |
| |
| histogram_tester_.ExpectTotalCount(kIceConfigFetchedMetric, 2); |
| histogram_tester_.ExpectBucketCount(kIceConfigFetchedMetric, 2, 2); |
| histogram_tester_.ExpectTotalCount(kResultMetric, 2); |
| histogram_tester_.ExpectBucketCount(kResultMetric, 1, 2); |
| histogram_tester_.ExpectTotalCount(kCacheHitMetric, 2); |
| histogram_tester_.ExpectBucketCount(kCacheHitMetric, 0, 2); |
| histogram_tester_.ExpectTotalCount(kFailureReasonMetric, 0); |
| histogram_tester_.ExpectTotalCount(kTokenFetchSuccessMetric, 2); |
| histogram_tester_.ExpectBucketCount(kTokenFetchSuccessMetric, 1, 2); |
| } |