| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "lens_overlay_gen204_controller.h" |
| |
| #include "base/base64url.h" |
| #include "base/containers/span.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "chrome/browser/lens/core/mojom/lens.mojom-shared.h" |
| #include "chrome/browser/lens/core/mojom/lens.mojom.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/base32/base32.h" |
| #include "components/lens/lens_overlay_invocation_source.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "net/base/url_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace lens { |
| |
| using LatencyType = LensOverlayGen204Controller::LatencyType; |
| |
| // The gen204 id for testing. |
| constexpr uint64_t kGen204Id = 0; |
| |
| // The test latency. |
| constexpr base::TimeDelta kRequestLatency = base::Milliseconds(100); |
| constexpr base::TimeDelta kClusterInfoLatency = base::Milliseconds(200); |
| |
| // The test invocation source. |
| const lens::LensOverlayInvocationSource kInvocationSource = |
| lens::LensOverlayInvocationSource::kAppMenu; |
| |
| // The test encoded analytics id. |
| constexpr char kEncodedAnalyticsId[] = "test"; |
| |
| // Query parameter keys. |
| constexpr char kEncodedAnalyticsIdParameter[] = "cad"; |
| constexpr char kEncodedRequestIdParameter[] = "vsrid"; |
| constexpr char kGen204IdentifierQueryParameter[] = "plla"; |
| constexpr char kSemanticEventTimestampParameter[] = "zx"; |
| constexpr char kLatencyRequestTypeQueryParameter[] = "rt"; |
| constexpr char kVisualInputTypeQueryParameter[] = "vit"; |
| constexpr char kUserActionParameter[] = "uact"; |
| // Event id param used for both semantic events and task completions. |
| constexpr char kEventIdParameter[] = "rcid"; |
| |
| // Task completion ids. |
| constexpr int kCopyAsImageTaskCompletionID = 233325; |
| constexpr int kCopyTextTaskCompletionID = 198153; |
| constexpr int kSaveAsImageTaskCompletionID = 233326; |
| constexpr int kSelectTextTaskCompletionID = 198157; |
| constexpr int kTranslateTaskCompletionID = 198158; |
| |
| // Semantic event ids. |
| constexpr int kTextGleamsViewStartSemanticEventID = 234181; |
| constexpr int kTextGleamsViewEndSemanticEventID = 234180; |
| |
| // Creates a dummy request id for testing. |
| lens::LensOverlayRequestId CreateRequestId() { |
| lens::LensOverlayRequestId request_id; |
| request_id.set_image_sequence_id(10); |
| request_id.set_sequence_id(15); |
| return request_id; |
| } |
| |
| std::string EncodeRequestId(const lens::LensOverlayRequestId& request_id) { |
| std::string serialized_request_id; |
| CHECK(request_id.SerializeToString(&serialized_request_id)); |
| std::string encoded_request_id; |
| base::Base64UrlEncode(serialized_request_id, |
| base::Base64UrlEncodePolicy::OMIT_PADDING, |
| &encoded_request_id); |
| return encoded_request_id; |
| } |
| |
| class LensOverlayGen204ControllerMock : public LensOverlayGen204Controller { |
| public: |
| LensOverlayGen204ControllerMock() = default; |
| ~LensOverlayGen204ControllerMock() override = default; |
| |
| int num_gen204s_sent_ = 0; |
| |
| // The last gen204 url sent. |
| GURL last_url_sent_; |
| |
| protected: |
| void CheckMetricsConsentAndIssueGen204NetworkRequest(GURL url) override { |
| num_gen204s_sent_++; |
| last_url_sent_ = url; |
| } |
| }; |
| |
| class LensOverlayGen204ControllerTest : public testing::Test { |
| public: |
| std::optional<lens::mojom::UserAction> GetTaskCompletionIdFromUrl(GURL url) { |
| std::string event_id_param; |
| EXPECT_TRUE( |
| net::GetValueForKeyInQuery(url, kEventIdParameter, &event_id_param)); |
| int event_id; |
| base::StringToInt(event_id_param, &event_id); |
| switch (event_id) { |
| case kCopyAsImageTaskCompletionID: |
| return std::make_optional<lens::mojom::UserAction>( |
| lens::mojom::UserAction::kCopyAsImage); |
| case kCopyTextTaskCompletionID: |
| return std::make_optional<lens::mojom::UserAction>( |
| lens::mojom::UserAction::kCopyText); |
| case kSaveAsImageTaskCompletionID: |
| return std::make_optional<lens::mojom::UserAction>( |
| lens::mojom::UserAction::kSaveAsImage); |
| case kSelectTextTaskCompletionID: |
| return std::make_optional<lens::mojom::UserAction>( |
| lens::mojom::UserAction::kTextSelection); |
| case kTranslateTaskCompletionID: |
| return std::make_optional<lens::mojom::UserAction>( |
| lens::mojom::UserAction::kTranslateText); |
| default: |
| return std::nullopt; |
| } |
| } |
| |
| std::optional<lens::mojom::SemanticEvent> GetSemanticEventFromUrl(GURL url) { |
| std::string event_id_param; |
| EXPECT_TRUE( |
| net::GetValueForKeyInQuery(url, kEventIdParameter, &event_id_param)); |
| int event_id; |
| base::StringToInt(event_id_param, &event_id); |
| switch (event_id) { |
| case kTextGleamsViewStartSemanticEventID: |
| return std::make_optional<lens::mojom::SemanticEvent>( |
| lens::mojom::SemanticEvent::kTextGleamsViewStart); |
| case kTextGleamsViewEndSemanticEventID: |
| return std::make_optional<lens::mojom::SemanticEvent>( |
| lens::mojom::SemanticEvent::kTextGleamsViewEnd); |
| default: |
| return std::nullopt; |
| } |
| } |
| |
| protected: |
| content::BrowserTaskEnvironment task_environment_; |
| std::unique_ptr<TestingProfile> profile_; |
| |
| TestingProfile* profile() { return profile_.get(); } |
| |
| private: |
| void SetUp() override { |
| TestingProfile::Builder profile_builder; |
| profile_builder.AddTestingFactory( |
| TemplateURLServiceFactory::GetInstance(), |
| base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor)); |
| profile_ = profile_builder.Build(); |
| } |
| }; |
| |
| TEST_F(LensOverlayGen204ControllerTest, |
| LatencyGen204IfEnabled_AttachesAllParams) { |
| auto gen204_controller = std::make_unique<LensOverlayGen204ControllerMock>(); |
| gen204_controller->OnQueryFlowStart(kInvocationSource, profile(), kGen204Id); |
| gen204_controller->SendLatencyGen204IfEnabled( |
| LatencyType::kFullPageObjectsRequestFetchLatency, kRequestLatency, |
| /*vit_query_param_value=*/"image", |
| /*cluster_info_latency=*/std::nullopt, |
| /*encoded_analytics_id=*/std::nullopt, |
| /*request_id=*/std::nullopt); |
| |
| auto url = gen204_controller->last_url_sent_; |
| |
| // Check that the gen204 id param is present. |
| std::string gen204_id_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(url, kGen204IdentifierQueryParameter, |
| &gen204_id_param)); |
| |
| // Check that the request type param is present and contains the latency. |
| std::string request_type_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(url, kLatencyRequestTypeQueryParameter, |
| &request_type_param)); |
| ASSERT_EQ(request_type_param, "fpof.100"); |
| |
| // Check that the visual input type param is present. |
| std::string visual_input_type_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(url, kVisualInputTypeQueryParameter, |
| &visual_input_type_param)); |
| ASSERT_EQ(visual_input_type_param, "image"); |
| |
| ASSERT_EQ(gen204_controller->num_gen204s_sent_, 1); |
| |
| // Send a translate query. |
| gen204_controller->SendLatencyGen204IfEnabled( |
| LatencyType::kFullPageTranslateRequestFetchLatency, kRequestLatency, |
| /*vit_query_param_value=*/"pdf", |
| /*cluster_info_latency=*/std::nullopt, |
| /*encoded_analytics_id=*/std::nullopt, |
| /*request_id=*/std::nullopt); |
| |
| // Check that the new request type param is present and contains the latency. |
| EXPECT_TRUE(net::GetValueForKeyInQuery(gen204_controller->last_url_sent_, |
| kLatencyRequestTypeQueryParameter, |
| &request_type_param)); |
| ASSERT_EQ(request_type_param, "fptf.100"); |
| |
| // Check that the visual input type param is present. |
| EXPECT_TRUE(net::GetValueForKeyInQuery(gen204_controller->last_url_sent_, |
| kVisualInputTypeQueryParameter, |
| &visual_input_type_param)); |
| ASSERT_EQ(visual_input_type_param, "pdf"); |
| |
| ASSERT_EQ(gen204_controller->num_gen204s_sent_, 2); |
| |
| // Send an objects query with cluster info. |
| gen204_controller->SendLatencyGen204IfEnabled( |
| LatencyType::kFullPageObjectsRequestFetchLatency, kRequestLatency, |
| /*vit_query_param_value=*/"wp", |
| std::make_optional<base::TimeDelta>(kClusterInfoLatency), |
| /*encoded_analytics_id=*/std::nullopt, |
| /*request_id=*/std::nullopt); |
| |
| // Check that the new request type param is present and contains the latency. |
| EXPECT_TRUE(net::GetValueForKeyInQuery(gen204_controller->last_url_sent_, |
| kLatencyRequestTypeQueryParameter, |
| &request_type_param)); |
| ASSERT_EQ(request_type_param, "fpof.100,sctr.200"); |
| |
| // Check that the visual input type param is present. |
| EXPECT_TRUE(net::GetValueForKeyInQuery(gen204_controller->last_url_sent_, |
| kVisualInputTypeQueryParameter, |
| &visual_input_type_param)); |
| ASSERT_EQ(visual_input_type_param, "wp"); |
| |
| ASSERT_EQ(gen204_controller->num_gen204s_sent_, 3); |
| |
| // Send an objects query with an encoded analytics id. |
| gen204_controller->SendLatencyGen204IfEnabled( |
| LatencyType::kFullPageObjectsRequestFetchLatency, kRequestLatency, |
| /*vit_query_param_value=*/"wp", |
| std::make_optional<base::TimeDelta>(kClusterInfoLatency), |
| std::make_optional<std::string>(kEncodedAnalyticsId), |
| /*request_id=*/std::nullopt); |
| |
| // Check that the encoded analytics id param is present. |
| std::string encoded_analytics_id_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(gen204_controller->last_url_sent_, |
| kEncodedAnalyticsIdParameter, |
| &encoded_analytics_id_param)); |
| ASSERT_EQ(encoded_analytics_id_param, kEncodedAnalyticsId); |
| |
| ASSERT_EQ(gen204_controller->num_gen204s_sent_, 4); |
| |
| // Send an objects query with a request id. |
| gen204_controller->SendLatencyGen204IfEnabled( |
| LatencyType::kFullPageObjectsRequestFetchLatency, kRequestLatency, |
| /*vit_query_param_value=*/"wp", |
| std::make_optional<base::TimeDelta>(kClusterInfoLatency), |
| std::make_optional<std::string>(kEncodedAnalyticsId), |
| std::make_optional<lens::LensOverlayRequestId>(CreateRequestId())); |
| |
| // Check that the encoded request id param is present. |
| std::string encoded_request_id_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(gen204_controller->last_url_sent_, |
| kEncodedRequestIdParameter, |
| &encoded_request_id_param)); |
| ASSERT_EQ(encoded_request_id_param, EncodeRequestId(CreateRequestId())); |
| |
| ASSERT_EQ(gen204_controller->num_gen204s_sent_, 5); |
| } |
| |
| TEST_F(LensOverlayGen204ControllerTest, |
| TaskCompletionGen204IfEnabled_AttachesAllParams) { |
| auto gen204_controller = std::make_unique<LensOverlayGen204ControllerMock>(); |
| gen204_controller->OnQueryFlowStart(kInvocationSource, profile(), kGen204Id); |
| gen204_controller->SendTaskCompletionGen204IfEnabled( |
| kEncodedAnalyticsId, lens::mojom::UserAction::kCopyText, |
| CreateRequestId()); |
| |
| auto url = gen204_controller->last_url_sent_; |
| EXPECT_THAT(GetTaskCompletionIdFromUrl(url), |
| testing::Optional(lens::mojom::UserAction::kCopyText)); |
| |
| // Check for the uact param. |
| std::string uact_param; |
| EXPECT_TRUE( |
| net::GetValueForKeyInQuery(url, kUserActionParameter, &uact_param)); |
| ASSERT_EQ(uact_param, "4"); |
| |
| // Check that the gen204 id param is present. |
| std::string gen204_id_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(url, kGen204IdentifierQueryParameter, |
| &gen204_id_param)); |
| |
| // Check that the encoded analytics id param is correct. |
| std::string encoded_analytics_id_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(url, kEncodedAnalyticsIdParameter, |
| &encoded_analytics_id_param)); |
| EXPECT_EQ(encoded_analytics_id_param, kEncodedAnalyticsId); |
| |
| // Check that the encoded request id param is correct. |
| std::string encoded_request_id_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(url, kEncodedRequestIdParameter, |
| &encoded_request_id_param)); |
| EXPECT_EQ(encoded_request_id_param, EncodeRequestId(CreateRequestId())); |
| |
| ASSERT_EQ(gen204_controller->num_gen204s_sent_, 1); |
| } |
| |
| TEST_F(LensOverlayGen204ControllerTest, |
| TaskCompletionGen204IfEnabled_AttachesCorrectEventId) { |
| auto gen204_controller = std::make_unique<LensOverlayGen204ControllerMock>(); |
| gen204_controller->OnQueryFlowStart(kInvocationSource, profile(), kGen204Id); |
| |
| gen204_controller->SendTaskCompletionGen204IfEnabled( |
| kEncodedAnalyticsId, lens::mojom::UserAction::kCopyText, |
| CreateRequestId()); |
| EXPECT_THAT(GetTaskCompletionIdFromUrl(gen204_controller->last_url_sent_), |
| testing::Optional(lens::mojom::UserAction::kCopyText)); |
| |
| gen204_controller->SendTaskCompletionGen204IfEnabled( |
| kEncodedAnalyticsId, lens::mojom::UserAction::kTranslateText, |
| CreateRequestId()); |
| EXPECT_THAT(GetTaskCompletionIdFromUrl(gen204_controller->last_url_sent_), |
| testing::Optional(lens::mojom::UserAction::kTranslateText)); |
| |
| gen204_controller->SendTaskCompletionGen204IfEnabled( |
| kEncodedAnalyticsId, lens::mojom::UserAction::kCopyAsImage, |
| CreateRequestId()); |
| EXPECT_THAT(GetTaskCompletionIdFromUrl(gen204_controller->last_url_sent_), |
| testing::Optional(lens::mojom::UserAction::kCopyAsImage)); |
| |
| gen204_controller->SendTaskCompletionGen204IfEnabled( |
| kEncodedAnalyticsId, lens::mojom::UserAction::kSaveAsImage, |
| CreateRequestId()); |
| EXPECT_THAT(GetTaskCompletionIdFromUrl(gen204_controller->last_url_sent_), |
| testing::Optional(lens::mojom::UserAction::kSaveAsImage)); |
| |
| gen204_controller->SendTaskCompletionGen204IfEnabled( |
| kEncodedAnalyticsId, lens::mojom::UserAction::kTextSelection, |
| CreateRequestId()); |
| EXPECT_THAT(GetTaskCompletionIdFromUrl(gen204_controller->last_url_sent_), |
| testing::Optional(lens::mojom::UserAction::kTextSelection)); |
| |
| // Check that the encoded request id param is present and correct. |
| std::string encoded_request_id_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(gen204_controller->last_url_sent_, |
| kEncodedRequestIdParameter, |
| &encoded_request_id_param)); |
| EXPECT_EQ(encoded_request_id_param, EncodeRequestId(CreateRequestId())); |
| } |
| |
| TEST_F(LensOverlayGen204ControllerTest, |
| SemanticEventGen204IfEnabled_OnQueryFlowEndSendsTextEndEvent) { |
| auto gen204_controller = std::make_unique<LensOverlayGen204ControllerMock>(); |
| gen204_controller->OnQueryFlowStart(kInvocationSource, profile(), kGen204Id); |
| gen204_controller->SendSemanticEventGen204IfEnabled( |
| lens::mojom::SemanticEvent::kTextGleamsViewStart, |
| /*request_id=*/std::nullopt); |
| |
| EXPECT_THAT( |
| GetSemanticEventFromUrl(gen204_controller->last_url_sent_), |
| testing::Optional(lens::mojom::SemanticEvent::kTextGleamsViewStart)); |
| ASSERT_EQ(gen204_controller->num_gen204s_sent_, 1); |
| |
| gen204_controller->OnQueryFlowEnd(); |
| |
| // The query flow ending should cause another gen204 event to fire. |
| EXPECT_THAT( |
| GetSemanticEventFromUrl(gen204_controller->last_url_sent_), |
| testing::Optional(lens::mojom::SemanticEvent::kTextGleamsViewEnd)); |
| ASSERT_EQ(gen204_controller->num_gen204s_sent_, 2); |
| } |
| |
| TEST_F(LensOverlayGen204ControllerTest, |
| SemanticEventGen204IfEnabled_AttachesAllParams) { |
| auto gen204_controller = std::make_unique<LensOverlayGen204ControllerMock>(); |
| gen204_controller->OnQueryFlowStart(kInvocationSource, profile(), kGen204Id); |
| gen204_controller->SendSemanticEventGen204IfEnabled( |
| lens::mojom::SemanticEvent::kTextGleamsViewStart, CreateRequestId()); |
| |
| auto url = gen204_controller->last_url_sent_; |
| EXPECT_THAT( |
| GetSemanticEventFromUrl(url), |
| testing::Optional(lens::mojom::SemanticEvent::kTextGleamsViewStart)); |
| |
| // Check for the uact param. |
| std::string uact_param; |
| EXPECT_TRUE( |
| net::GetValueForKeyInQuery(url, kUserActionParameter, &uact_param)); |
| ASSERT_EQ(uact_param, "1"); |
| |
| // Check that the timestamp param is present. |
| std::string timestamp_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(url, kSemanticEventTimestampParameter, |
| ×tamp_param)); |
| |
| // Check that the gen204 id param is present. |
| std::string gen204_id_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(url, kGen204IdentifierQueryParameter, |
| &gen204_id_param)); |
| |
| // Check that the encoded request id param is present and correct. |
| std::string encoded_request_id_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(url, kEncodedRequestIdParameter, |
| &encoded_request_id_param)); |
| EXPECT_EQ(encoded_request_id_param, EncodeRequestId(CreateRequestId())); |
| |
| ASSERT_EQ(gen204_controller->num_gen204s_sent_, 1); |
| } |
| |
| TEST_F(LensOverlayGen204ControllerTest, |
| SemanticEventGen204IfEnabled_AttachesCorrectEventId) { |
| auto gen204_controller = std::make_unique<LensOverlayGen204ControllerMock>(); |
| gen204_controller->OnQueryFlowStart(kInvocationSource, profile(), kGen204Id); |
| |
| // Send a text gleams view start event with an encoded request id. |
| gen204_controller->SendSemanticEventGen204IfEnabled( |
| lens::mojom::SemanticEvent::kTextGleamsViewStart, |
| std::make_optional<lens::LensOverlayRequestId>(CreateRequestId())); |
| EXPECT_THAT( |
| GetSemanticEventFromUrl(gen204_controller->last_url_sent_), |
| testing::Optional(lens::mojom::SemanticEvent::kTextGleamsViewStart)); |
| |
| // Check that the encoded request id param is present and correct. |
| std::string encoded_request_id_param; |
| EXPECT_TRUE(net::GetValueForKeyInQuery(gen204_controller->last_url_sent_, |
| kEncodedRequestIdParameter, |
| &encoded_request_id_param)); |
| EXPECT_EQ(encoded_request_id_param, EncodeRequestId(CreateRequestId())); |
| |
| // Send a text gleams view end event without an encoded request id. |
| gen204_controller->SendSemanticEventGen204IfEnabled( |
| lens::mojom::SemanticEvent::kTextGleamsViewEnd, |
| /*request_id=*/std::nullopt); |
| EXPECT_THAT( |
| GetSemanticEventFromUrl(gen204_controller->last_url_sent_), |
| testing::Optional(lens::mojom::SemanticEvent::kTextGleamsViewEnd)); |
| |
| // Check that the encoded request id param is not present. |
| EXPECT_FALSE(net::GetValueForKeyInQuery(gen204_controller->last_url_sent_, |
| kEncodedRequestIdParameter, |
| &encoded_request_id_param)); |
| } |
| |
| } // namespace lens |