| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/time/calendar_event_fetch.h" |
| |
| #include <memory> |
| #include <mutex> |
| #include <string> |
| #include <utility> |
| |
| #include "ash/calendar/calendar_client.h" |
| #include "ash/calendar/calendar_controller.h" |
| #include "ash/shell.h" |
| #include "ash/system/power/peripheral_battery_listener.h" |
| #include "ash/system/time/calendar_event_fetch_types.h" |
| #include "ash/system/time/calendar_unittest_utils.h" |
| #include "ash/system/time/calendar_utils.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/test_shell_delegate.h" |
| #include "base/functional/bind.h" |
| #include "base/location.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "components/account_id/account_id.h" |
| #include "google_apis/common/api_error_codes.h" |
| |
| namespace ash { |
| |
| // Subclass of CalendarClient where we control the results of a call to |
| // GetEventList(), which is what's being called downstream when a |
| // CalendarEventFetch is instantiated. |
| class TestCalendarClient : public CalendarClient { |
| public: |
| TestCalendarClient() = default; |
| TestCalendarClient(const TestCalendarClient& other) = delete; |
| TestCalendarClient& operator=(const TestCalendarClient& other) = delete; |
| ~TestCalendarClient() override = default; |
| |
| base::OnceClosure GetEventList( |
| google_apis::calendar::CalendarEventListCallback callback, |
| const base::Time& start_time, |
| const base::Time& end_time) override { |
| // Store these off. |
| start_time_ = start_time; |
| callback_ = std::move(callback); |
| |
| // By delaying the response, we make the unit tests behave a little more |
| // like a event fetch in production. Use set_response_delay() to use a |
| // value that's different from the default. |
| StartResponseDelayTimeout(); |
| return base::BindOnce(&TestCalendarClient::CancelCallback, |
| weak_factory_.GetWeakPtr()); |
| } |
| |
| void CancelCallback() { set_api_error_code(google_apis::CANCELLED); } |
| |
| void set_event_list( |
| std::unique_ptr<google_apis::calendar::EventList> event_list) { |
| event_list_ = std::move(event_list); |
| } |
| void set_api_error_code(google_apis::ApiErrorCode api_error_code) { |
| api_error_code_ = api_error_code; |
| } |
| void set_response_delay(const base::TimeDelta& delay) { |
| response_delay_ = delay; |
| } |
| const base::TimeDelta get_response_delay() { return response_delay_; } |
| |
| private: |
| void StartResponseDelayTimeout() { |
| fetch_response_timeout_.Start( |
| FROM_HERE, response_delay_, |
| base::BindRepeating(&TestCalendarClient::OnResponseDelayTimeout, |
| base::Unretained(this))); |
| } |
| |
| void OnResponseDelayTimeout() { |
| // We send back an event list, possibly empty, with every response. |
| auto requested_event_list = |
| std::make_unique<google_apis::calendar::EventList>(); |
| requested_event_list->set_time_zone("Greenwich Mean Time"); |
| |
| // If we're set to return an error. |
| if (api_error_code_ != google_apis::HTTP_SUCCESS) { |
| std::move(callback_).Run(api_error_code_, |
| std::move(requested_event_list)); |
| return; |
| } |
| |
| // If we have some events, send back any that start in the month we |
| // requested. |
| if (event_list_) { |
| for (auto& event : event_list_->items()) { |
| if (calendar_test_utils::IsTheSameMonth(event->start_time().date_time(), |
| start_time_)) { |
| requested_event_list->InjectItemForTesting( |
| calendar_test_utils::CreateEvent(event->id().c_str(), |
| event->summary().c_str(), |
| event->start_time().date_time(), |
| event->end_time().date_time())); |
| } |
| } |
| } |
| |
| std::move(callback_).Run(api_error_code_, std::move(requested_event_list)); |
| } |
| |
| google_apis::calendar::CalendarEventListCallback callback_; |
| base::Time start_time_; |
| std::unique_ptr<google_apis::calendar::EventList> event_list_; |
| google_apis::ApiErrorCode api_error_code_ = google_apis::HTTP_SUCCESS; |
| base::RetainingOneShotTimer fetch_response_timeout_; |
| base::TimeDelta response_delay_ = base::Milliseconds(100); |
| |
| base::WeakPtrFactory<TestCalendarClient> weak_factory_{this}; |
| }; |
| |
| class CalendarEventFetchTest : public NoSessionAshTestBase { |
| public: |
| CalendarEventFetchTest() |
| : NoSessionAshTestBase( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| CalendarEventFetchTest(const CalendarEventFetchTest& other) = delete; |
| CalendarEventFetchTest& operator=(const CalendarEventFetchTest& other) = |
| delete; |
| ~CalendarEventFetchTest() override = default; |
| |
| void RegisterClient() { |
| DCHECK(Shell::HasInstance()); |
| Shell::Get()->calendar_controller()->SetActiveUserAccountIdForTesting( |
| GetDefaultUserId()); |
| Shell::Get()->calendar_controller()->RegisterClientForUser( |
| GetDefaultUserId(), |
| /*client=*/&client_); |
| } |
| |
| // Actual callback invoked when an event fetch is complete. |
| void OnEventsFetched(base::Time start_of_month, |
| google_apis::ApiErrorCode error, |
| const google_apis::calendar::EventList* events) { |
| api_error_code_ = error; |
| events_fetched_count_ = 0; |
| if (events) |
| events_fetched_count_ = events->items().size(); |
| } |
| |
| // Callback invoked when an event fetch failed with an internal error. |
| void OnEventFetchFailedInternalError( |
| base::Time start_of_month, |
| CalendarEventFetchInternalErrorCode error) { |
| internal_error_code_ = error; |
| } |
| |
| base::Time GetStartOfMonthFromString(const char* str) { |
| base::Time start_of_month; |
| bool result = base::Time::FromString(str, &start_of_month); |
| DCHECK(result); |
| return start_of_month.UTCMidnight(); |
| } |
| |
| std::unique_ptr<CalendarEventFetch> PerformFetch( |
| const base::Time& start_of_month) { |
| std::unique_ptr<CalendarEventFetch> fetch = |
| std::make_unique<CalendarEventFetch>( |
| start_of_month, |
| base::BindRepeating(&CalendarEventFetchTest::OnEventsFetched, |
| base::Unretained(this)), |
| base::BindRepeating( |
| &CalendarEventFetchTest::OnEventFetchFailedInternalError, |
| base::Unretained(this)), |
| task_environment()->GetMockTickClock()); |
| return fetch; |
| } |
| |
| absl::optional<int> events_fetched_count() { return events_fetched_count_; } |
| absl::optional<google_apis::ApiErrorCode> api_error_code() { |
| return api_error_code_; |
| } |
| absl::optional<CalendarEventFetchInternalErrorCode> internal_error_code() { |
| return internal_error_code_; |
| } |
| |
| protected: |
| TestCalendarClient client_; |
| |
| private: |
| const AccountId GetDefaultUserId() { |
| return AccountId::FromUserEmail("user0@tray"); |
| } |
| |
| absl::optional<int> events_fetched_count_; |
| absl::optional<google_apis::ApiErrorCode> api_error_code_; |
| absl::optional<CalendarEventFetchInternalErrorCode> internal_error_code_; |
| |
| base::WeakPtrFactory<CalendarEventFetchTest> weak_factory_{this}; |
| }; |
| |
| TEST_F(CalendarEventFetchTest, NoEvents) { |
| // Register our TestCalendarClient with the default user. |
| RegisterClient(); |
| |
| // Month whose events we want to fetch. |
| base::Time start_of_month = |
| GetStartOfMonthFromString("23 Oct 2009 11:30 GMT"); |
| |
| // Perform the fetch. |
| std::unique_ptr<CalendarEventFetch> fetch = PerformFetch(start_of_month); |
| |
| // Advance time to when the fetch is complete. `fetch` can no longer be used |
| // after this. |
| task_environment()->FastForwardBy(client_.get_response_delay()); |
| |
| // No events were set in the client, so fetch should return no results. |
| absl::optional<int> count = events_fetched_count(); |
| EXPECT_TRUE(count.has_value() && count.value() == 0); |
| |
| // API error is HTTP_SUCCESS. |
| absl::optional<google_apis::ApiErrorCode> return_error_code = |
| api_error_code(); |
| EXPECT_TRUE(return_error_code.has_value() && |
| return_error_code == google_apis::HTTP_SUCCESS); |
| |
| // No internal error. |
| EXPECT_FALSE(internal_error_code().has_value()); |
| } |
| |
| TEST_F(CalendarEventFetchTest, HaveEvents) { |
| // Register our TestCalendarClient with the default user. |
| RegisterClient(); |
| |
| // Inject two events, in different months. |
| auto event_list = std::make_unique<google_apis::calendar::EventList>(); |
| event_list->set_time_zone("Greenwich Mean Time"); |
| event_list->InjectItemForTesting(calendar_test_utils::CreateEvent( |
| "id_0", "summary_0", "23 Oct 2009 11:30 GMT", "23 Oct 2009 12:30 GMT")); |
| event_list->InjectItemForTesting(calendar_test_utils::CreateEvent( |
| "id_1", "summary_1", "18 Nov 2021 8:15 GMT", "18 Nov 2021 11:30 GMT")); |
| client_.set_event_list(std::move(event_list)); |
| |
| // Month whose events we want to fetch, for which no events have been |
| // injected. |
| base::Time start_of_month = |
| GetStartOfMonthFromString("27 Sep 1971 11:30 GMT"); |
| |
| // Perform the fetch. |
| std::unique_ptr<CalendarEventFetch> fetch = PerformFetch(start_of_month); |
| |
| // Advance time to when the fetch is complete. `fetch` can no longer be used |
| // after this. |
| task_environment()->FastForwardBy(client_.get_response_delay()); |
| |
| // No events for this month in the client, so fetch should return no results. |
| absl::optional<int> count = events_fetched_count(); |
| EXPECT_TRUE(count.has_value() && count.value() == 0); |
| |
| // No internal error. |
| EXPECT_FALSE(internal_error_code().has_value()); |
| |
| // Now use a month that has events |
| start_of_month = GetStartOfMonthFromString("23 Oct 2009 11:30 GMT"); |
| |
| // Perform the fetch. |
| std::unique_ptr<CalendarEventFetch> fetch2 = PerformFetch(start_of_month); |
| |
| // Advance time to when the fetch is complete. `fetch2` can no longer be |
| // used after this. |
| task_environment()->FastForwardBy(client_.get_response_delay()); |
| |
| // There is one event for this month in the client, so fetch should return one |
| // event. |
| count = events_fetched_count(); |
| EXPECT_TRUE(count.has_value() && count.value() == 1); |
| |
| // API error is HTTP_SUCCESS. |
| absl::optional<google_apis::ApiErrorCode> return_error_code = |
| api_error_code(); |
| EXPECT_TRUE(return_error_code.has_value() && |
| return_error_code == google_apis::HTTP_SUCCESS); |
| |
| // No internal error. |
| EXPECT_FALSE(internal_error_code().has_value()); |
| } |
| |
| TEST_F(CalendarEventFetchTest, ApiFailure) { |
| // Specifically set up the fetch to fail with an API error. |
| const google_apis::ApiErrorCode error_code = google_apis::HTTP_NOT_FOUND; |
| client_.set_api_error_code(error_code); |
| |
| // Register our TestCalendarClient with the default user. |
| RegisterClient(); |
| |
| // Month whose events we want to fetch. |
| base::Time start_of_month = |
| GetStartOfMonthFromString("23 Oct 2009 11:30 GMT"); |
| |
| // Perform the fetch. |
| std::unique_ptr<CalendarEventFetch> fetch = PerformFetch(start_of_month); |
| |
| // Advance time to when the fetch is complete. `fetch` can no longer be used |
| // after this. |
| task_environment()->FastForwardBy(client_.get_response_delay()); |
| |
| // No events were set in the client, so fetch should return no results. |
| absl::optional<int> count = events_fetched_count(); |
| EXPECT_TRUE(count.has_value() && count.value() == 0); |
| |
| // API error is what we set. |
| absl::optional<google_apis::ApiErrorCode> return_error_code = |
| api_error_code(); |
| EXPECT_TRUE(return_error_code.has_value() && return_error_code == error_code); |
| |
| // No internal error. |
| EXPECT_FALSE(internal_error_code().has_value()); |
| } |
| |
| TEST_F(CalendarEventFetchTest, Timeout) { |
| base::HistogramTester histogram_tester; |
| |
| // No metrics recorded yet. |
| histogram_tester.ExpectBucketCount("Ash.Calendar.FetchEvents.Timeout", true, |
| /*expected_count=*/0); |
| |
| // Specifically delay the response until after CalendarEventFetch declares a |
| // timeout. |
| client_.set_response_delay(calendar_utils::kEventFetchTimeout + |
| base::Milliseconds(100)); |
| |
| // Register our TestCalendarClient with the default user. |
| RegisterClient(); |
| |
| // Month whose events we want to fetch. |
| base::Time start_of_month = |
| GetStartOfMonthFromString("23 Oct 2009 11:30 GMT"); |
| |
| // No internal error code reported. |
| absl::optional<CalendarEventFetchInternalErrorCode> internal_error = |
| internal_error_code(); |
| EXPECT_FALSE(internal_error.has_value()); |
| |
| // Perform the fetch. |
| std::unique_ptr<CalendarEventFetch> fetch = PerformFetch(start_of_month); |
| |
| // Advance time to when the fetch times out. `fetch` can no longer be used |
| // after this. |
| task_environment()->FastForwardBy(calendar_utils::kEventFetchTimeout); |
| |
| // Events should be completely nonexistent. |
| EXPECT_FALSE(events_fetched_count().has_value()); |
| |
| // API error should be completely nonexistent. |
| absl::optional<google_apis::ApiErrorCode> return_error_code = |
| api_error_code(); |
| EXPECT_FALSE(return_error_code.has_value()); |
| |
| // Internal error code reported is kTimeout. |
| internal_error = internal_error_code(); |
| EXPECT_TRUE(internal_error.has_value() && |
| internal_error == CalendarEventFetchInternalErrorCode::kTimeout); |
| |
| // Metrics now recorded. |
| histogram_tester.ExpectBucketCount("Ash.Calendar.FetchEvents.Timeout", true, |
| /*expected_count=*/1); |
| } |
| |
| TEST_F(CalendarEventFetchTest, Cancel) { |
| // Register our TestCalendarClient with the default user. |
| RegisterClient(); |
| |
| // Month whose events we want to fetch. |
| base::Time start_of_month = |
| GetStartOfMonthFromString("23 Oct 2009 11:30 GMT"); |
| |
| // Perform the fetch. |
| std::unique_ptr<CalendarEventFetch> fetch = PerformFetch(start_of_month); |
| |
| // Cancel the request. |
| fetch->Cancel(); |
| |
| // Advance time to when the fetch is complete. `fetch` can no longer be used |
| // after this. |
| task_environment()->FastForwardBy(client_.get_response_delay()); |
| |
| // API error is CANCELLED. |
| absl::optional<google_apis::ApiErrorCode> return_error_code = |
| api_error_code(); |
| EXPECT_TRUE(return_error_code.has_value() && |
| return_error_code == google_apis::CANCELLED); |
| } |
| |
| } // namespace ash |