Repository: brave/brave-core
Pull request #28606: [ads] Fixes crash in AdsServiceImpl::PrefetchNewTabPageAdCallback
Author: login: aseren
Created: 2025-04-10T20:39:24Z | Merged: 2025-04-14T22:18:53Z

<!-- Add brave-browser issue below that this PR will resolve -->
Resolves https://github.com/brave/brave-browser/issues/45295

<!-- CI-related labels that can be applied to this PR:
* CI/run-audit-deps (1) - check for known npm/cargo vulnerabilities (audit_deps)
* CI/run-network-audit (1) - run network-audit
* CI/run-upstream-tests - run Chromium unit and browser tests on Linux and Windows (otherwise only on Linux)
* CI/run-linux-arm64, CI/run-macos-x64, CI/run-windows-arm64, CI/run-windows-x86 - run builds that would otherwise be skipped
* CI/skip - do not run CI builds (except noplatform)
* CI/skip-linux-x64, CI/skip-android, CI/skip-macos-arm64, CI/skip-ios, CI/skip-windows-x64 - skip CI builds for specific platforms
* CI/skip-upstream-tests - do not run Chromium unit, or browser tests (otherwise only on Linux)
* CI/skip-all-linters - do not run presubmit and lint checks
* CI/storybook-url (1) - deploy storybook and provide a unique URL for each build

(1) applied automatically when some files are changed (see: https://github.com/brave/brave-core/blob/master/.github/labeler.yml)
-->

<!--
## Checklist:

- Review design docs
  [Browser design principles](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/chrome_browser_design_principles.md)
  [Style guide](https://chromium.googlesource.com/chromium/src/+/main/styleguide/c++/c++.md)
  [Core principles](https://www.chromium.org/developers/core-principles/)
- Ensure there are (tests)[https://www.chromium.org/developers/testing/]. Unit test as much as possible (including edge cases), but also include browser tests covering high level functionality.
- Ensure that there are comments explaining what classes/methods are/do. The "why" is often more important than the "what" in comments. Also update any relevant docs (moving docs from wiki to brave-core if necessary).
- Request security or other review (third-party libraries, rust code, etc...) if applicable [security/privacy review is needed](https://github.com/brave/brave-browser/wiki/Security-reviews) [other review](https://github.com/brave/reviews/issues/new/choose)
  Also see [adding third-party libraries](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/adding_to_third_party.md) for general guidelines on using third party code
- Maks sure there is a [ticket](https://github.com/brave/brave-browser/issues) for your issue
- Use Github [auto-closing keywords](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) in the PR description above
- Write a good [PR/commit description](https://google.github.io/eng-practices/review/developer/cl-descriptions.html)
- Squash any review feedback or "fixup" commits before merge, so that history is a record of what happened in the repo, not your PR
- Add appropriate labels (`QA/Yes` or `QA/No`; `release-notes/include` or `release-notes/exclude`; `OS/...`) to the associated issue
- Checked the PR locally:
  * `npm run test -- brave_browser_tests`, `npm run test -- brave_unit_tests` [wiki](https://github.com/brave/brave-browser/wiki/Tests)
  * `npm run presubmit` [wiki](https://github.com/brave/brave-browser/wiki/Presubmit-checks), `npm run gn_check`, `npm run tslint`
- Run `git rebase master` (if needed)
-->

## Test plan:

### Test case 1
* Fresh install
* Open NTP until NTT is shown
* Make sure that `views` p3a metric for NTT creative is recorded

### Test case 2
* Fresh install
* Join Brave Rewards
* Open NTP until NTT is shown
* Make sure viewed impression for NTT creative is triggered



Diff:
diff --git a/components/brave_ads/browser/BUILD.gn b/components/brave_ads/browser/BUILD.gn
index c8db4a312cbd..abd37f954e33 100644
--- a/components/brave_ads/browser/BUILD.gn
+++ b/components/brave_ads/browser/BUILD.gn
@@ -91,9 +91,13 @@ static_library("browser") {
 source_set("unit_tests") {
   testonly = true
 
-  sources = [ "analytics/p3a/notification_ad_unittest.cc" ]
+  sources = [
+    "ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc",
+    "analytics/p3a/notification_ad_unittest.cc",
+  ]
 
   deps = [
+    ":test_support",
     "//base",
     "//base/test:test_support",
     "//brave/components/brave_ads/browser",
diff --git a/components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc b/components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
new file mode 100644
index 000000000000..d609db8ac367
--- /dev/null
+++ b/components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
@@ -0,0 +1,209 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(/*delegate=*/nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  AdsServiceMock& ads_service() const { return *ads_service_; }
+
+  NewTabPageAdPrefetcher& prefetcher() const { return *prefetcher_; }
+
+  void ResetPrefetcher() {
+    prefetcher_ = std::make_unique<NewTabPageAdPrefetcher>(*ads_service_);
+  }
+
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+ private:
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Act & Assert
+  EXPECT_FALSE(prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd)
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd)
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(/*ad=*/std::nullopt);
+      }));
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_FALSE(prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  const NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd)
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_FALSE(prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldPrefetchAdAfterGettingPrefetchedAd) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd)
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+  ASSERT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldNotPrefetchAdWhenAlreadyPrefetched) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd)
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldNotPrefetchAdWhenAnotherPrefetchIsInProgress) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  MaybeServeNewTabPageAdCallback deferred_maybe_serve_ad_callback;
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd)
+      .WillOnce(::testing::Invoke([&deferred_maybe_serve_ad_callback](
+                                      MaybeServeNewTabPageAdCallback callback) {
+        deferred_maybe_serve_ad_callback = std::move(callback);
+      }));
+
+  prefetcher().Prefetch();
+
+  // Act
+  prefetcher().Prefetch();
+  std::move(deferred_maybe_serve_ad_callback).Run(expected_ad);
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, ShouldOnlyGetPrefetchedAdOnce) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd)
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+  ASSERT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+
+  // Act & Assert
+  EXPECT_FALSE(prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, CancelPrefetch) {
+  // Arrange
+  MaybeServeNewTabPageAdCallback deferred_maybe_serve_ad_callback;
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd)
+      .WillOnce(::testing::Invoke([&deferred_maybe_serve_ad_callback](
+                                      MaybeServeNewTabPageAdCallback callback) {
+        deferred_maybe_serve_ad_callback = std::move(callback);
+      }));
+
+  // Act
+  prefetcher().Prefetch();
+  ResetPrefetcher();
+
+  // Assert
+  // Run the deferred callback and make sure no crash occurs
+  std::move(deferred_maybe_serve_ad_callback).Run(/*ad=*/std::nullopt);
+}
+
+}  // namespace brave_ads
diff --git a/components/brave_ads/browser/ads_service_impl.cc b/components/brave_ads/browser/ads_service_impl.cc
index caba82299cf7..15933e490f91 100644
--- a/components/brave_ads/browser/ads_service_impl.cc
+++ b/components/brave_ads/browser/ads_service_impl.cc
@@ -39,6 +39,7 @@
 #include "brave/components/brave_ads/browser/reminder/reminder_util.h"
 #include "brave/components/brave_ads/browser/tooltips/ads_tooltips_delegate.h"
 #include "brave/components/brave_ads/core/browser/service/ads_service_observer.h"
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
 #include "brave/components/brave_ads/core/public/ad_units/inline_content_ad/inline_content_ad_value_util.h"
 #include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_value_util.h"
 #include "brave/components/brave_ads/core/public/ad_units/notification_ad/notification_ad_feature.h"
@@ -208,6 +209,8 @@ AdsServiceImpl::AdsServiceImpl(
       host_content_settings_map_(host_content_settings_map),
       ads_tooltips_delegate_(std::move(ads_tooltips_delegate)),
       device_id_(std::move(device_id)),
+      new_tab_page_ad_prefetcher_(
+          std::make_unique<NewTabPageAdPrefetcher>(/*ads_service=*/*this)),
       bat_ads_service_factory_(std::move(bat_ads_service_factory)),
       file_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
@@ -859,20 +862,19 @@ void AdsServiceImpl::RefetchNewTabPageAd() {
 
 void AdsServiceImpl::RefetchNewTabPageAdCallback(bool success) {
   if (success) {
-    PrefetchNewTabPageAd();
+    new_tab_page_ad_prefetcher_->Prefetch();
   }
 }
 
 void AdsServiceImpl::ResetNewTabPageAd() {
-  prefetched_new_tab_page_ad_.reset();
-  is_prefetching_new_tab_page_ad_ = false;
+  new_tab_page_ad_prefetcher_ = std::make_unique<NewTabPageAdPrefetcher>(*this);
 }
 
 void AdsServiceImpl::OnParseAndSaveNewTabPageAdsCallback(
     ParseAndSaveNewTabPageAdsCallback callback,
     bool success) {
   if (success) {
-    PrefetchNewTabPageAd();
+    new_tab_page_ad_prefetcher_->Prefetch();
   }
 
   std::move(callback).Run(success);
@@ -1042,23 +1044,6 @@ void AdsServiceImpl::CloseAllNotificationAds() {
   prefs_->SetList(prefs::kNotificationAds, {});
 }
 
-void AdsServiceImpl::PrefetchNewTabPageAdCallback(
-    std::optional<base::Value::Dict> dict) {
-  CHECK(!prefetched_new_tab_page_ad_);
-
-  if (!is_prefetching_new_tab_page_ad_) {
-    // `is_prefetching_new_tab_page_ad_` can be reset during shutdown, so fail
-    // gracefully.
-    return;
-  }
-
-  is_prefetching_new_tab_page_ad_ = false;
-
-  if (dict) {
-    prefetched_new_tab_page_ad_ = NewTabPageAdFromValue(*dict);
-  }
-}
-
 void AdsServiceImpl::MaybeOpenNewTabWithAd() {
   if (!retry_opening_new_tab_for_ad_with_placement_id_) {
     return;
@@ -1379,32 +1364,12 @@ void AdsServiceImpl::TriggerInlineContentAdEvent(
 }
 
 void AdsServiceImpl::PrefetchNewTabPageAd() {
-  if (!bat_ads_associated_remote_.is_bound()) {
-    return;
-  }
-
-  if (!prefetched_new_tab_page_ad_ && !is_prefetching_new_tab_page_ad_) {
-    is_prefetching_new_tab_page_ad_ = true;
-
-    bat_ads_associated_remote_->MaybeServeNewTabPageAd(
-        base::BindOnce(&AdsServiceImpl::PrefetchNewTabPageAdCallback,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
+  new_tab_page_ad_prefetcher_->Prefetch();
 }
 
 std::optional<NewTabPageAdInfo>
 AdsServiceImpl::MaybeGetPrefetchedNewTabPageAd() {
-  if (!bat_ads_associated_remote_.is_bound()) {
-    return std::nullopt;
-  }
-
-  std::optional<NewTabPageAdInfo> ad;
-  if (prefetched_new_tab_page_ad_ && prefetched_new_tab_page_ad_->IsValid()) {
-    ad = prefetched_new_tab_page_ad_;
-    prefetched_new_tab_page_ad_.reset();
-  }
-
-  return ad;
+  return new_tab_page_ad_prefetcher_->MaybeGetPrefetchedAd();
 }
 
 void AdsServiceImpl::OnFailedToPrefetchNewTabPageAd(
@@ -1429,6 +1394,28 @@ void AdsServiceImpl::ParseAndSaveNewTabPageAds(
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+void AdsServiceImpl::MaybeServeNewTabPageAd(
+    MaybeServeNewTabPageAdCallback callback) {
+  if (!bat_ads_associated_remote_.is_bound()) {
+    return std::move(callback).Run(/*ad=*/std::nullopt);
+  }
+
+  auto callback_wrapper = base::BindOnce(
+      [](MaybeServeNewTabPageAdCallback callback,
+         std::optional<base::Value::Dict> dict) {
+        if (!dict) {
+          return std::move(callback).Run(
+              /*ad=*/std::nullopt);
+        }
+
+        std::move(callback).Run(NewTabPageAdFromValue(*dict));
+      },
+      std::move(callback));
+
+  bat_ads_associated_remote_->MaybeServeNewTabPageAd(
+      std::move(callback_wrapper));
+}
+
 void AdsServiceImpl::TriggerNewTabPageAdEvent(
     const std::string& placement_id,
     const std::string& creative_instance_id,
diff --git a/components/brave_ads/browser/ads_service_impl.h b/components/brave_ads/browser/ads_service_impl.h
index 7dbc30d6c90f..06ab2aa60efc 100644
--- a/components/brave_ads/browser/ads_service_impl.h
+++ b/components/brave_ads/browser/ads_service_impl.h
@@ -67,6 +67,7 @@ class BatAdsServiceFactory;
 class DeviceId;
 class ResourceComponent;
 struct NewTabPageAdInfo;
+class NewTabPageAdPrefetcher;
 
 class AdsServiceImpl final : public AdsService,
                              public bat_ads::mojom::BatAdsClient,
@@ -203,10 +204,6 @@ class AdsServiceImpl final : public AdsService,
   void NotificationAdTimedOut(const std::string& placement_id);
   void CloseAllNotificationAds();
 
-  // TODO(https://github.com/brave/brave-browser/issues/26192) Decouple new
-  // tab page ad business logic.
-  void PrefetchNewTabPageAdCallback(std::optional<base::Value::Dict> dict);
-
   // TODO(https://github.com/brave/brave-browser/issues/26193) Decouple open
   // new tab with ad business logic.
   void MaybeOpenNewTabWithAd();
@@ -273,6 +270,7 @@ class AdsServiceImpl final : public AdsService,
   void ParseAndSaveNewTabPageAds(
       base::Value::Dict dict,
       ParseAndSaveNewTabPageAdsCallback callback) override;
+  void MaybeServeNewTabPageAd(MaybeServeNewTabPageAdCallback callback) override;
   void TriggerNewTabPageAdEvent(
       const std::string& placement_id,
       const std::string& creative_instance_id,
@@ -458,9 +456,6 @@ class AdsServiceImpl final : public AdsService,
   std::map<std::string, std::unique_ptr<base::OneShotTimer>>
       notification_ad_timers_;
 
-  std::optional<NewTabPageAdInfo> prefetched_new_tab_page_ad_;
-  bool is_prefetching_new_tab_page_ad_ = false;
-
   std::optional<std::string> retry_opening_new_tab_for_ad_with_placement_id_;
 
   base::CancelableTaskTracker history_service_task_tracker_;
@@ -491,6 +486,8 @@ class AdsServiceImpl final : public AdsService,
 
   const std::unique_ptr<DeviceId> device_id_;
 
+  std::unique_ptr<NewTabPageAdPrefetcher> new_tab_page_ad_prefetcher_;
+
   const std::unique_ptr<BatAdsServiceFactory> bat_ads_service_factory_;
 
   const scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
diff --git a/components/brave_ads/browser/ads_service_mock.h b/components/brave_ads/browser/ads_service_mock.h
index c785e47f16a2..930dfe40709a 100644
--- a/components/brave_ads/browser/ads_service_mock.h
+++ b/components/brave_ads/browser/ads_service_mock.h
@@ -60,6 +60,7 @@ class AdsServiceMock final : public AdsService {
               MaybeGetPrefetchedNewTabPageAd,
               ());
   MOCK_METHOD(void, PrefetchNewTabPageAd, ());
+  MOCK_METHOD(void, MaybeServeNewTabPageAd, (MaybeServeNewTabPageAdCallback));
   MOCK_METHOD(void,
               TriggerNewTabPageAdEvent,
               (const std::string&,
diff --git a/components/brave_ads/core/browser/service/BUILD.gn b/components/brave_ads/core/browser/service/BUILD.gn
index 7048665bd445..bc9fc3067c05 100644
--- a/components/brave_ads/core/browser/service/BUILD.gn
+++ b/components/brave_ads/core/browser/service/BUILD.gn
@@ -8,6 +8,8 @@ source_set("service") {
     "ads_service.cc",
     "ads_service.h",
     "ads_service_observer.h",
+    "new_tab_page_ad_prefetcher.cc",
+    "new_tab_page_ad_prefetcher.h",
   ]
 
   public_deps = [
diff --git a/components/brave_ads/core/browser/service/ads_service.h b/components/brave_ads/core/browser/service/ads_service.h
index d8727c6bdc24..7a5610cf85ce 100644
--- a/components/brave_ads/core/browser/service/ads_service.h
+++ b/components/brave_ads/core/browser/service/ads_service.h
@@ -145,6 +145,11 @@ class AdsService : public KeyedService {
       base::Value::Dict dict,
       ParseAndSaveNewTabPageAdsCallback callback) = 0;
 
+  // Called to serve a new tab page ad. The callback takes one argument -
+  // `NewTabPageAdInfo` containing the info for the ad.
+  virtual void MaybeServeNewTabPageAd(
+      MaybeServeNewTabPageAdCallback callback) = 0;
+
   // Called when a user views or interacts with a new tab page ad to trigger a
   // `mojom_ad_event_type` event for the specified `placement_id` and
   // `creative_instance_id`. `placement_id` should be a 128-bit random GUID in
diff --git a/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.cc b/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.cc
new file mode 100644
index 000000000000..87f476041a3e
--- /dev/null
+++ b/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.cc
@@ -0,0 +1,57 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include "base/check.h"
+#include "brave/components/brave_ads/core/browser/service/ads_service.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+NewTabPageAdPrefetcher::NewTabPageAdPrefetcher(AdsService& ads_service)
+    : ads_service_(ads_service) {}
+
+NewTabPageAdPrefetcher::~NewTabPageAdPrefetcher() = default;
+
+std::optional<NewTabPageAdInfo> NewTabPageAdPrefetcher::MaybeGetPrefetchedAd() {
+  std::optional<NewTabPageAdInfo> ad;
+  if (prefetched_ad_) {
+    if (prefetched_ad_->IsValid()) {
+      ad = prefetched_ad_;
+    }
+
+    prefetched_ad_.reset();
+  }
+
+  return ad;
+}
+
+void NewTabPageAdPrefetcher::Prefetch() {
+  if (!prefetched_ad_ && !is_prefetching_) {
+    is_prefetching_ = true;
+
+    ads_service_->MaybeServeNewTabPageAd(
+        base::BindOnce(&NewTabPageAdPrefetcher::PrefetchCallback,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
+void NewTabPageAdPrefetcher::PrefetchCallback(
+    base::optional_ref<const NewTabPageAdInfo> ad) {
+  CHECK(!prefetched_ad_);
+
+  if (!is_prefetching_) {
+    // `is_prefetching_` can be reset during shutdown, so fail gracefully.
+    return;
+  }
+  is_prefetching_ = false;
+
+  if (ad) {
+    prefetched_ad_ = *ad;
+  }
+}
+
+}  // namespace brave_ads
diff --git a/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h b/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h
new file mode 100644
index 000000000000..68d48fefd86c
--- /dev/null
+++ b/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h
@@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCHER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCHER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetcher {
+ public:
+  explicit NewTabPageAdPrefetcher(AdsService& ads_service);
+
+  NewTabPageAdPrefetcher(const NewTabPageAdPrefetcher&) = delete;
+  NewTabPageAdPrefetcher& operator=(const NewTabPageAdPrefetcher&) = delete;
+
+  ~NewTabPageAdPrefetcher();
+
+  void Prefetch();
+  std::optional<NewTabPageAdInfo> MaybeGetPrefetchedAd();
+
+ private:
+  void PrefetchCallback(base::optional_ref<const NewTabPageAdInfo> ad);
+
+  std::optional<NewTabPageAdInfo> prefetched_ad_;
+  bool is_prefetching_ = false;
+
+  const raw_ref<AdsService> ads_service_;
+
+  base::WeakPtrFactory<NewTabPageAdPrefetcher> weak_ptr_factory_{this};
+};
+
+}  // namespace brave_ads
+
+#endif  // BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCHER_H_
diff --git a/ios/browser/brave_ads/ads_service_impl_ios.h b/ios/browser/brave_ads/ads_service_impl_ios.h
index 615897270249..858dff99b684 100644
--- a/ios/browser/brave_ads/ads_service_impl_ios.h
+++ b/ios/browser/brave_ads/ads_service_impl_ios.h
@@ -31,7 +31,7 @@ namespace brave_ads {
 class Ads;
 class AdsClient;
 struct NewTabPageAdInfo;
-
+class NewTabPageAdPrefetcher;
 class AdsServiceImplIOS : public AdsService {
  public:
   explicit AdsServiceImplIOS(PrefService* prefs);
@@ -96,6 +96,7 @@ class AdsServiceImplIOS : public AdsService {
   void ParseAndSaveNewTabPageAds(
       base::Value::Dict dict,
       ParseAndSaveNewTabPageAdsCallback callback) override;
+  void MaybeServeNewTabPageAd(MaybeServeNewTabPageAdCallback callback) override;
   void TriggerNewTabPageAdEvent(
       const std::string& placement_id,
       const std::string& creative_instance_id,
@@ -171,13 +172,10 @@ class AdsServiceImplIOS : public AdsService {
   void ClearAdsData(ClearDataCallback callback, bool success);
   void ClearAdsDataCallback(ClearDataCallback callback);
 
-  // TODO(https://github.com/brave/brave-browser/issues/26192) Decouple new
-  // tab page ad business logic.
-  void PrefetchNewTabPageAdCallback(
-      base::optional_ref<const NewTabPageAdInfo> new_tab_page_ad);
   void OnParseAndSaveNewTabPageAdsCallback(
       ParseAndSaveNewTabPageAdsCallback callback,
       bool success);
+  void ResetNewTabPageAd();
 
   const raw_ptr<PrefService> prefs_ = nullptr;  // Not owned.
 
@@ -191,8 +189,7 @@ class AdsServiceImplIOS : public AdsService {
   mojom::BuildChannelInfoPtr mojom_build_channel_;
   mojom::WalletInfoPtr mojom_wallet_;
 
-  std::optional<NewTabPageAdInfo> prefetched_new_tab_page_ad_;
-  bool is_prefetching_new_tab_page_ad_ = false;
+  std::unique_ptr<NewTabPageAdPrefetcher> new_tab_page_ad_prefetcher_;
 
   std::unique_ptr<Ads> ads_;
 
diff --git a/ios/browser/brave_ads/ads_service_impl_ios.mm b/ios/browser/brave_ads/ads_service_impl_ios.mm
index c62430a0bec7..19e7fe6861be 100644
--- a/ios/browser/brave_ads/ads_service_impl_ios.mm
+++ b/ios/browser/brave_ads/ads_service_impl_ios.mm
@@ -20,9 +20,11 @@
 #include "base/notimplemented.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/thread_pool.h"
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
 #include "brave/components/brave_ads/core/mojom/brave_ads.mojom.h"
 #include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
 #include "brave/components/brave_ads/core/public/ads.h"
+#include "brave/components/brave_ads/core/public/ads_callback.h"
 #include "brave/components/brave_ads/core/public/ads_client/ads_client.h"
 #include "brave/components/brave_ads/core/public/ads_constants.h"
 #include "brave/components/brave_ads/core/public/flags/flags_util.h"
@@ -43,7 +45,9 @@
       prefs_(prefs),
       file_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
-           base::TaskShutdownBehavior::BLOCK_SHUTDOWN})) {
+           base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
+      new_tab_page_ad_prefetcher_(
+          std::make_unique<NewTabPageAdPrefetcher>(/*ads_service=*/*this)) {
   CHECK(prefs_);
 }
 
@@ -201,13 +205,7 @@
     return;
   }
 
-  if (!prefetched_new_tab_page_ad_ && !is_prefetching_new_tab_page_ad_) {
-    is_prefetching_new_tab_page_ad_ = true;
-
-    ads_->MaybeServeNewTabPageAd(
-        base::BindOnce(&AdsServiceImplIOS::PrefetchNewTabPageAdCallback,
-                       weak_ptr_factory_.GetWeakPtr()));
-  }
+  new_tab_page_ad_prefetcher_->Prefetch();
 }
 
 std::optional<NewTabPageAdInfo>
@@ -216,20 +214,13 @@
     return std::nullopt;
   }
 
-  std::optional<NewTabPageAdInfo> ad;
-  if (prefetched_new_tab_page_ad_ && prefetched_new_tab_page_ad_->IsValid()) {
-    ad = prefetched_new_tab_page_ad_;
-    prefetched_new_tab_page_ad_.reset();
-  }
-
-  return ad;
+  return new_tab_page_ad_prefetcher_->MaybeGetPrefetchedAd();
 }
 
 void AdsServiceImplIOS::OnFailedToPrefetchNewTabPageAd(
     const std::string& /*placement_id*/,
     const std::string& /*creative_instance_id*/) {
-  prefetched_new_tab_page_ad_.reset();
-  is_prefetching_new_tab_page_ad_ = false;
+  ResetNewTabPageAd();
 
   PurgeOrphanedAdEventsForType(mojom::AdType::kNewTabPageAd,
                                /*intentional*/ base::DoNothing());
@@ -257,6 +248,15 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+void AdsServiceImplIOS::MaybeServeNewTabPageAd(
+    MaybeServeNewTabPageAdCallback callback) {
+  if (!IsInitialized()) {
+    return std::move(callback).Run(/*ad=*/std::nullopt);
+  }
+
+  ads_->MaybeServeNewTabPageAd(std::move(callback));
+}
+
 void AdsServiceImplIOS::TriggerNewTabPageAdEvent(
     const std::string& placement_id,
     const std::string& creative_instance_id,
@@ -472,8 +472,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void AdsServiceImplIOS::Shutdown() {
-  prefetched_new_tab_page_ad_.reset();
-  is_prefetching_new_tab_page_ad_ = false;
+  ResetNewTabPageAd();
 
   ads_.reset();
 }
@@ -541,23 +540,6 @@
   InitializeAds(std::move(callback));
 }
 
-void AdsServiceImplIOS::PrefetchNewTabPageAdCallback(
-    base::optional_ref<const NewTabPageAdInfo> new_tab_page_ad) {
-  CHECK(!prefetched_new_tab_page_ad_);
-
-  if (!is_prefetching_new_tab_page_ad_) {
-    // `is_prefetching_new_tab_page_ad_` can be reset during shutdown, so fail
-    // gracefully.
-    return;
-  }
-
-  is_prefetching_new_tab_page_ad_ = false;
-
-  if (new_tab_page_ad) {
-    prefetched_new_tab_page_ad_ = *new_tab_page_ad;
-  }
-}
-
 void AdsServiceImplIOS::OnParseAndSaveNewTabPageAdsCallback(
     ParseAndSaveNewTabPageAdsCallback callback,
     bool success) {
@@ -568,4 +550,9 @@
   std::move(callback).Run(success);
 }
 
+void AdsServiceImplIOS::ResetNewTabPageAd() {
+  new_tab_page_ad_prefetcher_ =
+      std::make_unique<NewTabPageAdPrefetcher>(/*ads_service=*/*this);
+}
+
 }  // namespace brave_ads


Reviews:
user: login: kylehickinson
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-11T19:57:03Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T19:58:21Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T19:59:09Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T19:59:24Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T19:59:42Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T19:59:55Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T20:00:35Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T20:03:10Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T20:07:44Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T20:07:51Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T20:10:04Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T20:11:07Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T20:11:47Z
user: login: tmancey
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-11T20:12:16Z
user: login: tmancey
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-12T22:03:26Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-12T22:06:31Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-12T22:06:42Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-12T22:07:14Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-12T23:49:14Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-12T23:55:18Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-12T23:55:37Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T15:50:07Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:22:26Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:22:29Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:22:35Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:22:38Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:22:44Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:22:49Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:22:56Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:23:01Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:23:05Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:23:11Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:23:14Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:23:19Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:23:27Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:26:15Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:27:05Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:27:53Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:34:51Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:34:56Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:00Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:04Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:07Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:11Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:17Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:22Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:26Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:33Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:36Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:41Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:44Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:48Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:55Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:35:59Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:36:05Z
user: login: tmancey
body: LGTM++
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:46:31Z

Review comments:
diff_hunk: @@ -208,6 +209,8 @@ AdsServiceImpl::AdsServiceImpl(
       host_content_settings_map_(host_content_settings_map),
       ads_tooltips_delegate_(std::move(ads_tooltips_delegate)),
       device_id_(std::move(device_id)),
+      new_tab_page_ad_prefetch_handler_(
path: components/brave_ads/browser/ads_service_impl.cc
user: login: tmancey
body: `new_tab_page_ad_prefetcher`
created_at: 2025-04-11T19:58:21Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
+  NewTabPageAdPrefetchHandler& operator=(const NewTabPageAdPrefetchHandler&) =
+      delete;
+  ~NewTabPageAdPrefetchHandler();
+
+  void PrefetchNewTabPageAd();
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: tmancey
body: `Prefetch`
created_at: 2025-04-11T19:59:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
+  NewTabPageAdPrefetchHandler& operator=(const NewTabPageAdPrefetchHandler&) =
+      delete;
+  ~NewTabPageAdPrefetchHandler();
+
+  void PrefetchNewTabPageAd();
+  std::optional<NewTabPageAdInfo> MaybeGetPrefetchedNewTabPageAd();
+
+ private:
+  void PrefetchNewTabPageAdCallback(
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: tmancey
body: `PrefetchCallback`
created_at: 2025-04-11T19:59:23Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
+  NewTabPageAdPrefetchHandler& operator=(const NewTabPageAdPrefetchHandler&) =
+      delete;
+  ~NewTabPageAdPrefetchHandler();
+
+  void PrefetchNewTabPageAd();
+  std::optional<NewTabPageAdInfo> MaybeGetPrefetchedNewTabPageAd();
+
+ private:
+  void PrefetchNewTabPageAdCallback(
+      base::optional_ref<const NewTabPageAdInfo> ad);
+
+  std::optional<NewTabPageAdInfo> prefetched_new_tab_page_ad_;
+  bool is_prefetching_new_tab_page_ad_ = false;
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: tmancey
body: `is_prefetching_`
created_at: 2025-04-11T19:59:41Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
+  NewTabPageAdPrefetchHandler& operator=(const NewTabPageAdPrefetchHandler&) =
+      delete;
+  ~NewTabPageAdPrefetchHandler();
+
+  void PrefetchNewTabPageAd();
+  std::optional<NewTabPageAdInfo> MaybeGetPrefetchedNewTabPageAd();
+
+ private:
+  void PrefetchNewTabPageAdCallback(
+      base::optional_ref<const NewTabPageAdInfo> ad);
+
+  std::optional<NewTabPageAdInfo> prefetched_new_tab_page_ad_;
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: tmancey
body: `prefetched_ad_`
created_at: 2025-04-11T19:59:55Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,57 @@
+/* Copyright (c) 2020 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h"
+
+#include "base/check.h"
+#include "brave/components/brave_ads/core/browser/service/ads_service.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+NewTabPageAdPrefetchHandler::NewTabPageAdPrefetchHandler(
+    AdsService& ads_service)
+    : ads_service_(ads_service) {}
+
+NewTabPageAdPrefetchHandler::~NewTabPageAdPrefetchHandler() = default;
+
+std::optional<NewTabPageAdInfo>
+NewTabPageAdPrefetchHandler::MaybeGetPrefetchedNewTabPageAd() {
+  std::optional<NewTabPageAdInfo> ad;
+  if (prefetched_new_tab_page_ad_ && prefetched_new_tab_page_ad_->IsValid()) {
+    ad = prefetched_new_tab_page_ad_;
+  }
+  prefetched_new_tab_page_ad_.reset();
+
+  return ad;
+}
+
+void NewTabPageAdPrefetchHandler::PrefetchNewTabPageAd() {
+  if (!prefetched_new_tab_page_ad_ && !is_prefetching_new_tab_page_ad_) {
+    is_prefetching_new_tab_page_ad_ = true;
+
+    ads_service_->MaybeServeNewTabPageAd(base::BindOnce(
+        &NewTabPageAdPrefetchHandler::PrefetchNewTabPageAdCallback,
+        weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
+void NewTabPageAdPrefetchHandler::PrefetchNewTabPageAdCallback(
+    base::optional_ref<const NewTabPageAdInfo> ad) {
+  CHECK(!prefetched_new_tab_page_ad_);
+
+  if (!is_prefetching_new_tab_page_ad_) {
+    // `is_prefetching_new_tab_page_ad_` can be reset during shutdown, so fail
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.cc
user: login: tmancey
body: To be updated when renaming member variable.
created_at: 2025-04-11T20:00:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,57 @@
+/* Copyright (c) 2020 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h"
+
+#include "base/check.h"
+#include "brave/components/brave_ads/core/browser/service/ads_service.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+NewTabPageAdPrefetchHandler::NewTabPageAdPrefetchHandler(
+    AdsService& ads_service)
+    : ads_service_(ads_service) {}
+
+NewTabPageAdPrefetchHandler::~NewTabPageAdPrefetchHandler() = default;
+
+std::optional<NewTabPageAdInfo>
+NewTabPageAdPrefetchHandler::MaybeGetPrefetchedNewTabPageAd() {
+  std::optional<NewTabPageAdInfo> ad;
+  if (prefetched_new_tab_page_ad_ && prefetched_new_tab_page_ad_->IsValid()) {
+    ad = prefetched_new_tab_page_ad_;
+  }
+  prefetched_new_tab_page_ad_.reset();
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.cc
user: login: tmancey
body: ```
std::optional<NewTabPageAdInfo> ad;
  if (prefetched_new_tab_page_ad_) {
    if (prefetched_new_tab_page_ad_->IsValid()) {
    ad = prefetched_new_tab_page_ad_;
  }

  prefetched_new_tab_page_ad_.reset();
}
```
created_at: 2025-04-11T20:03:10Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: tmancey
body: nit: gap above
created_at: 2025-04-11T20:07:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
+  NewTabPageAdPrefetchHandler& operator=(const NewTabPageAdPrefetchHandler&) =
+      delete;
+  ~NewTabPageAdPrefetchHandler();
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: tmancey
body: nit: gap above
created_at: 2025-04-11T20:07:51Z
author_association: COLLABORATOR
diff_hunk: @@ -257,6 +248,15 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+void AdsServiceImplIOS::MaybeServeNewTabPageAd(
+    MaybeServeNewTabPageAdCallback callback) {
+  if (!IsInitialized()) {
+    return std::move(callback).Run(/*ad*/ std::nullopt);
path: ios/browser/brave_ads/ads_service_impl_ios.mm
user: login: tmancey
body: /*ad=*/
created_at: 2025-04-11T20:10:04Z
author_association: COLLABORATOR
diff_hunk: @@ -43,7 +45,9 @@
       prefs_(prefs),
       file_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
-           base::TaskShutdownBehavior::BLOCK_SHUTDOWN})) {
+           base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
+      new_tab_page_ad_prefetch_handler_(
+          std::make_unique<NewTabPageAdPrefetchHandler>(*this)) {
path: ios/browser/brave_ads/ads_service_impl_ios.mm
user: login: tmancey
body: `/*ads_service=*/*this`
created_at: 2025-04-11T20:11:07Z
author_association: COLLABORATOR
diff_hunk: @@ -208,6 +209,8 @@ AdsServiceImpl::AdsServiceImpl(
       host_content_settings_map_(host_content_settings_map),
       ads_tooltips_delegate_(std::move(ads_tooltips_delegate)),
       device_id_(std::move(device_id)),
+      new_tab_page_ad_prefetch_handler_(
+          std::make_unique<NewTabPageAdPrefetchHandler>(*this)),
path: components/brave_ads/browser/ads_service_impl.cc
user: login: tmancey
body: `/*ads_service=*/*this`
created_at: 2025-04-11T20:11:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: Maybe we could use transition components/brave_ads/core/internal/ad_units/new_tab_page_ad/new_tab_page_ad_test_util.h to public and use this?
created_at: 2025-04-12T21:27:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: nit: `/*delegate=*/` param comment
created_at: 2025-04-12T21:28:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: nit: remove has_value because std::optional has a operator bool() overload that returns whether it contains a value or not
created_at: 2025-04-12T21:30:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: Could we not have:
```
// Act
prefetcher_->Prefetch();

// Assert
EXPECT_FALSE(prefetcher_->MaybeGetPrefetchedAd());
```
created_at: 2025-04-12T21:53:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: Could we not have:
```
// Act
prefetcher_->Prefetch();

// Assert
EXPECT_FALSE(prefetcher_->MaybeGetPrefetchedAd());
```
created_at: 2025-04-12T21:54:08Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: Could we not have:
```
// Act
prefetcher_->Prefetch();

// Assert
EXPECT_EQ(expected_ad, prefetcher_->MaybeGetPrefetchedAd());
```
created_at: 2025-04-12T21:54:46Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: When prefetching multiple times would this add multiple `served` events to the `ad_events` database table?
created_at: 2025-04-12T21:56:34Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: Your thoughts on `ad_1` rather than `result1`?
created_at: 2025-04-12T21:57:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: Your thoughts on `ad_2` rather than `result2`?
created_at: 2025-04-12T21:57:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(*result1, expected_ad);
+  ASSERT_TRUE(result2.has_value());
+  EXPECT_EQ(*result2, expected_ad);
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: Could we not have:
```
// Assert
EXPECT_EQ(expected_ad, ad_1);
EXPECT_EQ(expected_ad, ad_2);
```
created_at: 2025-04-12T21:59:08Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(*result1, expected_ad);
+  ASSERT_TRUE(result2.has_value());
+  EXPECT_EQ(*result2, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       MultiplePrefetchCallsInSequenceWithOneGetAd) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  // Second prefetch should not trigger a new ad request
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: Please see other comments.
created_at: 2025-04-12T22:00:26Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(*result1, expected_ad);
+  ASSERT_TRUE(result2.has_value());
+  EXPECT_EQ(*result2, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       MultiplePrefetchCallsInSequenceWithOneGetAd) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  // Second prefetch should not trigger a new ad request
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInParallel) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  base::RunLoop run_loop;
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad, &run_loop](MaybeServeNewTabPageAdCallback callback) {
+            base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+                FROM_HERE,
+                base::BindOnce(
+                    [](base::OnceClosure quit_closure,
+                       MaybeServeNewTabPageAdCallback callback,
+                       NewTabPageAdInfo ad) {
+                      std::move(callback).Run(ad);
+                      std::move(quit_closure).Run();
+                    },
+                    run_loop.QuitClosure(), std::move(callback), expected_ad));
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  // Second prefetch should not trigger a new ad request
+  prefetcher_->Prefetch();
+  run_loop.Run();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, GetPrefetchedAdMultipleTimes) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(result1->placement_id, expected_ad.placement_id);
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: Is there a reason we’re not comparing the whole struct?
created_at: 2025-04-12T22:02:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(*result1, expected_ad);
+  ASSERT_TRUE(result2.has_value());
+  EXPECT_EQ(*result2, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       MultiplePrefetchCallsInSequenceWithOneGetAd) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  // Second prefetch should not trigger a new ad request
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInParallel) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  base::RunLoop run_loop;
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad, &run_loop](MaybeServeNewTabPageAdCallback callback) {
+            base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+                FROM_HERE,
+                base::BindOnce(
+                    [](base::OnceClosure quit_closure,
+                       MaybeServeNewTabPageAdCallback callback,
+                       NewTabPageAdInfo ad) {
+                      std::move(callback).Run(ad);
+                      std::move(quit_closure).Run();
+                    },
+                    run_loop.QuitClosure(), std::move(callback), expected_ad));
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  // Second prefetch should not trigger a new ad request
+  prefetcher_->Prefetch();
+  run_loop.Run();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, GetPrefetchedAdMultipleTimes) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(result1->placement_id, expected_ad.placement_id);
+  EXPECT_FALSE(result2.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, CancelPrefetch) {
+  // Arrange
+  base::RunLoop run_loop;
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&run_loop](MaybeServeNewTabPageAdCallback callback) {
+            base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+                FROM_HERE, base::BindOnce(
+                               [](base::OnceClosure quit_closure,
+                                  MaybeServeNewTabPageAdCallback callback) {
+                                 std::move(callback).Run(std::nullopt);
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: /*ad=*
created_at: 2025-04-12T22:06:30Z
author_association: COLLABORATOR
diff_hunk: @@ -1429,6 +1394,28 @@ void AdsServiceImpl::ParseAndSaveNewTabPageAds(
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+void AdsServiceImpl::MaybeServeNewTabPageAd(
+    MaybeServeNewTabPageAdCallback callback) {
+  if (!bat_ads_associated_remote_.is_bound()) {
+    return std::move(callback).Run(/*ad*/ std::nullopt);
+  }
+
+  auto callback_wrapper = base::BindOnce(
+      [](MaybeServeNewTabPageAdCallback callback,
+         std::optional<base::Value::Dict> dict) {
+        if (!dict) {
+          return std::move(callback).Run(
+              /*ad*/ std::nullopt);
path: components/brave_ads/browser/ads_service_impl.cc
user: login: tmancey
body: /*ad=*/
created_at: 2025-04-12T22:06:42Z
author_association: COLLABORATOR
diff_hunk: @@ -568,4 +550,8 @@
   std::move(callback).Run(success);
 }
 
+void AdsServiceImplIOS::ResetNewTabPageAd() {
+  new_tab_page_ad_prefetcher_ = std::make_unique<NewTabPageAdPrefetcher>(*this);
path: ios/browser/brave_ads/ads_service_impl_ios.mm
user: login: tmancey
body: `/*ads_service=*/*this`
created_at: 2025-04-12T22:07:14Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: private
created_at: 2025-04-12T23:49:14Z
author_association: COLLABORATOR
diff_hunk: @@ -171,13 +172,10 @@ class AdsServiceImplIOS : public AdsService {
   void ClearAdsData(ClearDataCallback callback, bool success);
   void ClearAdsDataCallback(ClearDataCallback callback);
 
-  // TODO(https://github.com/brave/brave-browser/issues/26192) Decouple new
-  // tab page ad business logic.
path: ios/browser/brave_ads/ads_service_impl_ios.h
user: login: tmancey
body: Nice. Lets close 26192 as resolved by this issue.
created_at: 2025-04-12T23:55:18Z
author_association: COLLABORATOR
diff_hunk: @@ -203,10 +204,6 @@ class AdsServiceImpl final : public AdsService,
   void NotificationAdTimedOut(const std::string& placement_id);
   void CloseAllNotificationAds();
 
-  // TODO(https://github.com/brave/brave-browser/issues/26192) Decouple new
-  // tab page ad business logic.
path: components/brave_ads/browser/ads_service_impl.h
user: login: tmancey
body: Nice. Lets close 26192 as resolved by this issue.
created_at: 2025-04-12T23:55:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Discusssed to do it as a follow-up: https://github.com/brave/brave-browser/issues/45425
created_at: 2025-04-14T15:50:07Z
author_association: COLLABORATOR
diff_hunk: @@ -208,6 +209,8 @@ AdsServiceImpl::AdsServiceImpl(
       host_content_settings_map_(host_content_settings_map),
       ads_tooltips_delegate_(std::move(ads_tooltips_delegate)),
       device_id_(std::move(device_id)),
+      new_tab_page_ad_prefetch_handler_(
path: components/brave_ads/browser/ads_service_impl.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:22:26Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
+  NewTabPageAdPrefetchHandler& operator=(const NewTabPageAdPrefetchHandler&) =
+      delete;
+  ~NewTabPageAdPrefetchHandler();
+
+  void PrefetchNewTabPageAd();
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:22:29Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
+  NewTabPageAdPrefetchHandler& operator=(const NewTabPageAdPrefetchHandler&) =
+      delete;
+  ~NewTabPageAdPrefetchHandler();
+
+  void PrefetchNewTabPageAd();
+  std::optional<NewTabPageAdInfo> MaybeGetPrefetchedNewTabPageAd();
+
+ private:
+  void PrefetchNewTabPageAdCallback(
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:22:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
+  NewTabPageAdPrefetchHandler& operator=(const NewTabPageAdPrefetchHandler&) =
+      delete;
+  ~NewTabPageAdPrefetchHandler();
+
+  void PrefetchNewTabPageAd();
+  std::optional<NewTabPageAdInfo> MaybeGetPrefetchedNewTabPageAd();
+
+ private:
+  void PrefetchNewTabPageAdCallback(
+      base::optional_ref<const NewTabPageAdInfo> ad);
+
+  std::optional<NewTabPageAdInfo> prefetched_new_tab_page_ad_;
+  bool is_prefetching_new_tab_page_ad_ = false;
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:22:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
+  NewTabPageAdPrefetchHandler& operator=(const NewTabPageAdPrefetchHandler&) =
+      delete;
+  ~NewTabPageAdPrefetchHandler();
+
+  void PrefetchNewTabPageAd();
+  std::optional<NewTabPageAdInfo> MaybeGetPrefetchedNewTabPageAd();
+
+ private:
+  void PrefetchNewTabPageAdCallback(
+      base::optional_ref<const NewTabPageAdInfo> ad);
+
+  std::optional<NewTabPageAdInfo> prefetched_new_tab_page_ad_;
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:22:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,57 @@
+/* Copyright (c) 2020 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h"
+
+#include "base/check.h"
+#include "brave/components/brave_ads/core/browser/service/ads_service.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+NewTabPageAdPrefetchHandler::NewTabPageAdPrefetchHandler(
+    AdsService& ads_service)
+    : ads_service_(ads_service) {}
+
+NewTabPageAdPrefetchHandler::~NewTabPageAdPrefetchHandler() = default;
+
+std::optional<NewTabPageAdInfo>
+NewTabPageAdPrefetchHandler::MaybeGetPrefetchedNewTabPageAd() {
+  std::optional<NewTabPageAdInfo> ad;
+  if (prefetched_new_tab_page_ad_ && prefetched_new_tab_page_ad_->IsValid()) {
+    ad = prefetched_new_tab_page_ad_;
+  }
+  prefetched_new_tab_page_ad_.reset();
+
+  return ad;
+}
+
+void NewTabPageAdPrefetchHandler::PrefetchNewTabPageAd() {
+  if (!prefetched_new_tab_page_ad_ && !is_prefetching_new_tab_page_ad_) {
+    is_prefetching_new_tab_page_ad_ = true;
+
+    ads_service_->MaybeServeNewTabPageAd(base::BindOnce(
+        &NewTabPageAdPrefetchHandler::PrefetchNewTabPageAdCallback,
+        weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
+void NewTabPageAdPrefetchHandler::PrefetchNewTabPageAdCallback(
+    base::optional_ref<const NewTabPageAdInfo> ad) {
+  CHECK(!prefetched_new_tab_page_ad_);
+
+  if (!is_prefetching_new_tab_page_ad_) {
+    // `is_prefetching_new_tab_page_ad_` can be reset during shutdown, so fail
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:22:49Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,57 @@
+/* Copyright (c) 2020 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h"
+
+#include "base/check.h"
+#include "brave/components/brave_ads/core/browser/service/ads_service.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+NewTabPageAdPrefetchHandler::NewTabPageAdPrefetchHandler(
+    AdsService& ads_service)
+    : ads_service_(ads_service) {}
+
+NewTabPageAdPrefetchHandler::~NewTabPageAdPrefetchHandler() = default;
+
+std::optional<NewTabPageAdInfo>
+NewTabPageAdPrefetchHandler::MaybeGetPrefetchedNewTabPageAd() {
+  std::optional<NewTabPageAdInfo> ad;
+  if (prefetched_new_tab_page_ad_ && prefetched_new_tab_page_ad_->IsValid()) {
+    ad = prefetched_new_tab_page_ad_;
+  }
+  prefetched_new_tab_page_ad_.reset();
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:22:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:23:01Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,45 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_NEW_TAB_PAGE_AD_PREFETCH_HANDLER_H_
+
+#include <optional>
+
+#include "base/memory/raw_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "base/types/optional_ref.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+
+namespace brave_ads {
+
+class AdsService;
+
+class NewTabPageAdPrefetchHandler {
+ public:
+  explicit NewTabPageAdPrefetchHandler(AdsService& ads_service);
+  NewTabPageAdPrefetchHandler(const NewTabPageAdPrefetchHandler&) = delete;
+  NewTabPageAdPrefetchHandler& operator=(const NewTabPageAdPrefetchHandler&) =
+      delete;
+  ~NewTabPageAdPrefetchHandler();
path: components/brave_ads/core/browser/service/new_tab_page_ad_prefetch_handler.h
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:23:05Z
author_association: COLLABORATOR
diff_hunk: @@ -257,6 +248,15 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+void AdsServiceImplIOS::MaybeServeNewTabPageAd(
+    MaybeServeNewTabPageAdCallback callback) {
+  if (!IsInitialized()) {
+    return std::move(callback).Run(/*ad*/ std::nullopt);
path: ios/browser/brave_ads/ads_service_impl_ios.mm
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:23:11Z
author_association: COLLABORATOR
diff_hunk: @@ -43,7 +45,9 @@
       prefs_(prefs),
       file_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
-           base::TaskShutdownBehavior::BLOCK_SHUTDOWN})) {
+           base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
+      new_tab_page_ad_prefetch_handler_(
+          std::make_unique<NewTabPageAdPrefetchHandler>(*this)) {
path: ios/browser/brave_ads/ads_service_impl_ios.mm
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:23:14Z
author_association: COLLABORATOR
diff_hunk: @@ -208,6 +209,8 @@ AdsServiceImpl::AdsServiceImpl(
       host_content_settings_map_(host_content_settings_map),
       ads_tooltips_delegate_(std::move(ads_tooltips_delegate)),
       device_id_(std::move(device_id)),
+      new_tab_page_ad_prefetch_handler_(
+          std::make_unique<NewTabPageAdPrefetchHandler>(*this)),
path: components/brave_ads/browser/ads_service_impl.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:23:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:23:26Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,217 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(/*delegate=*/nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(ads_service())) {}
+
+ protected:
+  AdsServiceMock& ads_service() const { return *ads_service_; }
+
+  NewTabPageAdPrefetcher& prefetcher() const { return *prefetcher_; }
+
+  void ResetPrefetcher() {
+    prefetcher_ = std::make_unique<NewTabPageAdPrefetcher>(ads_service());
+  }
+
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+ private:
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: ```
  // Act & Assert
  EXPECT_FALSE(prefetcher().MaybeGetPrefetchedAd());
```
created_at: 2025-04-14T18:26:15Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,217 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(/*delegate=*/nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(ads_service())) {}
+
+ protected:
+  AdsServiceMock& ads_service() const { return *ads_service_; }
+
+  NewTabPageAdPrefetcher& prefetcher() const { return *prefetcher_; }
+
+  void ResetPrefetcher() {
+    prefetcher_ = std::make_unique<NewTabPageAdPrefetcher>(ads_service());
+  }
+
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+ private:
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(/*ad=*/std::nullopt);
+      }));
+
+  // Act
+  prefetcher().Prefetch();
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  const NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher().Prefetch();
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldPrefetchAdAfterGettingPrefetchedAd) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+  ASSERT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldNotPrefetchAdWhenAlreadyPrefetched) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldNotPrefetchAdWhenAnotherPrefetchIsInProgress) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  MaybeServeNewTabPageAdCallback deferred_serve_ad_callback;
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([&deferred_serve_ad_callback](
+                                      MaybeServeNewTabPageAdCallback callback) {
+        deferred_serve_ad_callback = std::move(callback);
+      }));
+
+  prefetcher().Prefetch();
+
+  // Act
+  prefetcher().Prefetch();
+  std::move(deferred_serve_ad_callback).Run(expected_ad);
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, ShouldOnlyGetPrefetchedAdOnce) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+  ASSERT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+
+  // Act
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: Do not need has value.
created_at: 2025-04-14T18:27:05Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,217 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(/*delegate=*/nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(ads_service())) {}
+
+ protected:
+  AdsServiceMock& ads_service() const { return *ads_service_; }
+
+  NewTabPageAdPrefetcher& prefetcher() const { return *prefetcher_; }
+
+  void ResetPrefetcher() {
+    prefetcher_ = std::make_unique<NewTabPageAdPrefetcher>(ads_service());
+  }
+
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+ private:
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(/*ad=*/std::nullopt);
+      }));
+
+  // Act
+  prefetcher().Prefetch();
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  const NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher().Prefetch();
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldPrefetchAdAfterGettingPrefetchedAd) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+  ASSERT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldNotPrefetchAdWhenAlreadyPrefetched) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldNotPrefetchAdWhenAnotherPrefetchIsInProgress) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  MaybeServeNewTabPageAdCallback deferred_serve_ad_callback;
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([&deferred_serve_ad_callback](
+                                      MaybeServeNewTabPageAdCallback callback) {
+        deferred_serve_ad_callback = std::move(callback);
+      }));
+
+  prefetcher().Prefetch();
+
+  // Act
+  prefetcher().Prefetch();
+  std::move(deferred_serve_ad_callback).Run(expected_ad);
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, ShouldOnlyGetPrefetchedAdOnce) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+  ASSERT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+
+  // Act
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, CancelPrefetch) {
+  // Arrange
+  MaybeServeNewTabPageAdCallback deferred_serve_ad_callback;
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: tmancey
body: deferred_maybe_serve_ad_callback
created_at: 2025-04-14T18:27:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:34:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:34:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:00Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:03Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:07Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:11Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(*result1, expected_ad);
+  ASSERT_TRUE(result2.has_value());
+  EXPECT_EQ(*result2, expected_ad);
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(*result1, expected_ad);
+  ASSERT_TRUE(result2.has_value());
+  EXPECT_EQ(*result2, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       MultiplePrefetchCallsInSequenceWithOneGetAd) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  // Second prefetch should not trigger a new ad request
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:26Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(*result1, expected_ad);
+  ASSERT_TRUE(result2.has_value());
+  EXPECT_EQ(*result2, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       MultiplePrefetchCallsInSequenceWithOneGetAd) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  // Second prefetch should not trigger a new ad request
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInParallel) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  base::RunLoop run_loop;
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad, &run_loop](MaybeServeNewTabPageAdCallback callback) {
+            base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+                FROM_HERE,
+                base::BindOnce(
+                    [](base::OnceClosure quit_closure,
+                       MaybeServeNewTabPageAdCallback callback,
+                       NewTabPageAdInfo ad) {
+                      std::move(callback).Run(ad);
+                      std::move(quit_closure).Run();
+                    },
+                    run_loop.QuitClosure(), std::move(callback), expected_ad));
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  // Second prefetch should not trigger a new ad request
+  prefetcher_->Prefetch();
+  run_loop.Run();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, GetPrefetchedAdMultipleTimes) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(result1->placement_id, expected_ad.placement_id);
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:33Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(std::nullopt);
+      }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(result.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInSequence) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(*result1, expected_ad);
+  ASSERT_TRUE(result2.has_value());
+  EXPECT_EQ(*result2, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       MultiplePrefetchCallsInSequenceWithOneGetAd) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  // Second prefetch should not trigger a new ad request
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, MultiplePrefetchCallsInParallel) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  base::RunLoop run_loop;
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad, &run_loop](MaybeServeNewTabPageAdCallback callback) {
+            base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+                FROM_HERE,
+                base::BindOnce(
+                    [](base::OnceClosure quit_closure,
+                       MaybeServeNewTabPageAdCallback callback,
+                       NewTabPageAdInfo ad) {
+                      std::move(callback).Run(ad);
+                      std::move(quit_closure).Run();
+                    },
+                    run_loop.QuitClosure(), std::move(callback), expected_ad));
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  // Second prefetch should not trigger a new ad request
+  prefetcher_->Prefetch();
+  run_loop.Run();
+  std::optional<NewTabPageAdInfo> result = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result.has_value());
+  EXPECT_EQ(*result, expected_ad);
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, GetPrefetchedAdMultipleTimes) {
+  // Arrange
+  NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher_->Prefetch();
+  std::optional<NewTabPageAdInfo> result1 = prefetcher_->MaybeGetPrefetchedAd();
+  std::optional<NewTabPageAdInfo> result2 = prefetcher_->MaybeGetPrefetchedAd();
+
+  // Assert
+  ASSERT_TRUE(result1.has_value());
+  EXPECT_EQ(result1->placement_id, expected_ad.placement_id);
+  EXPECT_FALSE(result2.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, CancelPrefetch) {
+  // Arrange
+  base::RunLoop run_loop;
+  EXPECT_CALL(*ads_service_, MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&run_loop](MaybeServeNewTabPageAdCallback callback) {
+            base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+                FROM_HERE, base::BindOnce(
+                               [](base::OnceClosure quit_closure,
+                                  MaybeServeNewTabPageAdCallback callback) {
+                                 std::move(callback).Run(std::nullopt);
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:36Z
author_association: COLLABORATOR
diff_hunk: @@ -1429,6 +1394,28 @@ void AdsServiceImpl::ParseAndSaveNewTabPageAds(
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+void AdsServiceImpl::MaybeServeNewTabPageAd(
+    MaybeServeNewTabPageAdCallback callback) {
+  if (!bat_ads_associated_remote_.is_bound()) {
+    return std::move(callback).Run(/*ad*/ std::nullopt);
+  }
+
+  auto callback_wrapper = base::BindOnce(
+      [](MaybeServeNewTabPageAdCallback callback,
+         std::optional<base::Value::Dict> dict) {
+        if (!dict) {
+          return std::move(callback).Run(
+              /*ad*/ std::nullopt);
path: components/brave_ads/browser/ads_service_impl.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:41Z
author_association: COLLABORATOR
diff_hunk: @@ -568,4 +550,8 @@
   std::move(callback).Run(success);
 }
 
+void AdsServiceImplIOS::ResetNewTabPageAd() {
+  new_tab_page_ad_prefetcher_ = std::make_unique<NewTabPageAdPrefetcher>(*this);
path: ios/browser/brave_ads/ads_service_impl_ios.mm
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,231 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/task/sequenced_task_runner.h"
+#include "base/test/task_environment.h"
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(*ads_service_)) {}
+
+ protected:
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,217 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(/*delegate=*/nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(ads_service())) {}
+
+ protected:
+  AdsServiceMock& ads_service() const { return *ads_service_; }
+
+  NewTabPageAdPrefetcher& prefetcher() const { return *prefetcher_; }
+
+  void ResetPrefetcher() {
+    prefetcher_ = std::make_unique<NewTabPageAdPrefetcher>(ads_service());
+  }
+
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+ private:
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:55Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,217 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(/*delegate=*/nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(ads_service())) {}
+
+ protected:
+  AdsServiceMock& ads_service() const { return *ads_service_; }
+
+  NewTabPageAdPrefetcher& prefetcher() const { return *prefetcher_; }
+
+  void ResetPrefetcher() {
+    prefetcher_ = std::make_unique<NewTabPageAdPrefetcher>(ads_service());
+  }
+
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+ private:
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(/*ad=*/std::nullopt);
+      }));
+
+  // Act
+  prefetcher().Prefetch();
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  const NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher().Prefetch();
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldPrefetchAdAfterGettingPrefetchedAd) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+  ASSERT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldNotPrefetchAdWhenAlreadyPrefetched) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldNotPrefetchAdWhenAnotherPrefetchIsInProgress) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  MaybeServeNewTabPageAdCallback deferred_serve_ad_callback;
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([&deferred_serve_ad_callback](
+                                      MaybeServeNewTabPageAdCallback callback) {
+        deferred_serve_ad_callback = std::move(callback);
+      }));
+
+  prefetcher().Prefetch();
+
+  // Act
+  prefetcher().Prefetch();
+  std::move(deferred_serve_ad_callback).Run(expected_ad);
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, ShouldOnlyGetPrefetchedAdOnce) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+  ASSERT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+
+  // Act
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:35:58Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,217 @@
+/* Copyright (c) 2025 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
+
+#include <memory>
+#include <optional>
+
+#include "brave/components/brave_ads/browser/ads_service_mock.h"
+#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+// npm run test -- brave_unit_tests --filter=BraveAds*
+
+namespace brave_ads {
+
+class BraveAdsNewTabPageAdPrefetcherTest : public ::testing::Test {
+ public:
+  BraveAdsNewTabPageAdPrefetcherTest()
+      : ads_service_(std::make_unique<AdsServiceMock>(/*delegate=*/nullptr)),
+        prefetcher_(std::make_unique<NewTabPageAdPrefetcher>(ads_service())) {}
+
+ protected:
+  AdsServiceMock& ads_service() const { return *ads_service_; }
+
+  NewTabPageAdPrefetcher& prefetcher() const { return *prefetcher_; }
+
+  void ResetPrefetcher() {
+    prefetcher_ = std::make_unique<NewTabPageAdPrefetcher>(ads_service());
+  }
+
+  NewTabPageAdInfo BuildNewTabPageAd() {
+    NewTabPageAdInfo ad;
+    ad.type = mojom::AdType::kNewTabPageAd;
+    ad.placement_id = "placement_id";
+    ad.creative_instance_id = "creative_instance_id";
+    ad.creative_set_id = "creative_set_id";
+    ad.campaign_id = "campaign_id";
+    ad.advertiser_id = "advertiser_id";
+    ad.segment = "segment";
+    ad.target_url = GURL("https://brave.com");
+    ad.company_name = "company_name";
+    ad.alt = "alt";
+    return ad;
+  }
+
+ private:
+  std::unique_ptr<AdsServiceMock> ads_service_;
+  std::unique_ptr<NewTabPageAdPrefetcher> prefetcher_;
+};
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, NoAdWithoutPrefetch) {
+  // Arrange & Act
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, Prefetch) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchFailed) {
+  // Arrange
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([](MaybeServeNewTabPageAdCallback callback) {
+        std::move(callback).Run(/*ad=*/std::nullopt);
+      }));
+
+  // Act
+  prefetcher().Prefetch();
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, PrefetchInvalidAd) {
+  // Arrange
+  const NewTabPageAdInfo invalid_ad;
+  ASSERT_FALSE(invalid_ad.IsValid());
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&invalid_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(invalid_ad);
+          }));
+
+  // Act
+  prefetcher().Prefetch();
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldPrefetchAdAfterGettingPrefetchedAd) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .Times(2)
+      .WillRepeatedly(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+  ASSERT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldNotPrefetchAdWhenAlreadyPrefetched) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+
+  // Act
+  prefetcher().Prefetch();
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest,
+       ShouldNotPrefetchAdWhenAnotherPrefetchIsInProgress) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  MaybeServeNewTabPageAdCallback deferred_serve_ad_callback;
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke([&deferred_serve_ad_callback](
+                                      MaybeServeNewTabPageAdCallback callback) {
+        deferred_serve_ad_callback = std::move(callback);
+      }));
+
+  prefetcher().Prefetch();
+
+  // Act
+  prefetcher().Prefetch();
+  std::move(deferred_serve_ad_callback).Run(expected_ad);
+
+  // Assert
+  EXPECT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, ShouldOnlyGetPrefetchedAdOnce) {
+  // Arrange
+  const NewTabPageAdInfo expected_ad = BuildNewTabPageAd();
+
+  EXPECT_CALL(ads_service(), MaybeServeNewTabPageAd(::testing::_))
+      .WillOnce(::testing::Invoke(
+          [&expected_ad](MaybeServeNewTabPageAdCallback callback) {
+            std::move(callback).Run(expected_ad);
+          }));
+
+  prefetcher().Prefetch();
+  ASSERT_EQ(expected_ad, prefetcher().MaybeGetPrefetchedAd());
+
+  // Act
+  std::optional<NewTabPageAdInfo> ad = prefetcher().MaybeGetPrefetchedAd();
+
+  // Assert
+  EXPECT_FALSE(ad.has_value());
+}
+
+TEST_F(BraveAdsNewTabPageAdPrefetcherTest, CancelPrefetch) {
+  // Arrange
+  MaybeServeNewTabPageAdCallback deferred_serve_ad_callback;
path: components/brave_ads/browser/ad_units/new_tab_page_ad/new_tab_page_ad_prefetcher_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-04-14T18:36:05Z
author_association: COLLABORATOR

PR comments:
user: login: aseren
created_at: 2025-04-11T20:50:38Z
body: Need to add tests
author_association: COLLABORATOR
user: login: brave-builds
created_at: 2025-04-15T00:04:01Z
body: Released in [v1.79.64](https://github.com/brave/brave-browser/releases/tag/v1.79.64)
author_association: COLLABORATOR
user: login: btlechowski
created_at: 2025-04-15T20:04:02Z
body: Verified with


Brave | 1.79.65 Chromium: 135.0.7049.84 (Official Build) nightly (64-bit)
-- | --
Revision | 8d8f7e9ded2283015a31ac48ac651ead68303b56
OS | Linux

#### Test case 1

<details>

Fresh install
Open NTP until NTT is shown
Make sure that views p3a metric for NTT creative is recorded

![image](https://github.com/user-attachments/assets/e6a4908b-8473-4132-b5b2-e2c2199e8210)


</details>



#### Test case 2

<details>

Fresh install
Join Brave Rewards
Open NTP until NTT is shown
Make sure viewed impression for NTT creative is triggered

view
```
[43563:43563:0415/220044.939041:VERBOSE3:new_tab_page_ad_handler.cc(161)] Viewed new tab page ad impression with placement id 96a924f7-472f-4298-a6f0-dd5383e3fbbb and creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063
[43563:43563:0415/220045.004436:VERBOSE1:confirmations.cc(39)] Confirming kViewedImpression for kNewTabPageAd with transaction id e543c79e-b879-49f5-b403-9b37d39aa584 and creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063

```

click
```
[43563:43563:0415/220209.713161:VERBOSE3:new_tab_page_ad_handler.cc(174)] Clicked new tab page ad with placement id 96a924f7-472f-4298-a6f0-dd5383e3fbbb and creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063

[43563:43563:0415/220209.731834:VERBOSE9:confirmation_state_manager.cc(79)] Saving confirmation state
[43563:43563:0415/220209.739327:VERBOSE9:confirmation_state_manager.cc(87)] Successfully saved confirmation state
[43563:43563:0415/220209.741977:VERBOSE3:ad_history_database_table_util.cc(33)] Successfully saved ad history
[43563:43563:0415/220209.750591:VERBOSE3:account.cc(165)] Successfully processed deposit for kNewTabPageAd with creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063 and kClicked valued at 0
[43563:43563:0415/220209.750939:VERBOSE1:confirmations.cc(39)] Confirming kClicked for kNewTabPageAd with transaction id 5840db5d-3e58-4718-bb18-4f2589af18c5 and creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063
[43563:43563:0415/220209.758316:VERBOSE9:confirmation_state_manager.cc(79)] Saving confirmation state


```

interaction
```
[43563:43563:0415/220209.726466:VERBOSE3:account.cc(165)] Successfully processed deposit for kNewTabPageAd with creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063 and kInteraction valued at 0
[43563:43563:0415/220209.726538:VERBOSE1:confirmations.cc(39)] Confirming kInteraction for kNewTabPageAd with transaction id ed70a26f-cc48-4f9b-8030-4bdd73d2acb4 and creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063
[43563:43563:0415/220209.760929:VERBOSE1:confirmations.cc(72)] Successfully added kInteraction confirmation for kNewTabPageAd with transaction id ed70a26f-cc48-4f9b-8030-4bdd73d2acb4 and creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063 to the queue
```

landed
```
[43563:43563:0415/220328.364193:VERBOSE1:ad_handler.cc(177)] Landed on page for https://brave.com/ on tab id 335164393
[43563:43563:0415/220328.394676:VERBOSE3:account.cc(165)] Successfully processed deposit for kNewTabPageAd with creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063 and kLanded valued at 0
[43563:43563:0415/220328.394744:VERBOSE1:confirmations.cc(39)] Confirming kLanded for kNewTabPageAd with transaction id 971a18c9-012e-4c41-9461-4735d6dac6e8 and creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063
[43563:43563:0415/220328.398934:VERBOSE9:confirmation_state_manager.cc(79)] Saving confirmation state
[43563:43563:0415/220328.406093:VERBOSE9:confirmation_state_manager.cc(87)] Successfully saved confirmation state
[43563:43563:0415/220328.412250:VERBOSE1:confirmations.cc(72)] Successfully added kLanded confirmation for kNewTabPageAd with transaction id 971a18c9-012e-4c41-9461-4735d6dac6e8 and creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063 to the queue
[43563:43563:0415/220328.414162:VERBOSE1:confirmations.cc(83)] Process kLanded confirmation for kNewTabPageAd with transaction id 971a18c9-012e-4c41-9461-4735d6dac6e8 and creative instance id e3b7b153-a3e2-4df7-9df8-88decc5dd063 in 0 hours, 0 minutes, 5 seconds at 22:03:33.412

```



</details>
author_association: NONE
user: login: btlechowski
created_at: 2025-04-15T21:23:07Z
body: Verification passed on Brave v1.79.65 Chromium: 135.0.7049.84 on Lenovo TB-8506FS (Android 11.0)


#### Test case 1

<details>

Fresh install
Open NTP until NTT is shown
Make sure that views p3a metric for NTT creative is recorded

![image](https://github.com/user-attachments/assets/132db517-aae3-4d3f-bfc4-9ae4aea807f5)



</details>



#### Test case 2

<details>

Fresh install
Join Brave Rewards
Open NTP until NTT is shown
Make sure viewed impression for NTT creative is triggered

view
```
23:14:14.214  V  [VERBOSE3:new_tab_page_ad_handler.cc(161)] Viewed new tab page ad impression with placement id 697dfae8-2ecd-4e50-9d29-618df88c67e0 and creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0
23:14:14.234  V  [VERBOSE3:ad_history_database_table_util.cc(33)] Successfully saved ad history
23:14:14.251  V  [VERBOSE3:account.cc(165)] Successfully processed deposit for kNewTabPageAd with creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0 and kViewedImpression valued at 0
23:14:14.251  V  [VERBOSE1:confirmations.cc(39)] Confirming kViewedImpression for kNewTabPageAd with transaction id c9e24bc3-2d37-4b8f-9b51-3b4a69cf5bda and creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0
23:14:14.281  V  [VERBOSE1:confirmations.cc(72)] Successfully added kViewedImpression confirmation for kNewTabPageAd with transaction id c9e24bc3-2d37-4b8f-9b51-3b4a69cf5bda and creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0 to the queue
23:14:14.285  V  [VERBOSE1:confirmations.cc(83)] Process kViewedImpression confirmation for kNewTabPageAd with transaction id c9e24bc3-2d37-4b8f-9b51-3b4a69cf5bda and creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0 in 0 hours, 0 minutes, 5 seconds at 23:14:19.281


```

click
```
23:15:49.486  V  [VERBOSE3:new_tab_page_ad_handler.cc(174)] Clicked new tab page ad with placement id 697dfae8-2ecd-4e50-9d29-618df88c67e0 and creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0
23:15:49.501  W  [WARNING:ev_root_ca_metadata.cc(119)] Not implemented
23:15:49.508  V  [VERBOSE3:ad_history_database_table_util.cc(33)] Successfully saved ad history
23:15:49.521  V  [VERBOSE3:account.cc(165)] Successfully processed deposit for kNewTabPageAd with creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0 and kClicked valued at 0
23:15:49.521  V  [VERBOSE1:confirmations.cc(39)] Confirming kClicked for kNewTabPageAd with transaction id ce255906-52a7-430a-ad80-15b5c224d1d0 and creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0
23:15:49.551  V  [VERBOSE1:confirmations.cc(72)] Successfully added kClicked confirmation for kNewTabPageAd with transaction id ce255906-52a7-430a-ad80-15b5c224d1d0 and creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0 to the queue
23:15:49.556  V  [VERBOSE1:confirmations.cc(83)] Process kClicked confirmation for kNewTabPageAd with transaction id ce255906-52a7-430a-ad80-15b5c224d1d0 and creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0 in 0 hours, 0 minutes, 5 seconds at 23:15:54.551
```

landed
```
23:15:54.923  V  [VERBOSE1:ad_handler.cc(177)] Landed on page for https://www.bmwusa.com/?brave-creative-id=026a190e-022e-40d1-8ed5-e8f152dfa9b0&source=image1 on tab id 31160093
23:15:54.948  V  [VERBOSE3:account.cc(165)] Successfully processed deposit for kNewTabPageAd with creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0 and kLanded valued at 0
23:15:54.949  V  [VERBOSE1:confirmations.cc(39)] Confirming kLanded for kNewTabPageAd with transaction id 57be7ce7-f675-47a0-b2a1-b3d627afea1c and creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0
23:15:54.963  W  [WARNING:ev_root_ca_metadata.cc(119)] Not implemented
23:15:54.980  V  [VERBOSE1:confirmations.cc(72)] Successfully added kLanded confirmation for kNewTabPageAd with transaction id 57be7ce7-f675-47a0-b2a1-b3d627afea1c and creative instance id 026a190e-022e-40d1-8ed5-e8f152dfa9b0 to the queue

```



</details>
author_association: NONE
user: login: btlechowski
created_at: 2025-04-15T23:46:43Z
body: Verification passed on v1.79.65 on iPhone 13 Pro Max (iOS 18.4)


#### Test case 1 - unable to test on iOS

<details>

Fresh install
Open NTP until NTT is shown
Make sure that views p3a metric for NTT creative is recorded

Unable to test on iOS


</details>



#### Test case 2

<details>

Fresh install
Join Brave Rewards
Open NTP until NTT is shown
Make sure viewed impression for NTT creative is triggered

view
```
info	01:44:02.176214+0200	Client	[ads] Viewed new tab page ad impression with placement id 29AFF855-BB83-4469-8A86-0298A1FAB94D and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8
info	01:44:02.177361+0200	Client	[ads] Successfully saved ad history
info	01:44:02.180747+0200	Client	[ads] Successfully processed deposit for kNewTabPageAd with creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8 and kViewedImpression valued at 0
info	01:44:02.181025+0200	Client	[ads] Confirming kViewedImpression for kNewTabPageAd with transaction id 86612f2b-d14a-48de-9615-98568fdf0a1d and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8
info	01:44:02.194844+0200	Client	[ads] Successfully added kViewedImpression confirmation for kNewTabPageAd with transaction id 86612f2b-d14a-48de-9615-98568fdf0a1d and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8 to the queue
info	01:44:02.195218+0200	Client	[ads] Process kViewedImpression confirmation for kNewTabPageAd with transaction id 86612f2b-d14a-48de-9615-98568fdf0a1d and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8 in 0 hours, 0 minutes, 5 seconds at 01:44:07.191
info	01:44:07.192732+0200	Client	[ads] Redeem reward confirmation
info	01:44:07.193196+0200	Client	[ads] Create reward confirmation
info	01:44:07.193378+0200	Client	[ads] URL Request:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/86612f2b-d14a-48de-9615-98568fdf0a1d/eyJzaWduYXR1cmUiOiJ4dHIrc0hvN3k0NHBpMUtCdlVxbWhSeUl6Rk1UZ0YyQVFzVE9ZWk9hVm1maE82cjJiT1FDbHUxaUlPb1JpNm53MnV2d3FqMFRZMm1hK2pJdTk3Zzd4dz09IiwidCI6IlVia25CUjM5YWM5cXpZNzhQcDlmTWxnVkttV2hDdFhGOXgxMEw1MnJsUDJZdmhQN2x4STJXSjZ4ZW5tQzBDRlo4TkZDa3BhTEw2Nk9YdXArRGJqL3lRPT0ifQ==
  Content: {"blindedPaymentTokens":["gl3R1kXgQRy0s9U1UIzFGpeNMt0npwCnZSsYHCMYVUo="],"buildChannel":"nightly","catalog":[{"id":"GijrzUpV98S7yV8G5l+ttN5vJCrfO5/dCo0FuxUH+f8="}],"createdAtTimestamp":"2025-04-15T23:00:00.000Z","creativeInstanceId":"8ea7efdf-00cb-4c66-981d-31216b82b2f8","platform":"ios","publicKey":"hLaC3RQw5++hx8zaT+47CA8r8cOlAm+ygeky/ci6T1s=","rotatingHash":"4eQKw5nQ7mSgFiZOYlu5Qx6/SUlBo8zwuin1QikVsu8=","segment":"untargeted","studies":[],"systemTimestamp":"2025-04-15T23:00:00.000Z","transactionId":"86612f2b-d14a-48de-9615-98568fdf0a1d","type":"view","versionNumber":"135.0.7049.84"}
  Content Type: applica<…>
info	01:44:08.052314+0200	Client	[ads] URL Response:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/86612f2b-d14a-48de-9615-98568fdf0a1d/eyJzaWduYXR1cmUiOiJ4dHIrc0hvN3k0NHBpMUtCdlVxbWhSeUl6Rk1UZ0YyQVFzVE9ZWk9hVm1maE82cjJiT1FDbHUxaUlPb1JpNm53MnV2d3FqMFRZMm1hK2pJdTk3Zzd4dz09IiwidCI6IlVia25CUjM5YWM5cXpZNzhQcDlmTWxnVkttV2hDdFhGOXgxMEw1MnJsUDJZdmhQN2x4STJXSjZ4ZW5tQzBDRlo4TkZDa3BhTEw2Nk9YdXArRGJqL3lRPT0ifQ==
  Response Status Code: 201
  Response: {"id":"86612f2b-d14a-48de-9615-98568fdf0a1d","payload":{},"type":"view","createdAt":"2025-04-15T23:44:07.918Z","modifiedAt":"2025-04-15T23:44:07.918Z","creativeInstanceId":"8ea7efdf-00cb-4c66-981d-31216b82b2f8"}
info	01:44:08.052419+0200	Client	[ads] Fetch payment token in 3 s
info	01:44:08.987849+0200	Client	[ads] Scheduled database maintenance in 24 hours, 0 minutes, 0 seconds at 01:44:08.981
info	01:44:08.988833+0200	Client	[ads] Successfully purged expired ad events
info	01:44:08.988903+0200	Client	[ads] Successfully purged expired ad history
info	01:44:08.989054+0200	Client	[ads] Successfully purged expired creative set conversions
info	01:44:08.989192+0200	Client	[ads] Successfully purged expired deposits
info	01:44:08.989399+0200	Client	[ads] Successfully purged expired transactions
info	01:44:11.054421+0200	Client	[ads] Fetch payment token
info	01:44:11.054491+0200	Client	[ads] URL Request:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/86612f2b-d14a-48de-9615-98568fdf0a1d/paymentToken
  Method: kGet
info	01:44:11.291620+0200	Client	[ads] URL Response:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/86612f2b-d14a-48de-9615-98568fdf0a1d/paymentToken
  Response Status Code: 200
  Response: {"id":"86612f2b-d14a-48de-9615-98568fdf0a1d","type":"view","createdAt":"2025-04-15T23:44:07.918Z","modifiedAt":"2025-04-15T23:44:10.351Z","creativeInstanceId":"8ea7efdf-00cb-4c66-981d-31216b82b2f8","paymentToken":{"publicKey":"vlGTZ+NzScItHa2Z4YszNVzvOC11WYmiQ1zLZ84cl20=","batchProof":"jPUyzH98HfHCIy9tgZp0xnbUqP9PnA8Y0aLRVAr2EAkTuxNKChH/fCTPDEwlxX5+dsXBeNEwTmW9jKDw8CoBDA==","signedTokens":["QGQ+VdxHp3j0dLYoXGjdbTff3bKY4UhKF3xoEOvggks="]}}
info	01:44:11.302864+0200	Client	[ads] You have 1 payment tokens which will be redeemed in 0 hours, 0 minutes, 27 seconds at 01:44:37.810
info	01:44:11.305913+0200	Client	[ads] Successfully processed kViewedImpression confirmation for kNewTabPageAd with transaction id 86612f2b-d14a-48de-9615-98568fdf0a1d and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8

```

click
```
info	01:44:26.690267+0200	Client	[ads] Clicked new tab page ad with placement id 29AFF855-BB83-4469-8A86-0298A1FAB94D and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8
debug	01:44:26.692009+0200	Client	Set rule lists:
<private>
info	01:44:26.694914+0200	Client	[ads] Successfully saved ad history
info	01:44:26.696288+0200	Client	[ads] Successfully processed deposit for kNewTabPageAd with creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8 and kClicked valued at 0
info	01:44:26.696336+0200	Client	[ads] Confirming kClicked for kNewTabPageAd with transaction id 3a2af9ed-606b-4942-bcfb-d8a574d4a1e5 and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8
info	01:44:26.707084+0200	Client	[ads] Successfully added kClicked confirmation for kNewTabPageAd with transaction id 3a2af9ed-606b-4942-bcfb-d8a574d4a1e5 and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8 to the queue
info	01:44:26.707473+0200	Client	[ads] Process kClicked confirmation for kNewTabPageAd with transaction id 3a2af9ed-606b-4942-bcfb-d8a574d4a1e5 and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8 in 0 hours, 0 minutes, 5 seconds at 01:44:31.700
info	01:44:26.983735+0200	Client	[ads] Tab id 70987426 did change
info	01:44:26.983761+0200	Client	[ads] Triggered event: 11 (0:0:900 s)
info	01:44:26.984985+0200	Client	[ads] Extracting purchase intent signal from visited URL
info	01:44:26.985012+0200	Client	[ads] No purchase intent matches found
info	01:44:26.985038+0200	Client	[ads] Maybe land on page for https://www.bmwusa.com/?brave-creative-id=8ea7efdf-00cb-4c66-981d-31216b82b2f8&source=image2 in 5 s
debug	01:44:27.153918+0200	Client	Page contains no metadata!
debug	01:44:27.589503+0200	Client	Fetched Favicon: <private>, for page: <private>
debug	01:44:27.590008+0200	Client	Fetched Favicon: <private>, for page: <private>
info	01:44:27.853791+0200	Client	Publisher list is empty
info	01:44:27.864765+0200	Client	Search Backup results are ready but the page has not been loaded yet
info	01:44:27.866755+0200	Client	[ads] Tab id 70987426 HTML content changed
info	01:44:27.866787+0200	Client	[ads] Checking for creative set conversions
info	01:44:27.867472+0200	Client	[ads] Tab id 70987426 text content changed
info	01:44:27.874434+0200	Client	[ads] Classified text with the top segment as personal finance-credit & debt & loans
info	01:44:27.879208+0200	Client	[ads] There are no matching creative set conversions
info	01:44:31.702913+0200	Client	[ads] Redeem reward confirmation
info	01:44:31.703882+0200	Client	[ads] Create reward confirmation
info	01:44:31.704535+0200	Client	[ads] URL Request:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/3a2af9ed-606b-4942-bcfb-d8a574d4a1e5/eyJzaWduYXR1cmUiOiJRVnlJSUZvcTM2NGRSZjlJY3JtQVVBQlJjeVdpYzhzNVFia3hmVHh0dWFvMWl0am5zVjRBZWVMUWxhU2kycjAzU0pGNG1aRmNveDlWVXdwTlRTRzNzUT09IiwidCI6IlR5SzIyTkpmN0Y0Y0NVVWk5ZFIra2xGZURFdlZPOTUxYzMwNlFvZGZWcjk2a1BOSjJTRXA3U1l2andnMkgrZUlWWWxMalpZUUNMNCtEUUNRZStxSk5nPT0ifQ==
  Content: {"blindedPaymentTokens":["WPY5OH0GBYzWjrujy4ZkqoPlwWzLcngT+hdC4SqO3QU="],"buildChannel":"nightly","catalog":[{"id":"GijrzUpV98S7yV8G5l+ttN5vJCrfO5/dCo0FuxUH+f8="}],"createdAtTimestamp":"2025-04-15T23:00:00.000Z","creativeInstanceId":"8ea7efdf-00cb-4c66-981d-31216b82b2f8","platform":"ios","publicKey":"hLaC3RQw5++hx8zaT+47CA8r8cOlAm+ygeky/ci6T1s=","rotatingHash":"4eQKw5nQ7mSgFiZOYlu5Qx6/SUlBo8zwuin1QikVsu8=","segment":"untargeted","studies":[],"systemTimestamp":"2025-04-15T23:00:00.000Z","transactionId":"3a2af9ed-606b-4942-bcfb-d8a574d4a1e5","type":"click","versionNumber":"135.0.7049.84"}
  Content Type: applic<…>
info	01:44:31.994036+0200	Client	[ads] Landed on page for https://www.bmwusa.com/?brave-creative-id=8ea7efdf-00cb-4c66-981d-31216b82b2f8&source=image2 on tab id 70987426
info	01:44:32.000401+0200	Client	[ads] Successfully processed deposit for kNewTabPageAd with creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8 and kLanded valued at 0
info	01:44:32.000535+0200	Client	[ads] Confirming kLanded for kNewTabPageAd with transaction id abe74fd6-432b-488c-9c46-71c22a8740de and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8
info	01:44:32.016433+0200	Client	[ads] Successfully added kLanded confirmation for kNewTabPageAd with transaction id abe74fd6-432b-488c-9c46-71c22a8740de and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8 to the queue
info	01:44:32.482597+0200	Client	[ads] URL Response:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/3a2af9ed-606b-4942-bcfb-d8a574d4a1e5/eyJzaWduYXR1cmUiOiJRVnlJSUZvcTM2NGRSZjlJY3JtQVVBQlJjeVdpYzhzNVFia3hmVHh0dWFvMWl0am5zVjRBZWVMUWxhU2kycjAzU0pGNG1aRmNveDlWVXdwTlRTRzNzUT09IiwidCI6IlR5SzIyTkpmN0Y0Y0NVVWk5ZFIra2xGZURFdlZPOTUxYzMwNlFvZGZWcjk2a1BOSjJTRXA3U1l2andnMkgrZUlWWWxMalpZUUNMNCtEUUNRZStxSk5nPT0ifQ==
  Response Status Code: 201
  Response: {"id":"3a2af9ed-606b-4942-bcfb-d8a574d4a1e5","payload":{},"type":"click","createdAt":"2025-04-15T23:44:32.299Z","modifiedAt":"2025-04-15T23:44:32.299Z","creativeInstanceId":"8ea7efdf-00cb-4c66-981d-31216b82b2f8"}
info	01:44:32.482803+0200	Client	[ads] Fetch payment token in 3 s
info	01:44:35.484595+0200	Client	[ads] Fetch payment token
info	01:44:35.485001+0200	Client	[ads] URL Request:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/3a2af9ed-606b-4942-bcfb-d8a574d4a1e5/paymentToken
  Method: kGet
info	01:44:35.759457+0200	Client	[ads] URL Response:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/3a2af9ed-606b-4942-bcfb-d8a574d4a1e5/paymentToken
  Response Status Code: 200
  Response: {"id":"3a2af9ed-606b-4942-bcfb-d8a574d4a1e5","type":"click","createdAt":"2025-04-15T23:44:32.299Z","modifiedAt":"2025-04-15T23:44:34.476Z","creativeInstanceId":"8ea7efdf-00cb-4c66-981d-31216b82b2f8","paymentToken":{"publicKey":"vlGTZ+NzScItHa2Z4YszNVzvOC11WYmiQ1zLZ84cl20=","batchProof":"bG1+EVKJ5QfPk8nYN5WgsKt78NWK6bH8CHZUGwiajAjYwhLVxyrtehcXDkOcJ0ZlLZvDWxsUlmF5EG1ISo9xAw==","signedTokens":["7l/IdvtIgoo4cbrzQ6xJ0g5Sx93JtxFT72Vz+UHSxWE="]}}
info	01:44:35.774060+0200	Client	[ads] You have 2 payment tokens which will be redeemed in 0 hours, 0 minutes, 2 seconds at 01:44:37.810
info	01:44:35.777681+0200	Client	[ads] Successfully processed kClicked confirmation for kNewTabPageAd with transaction id 3a2af9ed-606b-4942-bcfb-d8a574d4a1e5 and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8
```

landed
```
info	01:44:31.994036+0200	Client	[ads] Landed on page for https://www.bmwusa.com/?brave-creative-id=8ea7efdf-00cb-4c66-981d-31216b82b2f8&source=image2 on tab id 70987426
info	01:44:32.000401+0200	Client	[ads] Successfully processed deposit for kNewTabPageAd with creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8 and kLanded valued at 0
info	01:44:32.000535+0200	Client	[ads] Confirming kLanded for kNewTabPageAd with transaction id abe74fd6-432b-488c-9c46-71c22a8740de and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8
info	01:44:32.016433+0200	Client	[ads] Successfully added kLanded confirmation for kNewTabPageAd with transaction id abe74fd6-432b-488c-9c46-71c22a8740de and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8 to the queue
info	01:44:35.783360+0200	Client	[ads] Process kLanded confirmation for kNewTabPageAd with transaction id abe74fd6-432b-488c-9c46-71c22a8740de and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8 in 0 hours, 0 minutes, 5 seconds at 01:44:40.781
info	01:44:37.811391+0200	Client	[ads] Redeem payment tokens
info	01:44:37.811844+0200	Client	[ads] URL Request:
  URL: https://mywallet.ads.bravesoftware.com/v3/confirmation/payment/522f8de7-ea59-4391-968c-7e29ceb678e1
  Content: {"payload":"{\"paymentId\":\"522f8de7-ea59-4391-968c-7e29ceb678e1\"}","paymentCredentials":[{"confirmationType":"view","credential":{"signature":"hin8f+uWSBRJjhw426RoNOCK6U6bt6VFhuJRCjWAJiJB1fsli9n5SSA9/edDhw9XOUjVeWBakS9ZOR6vforOUw==","t":"fz8dmC0ysXY4kOZ3nmMs+33tnJk7JPvpMUU29n4YFhB4cl8jF4eIpeqLa5ezdkIpAkqEjxAWQMbG6rWO3T52fQ=="},"publicKey":"vlGTZ+NzScItHa2Z4YszNVzvOC11WYmiQ1zLZ84cl20="},{"confirmationType":"click","credential":{"signature":"C6Tg3SSjdbmxAJKeQiONCxi21lvZUZd2axOGB3TwtBklTIP71Wbbt5j01uz0eZznzKRvfSVGQwJGq3vu+7jooQ==","t":"xhf4s2upmG34biDFXFSMtGwU0C1SllZ6unfeAa64/FN5M/P4RCkdeQcvRyvdDzge2T5HybDIDZVF+JT6kEUgqA=="},"publicKey":"vlGTZ+NzScItHa2Z4YszNVzvOC11WYmiQ1zLZ84cl20="}],"platform":"ios","totals":[{"ad_format":"new_tab_page_ad","click":1,"view":1}]}
  Content Type: application/json
  Method: kPut
info	01:44:38.626646+0200	Client	[ads] URL Response:
  URL: https://mywallet.ads.bravesoftware.com/v3/confirmation/payment/522f8de7-ea59-4391-968c-7e29ceb678e1
  Response Status Code: 200
  Response: {"message":"Ok","status":200}
info	01:44:38.626837+0200	Client	[ads] Successfully redeemed payment tokens
info	01:44:38.643703+0200	Client	[ads] Redeem payment tokens in 23 hours, 3 minutes, 10 seconds at 00:47:48.641
info	01:44:38.644778+0200	Client	[ads] Successfully reconciled transactions
info	01:44:40.783565+0200	Client	[ads] Redeem reward confirmation
info	01:44:40.784646+0200	Client	[ads] Create reward confirmation
info	01:44:40.784991+0200	Client	[ads] URL Request:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/abe74fd6-432b-488c-9c46-71c22a8740de/eyJzaWduYXR1cmUiOiJnSktzNGp6WTBZMUdFWXliNElHUHRnTFo1WGVhV1NzQncwMjg1d3R4Zjg3Y2QwWHJCWUh4L0dCQW01enNLcDRmcE1VM2lQWXJwWFlyUlpwY3krWHJYUT09IiwidCI6IjA1TXBRS0NxSkdEK01IR0daR3dray9LaTBoejJ6M3JQMklLTC9FRmN4Z3Zjd0hxM2lqblVqYUxnaEZ2bGIrOHN1MHI1Z20xL2EzMkdLeW5KdVhYYVZBPT0ifQ==
  Content: {"blindedPaymentTokens":["BnTrebcSJ2a/cmFVTB8OXI8CUqXtxYS8QU7ipIml8Co="],"buildChannel":"nightly","catalog":[{"id":"GijrzUpV98S7yV8G5l+ttN5vJCrfO5/dCo0FuxUH+f8="}],"createdAtTimestamp":"2025-04-15T23:00:00.000Z","creativeInstanceId":"8ea7efdf-00cb-4c66-981d-31216b82b2f8","httpResponseStatus":"2xx","platform":"ios","publicKey":"hLaC3RQw5++hx8zaT+47CA8r8cOlAm+ygeky/ci6T1s=","rotatingHash":"4eQKw5nQ7mSgFiZOYlu5Qx6/SUlBo8zwuin1QikVsu8=","segment":"untargeted","studies":[],"systemTimestamp":"2025-04-15T23:00:00.000Z","transactionId":"abe74fd6-432b-488c-9c46-71c22a8740de","type":"landed","versionNumber":"135.0.7049<…>
info	01:44:41.083281+0200	Client	[ads] URL Response:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/abe74fd6-432b-488c-9c46-71c22a8740de/eyJzaWduYXR1cmUiOiJnSktzNGp6WTBZMUdFWXliNElHUHRnTFo1WGVhV1NzQncwMjg1d3R4Zjg3Y2QwWHJCWUh4L0dCQW01enNLcDRmcE1VM2lQWXJwWFlyUlpwY3krWHJYUT09IiwidCI6IjA1TXBRS0NxSkdEK01IR0daR3dray9LaTBoejJ6M3JQMklLTC9FRmN4Z3Zjd0hxM2lqblVqYUxnaEZ2bGIrOHN1MHI1Z20xL2EzMkdLeW5KdVhYYVZBPT0ifQ==
  Response Status Code: 201
  Response: {"id":"abe74fd6-432b-488c-9c46-71c22a8740de","payload":{},"type":"landed","createdAt":"2025-04-15T23:44:40.907Z","modifiedAt":"2025-04-15T23:44:40.907Z","creativeInstanceId":"8ea7efdf-00cb-4c66-981d-31216b82b2f8"}
info	01:44:41.083393+0200	Client	[ads] Fetch payment token in 3 s
info	01:44:44.085556+0200	Client	[ads] Fetch payment token
info	01:44:44.086332+0200	Client	[ads] URL Request:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/abe74fd6-432b-488c-9c46-71c22a8740de/paymentToken
  Method: kGet
info	01:44:44.774285+0200	Client	[ads] URL Response:
  URL: https://anonymous.ads.bravesoftware.com/v3/confirmation/abe74fd6-432b-488c-9c46-71c22a8740de/paymentToken
  Response Status Code: 200
  Response: {"id":"abe74fd6-432b-488c-9c46-71c22a8740de","type":"landed","createdAt":"2025-04-15T23:44:40.907Z","modifiedAt":"2025-04-15T23:44:43.095Z","creativeInstanceId":"8ea7efdf-00cb-4c66-981d-31216b82b2f8","paymentToken":{"publicKey":"vlGTZ+NzScItHa2Z4YszNVzvOC11WYmiQ1zLZ84cl20=","batchProof":"7xBnBOAw+4Og4mzn/Zouf8QB6LqG5U27HtWlcZj1BgLQJ0fyIJKjmXu9v48f1M2FxgNd5df+DwRIVDXN25QAAg==","signedTokens":["NK3VXB0ivnzJTFRMYbVyJo+nIXoAC8ipWAm602znRh0="]}}
info	01:44:44.788897+0200	Client	[ads] You have 1 payment tokens which will be redeemed in 23 hours, 3 minutes, 4 seconds at 00:47:48.641
info	01:44:44.792567+0200	Client	[ads] Successfully processed kLanded confirmation for kNewTabPageAd with transaction id abe74fd6-432b-488c-9c46-71c22a8740de and creative instance id 8ea7efdf-00cb-4c66-981d-31216b82b2f8
```



</details>
author_association: NONE