// 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/ash/calendar/calendar_keyed_service.h"
#include <memory>
#include <vector>
#include "ash/calendar/calendar_controller.h"
#include "ash/shell.h"
#include "base/strings/strcat.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/calendar/calendar_keyed_service_factory.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/account_id/account_id.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "google_apis/calendar/calendar_api_requests.h"
#include "google_apis/common/api_error_codes.h"
#include "google_apis/common/dummy_auth_service.h"
#include "google_apis/common/test_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/test/test_shared_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash {
namespace {
const char kPrimaryProfileName[] = "primary_profile";
const char kSecondaryProfileName[] = "secondary_profile";
const char kTestGroupCalendarId[] =
"[email protected]";
const char kTestGroupCalendarColorId[] = "3";
const char kTestUserAgent[] = "test-user-agent";
} // namespace
class CalendarKeyedServiceTest : public BrowserWithTestWindowTest {
public:
CalendarKeyedServiceTest() = default;
CalendarKeyedServiceTest(const CalendarKeyedServiceTest& other) = delete;
CalendarKeyedServiceTest& operator=(const CalendarKeyedServiceTest& other) =
delete;
~CalendarKeyedServiceTest() override = default;
void SetUp() override {
ProfileHelper::SetProfileToUserForTestingEnabled(true);
BrowserWithTestWindowTest::SetUp();
}
void TearDown() override {
BrowserWithTestWindowTest::TearDown();
ProfileHelper::SetProfileToUserForTestingEnabled(false);
}
std::string GetDefaultProfileName() override { return kPrimaryProfileName; }
TestingProfile* CreateSecondaryProfile() {
LogIn(kSecondaryProfileName);
return CreateProfile(kSecondaryProfileName);
}
};
class CalendarKeyedServiceIOTest : public testing::Test {
public:
CalendarKeyedServiceIOTest()
: test_shared_loader_factory_(
base::MakeRefCounted<network::TestSharedURLLoaderFactory>(
/*network_service=*/nullptr,
/*is_trusted=*/true)) {}
CalendarKeyedServiceIOTest(const CalendarKeyedServiceIOTest& other) = delete;
CalendarKeyedServiceIOTest& operator=(
const CalendarKeyedServiceIOTest& other) = delete;
~CalendarKeyedServiceIOTest() override = default;
void SetUp() override {
request_sender_ = std::make_unique<google_apis::RequestSender>(
std::make_unique<google_apis::DummyAuthService>(),
test_shared_loader_factory_,
task_environment_.GetMainThreadTaskRunner(), kTestUserAgent,
TRAFFIC_ANNOTATION_FOR_TESTS);
test_server_.RegisterRequestHandler(base::BindRepeating(
&CalendarKeyedServiceIOTest::HandleRequest, base::Unretained(this)));
ASSERT_TRUE(test_server_.Start());
}
void TearDown() override {
// Deleting the sender here will delete all request objects.
request_sender_.reset();
// Wait for any DeleteSoon tasks to run.
task_environment_.RunUntilIdle();
}
// Returns the response from a mock json file in google_apis/test/data/
// depending on whether the request is for the calendar list or the event
// list.
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request) {
if (net::test_server::ShouldHandle(request,
"/calendar/v3/users/me/calendarList")) {
return google_apis::test_util::CreateHttpResponseFromFile(
google_apis::test_util::GetTestFilePath(
"calendar/calendar_list.json"));
}
if (net::test_server::ShouldHandle(request,
"/calendar/v3/calendars/primary")) {
return google_apis::test_util::CreateHttpResponseFromFile(
google_apis::test_util::GetTestFilePath("calendar/events.json"));
}
if (net::test_server::ShouldHandle(
request,
base::StrCat({"/calendar/v3/calendars/", kTestGroupCalendarId}))) {
return google_apis::test_util::CreateHttpResponseFromFile(
google_apis::test_util::GetTestFilePath(
"calendar/group_calendar_events.json"));
}
NOTREACHED();
}
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::MainThreadType::IO};
net::EmbeddedTestServer test_server_;
std::unique_ptr<google_apis::RequestSender> request_sender_;
scoped_refptr<network::TestSharedURLLoaderFactory>
test_shared_loader_factory_;
};
// Calendar service does not support guest user.
TEST_F(CalendarKeyedServiceTest, GuestUserProfile) {
// Construct a guest session profile.
TestingProfile::Builder guest_profile_builder;
guest_profile_builder.SetGuestSession();
guest_profile_builder.SetProfileName("guest_profile");
guest_profile_builder.AddTestingFactories({});
std::unique_ptr<TestingProfile> guest_profile = guest_profile_builder.Build();
// Profile should be created for guest sessions.
ASSERT_TRUE(guest_profile);
CalendarKeyedService* const guest_profile_service =
CalendarKeyedServiceFactory::GetInstance()->GetService(
// Use OTR profile for guest.
guest_profile->GetPrimaryOTRProfile(/*create_if_needed=*/true));
EXPECT_FALSE(guest_profile_service);
}
// Calendar service does not support incognito user.
TEST_F(CalendarKeyedServiceTest, OffTheRecordProfile) {
// Service instances should be created for on the record profiles.
CalendarKeyedService* const primary_profile_service =
CalendarKeyedServiceFactory::GetInstance()->GetService(GetProfile());
EXPECT_TRUE(primary_profile_service);
// Construct an incognito profile from the primary profile.
TestingProfile::Builder incognito_primary_profile_builder;
incognito_primary_profile_builder.SetProfileName(
GetProfile()->GetProfileUserName());
Profile* const incognito_primary_profile =
incognito_primary_profile_builder.BuildIncognito(GetProfile());
ASSERT_TRUE(incognito_primary_profile);
EXPECT_TRUE(incognito_primary_profile->IsOffTheRecord());
// Service instances should *not* typically be created for OTR profiles.
CalendarKeyedService* const incognito_primary_profile_service =
CalendarKeyedServiceFactory::GetInstance()->GetService(
incognito_primary_profile);
EXPECT_FALSE(incognito_primary_profile_service);
}
// Calendar service should support switching users.
TEST_F(CalendarKeyedServiceTest, SecondaryUserProfile) {
CalendarKeyedService* const primary_calendar_service =
CalendarKeyedServiceFactory::GetInstance()->GetService(GetProfile());
TestingProfile* const second_profile = CreateSecondaryProfile();
CalendarKeyedService* const secondary_calendar_service =
CalendarKeyedServiceFactory::GetInstance()->GetService(second_profile);
// Just creating a secondary profile shouldn't change the active client.
EXPECT_EQ(ash::Shell::Get()->calendar_controller()->GetClient(),
primary_calendar_service->client());
// Switching the active user should change the active client (multi-user
// support).
SwitchActiveUser(kSecondaryProfileName);
EXPECT_EQ(ash::Shell::Get()->calendar_controller()->GetClient(),
secondary_calendar_service->client());
}
TEST_F(CalendarKeyedServiceIOTest, GetCalendarList) {
// Creating the service with a test profile and account ID.
std::unique_ptr<TestingProfile> profile = std::make_unique<TestingProfile>();
auto calendar_service = std::make_unique<CalendarKeyedService>(
profile.get(), AccountId::FromUserEmail("[email protected]"));
calendar_service->set_sender_for_testing(std::move(request_sender_));
calendar_service->SetUrlForTesting(test_server_.base_url().spec());
// The error code should be overwritten by `HTTP_SUCCESS` after the
// `GetCalendarList` call.
google_apis::ApiErrorCode error = google_apis::OTHER_ERROR;
// Declaring the mock 'GetCalendarList' result.
std::unique_ptr<google_apis::calendar::CalendarList> calendar_list;
{
base::RunLoop run_loop;
calendar_service->GetCalendarList(
google_apis::test_util::CreateQuitCallback(
&run_loop, google_apis::test_util::CreateCopyResultCallback(
&error, &calendar_list)));
run_loop.Run();
}
// Verify that the result has expected values.
EXPECT_EQ(google_apis::HTTP_SUCCESS, error);
EXPECT_EQ("calendar#calendarList", calendar_list->kind());
EXPECT_EQ(3U, calendar_list->items().size());
const google_apis::calendar::SingleCalendar& calendar =
*calendar_list->items()[1];
EXPECT_EQ(calendar.id(),
"google.com_zu35dc5syt5k0fddetqqfggb75test@"
"group.calendar.google.com");
EXPECT_FALSE(calendar.selected());
}
TEST_F(CalendarKeyedServiceIOTest, GetEventListForDefaultCalendar) {
// Creating the service with some testing profile and account id. Since in
// this test we are using the IO thread, the service can not be created from
// the factory.
std::unique_ptr<TestingProfile> profile = std::make_unique<TestingProfile>();
auto calendar_service = std::make_unique<CalendarKeyedService>(
profile.get(), AccountId::FromUserEmail("[email protected]"));
calendar_service->set_sender_for_testing(std::move(request_sender_));
calendar_service->SetUrlForTesting(test_server_.base_url().spec());
// The error code should be overwritten by `HTTP_SUCCESS` after the
// `GetEventList` call.
google_apis::ApiErrorCode error = google_apis::OTHER_ERROR;
// This events should be the mock response after the `GetEventList` call.
std::unique_ptr<google_apis::calendar::EventList> events;
base::Time start;
base::Time end;
ASSERT_TRUE(base::Time::FromString("13 Jun 2021 10:00 GMT", &start));
ASSERT_TRUE(base::Time::FromString("16 Jun 2021 10:00 GMT", &end));
{
base::RunLoop run_loop;
calendar_service->GetEventList(
google_apis::test_util::CreateQuitCallback(
&run_loop,
google_apis::test_util::CreateCopyResultCallback(&error, &events)),
start, end);
run_loop.Run();
}
EXPECT_EQ(google_apis::HTTP_SUCCESS, error);
const google_apis::calendar::CalendarEvent& event = *events->items()[0];
EXPECT_EQ(event.summary(), "Mobile weekly team meeting ");
EXPECT_EQ(event.id(), "or8221sirt4ogftest");
EXPECT_EQ(events->time_zone(), "America/Los_Angeles");
}
TEST_F(CalendarKeyedServiceIOTest, GetEventListForNonDefaultCalendar) {
// Creating the service with a test profile and account ID.
std::unique_ptr<TestingProfile> profile = std::make_unique<TestingProfile>();
auto calendar_service = std::make_unique<CalendarKeyedService>(
profile.get(), AccountId::FromUserEmail("[email protected]"));
calendar_service->set_sender_for_testing(std::move(request_sender_));
calendar_service->SetUrlForTesting(test_server_.base_url().spec());
// The error code should be overwritten by `HTTP_SUCCESS` after the
// `GetEventList` call.
google_apis::ApiErrorCode error = google_apis::OTHER_ERROR;
// Declaring the mock 'GetEventList' result.
std::unique_ptr<google_apis::calendar::EventList> events;
base::Time start;
base::Time end;
ASSERT_TRUE(base::Time::FromString("19 Jan 2024 5:00 GMT", &start));
ASSERT_TRUE(base::Time::FromString("01 Feb 2024 5:00 GMT", &end));
{
base::RunLoop run_loop;
calendar_service->GetEventList(
google_apis::test_util::CreateQuitCallback(
&run_loop,
google_apis::test_util::CreateCopyResultCallback(&error, &events)),
start, end, /*calendar_id=*/kTestGroupCalendarId,
/*calendar_color_id=*/kTestGroupCalendarColorId);
run_loop.Run();
}
EXPECT_EQ(google_apis::HTTP_SUCCESS, error);
const google_apis::calendar::CalendarEvent& event = *events->items()[1];
// Verify that a returned event matches one at the same position on the mock
// group calendar events list.
EXPECT_EQ(event.summary(), "Strawberry Refreshers @ 4th Floor Cafe");
EXPECT_EQ(event.id(), "z27t6aqhloxm19wpwcrk7gyycy");
// Verify that the event color ID has not been injected with the value passed
// in for calendar_color_id because the field was already populated.
EXPECT_EQ(event.color_id(), "7");
const google_apis::calendar::CalendarEvent& event2 = *events->items()[2];
// Verify that a returned event matches one at the same position on the mock
// group calendar events list.
EXPECT_EQ(event2.summary(), "Popcorn Pop-Up");
EXPECT_EQ(event2.id(), "kff9ghr5gt8fhhechomqnld9et");
// Verify that the event color ID is now equal to the value passed into
// calendar_color_id (prepended by a marker).
EXPECT_EQ(event2.color_id(), google_apis::calendar::kInjectedColorIdPrefix +
kTestGroupCalendarColorId);
}
} // namespace ash