910e62b5创建于 1月15日历史提交
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_BTM_BTM_PAGE_VISIT_OBSERVER_TEST_UTILS_H_
#define CONTENT_BROWSER_BTM_BTM_PAGE_VISIT_OBSERVER_TEST_UTILS_H_

#include <iosfwd>
#include <optional>
#include <vector>

#include "base/run_loop.h"
#include "content/browser/btm/btm_page_visit_observer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "url/gurl.h"

namespace content {

std::ostream& operator<<(std::ostream& out, const BtmPageVisitInfo& page);
std::ostream& operator<<(std::ostream& out,
                         const BtmServerRedirectInfo& redirect);
std::ostream& operator<<(std::ostream& out, const BtmNavigationInfo& nav);
std::ostream& operator<<(std::ostream& out,
                         const BtmPageVisitObserver::VisitTuple& visit);

class BtmPageVisitRecorder {
 public:
  using VisitTuple = BtmPageVisitObserver::VisitTuple;

  explicit BtmPageVisitRecorder(
      WebContents* web_contents,
      base::Clock* clock = base::DefaultClock::GetInstance());
  ~BtmPageVisitRecorder();

  // Returns all visits observed so far.
  const std::vector<VisitTuple>& visits() const { return visits_; }

  // Wait until `visits()` returns at least `n` elements. Returns `true` if
  // successful, or `false` if it times out.
  [[nodiscard]] bool WaitForSize(size_t n);

 private:
  // The state needed to implement `WaitForSize()`.
  struct WaitState {
    explicit WaitState(size_t wanted_count) : wanted_count(wanted_count) {}

    const size_t wanted_count;
    base::RunLoop run_loop;
  };

  // Called by `observer_` on each page visit; appends to `visits_`.
  void OnVisit(BtmPageVisitInfo prev_page, BtmNavigationInfo navigation);

  std::vector<VisitTuple> visits_;
  // Populated only during a call to `WaitForSize()`. Mostly a `RunLoop` wrapped
  // in `optional<>` to allow for re-creation.
  std::optional<WaitState> wait_state_;
  BtmPageVisitObserver observer_;
};

// Matches the `url` property of `BtmPageVisitInfo`,
// `BtmNavigationInfo::destination`, or `BtmServerRedirectInfo`.
MATCHER_P(HasUrl,
          matcher,
          "has url that " + testing::DescribeMatcher<GURL>(matcher, negation)) {
  return testing::ExplainMatchResult(matcher, arg.url, result_listener);
}

// Matches the `source_id` property of `BtmPageVisitInfo` or
// `BtmServerRedirectInfo`.
MATCHER_P(HasSourceId,
          matcher,
          "has source_id that " +
              testing::DescribeMatcher<ukm::SourceId>(matcher, negation)) {
  return testing::ExplainMatchResult(matcher, arg.source_id, result_listener);
}

MATCHER(HasUrlAndSourceIdForBlankPage,
        "has url and source_id for a blank page (url=GURL(), "
        "source_id=ukm::kInvalidSourceId)") {
  bool matched = true;
  if (arg.url != GURL()) {
    matched = false;
    *result_listener << "url was " << arg.url;
  }
  if (arg.source_id != ukm::kInvalidSourceId) {
    if (!matched) {
      *result_listener << " and ";
    }
    matched = false;
    *result_listener << "source_id was " << arg.source_id;
  }
  return matched;
}

MATCHER_P2(HasUrlAndMatchingSourceId,
           matcher,
           ukm_recorder_ptr,
           "has url that " + testing::DescribeMatcher<GURL>(matcher, negation) +
               " and source_id with a corresponding source URL that also " +
               testing::DescribeMatcher<GURL>(matcher, negation)) {
  bool matched = true;
  if (!testing::ExplainMatchResult(matcher, arg.url, result_listener)) {
    *result_listener << "url was " << arg.url;
    matched = false;
  }
  const GURL url_for_source_id =
      ukm_recorder_ptr->GetSourceForSourceId(arg.source_id)->url();
  if (!testing::ExplainMatchResult(matcher, url_for_source_id,
                                   result_listener)) {
    if (!matched) {
      *result_listener << " and ";
    }
    *result_listener << "source_id had corresponding URL " << url_for_source_id;
    matched = false;
  }
  return matched;
}

MATCHER_P2(HasDestinationUrlAndMatchingDestinationSourceId,
           matcher,
           ukm_recorder_ptr,
           "has destination_url that " +
               testing::DescribeMatcher<GURL>(matcher, negation) +
               " and destination_source_id with a corresponding source URL "
               "that also " +
               testing::DescribeMatcher<GURL>(matcher, negation)) {
  bool matched = true;
  if (!testing::ExplainMatchResult(matcher, arg.destination_url,
                                   result_listener)) {
    *result_listener << "destination_url was " << arg.destination_url;
    matched = false;
  }
  const GURL url_for_source_id =
      ukm_recorder_ptr->GetSourceForSourceId(arg.destination_source_id)->url();
  if (!testing::ExplainMatchResult(matcher, url_for_source_id,
                                   result_listener)) {
    if (!matched) {
      *result_listener << " and ";
    }
    *result_listener << "destination_source_id had corresponding URL "
                     << url_for_source_id;
    matched = false;
  }
  return matched;
}

// Matches the URL for the `source_id` property of `BtmPageVisitInfo` or
// `BtmServerRedirectInfo`, as recorded by `ukm_recorder`.
MATCHER_P2(HasSourceIdForUrl,
           matcher,
           ukm_recorder,
           "has source_id with a corresponding URL that " +
               testing::DescribeMatcher<GURL>(matcher, negation)) {
  const GURL url_for_source_id =
      ukm_recorder->GetSourceForSourceId(arg.source_id)->url();
  if (!testing::ExplainMatchResult(matcher, url_for_source_id,
                                   result_listener)) {
    *result_listener << "source_id had corresponding URL " << url_for_source_id;
    return false;
  }
  return true;
}

MATCHER_P2(SourceIdUrlIs,
           matcher,
           ukm_recorder_ptr,
           "has a corresponding URL that " +
               testing::DescribeMatcher<GURL>(matcher, negation)) {
  const GURL url_for_source_id =
      ukm_recorder_ptr->GetSourceForSourceId(arg)->url();
  if (!testing::ExplainMatchResult(matcher, url_for_source_id,
                                   result_listener)) {
    *result_listener << "corresponding URL was " << url_for_source_id;
    return false;
  }
  return true;
}

// Matches `VisitTuple::prev_page`.
MATCHER_P(PreviousPage,
          matcher,
          "has prev_page that " +
              testing::DescribeMatcher<BtmPageVisitInfo>(matcher, negation)) {
  return testing::ExplainMatchResult(matcher, arg.prev_page, result_listener);
}

// Matches `VisitTuple::navigation`.
MATCHER_P(Navigation,
          matcher,
          "has navigation that " +
              testing::DescribeMatcher<BtmNavigationInfo>(matcher, negation)) {
  return testing::ExplainMatchResult(matcher, arg.navigation, result_listener);
}

// Matches `BtmNavigationInfo::server_redirects`.
MATCHER_P(ServerRedirects,
          matcher,
          "has server_redirects that " +
              testing::DescribeMatcher<std::vector<BtmServerRedirectInfo>>(
                  matcher,
                  negation)) {
  return testing::ExplainMatchResult(matcher, arg.server_redirects,
                                     result_listener);
}

// Matches `BtmNavigationInfo::was_user_initiated`.
MATCHER_P(WasUserInitiated,
          matcher,
          "has was_user_initiated that " +
              testing::DescribeMatcher<bool>(matcher, negation)) {
  return testing::ExplainMatchResult(matcher, arg.was_user_initiated,
                                     result_listener);
}

// Matches `BtmNavigationInfo::was_renderer_initiated`.
MATCHER_P(WasRendererInitiated,
          matcher,
          "has was_renderer_initiated that " +
              testing::DescribeMatcher<bool>(matcher, negation)) {
  return testing::ExplainMatchResult(matcher, arg.was_renderer_initiated,
                                     result_listener);
}

// Matches `BtmNavigationInfo::page_transition`. Param must be a
// `ui::PageTransition`.
MATCHER_P(PageTransitionCoreTypeIs,
          matcher,
          "has page_transition core type that " +
              std::string(negation ? "is " : "isn't ") +
              ui::PageTransitionGetCoreTransitionString(matcher)) {
  return testing::ExplainMatchResult(
      ui::PageTransitionGetCoreTransitionString(matcher),
      ui::PageTransitionGetCoreTransitionString(arg.page_transition),
      result_listener);
}

// Matches a `ui::PageTransition`. Param must also be a `ui::PageTransition`.
MATCHER_P(CoreTypeIs,
          matcher,
          "has core type that " + std::string(negation ? "is " : "isn't ") +
              ui::PageTransitionGetCoreTransitionString(matcher)) {
  return testing::ExplainMatchResult(
      ui::PageTransitionGetCoreTransitionString(matcher),
      ui::PageTransitionGetCoreTransitionString(arg), result_listener);
}

// Matches `BtmPageVisitInfo::had_qualifying_storage_access`.
MATCHER_P(HadQualifyingStorageAccess,
          matcher,
          "has had_qualifying_storage_access that " +
              testing::DescribeMatcher<bool>(matcher, negation)) {
  return testing::ExplainMatchResult(matcher, arg.had_qualifying_storage_access,
                                     result_listener);
}

// Matches `BtmServerRedirectInfo::did_write_cookies`.
MATCHER_P(DidWriteCookies,
          matcher,
          "has did_write_cookies that " +
              testing::DescribeMatcher<bool>(matcher, negation)) {
  return testing::ExplainMatchResult(matcher, arg.did_write_cookies,
                                     result_listener);
}

MATCHER_P(ReceivedUserActivation,
          matcher,
          "has received_user_activation that " +
              testing::DescribeMatcher<bool>(matcher, negation)) {
  return testing::ExplainMatchResult(matcher, arg.received_user_activation,
                                     result_listener);
}

MATCHER_P(HadSuccessfulWebAuthnAssertion,
          matcher,
          "has had_successful_web_authn_assertion that " +
              testing::DescribeMatcher<bool>(matcher, negation)) {
  return testing::ExplainMatchResult(
      matcher, arg.had_successful_web_authn_assertion, result_listener);
}

MATCHER_P(VisitDuration,
          matcher,
          "has visit_duration that " +
              testing::DescribeMatcher<base::TimeDelta>(matcher, negation)) {
  return testing::ExplainMatchResult(matcher, arg.visit_duration,
                                     result_listener);
}

}  // namespace content

#endif  // CONTENT_BROWSER_BTM_BTM_PAGE_VISIT_OBSERVER_TEST_UTILS_H_