chromium/ios/chrome/browser/crash_report/model/breadcrumbs/breadcrumb_manager_browser_agent_unittest.mm

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/crash_report/model/breadcrumbs/breadcrumb_manager_browser_agent.h"

#import "base/containers/circular_deque.h"
#import "base/containers/contains.h"
#import "base/functional/bind.h"
#import "components/breadcrumbs/core/breadcrumb_manager.h"
#import "ios/chrome/browser/crash_report/model/breadcrumbs/breadcrumb_manager_tab_helper.h"
#import "ios/chrome/browser/download/model/confirm_download_replacing_overlay.h"
#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
#import "ios/chrome/browser/overlays/model/public/overlay_request.h"
#import "ios/chrome/browser/overlays/model/public/overlay_request_queue.h"
#import "ios/chrome/browser/overlays/model/public/web_content_area/app_launcher_overlay.h"
#import "ios/chrome/browser/overlays/model/public/web_content_area/http_auth_overlay.h"
#import "ios/chrome/browser/overlays/model/public/web_content_area/java_script_alert_dialog_overlay.h"
#import "ios/chrome/browser/overlays/model/public/web_content_area/java_script_confirm_dialog_overlay.h"
#import "ios/chrome/browser/overlays/model/public/web_content_area/java_script_prompt_dialog_overlay.h"
#import "ios/chrome/browser/overlays/model/test/fake_overlay_presentation_context.h"
#import "ios/chrome/browser/shared/model/browser/test/test_browser.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_opener.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "ios/web/public/web_state.h"
#import "testing/platform_test.h"

namespace {

// Creates test state, inserts it into WebState list and activates.
void InsertWebState(Browser* browser) {
  auto web_state = std::make_unique<web::FakeWebState>();
  InfoBarManagerImpl::CreateForWebState(web_state.get());
  BreadcrumbManagerTabHelper::CreateForWebState(web_state.get());
  browser->GetWebStateList()->InsertWebState(
      std::move(web_state),
      WebStateList::InsertionParams::Automatic().Activate());
}

const base::circular_deque<std::string>& GetEvents() {
  return breadcrumbs::BreadcrumbManager::GetInstance().GetEvents();
}

}  // namespace

// Test fixture for testing BreadcrumbManagerBrowserAgent class.
class BreadcrumbManagerBrowserAgentTest : public PlatformTest {
 protected:
  BreadcrumbManagerBrowserAgentTest() {
    TestChromeBrowserState::Builder test_cbs_builder;
    browser_state_ = std::move(test_cbs_builder).Build();
    browser_ = std::make_unique<TestBrowser>(browser_state_.get());

    OverlayPresenter::FromBrowser(browser_.get(),
                                  OverlayModality::kWebContentArea)
        ->SetPresentationContext(&presentation_context_);
  }

  ~BreadcrumbManagerBrowserAgentTest() override { browser_.reset(); }

  web::WebTaskEnvironment task_env_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  std::unique_ptr<Browser> browser_;
  FakeOverlayPresentationContext presentation_context_;
};

// Tests that an event logged by the BrowserAgent is returned with events for
// the associated `browser_state_`.
TEST_F(BreadcrumbManagerBrowserAgentTest, LogEvent) {
  ASSERT_EQ(0u, GetEvents().size());

  BreadcrumbManagerBrowserAgent::CreateForBrowser(browser_.get());

  InsertWebState(browser_.get());

  EXPECT_EQ(1u, GetEvents().size());
}

// Tests that events logged through BrowserAgents associated with different
// Browser instances are returned with events for the associated
// `browser_state_` and are uniquely identifiable.
TEST_F(BreadcrumbManagerBrowserAgentTest, MultipleBrowsers) {
  ASSERT_EQ(0u, GetEvents().size());

  BreadcrumbManagerBrowserAgent::CreateForBrowser(browser_.get());

  // Insert WebState into `browser`.
  InsertWebState(browser_.get());

  // Create and setup second Browser.
  std::unique_ptr<Browser> browser2 =
      std::make_unique<TestBrowser>(browser_state_.get());
  BreadcrumbManagerBrowserAgent::CreateForBrowser(browser2.get());

  // Insert WebState into `browser2`.
  InsertWebState(browser2.get());

  const auto& events = GetEvents();
  EXPECT_EQ(2u, events.size());

  // Seperately compare the start and end of the event strings to ensure
  // uniqueness at both the Browser and WebState layer.
  std::size_t browser1_split_pos = events.front().find("Insert");
  std::size_t browser2_split_pos = events.back().find("Insert");
  // The start of the string must be unique to differentiate the associated
  // Browser object by including the BreadcrumbManagerBrowserAgent's
  // `unique_id_`.
  // (The Timestamp will match due to TimeSource::MOCK_TIME in the `task_env_`.)
  std::string browser1_start = events.front().substr(browser1_split_pos);
  std::string browser2_start = events.back().substr(browser2_split_pos);
  EXPECT_STRNE(browser1_start.c_str(), browser2_start.c_str());
  // The end of the string must be unique because the WebStates are different
  // and that needs to be represented in the event string.
  std::string browser1_end = events.front().substr(
      browser1_split_pos, events.front().length() - browser1_split_pos);
  std::string browser2_end = events.back().substr(
      browser2_split_pos, events.back().length() - browser2_split_pos);
  EXPECT_STRNE(browser1_end.c_str(), browser2_end.c_str());
}

// Tests WebStateList's batch insertion and closing.
TEST_F(BreadcrumbManagerBrowserAgentTest, BatchOperations) {
  BreadcrumbManagerBrowserAgent::CreateForBrowser(browser_.get());

  // Insert multiple WebStates in a batch operation.
  {
    WebStateList::ScopedBatchOperation lock =
        browser_->GetWebStateList()->StartBatchOperation();
    InsertWebState(browser_.get());
    InsertWebState(browser_.get());
  }

  const auto& events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_TRUE(base::Contains(events.front(), "Inserted 2 tabs"))
      << events.front();

  // Close multiple WebStates in a batch operation.
  {
    WebStateList::ScopedBatchOperation lock =
        browser_->GetWebStateList()->StartBatchOperation();
    browser_->GetWebStateList()->CloseWebStateAt(
        0, WebStateList::ClosingFlags::CLOSE_NO_FLAGS);
    browser_->GetWebStateList()->CloseWebStateAt(
        0, WebStateList::ClosingFlags::CLOSE_NO_FLAGS);
  }

  ASSERT_EQ(2u, events.size());
  EXPECT_TRUE(base::Contains(events.back(), "Closed 2 tabs")) << events.back();
}

// Tests logging kBreadcrumbOverlayJsAlert.
TEST_F(BreadcrumbManagerBrowserAgentTest, JavaScriptAlertOverlay) {
  InsertWebState(browser_.get());

  BreadcrumbManagerBrowserAgent::CreateForBrowser(browser_.get());

  OverlayRequestQueue* queue = OverlayRequestQueue::FromWebState(
      browser_->GetWebStateList()->GetWebStateAt(0),
      OverlayModality::kWebContentArea);
  queue->AddRequest(
      OverlayRequest::CreateWithConfig<JavaScriptAlertDialogRequest>(
          browser_->GetWebStateList()->GetWebStateAt(0), GURL(),
          /*is_main_frame=*/true, @"message"));
  queue->CancelAllRequests();

  const auto& events = GetEvents();
  ASSERT_EQ(1u, events.size());

  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlay))
      << events.back();
  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlayJsAlert))
      << events.back();
}

// Tests logging kBreadcrumbOverlayJsConfirm.
TEST_F(BreadcrumbManagerBrowserAgentTest, JavaScriptConfirmOverlay) {
  InsertWebState(browser_.get());

  BreadcrumbManagerBrowserAgent::CreateForBrowser(browser_.get());

  OverlayRequestQueue* queue = OverlayRequestQueue::FromWebState(
      browser_->GetWebStateList()->GetWebStateAt(0),
      OverlayModality::kWebContentArea);
  queue->AddRequest(
      OverlayRequest::CreateWithConfig<JavaScriptConfirmDialogRequest>(
          browser_->GetWebStateList()->GetWebStateAt(0), GURL(),
          /*is_main_frame=*/true, @"message"));
  queue->CancelAllRequests();

  const auto& events = GetEvents();
  ASSERT_EQ(1u, events.size());

  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlay))
      << events.back();
  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlayJsConfirm))
      << events.back();
}

// Tests logging kBreadcrumbOverlayJsPrompt.
TEST_F(BreadcrumbManagerBrowserAgentTest, JavaScriptPromptOverlay) {
  InsertWebState(browser_.get());

  BreadcrumbManagerBrowserAgent::CreateForBrowser(browser_.get());

  OverlayRequestQueue* queue = OverlayRequestQueue::FromWebState(
      browser_->GetWebStateList()->GetWebStateAt(0),
      OverlayModality::kWebContentArea);
  queue->AddRequest(
      OverlayRequest::CreateWithConfig<JavaScriptPromptDialogRequest>(
          browser_->GetWebStateList()->GetWebStateAt(0), GURL(),
          /*is_main_frame=*/true, @"message",
          /*default_text_field_value=*/nil));
  queue->CancelAllRequests();

  const auto& events = GetEvents();
  ASSERT_EQ(1u, events.size());

  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlay))
      << events.back();
  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlayJsPrompt))
      << events.back();
}

// Tests logging kBreadcrumbOverlayHttpAuth.
TEST_F(BreadcrumbManagerBrowserAgentTest, HttpAuthOverlay) {
  InsertWebState(browser_.get());

  BreadcrumbManagerBrowserAgent::CreateForBrowser(browser_.get());

  OverlayRequestQueue* queue = OverlayRequestQueue::FromWebState(
      browser_->GetWebStateList()->GetWebStateAt(0),
      OverlayModality::kWebContentArea);
  queue->AddRequest(
      OverlayRequest::CreateWithConfig<HTTPAuthOverlayRequestConfig>(
          GURL(), "message", "default text"));
  queue->CancelAllRequests();

  const auto& events = GetEvents();
  ASSERT_EQ(1u, events.size());

  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlay))
      << events.back();
  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlayHttpAuth))
      << events.back();
}

// Tests logging kBreadcrumbOverlayAppLaunch.
TEST_F(BreadcrumbManagerBrowserAgentTest, AppLaunchOverlay) {
  InsertWebState(browser_.get());

  BreadcrumbManagerBrowserAgent::CreateForBrowser(browser_.get());

  OverlayRequestQueue* queue = OverlayRequestQueue::FromWebState(
      browser_->GetWebStateList()->GetWebStateAt(0),
      OverlayModality::kWebContentArea);
  queue->AddRequest(OverlayRequest::CreateWithConfig<
                    app_launcher_overlays::AppLaunchConfirmationRequest>(
      app_launcher_overlays::AppLaunchConfirmationRequestCause::kOther));
  queue->CancelAllRequests();

  const auto& events = GetEvents();
  ASSERT_EQ(1u, events.size());

  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlay))
      << events.back();
  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlayAppLaunch))
      << events.back();
}

// Tests logging kBreadcrumbOverlayAlert with initial and repeated presentation.
TEST_F(BreadcrumbManagerBrowserAgentTest, AlertOverlay) {
  InsertWebState(browser_.get());

  BreadcrumbManagerBrowserAgent::CreateForBrowser(browser_.get());

  OverlayRequestQueue* queue = OverlayRequestQueue::FromWebState(
      browser_->GetWebStateList()->GetWebStateAt(0),
      OverlayModality::kWebContentArea);
  // ConfirmDownloadReplacingRequest logged as generic alert.
  queue->AddRequest(
      OverlayRequest::CreateWithConfig<ConfirmDownloadReplacingRequest>());

  const auto& events = GetEvents();
  ASSERT_EQ(1u, events.size());

  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlay))
      << events.back();
  EXPECT_TRUE(base::Contains(events.back(), kBreadcrumbOverlayAlert))
      << events.back();
  EXPECT_FALSE(base::Contains(events.back(), kBreadcrumbOverlayActivated))
      << events.back();

  // Switching tabs should log new overlay presentations.
  InsertWebState(browser_.get());
  ASSERT_EQ(2u, events.size());
  EXPECT_TRUE(base::Contains(events.back(), "Insert active Tab"))
      << events.back();

  browser_->GetWebStateList()->ActivateWebStateAt(0);
  ASSERT_EQ(4u, events.size());
  auto activation = std::next(events.begin(), 2);
  EXPECT_TRUE(base::Contains(*activation, kBreadcrumbOverlay)) << *activation;
  EXPECT_TRUE(base::Contains(*activation, kBreadcrumbOverlayAlert))
      << *activation;
  EXPECT_TRUE(base::Contains(*activation, kBreadcrumbOverlayActivated))
      << *activation;
  queue->CancelAllRequests();
}