From 082203b4cfdcd9da04b66e9221135aed8fb970ff Mon Sep 17 00:00:00 2001 From: Malcolm Sparrow Date: Mon, 4 Nov 2019 20:18:39 +0000 Subject: [PATCH] Fixes maximum daily ads at 21 instead of 20 (follow up to #3849) Fixes https://github.com/brave/brave-core/pull/3806 --- test/BUILD.gn | 11 +- vendor/bat-native-ads/BUILD.gn | 18 ++ .../src/bat/ads/internal/ads_impl.cc | 269 +++++------------- .../src/bat/ads/internal/ads_impl.h | 34 +-- .../src/bat/ads/internal/client.cc | 26 +- .../src/bat/ads/internal/client.h | 12 +- .../src/bat/ads/internal/client_mock.cc | 61 ++++ .../src/bat/ads/internal/client_mock.h | 37 +++ .../frequency_capping/exclusion_rule.h | 26 ++ .../daily_cap_frequency_cap.cc | 47 +++ .../exclusion_rules/daily_cap_frequency_cap.h | 40 +++ .../daily_cap_frequency_cap_unittest.cc | 202 +++++++++++++ .../exclusion_rules/per_day_frequency_cap.cc | 48 ++++ .../exclusion_rules/per_day_frequency_cap.h | 41 +++ .../per_day_frequency_cap_unittest.cc | 184 ++++++++++++ .../exclusion_rules/per_hour_frequency_cap.cc | 47 +++ .../exclusion_rules/per_hour_frequency_cap.h | 39 +++ .../per_hour_frequency_cap_unittest.cc | 137 +++++++++ .../total_max_frequency_cap.cc | 50 ++++ .../exclusion_rules/total_max_frequency_cap.h | 41 +++ .../total_max_frequency_cap_unittest.cc | 161 +++++++++++ .../frequency_capping/frequency_capping.cc | 92 ++++++ .../frequency_capping/frequency_capping.h | 46 +++ .../frequency_capping/permission_rule.h | 23 ++ .../ads_per_day_frequency_cap.cc | 52 ++++ .../ads_per_day_frequency_cap.h | 42 +++ .../ads_per_day_frequency_cap_unittest.cc | 146 ++++++++++ .../ads_per_hour_frequency_cap.cc | 59 ++++ .../ads_per_hour_frequency_cap.h | 47 +++ .../ads_per_hour_frequency_cap_unittest.cc | 146 ++++++++++ .../minimum_wait_time_frequency_cap.cc | 62 ++++ .../minimum_wait_time_frequency_cap.h | 47 +++ ...inimum_wait_time_frequency_cap_unittest.cc | 129 +++++++++ 33 files changed, 2185 insertions(+), 237 deletions(-) create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/client_mock.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/client_mock.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rule.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap_unittest.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap_unittest.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap_unittest.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap_unittest.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/frequency_capping.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/frequency_capping.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rule.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap_unittest.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap_unittest.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.cc create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.h create mode 100644 vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap_unittest.cc diff --git a/test/BUILD.gn b/test/BUILD.gn index 5748ebb0e8cc..d7d8b43ee517 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -175,7 +175,16 @@ test("brave_unit_tests") { if (brave_ads_enabled) { sources += [ - "//brave/components/brave_ads/browser/ads_service_impl_unittest.cc" + "//brave/components/brave_ads/browser/ads_service_impl_unittest.cc", + "//brave/vendor/bat-native-ads/src/bat/ads/internal/client_mock.h", + "//brave/vendor/bat-native-ads/src/bat/ads/internal/client_mock.cc", + "//brave/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap_unittest.cc", + "//brave/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap_unittest.cc", + "//brave/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap_unittest.cc", + "//brave/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap_unittest.cc", + "//brave/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap_unittest.cc", + "//brave/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap_unittest.cc", + "//brave/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap_unittest.cc", ] } diff --git a/vendor/bat-native-ads/BUILD.gn b/vendor/bat-native-ads/BUILD.gn index 093fbe35ae6a..226f5f2ac794 100644 --- a/vendor/bat-native-ads/BUILD.gn +++ b/vendor/bat-native-ads/BUILD.gn @@ -152,6 +152,24 @@ source_set("ads") { "src/bat/ads/internal/filtered_category.h", "src/bat/ads/internal/flagged_ad.cc", "src/bat/ads/internal/flagged_ad.h", + "src/bat/ads/internal/frequency_capping/exclusion_rule.h", + "src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.cc", + "src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.h", + "src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.cc", + "src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.h", + "src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.cc", + "src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.h", + "src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.cc", + "src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.h", + "src/bat/ads/internal/frequency_capping/frequency_capping.cc", + "src/bat/ads/internal/frequency_capping/frequency_capping.h", + "src/bat/ads/internal/frequency_capping/permission_rule.h", + "src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.cc", + "src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.h", + "src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.cc", + "src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.h", + "src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.cc", + "src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.h", "src/bat/ads/internal/json_helper.cc", "src/bat/ads/internal/json_helper.h", "src/bat/ads/internal/locale_helper.cc", diff --git a/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.cc b/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.cc index fe65147284e1..c983d90cbafa 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.cc @@ -22,6 +22,15 @@ #include "bat/ads/internal/static_values.h" #include "bat/ads/internal/time.h" #include "bat/ads/internal/uri_helper.h" +#include "bat/ads/internal/frequency_capping/exclusion_rule.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.h" #include "rapidjson/document.h" #include "rapidjson/error/en.h" @@ -79,6 +88,7 @@ AdsImpl::AdsImpl(AdsClient* ads_client) : client_(std::make_unique(this, ads_client)), bundle_(std::make_unique(this, ads_client)), ads_serve_(std::make_unique(this, ads_client, bundle_.get())), + frequency_capping_(std::make_unique(client_.get())), notifications_(std::make_unique(this, ads_client)), user_model_(nullptr), is_initialized_(false), @@ -1069,38 +1079,48 @@ void AdsImpl::FailedToServeAd( } } -std::vector AdsImpl::GetEligibleAds( - const std::vector& ads) { - std::vector eligible_ads = {}; +std::vector> + AdsImpl::CreateExclusionRules() const { + std::vector> exclusion_rules; - auto unseen_ads = GetUnseenAdsAndRoundRobinIfNeeded(ads); + std::unique_ptr daily_cap_frequency_cap = + std::make_unique(frequency_capping_.get()); + exclusion_rules.push_back(std::move(daily_cap_frequency_cap)); - for (const auto& ad : unseen_ads) { - if (!AdRespectsTotalMaxFrequencyCapping(ad)) { - BLOG(WARNING) << "creativeSetId " << ad.creative_set_id - << " has exceeded the frequency capping for totalMax"; + std::unique_ptr per_day_frequency_cap = + std::make_unique(frequency_capping_.get()); + exclusion_rules.push_back(std::move(per_day_frequency_cap)); - continue; - } + std::unique_ptr per_hour_frequency_cap = + std::make_unique(frequency_capping_.get()); + exclusion_rules.push_back(std::move(per_hour_frequency_cap)); - if (!AdRespectsPerHourFrequencyCapping(ad)) { - BLOG(WARNING) << "adUUID " << ad.uuid - << " has exceeded the frequency capping for perHour"; + std::unique_ptr total_max_frequency_cap = + std::make_unique(frequency_capping_.get()); + exclusion_rules.push_back(std::move(total_max_frequency_cap)); - continue; - } + return exclusion_rules; +} - if (!AdRespectsPerDayFrequencyCapping(ad)) { - BLOG(WARNING) << "creativeSetId " << ad.creative_set_id - << " has exceeded the frequency capping for perDay"; +std::vector AdsImpl::GetEligibleAds( + const std::vector& ads) { + std::vector eligible_ads = {}; - continue; - } + auto unseen_ads = GetUnseenAdsAndRoundRobinIfNeeded(ads); + + std::vector> exclusion_rules = + CreateExclusionRules(); - if (!AdRespectsDailyCapFrequencyCapping(ad)) { - BLOG(WARNING) << "campaignId " << ad.campaign_id - << " has exceeded the frequency capping for dailyCap"; + for (const auto& ad : unseen_ads) { + bool should_exclude = false; + for (std::unique_ptr& exclusion_rule : exclusion_rules) { + if (exclusion_rule->ShouldExclude(ad)) { + BLOG(INFO) << exclusion_rule->GetLastMessage(); + should_exclude = true; + } + } + if (should_exclude) { continue; } @@ -1151,81 +1171,6 @@ std::vector AdsImpl::GetUnseenAds( return unseen_ads; } -bool AdsImpl::AdRespectsTotalMaxFrequencyCapping( - const AdInfo& ad) { - auto creative_set = GetCreativeSetForId(ad.creative_set_id); - if (creative_set.size() >= ad.total_max) { - return false; - } - - return true; -} - -bool AdsImpl::AdRespectsPerHourFrequencyCapping( - const AdInfo& ad) { - auto ads_shown = GetAdsShownForId(ad.uuid); - auto hour_window = base::Time::kSecondsPerHour; - - return HistoryRespectsRollingTimeConstraint( - ads_shown, hour_window, 1); -} - -bool AdsImpl::AdRespectsPerDayFrequencyCapping( - const AdInfo& ad) { - auto creative_set = GetCreativeSetForId(ad.creative_set_id); - auto day_window = base::Time::kSecondsPerHour * base::Time::kHoursPerDay; - - return HistoryRespectsRollingTimeConstraint( - creative_set, day_window, ad.per_day); -} - -bool AdsImpl::AdRespectsDailyCapFrequencyCapping( - const AdInfo& ad) { - auto campaign = GetCampaignForId(ad.campaign_id); - auto day_window = base::Time::kSecondsPerHour * base::Time::kHoursPerDay; - - return HistoryRespectsRollingTimeConstraint( - campaign, day_window, ad.daily_cap); -} - -std::deque AdsImpl::GetAdsShownForId( - const std::string& id) { - std::deque ads_shown = {}; - - auto ads_shown_history = client_->GetAdsShownHistory(); - for (const auto& ad_shown : ads_shown_history) { - if (ad_shown.ad_content.uuid == id) { - ads_shown.push_back(ad_shown.timestamp_in_seconds); - } - } - - return ads_shown; -} - -std::deque AdsImpl::GetCreativeSetForId( - const std::string& id) { - std::deque creative_set = {}; - - auto creative_set_history = client_->GetCreativeSetHistory(); - if (creative_set_history.find(id) != creative_set_history.end()) { - creative_set = creative_set_history.at(id); - } - - return creative_set; -} - -std::deque AdsImpl::GetCampaignForId( - const std::string& id) { - std::deque campaign = {}; - - auto campaign_history = client_->GetCampaignHistory(); - if (campaign_history.find(id) != campaign_history.end()) { - campaign = campaign_history.at(id); - } - - return campaign; -} - bool AdsImpl::IsAdValid( const AdInfo& ad_info) { if (ad_info.advertiser.empty() || @@ -1256,6 +1201,15 @@ bool AdsImpl::ShowAd( return false; } + auto now_in_seconds = Time::NowInSeconds(); + + client_->AppendTimestampToCreativeSetHistoryForUuid(ad.creative_set_id, + now_in_seconds); + client_->AppendTimestampToCampaignHistoryForUuid(ad.campaign_id, + now_in_seconds); + + client_->UpdateAdsUUIDSeen(ad.uuid, 1); + auto notification_info = std::make_unique(); notification_info->id = base::GenerateGUID(); notification_info->advertiser = ad.advertiser; @@ -1287,114 +1241,45 @@ bool AdsImpl::ShowAd( } #endif - - client_->AppendCurrentTimeToCreativeSetHistory(ad.creative_set_id); - client_->AppendCurrentTimeToCampaignHistory(ad.campaign_id); - - client_->UpdateAdsUUIDSeen(ad.uuid, 1); - return true; } -bool AdsImpl::HistoryRespectsRollingTimeConstraint( - const std::deque history, - const uint64_t seconds_window, - const uint64_t allowable_ad_count) const { - uint64_t recent_count = 0; +std::vector> + AdsImpl::CreatePermissionRules() const { + std::vector> permission_rules; - auto now_in_seconds = Time::NowInSeconds(); + std::unique_ptr ads_per_hour_frequency_cap = + std::make_unique(this, ads_client_, + frequency_capping_.get()); + permission_rules.push_back(std::move(ads_per_hour_frequency_cap)); - for (const auto& timestamp_in_seconds : history) { - if (now_in_seconds - timestamp_in_seconds < seconds_window) { - recent_count++; - } - } + std::unique_ptr minimum_wait_time_frequency_cap = + std::make_unique(this, ads_client_, + frequency_capping_.get()); + permission_rules.push_back(std::move(minimum_wait_time_frequency_cap)); - if (recent_count <= allowable_ad_count) { - return true; - } + std::unique_ptr ads_per_day_frequency_cap = + std::make_unique(ads_client_, + frequency_capping_.get()); + permission_rules.push_back(std::move(ads_per_day_frequency_cap)); - return false; + return permission_rules; } -bool AdsImpl::HistoryRespectsRollingTimeConstraint( - const std::deque history, - const uint64_t seconds_window, - const uint64_t allowable_ad_count) const { - uint64_t recent_count = 0; +bool AdsImpl::IsAllowedToServeAds() { + std::vector> permission_rules = + CreatePermissionRules(); - auto now_in_seconds = Time::NowInSeconds(); + bool is_allowed = true; - for (const auto& detail : history) { - if (now_in_seconds - detail.timestamp_in_seconds < seconds_window) { - recent_count++; + for (std::unique_ptr& permission_rule : permission_rules) { + if (!permission_rule->IsAllowed()) { + BLOG(INFO) << permission_rule->GetLastMessage(); + is_allowed = false; } } - if (recent_count <= allowable_ad_count) { - return true; - } - - return false; -} - -bool AdsImpl::IsAllowedToServeAds() { - auto does_history_respect_ads_per_day_limit = - DoesHistoryRespectAdsPerDayLimit(); - - bool does_history_respect_minimum_wait_time; - if (!IsMobile()) { - does_history_respect_minimum_wait_time = - DoesHistoryRespectMinimumWaitTimeToServeAds(); - } else { - does_history_respect_minimum_wait_time = true; - } - - BLOG(INFO) << "IsAllowedToServeAds:"; - BLOG(INFO) << " does_history_respect_minimum_wait_time: " - << does_history_respect_minimum_wait_time; - BLOG(INFO) << " does_history_respect_ads_per_day_limit: " - << does_history_respect_ads_per_day_limit; - - return does_history_respect_minimum_wait_time && - does_history_respect_ads_per_day_limit; -} - -bool AdsImpl::DoesHistoryRespectMinimumWaitTimeToServeAds() { - auto ads_shown_history = client_->GetAdsShownHistory(); - - auto hour_window = base::Time::kSecondsPerHour; - auto hour_allowed = ads_client_->GetAdsPerHour(); - auto respects_hour_limit = HistoryRespectsRollingTimeConstraint( - ads_shown_history, hour_window, hour_allowed); - - auto minimum_wait_time = hour_window / hour_allowed; - auto respects_minimum_wait_time = HistoryRespectsRollingTimeConstraint( - ads_shown_history, minimum_wait_time, 0); - - BLOG(INFO) << "DoesHistoryRespectMinimumWaitTimeToServeAds:"; - BLOG(INFO) << " respects_hour_limit: " - << respects_hour_limit; - BLOG(INFO) << " respects_minimum_wait_time: " - << respects_minimum_wait_time; - - return respects_hour_limit && respects_minimum_wait_time; -} - -bool AdsImpl::DoesHistoryRespectAdsPerDayLimit() { - auto ads_shown_history = client_->GetAdsShownHistory(); - - auto day_window = base::Time::kSecondsPerHour * base::Time::kHoursPerDay; - auto day_allowed = ads_client_->GetAdsPerDay(); - - auto respects_day_limit = HistoryRespectsRollingTimeConstraint( - ads_shown_history, day_window, day_allowed); - - BLOG(INFO) << "DoesHistoryRespectAdsPerDayLimit:"; - BLOG(INFO) << " respects_day_limit: " - << respects_day_limit; - - return respects_day_limit; + return is_allowed; } void AdsImpl::StartCollectingActivity( diff --git a/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.h b/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.h index ecd03f415388..9aeaec384e71 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.h +++ b/vendor/bat-native-ads/src/bat/ads/internal/ads_impl.h @@ -37,6 +37,9 @@ class Client; class Bundle; class AdsServe; class Notifications; +class FrequencyCapping; +class ExclusionRule; +class PermissionRule; class AdsImpl : public Ads { public: @@ -216,39 +219,13 @@ class AdsImpl : public Ads { std::vector GetUnseenAds( const std::vector& ads) const; - bool AdRespectsTotalMaxFrequencyCapping( - const AdInfo& ad); - bool AdRespectsPerHourFrequencyCapping( - const AdInfo& ad); - bool AdRespectsPerDayFrequencyCapping( - const AdInfo& ad); - bool AdRespectsDailyCapFrequencyCapping( - const AdInfo& ad); - - std::deque GetAdsShownForId( - const std::string& id); - std::deque GetCreativeSetForId( - const std::string& id); - std::deque GetCampaignForId( - const std::string& id); - bool IsAdValid( const AdInfo& ad_info); NotificationInfo last_shown_notification_info_; bool ShowAd( const AdInfo& ad_info, const std::string& category); - bool HistoryRespectsRollingTimeConstraint( - const std::deque history, - const uint64_t seconds_window, - const uint64_t allowable_ad_count) const; - bool HistoryRespectsRollingTimeConstraint( - const std::deque history, - const uint64_t seconds_window, - const uint64_t allowable_ad_count) const; bool IsAllowedToServeAds(); - bool DoesHistoryRespectMinimumWaitTimeToServeAds(); - bool DoesHistoryRespectAdsPerDayLimit(); uint32_t collect_activity_timer_id_; void StartCollectingActivity( @@ -340,6 +317,7 @@ class AdsImpl : public Ads { std::unique_ptr client_; std::unique_ptr bundle_; std::unique_ptr ads_serve_; + std::unique_ptr frequency_capping_; std::unique_ptr notifications_; std::unique_ptr user_model_; @@ -350,6 +328,10 @@ class AdsImpl : public Ads { AdsClient* ads_client_; // NOT OWNED + std::vector> CreatePermissionRules() const; + + std::vector> CreateExclusionRules() const; + // Not copyable, not assignable AdsImpl(const AdsImpl&) = delete; AdsImpl& operator=(const AdsImpl&) = delete; diff --git a/vendor/bat-native-ads/src/bat/ads/internal/client.cc b/vendor/bat-native-ads/src/bat/ads/internal/client.cc index 6eaa1026e1de..8d2cb4159295 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/client.cc +++ b/vendor/bat-native-ads/src/bat/ads/internal/client.cc @@ -72,7 +72,7 @@ void Client::AppendAdToAdsShownHistory( SaveState(); } -const std::deque Client::GetAdsShownHistory() { +const std::deque Client::GetAdsShownHistory() const { return client_state_->ads_shown_history; } @@ -496,16 +496,16 @@ const std::deque> Client::GetPageScoreHistory() { return client_state_->page_score_history; } -void Client::AppendCurrentTimeToCreativeSetHistory( - const std::string& creative_set_id) { - if (client_state_->creative_set_history.find(creative_set_id) == +void Client::AppendTimestampToCreativeSetHistoryForUuid( + const std::string& uuid, + const uint64_t timestamp_in_seconds) { + if (client_state_->creative_set_history.find(uuid) == client_state_->creative_set_history.end()) { - client_state_->creative_set_history.insert({creative_set_id, {}}); + client_state_->creative_set_history.insert({uuid, {}}); } - auto now_in_seconds = Time::NowInSeconds(); client_state_->creative_set_history.at( - creative_set_id).push_back(now_in_seconds); + uuid).push_back(timestamp_in_seconds); SaveState(); } @@ -515,15 +515,15 @@ const std::map> return client_state_->creative_set_history; } -void Client::AppendCurrentTimeToCampaignHistory( - const std::string& campaign_id) { - if (client_state_->campaign_history.find(campaign_id) == +void Client::AppendTimestampToCampaignHistoryForUuid( + const std::string& uuid, + const uint64_t timestamp_in_seconds) { + if (client_state_->campaign_history.find(uuid) == client_state_->campaign_history.end()) { - client_state_->campaign_history.insert({campaign_id, {}}); + client_state_->campaign_history.insert({uuid, {}}); } - auto now_in_seconds = Time::NowInSeconds(); - client_state_->campaign_history.at(campaign_id).push_back(now_in_seconds); + client_state_->campaign_history.at(uuid).push_back(timestamp_in_seconds); SaveState(); } diff --git a/vendor/bat-native-ads/src/bat/ads/internal/client.h b/vendor/bat-native-ads/src/bat/ads/internal/client.h index 9e6964908e79..249d40c19e70 100644 --- a/vendor/bat-native-ads/src/bat/ads/internal/client.h +++ b/vendor/bat-native-ads/src/bat/ads/internal/client.h @@ -29,7 +29,7 @@ class Client { void Initialize(InitializeCallback callback); void AppendAdToAdsShownHistory(const AdHistoryDetail& ad_history_detail); - const std::deque GetAdsShownHistory(); + const std::deque GetAdsShownHistory() const; AdContent::LikeAction ToggleAdThumbUp(const std::string& id, const std::string& creative_set_id, AdContent::LikeAction action); @@ -79,12 +79,14 @@ class Client { void AppendPageScoreToPageScoreHistory( const std::vector& page_score); const std::deque> GetPageScoreHistory(); - void AppendCurrentTimeToCreativeSetHistory( - const std::string& creative_set_id); + void AppendTimestampToCreativeSetHistoryForUuid( + const std::string& uuid, + const uint64_t timestamp_in_seconds); const std::map> GetCreativeSetHistory() const; - void AppendCurrentTimeToCampaignHistory( - const std::string& campaign_id); + void AppendTimestampToCampaignHistoryForUuid( + const std::string& uuid, + const uint64_t timestamp_in_seconds); const std::map> GetCampaignHistory() const; std::string GetVersionCode() const; diff --git a/vendor/bat-native-ads/src/bat/ads/internal/client_mock.cc b/vendor/bat-native-ads/src/bat/ads/internal/client_mock.cc new file mode 100644 index 000000000000..d8dc697ebb6f --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/client_mock.cc @@ -0,0 +1,61 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include +#include + +#include "bat/ads/internal/client_mock.h" + +#include "bat/ads/ad_history_detail.h" +#include "bat/ads/internal/time.h" +#include "base/guid.h" + +namespace ads { + +void ClientMock::GeneratePastAdHistoryFromNow( + const std::string uuid, + const int64_t time_offset_per_ad_in_seconds, + const uint8_t count) { + auto now_in_seconds = Time::NowInSeconds(); + + auto ad_history_detail = std::make_unique(); + ad_history_detail->uuid = base::GenerateGUID(); + ad_history_detail->ad_content.uuid = uuid; + + for (uint8_t i = 0; i < count; i++) { + now_in_seconds -= time_offset_per_ad_in_seconds; + + ad_history_detail->timestamp_in_seconds = now_in_seconds; + AppendAdToAdsShownHistory(*ad_history_detail); + } +} + +void ClientMock::GeneratePastCreativeSetHistoryFromNow( + const std::string& creative_set_id, + const int64_t time_offset_per_ad_in_seconds, + const uint8_t count) { + auto now_in_seconds = Time::NowInSeconds(); + + for (uint8_t i = 0; i < count; i++) { + now_in_seconds -= time_offset_per_ad_in_seconds; + + AppendTimestampToCreativeSetHistoryForUuid(creative_set_id, now_in_seconds); + } +} + +void ClientMock::GeneratePastCampaignHistoryFromNow( + const std::string campaign_id, + const int64_t time_offset_per_ad_in_seconds, + const uint8_t count) { + auto now_in_seconds = Time::NowInSeconds(); + + for (uint8_t i = 0; i < count; i++) { + now_in_seconds -= time_offset_per_ad_in_seconds; + + AppendTimestampToCampaignHistoryForUuid(campaign_id, now_in_seconds); + } +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/client_mock.h b/vendor/bat-native-ads/src/bat/ads/internal/client_mock.h new file mode 100644 index 000000000000..80d6dfa73c71 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/client_mock.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BAT_ADS_INTERNAL_CLIENT_MOCK_H_ +#define BAT_ADS_INTERNAL_CLIENT_MOCK_H_ + +#include + +#include "bat/ads/internal/client.h" + +namespace ads { + +class ClientMock : public Client { + public: + ClientMock(AdsImpl* ads, AdsClient* ads_client): Client(ads, ads_client) { } + + void GeneratePastAdHistoryFromNow( + const std::string uuid, + const int64_t time_offset_per_ad_in_seconds, + const uint8_t count); + + void GeneratePastCreativeSetHistoryFromNow( + const std::string& creative_set_id, + const int64_t time_offset_per_ad_in_seconds, + const uint8_t count); + + void GeneratePastCampaignHistoryFromNow( + const std::string campaign_id, + const int64_t time_offset_per_ad_in_seconds, + const uint8_t count); +}; + +} // namespace ads + +#endif // BAT_ADS_INTERNAL_CLIENT_MOCK_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rule.h b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rule.h new file mode 100644 index 000000000000..d54f79a73fc0 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rule.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BAT_ADS_INTERNAL_EXCLUSION_RULE_H_ +#define BAT_ADS_INTERNAL_EXCLUSION_RULE_H_ + +#include + +namespace ads { + +struct AdInfo; + +class ExclusionRule { + public: + virtual ~ExclusionRule() = default; + virtual bool ShouldExclude( + const AdInfo& ad) = 0; + + virtual const std::string GetLastMessage() const = 0; +}; + +} // namespace ads + +#endif // BAT_ADS_INTERNAL_EXCLUSION_RULE_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.cc new file mode 100644 index 000000000000..56dcf9f0f519 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.cc @@ -0,0 +1,47 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/internal/time.h" +#include "bat/ads/internal/client.h" + +#include "bat/ads/ad_info.h" + +namespace ads { + +DailyCapFrequencyCap::DailyCapFrequencyCap( + const FrequencyCapping* const frequency_capping) + : frequency_capping_(frequency_capping) { +} + +DailyCapFrequencyCap::~DailyCapFrequencyCap() = default; + +bool DailyCapFrequencyCap::ShouldExclude( + const AdInfo& ad) { + if (!DoesAdRespectDailyCampaignCap(ad)) { + std::ostringstream string_stream; + string_stream << "campaignId " << ad.campaign_id << + " has exceeded the frequency capping for dailyCap"; + last_message_ = string_stream.str(); + return true; + } + return false; +} + +const std::string DailyCapFrequencyCap::GetLastMessage() const { + return last_message_; +} + +bool DailyCapFrequencyCap::DoesAdRespectDailyCampaignCap( + const AdInfo& ad) const { + auto campaign = frequency_capping_->GetCampaignForUuid(ad.campaign_id); + auto day_window = base::Time::kSecondsPerHour * base::Time::kHoursPerDay; + + return frequency_capping_->DoesHistoryRespectCapForRollingTimeConstraint( + campaign, day_window, ad.daily_cap); +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.h b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.h new file mode 100644 index 000000000000..523c30e1633b --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BAT_ADS_INTERNAL_DAILY_CAMPAIGN_FREQUENCY_CAP_H_ +#define BAT_ADS_INTERNAL_DAILY_CAMPAIGN_FREQUENCY_CAP_H_ + +#include + +#include "bat/ads/internal/frequency_capping/exclusion_rule.h" + +namespace ads { +struct AdInfo; +class FrequencyCapping; + +class DailyCapFrequencyCap : public ExclusionRule { + public: + DailyCapFrequencyCap( + const FrequencyCapping* const frequency_capping); + + ~DailyCapFrequencyCap() override; + + bool ShouldExclude( + const AdInfo& ad) override; + + const std::string GetLastMessage() const override; + + private: + const FrequencyCapping* const frequency_capping_; // NOT OWNED + + std::string last_message_; + + bool DoesAdRespectDailyCampaignCap( + const AdInfo& ad) const; +}; + +} // namespace ads + +#endif // BAT_ADS_INTERNAL_DAILY_CAMPAIGN_FREQUENCY_CAP_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap_unittest.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap_unittest.cc new file mode 100644 index 000000000000..eb0531d40b3e --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap_unittest.cc @@ -0,0 +1,202 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include +#include + +#include "testing/gtest/include/gtest/gtest.h" + +#include "bat/ads/internal/frequency_capping/exclusion_rule.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/internal/frequency_capping/exclusion_rules/daily_cap_frequency_cap.h" + +#include "bat/ads/internal/client_mock.h" +#include "bat/ads/internal/ads_client_mock.h" +#include "bat/ads/internal/ads_impl.h" +#include "bat/ads/ad_info.h" +#include "bat/ads/internal/time.h" + +// npm run test -- brave_unit_tests --filter=Ads* + +using std::placeholders::_1; +using ::testing::_; +using ::testing::Invoke; + +namespace { + +const std::vector kTestCampaignIds = { + "60267cee-d5bb-4a0d-baaf-91cd7f18e07e", + "90762cee-d5bb-4a0d-baaf-61cd7f18e07e" +}; + +} // namespace + +namespace ads { + +const uint64_t kSecondsPerDay = base::Time::kSecondsPerHour * + base::Time::kHoursPerDay; + +class BraveAdsDailyCapFrequencyCapTest : public ::testing::Test { + protected: + BraveAdsDailyCapFrequencyCapTest() : + mock_ads_client_(std::make_unique()), + ads_(std::make_unique(mock_ads_client_.get())) { + // You can do set-up work for each test here + } + + ~BraveAdsDailyCapFrequencyCapTest() override { + // You can do clean-up work that doesn't throw exceptions here + } + + // If the constructor and destructor are not enough for setting up and + // cleaning up each test, you can use the following methods + + void SetUp() override { + // Code here will be called immediately after the constructor (right before + // each test) + + auto callback = std::bind( + &BraveAdsDailyCapFrequencyCapTest::OnAdsImplInitialize, this, _1); + ads_->Initialize(callback); + + client_mock_ = std::make_unique(ads_.get(), + mock_ads_client_.get()); + frequency_capping_ = std::make_unique(client_mock_.get()); + exclusion_rule_ = std::make_unique + (frequency_capping_.get()); + } + + void OnAdsImplInitialize(const Result result) { + EXPECT_EQ(Result::SUCCESS, result); + } + + void TearDown() override { + // Code here will be called immediately after each test (right before the + // destructor) + } + + std::unique_ptr mock_ads_client_; + std::unique_ptr ads_; + + std::unique_ptr client_mock_; + std::unique_ptr frequency_capping_; + std::unique_ptr exclusion_rule_; + AdInfo ad_info_; +}; + +TEST_F(BraveAdsDailyCapFrequencyCapTest, AdAllowedWhenNoAds) { + // Arrange + ad_info_.campaign_id = kTestCampaignIds.at(0); + ad_info_.daily_cap = 2; + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsDailyCapFrequencyCapTest, AdAllowedWithAds) { + // Arrange + ad_info_.campaign_id = kTestCampaignIds[0]; + ad_info_.daily_cap = 2; + + client_mock_->GeneratePastCampaignHistoryFromNow( + ad_info_.campaign_id, 0, 1); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsDailyCapFrequencyCapTest, AdAllowedWithAdsWithinTheDay) { + // Arrange + ad_info_.campaign_id = kTestCampaignIds[0]; + ad_info_.daily_cap = 2; + + // 23hrs 59m 59s ago + client_mock_->GeneratePastCampaignHistoryFromNow( + ad_info_.campaign_id, kSecondsPerDay - 1, 1); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsDailyCapFrequencyCapTest, AdAllowedWithAdsOverTheDay) { + // Arrange + ad_info_.campaign_id = kTestCampaignIds[0]; + ad_info_.daily_cap = 2; + + // 24hs ago + client_mock_->GeneratePastCampaignHistoryFromNow( + ad_info_.campaign_id, kSecondsPerDay, 1); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsDailyCapFrequencyCapTest, + AdExcludedWithMatchingCampaignAds) { + // Arrange + ad_info_.campaign_id = kTestCampaignIds[0]; + ad_info_.daily_cap = 2; + + client_mock_->GeneratePastCampaignHistoryFromNow( + ad_info_.campaign_id, 0, 2); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_TRUE(is_ad_excluded); + EXPECT_EQ(exclusion_rule_->GetLastMessage(), "campaignId 60267cee-d5bb-4a0d-baaf-91cd7f18e07e has exceeded the frequency capping for dailyCap"); // NOLINT +} + +TEST_F(BraveAdsDailyCapFrequencyCapTest, + AdNotExcludedWithNoMatchingCampaignAds) { + // Arrange + client_mock_->GeneratePastCampaignHistoryFromNow( + kTestCampaignIds.at(0), 0, 2); + + ad_info_.campaign_id = kTestCampaignIds[1]; + ad_info_.daily_cap = 1; + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsDailyCapFrequencyCapTest, AdExcludedForIssue4207) { + // Arrange + uint64_t ads_per_day = 20; + + ad_info_.campaign_id = kTestCampaignIds[0]; + ad_info_.daily_cap = ads_per_day; + + uint64_t ads_per_hour = 5; + uint64_t ad_interval = base::Time::kSecondsPerHour / ads_per_hour; + + client_mock_->GeneratePastCampaignHistoryFromNow(ad_info_.campaign_id, + ad_interval, ads_per_day); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_TRUE(is_ad_excluded); + EXPECT_EQ(exclusion_rule_->GetLastMessage(), "campaignId 60267cee-d5bb-4a0d-baaf-91cd7f18e07e has exceeded the frequency capping for dailyCap"); // NOLINT +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.cc new file mode 100644 index 000000000000..b2b8ece2e502 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.cc @@ -0,0 +1,48 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/internal/time.h" +#include "bat/ads/internal/client.h" + +#include "bat/ads/ad_info.h" + +namespace ads { + +PerDayFrequencyCap::PerDayFrequencyCap( + const FrequencyCapping* const frequency_capping) + : frequency_capping_(frequency_capping) { +} + +PerDayFrequencyCap::~PerDayFrequencyCap() = default; + +bool PerDayFrequencyCap::ShouldExclude( + const AdInfo& ad) { + if (!DoesAdRespectPerDayCap(ad)) { + std::ostringstream string_stream; + string_stream << "creativeSetId " << ad.creative_set_id << + " has exceeded the frequency capping for perDay"; + last_message_ = string_stream.str(); + return true; + } + return false; +} + +const std::string PerDayFrequencyCap::GetLastMessage() const { + return last_message_; +} + +bool PerDayFrequencyCap::DoesAdRespectPerDayCap( + const AdInfo& ad) const { + auto creative_set = frequency_capping_->GetCreativeSetHistoryForUuid( + ad.creative_set_id); + auto day_window = base::Time::kSecondsPerHour * base::Time::kHoursPerDay; + + return frequency_capping_->DoesHistoryRespectCapForRollingTimeConstraint( + creative_set, day_window, ad.per_day); +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.h b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.h new file mode 100644 index 000000000000..106e546b6532 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BAT_ADS_INTERNAL_DAILY_FREQUENCY_CAP_H_ +#define BAT_ADS_INTERNAL_DAILY_FREQUENCY_CAP_H_ + +#include + +#include "bat/ads/internal/frequency_capping/exclusion_rule.h" + +namespace ads { + +struct AdInfo; +class FrequencyCapping; + +class PerDayFrequencyCap : public ExclusionRule { + public: + PerDayFrequencyCap( + const FrequencyCapping* const frequency_capping); + + ~PerDayFrequencyCap() override; + + bool ShouldExclude( + const AdInfo& ad) override; + + const std::string GetLastMessage() const override; + + private: + const FrequencyCapping* const frequency_capping_; // NOT OWNED + + std::string last_message_; + + bool DoesAdRespectPerDayCap( + const AdInfo& ad) const; +}; + +} // namespace ads + +#endif // BAT_ADS_INTERNAL_DAILY_FREQUENCY_CAP_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap_unittest.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap_unittest.cc new file mode 100644 index 000000000000..752d7d249655 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap_unittest.cc @@ -0,0 +1,184 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +#include "bat/ads/internal/frequency_capping/exclusion_rule.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/internal/frequency_capping/exclusion_rules/per_day_frequency_cap.h" + +#include "bat/ads/internal/client_mock.h" +#include "bat/ads/internal/ads_client_mock.h" +#include "bat/ads/internal/ads_impl.h" +#include "bat/ads/ad_info.h" +#include "bat/ads/internal/time.h" + +// npm run test -- brave_unit_tests --filter=Ads* + +using std::placeholders::_1; +using ::testing::_; +using ::testing::Invoke; + +namespace { + +const char kTestCreativeSetId[] = "654f10df-fbc4-4a92-8d43-2edf73734a60"; + +} // namespace + +namespace ads { + +const uint64_t kSecondsPerDay = base::Time::kSecondsPerHour * + base::Time::kHoursPerDay; + +class BraveAdsPerDayFrequencyCapTest : public ::testing::Test { + protected: + BraveAdsPerDayFrequencyCapTest() + : mock_ads_client_(std::make_unique()), + ads_(std::make_unique(mock_ads_client_.get())) { + // You can do set-up work for each test here + } + + ~BraveAdsPerDayFrequencyCapTest() override { + // You can do clean-up work that doesn't throw exceptions here + } + + // If the constructor and destructor are not enough for setting up and + // cleaning up each test, you can use the following methods + + void SetUp() override { + // Code here will be called immediately after the constructor (right before + // each test) + + auto callback = std::bind( + &BraveAdsPerDayFrequencyCapTest::OnAdsImplInitialize, this, _1); + ads_->Initialize(callback); + + client_mock_ = std::make_unique(ads_.get(), + mock_ads_client_.get()); + frequency_capping_ = std::make_unique(client_mock_.get()); + exclusion_rule_ = std::make_unique( + frequency_capping_.get()); + } + + void OnAdsImplInitialize(const Result result) { + EXPECT_EQ(Result::SUCCESS, result); + } + + void TearDown() override { + // Code here will be called immediately after each test (right before the + // destructor) + } + + std::unique_ptr mock_ads_client_; + std::unique_ptr ads_; + + std::unique_ptr client_mock_; + std::unique_ptr frequency_capping_; + std::unique_ptr exclusion_rule_; + AdInfo ad_info_; +}; + +TEST_F(BraveAdsPerDayFrequencyCapTest, AdAllowedWhenNoAds) { + // Arrange + ad_info_.creative_set_id = kTestCreativeSetId; + ad_info_.per_day = 2; + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsPerDayFrequencyCapTest, AdAllowedBelowDailyCap) { + // Arrange + ad_info_.creative_set_id = kTestCreativeSetId; + ad_info_.per_day = 2; + client_mock_->GeneratePastCreativeSetHistoryFromNow( + kTestCreativeSetId, 0, 1); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsPerDayFrequencyCapTest, AdAllowedWithAdOutsideDayWindow) { + // Arrange + ad_info_.creative_set_id = kTestCreativeSetId; + ad_info_.per_day = 2; + + client_mock_->GeneratePastCreativeSetHistoryFromNow( + kTestCreativeSetId, 0, 1); + client_mock_->GeneratePastCreativeSetHistoryFromNow( + kTestCreativeSetId, kSecondsPerDay, 1); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsPerDayFrequencyCapTest, AdExcludedAboveDailyCapWithRecentAds) { + // Arrange + ad_info_.creative_set_id = kTestCreativeSetId; + ad_info_.per_day = 2; + + client_mock_->GeneratePastCreativeSetHistoryFromNow( + kTestCreativeSetId, 0, 2); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_TRUE(is_ad_excluded); + EXPECT_EQ(exclusion_rule_->GetLastMessage(), "creativeSetId 654f10df-fbc4-4a92-8d43-2edf73734a60 has exceeded the frequency capping for perDay"); // NOLINT +} + +TEST_F(BraveAdsPerDayFrequencyCapTest, + AdExcludedAboveDailyCapWithAdsJustWithinDay) { + // Arrange + ad_info_.creative_set_id = kTestCreativeSetId; + ad_info_.per_day = 2; + + client_mock_->GeneratePastCreativeSetHistoryFromNow(kTestCreativeSetId, 0, + 1); + client_mock_->GeneratePastCreativeSetHistoryFromNow(kTestCreativeSetId, + kSecondsPerDay - 1, 1); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_TRUE(is_ad_excluded); + EXPECT_EQ(exclusion_rule_->GetLastMessage(), "creativeSetId 654f10df-fbc4-4a92-8d43-2edf73734a60 has exceeded the frequency capping for perDay"); // NOLINT +} + +TEST_F(BraveAdsPerDayFrequencyCapTest, AdExcludedForIssue4207) { + // Arrange + uint64_t ads_per_day = 20; + + ad_info_.creative_set_id = kTestCreativeSetId; + ad_info_.per_day = ads_per_day; + + uint64_t ads_per_hour = 5; + uint64_t ad_interval = base::Time::kSecondsPerHour / ads_per_hour; + + client_mock_->GeneratePastCreativeSetHistoryFromNow(kTestCreativeSetId, + ad_interval, ads_per_day); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_TRUE(is_ad_excluded); + EXPECT_EQ(exclusion_rule_->GetLastMessage(), "creativeSetId 654f10df-fbc4-4a92-8d43-2edf73734a60 has exceeded the frequency capping for perDay"); // NOLINT +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.cc new file mode 100644 index 000000000000..8d7fbe0bf97f --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.cc @@ -0,0 +1,47 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/internal/time.h" +#include "bat/ads/internal/client.h" + +#include "bat/ads/ad_info.h" + +namespace ads { + +PerHourFrequencyCap::PerHourFrequencyCap( + const FrequencyCapping* const frequency_capping) + : frequency_capping_(frequency_capping) { +} + +PerHourFrequencyCap::~PerHourFrequencyCap() = default; + +bool PerHourFrequencyCap::ShouldExclude( + const AdInfo& ad) { + if (!DoesAdRespectPerHourCap(ad)) { + std::ostringstream string_stream; + string_stream << "adUUID " << ad.uuid << + " has exceeded the frequency capping for perHour"; + last_message_ = string_stream.str(); + return true; + } + return false; +} + +const std::string PerHourFrequencyCap::GetLastMessage() const { + return last_message_; +} + +bool PerHourFrequencyCap::DoesAdRespectPerHourCap( + const AdInfo& ad) const { + auto ads_shown = frequency_capping_->GetAdsHistoryForUuid(ad.uuid); + auto hour_window = base::Time::kSecondsPerHour; + + return frequency_capping_->DoesHistoryRespectCapForRollingTimeConstraint( + ads_shown, hour_window, 1); +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.h b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.h new file mode 100644 index 000000000000..ca469e5b0bff --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BAT_ADS_INTERNAL_HOURLY_FREQUENCY_CAP_H_ +#define BAT_ADS_INTERNAL_HOURLY_FREQUENCY_CAP_H_ + +#include + +#include "bat/ads/internal/frequency_capping/exclusion_rule.h" + +namespace ads { +struct AdInfo; +class FrequencyCapping; + +class PerHourFrequencyCap : public ExclusionRule { + public: + PerHourFrequencyCap( + const FrequencyCapping* const frequency_capping); + + ~PerHourFrequencyCap() override; + + bool ShouldExclude( + const AdInfo& ad) override; + + const std::string GetLastMessage() const override; + + private: + const FrequencyCapping* const frequency_capping_; // NOT OWNED + + std::string last_message_; + + bool DoesAdRespectPerHourCap( + const AdInfo& ad) const; +}; +} // namespace ads + +#endif // BAT_ADS_INTERNAL_HOURLY_FREQUENCY_CAP_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap_unittest.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap_unittest.cc new file mode 100644 index 000000000000..432b8df2b968 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap_unittest.cc @@ -0,0 +1,137 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/time/time.h" + +#include "bat/ads/internal/frequency_capping/exclusion_rule.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/internal/frequency_capping/exclusion_rules/per_hour_frequency_cap.h" + +#include "bat/ads/internal/client_mock.h" +#include "bat/ads/internal/ads_client_mock.h" +#include "bat/ads/internal/ads_impl.h" +#include "bat/ads/ad_info.h" + +// npm run test -- brave_unit_tests --filter=Ads* + +using std::placeholders::_1; +using ::testing::_; +using ::testing::Invoke; + +namespace { + +const char kTestAdUuid[] = "9aea9a47-c6a0-4718-a0fa-706338bb2156"; + +} // namespace + +namespace ads { + +class BraveAdsPerHourFrequencyCapTest : public ::testing::Test { + protected: + BraveAdsPerHourFrequencyCapTest() + : mock_ads_client_(std::make_unique()), + ads_(std::make_unique(mock_ads_client_.get())) { + // You can do set-up work for each test here + } + + ~BraveAdsPerHourFrequencyCapTest() override { + // You can do clean-up work that doesn't throw exceptions here + } + + // If the constructor and destructor are not enough for setting up and + // cleaning up each test, you can use the following methods + + void SetUp() override { + // Code here will be called immediately after the constructor (right before + // each test) + + auto callback = std::bind( + &BraveAdsPerHourFrequencyCapTest::OnAdsImplInitialize, this, _1); + ads_->Initialize(callback); + + client_mock_ = std::make_unique(ads_.get(), + mock_ads_client_.get()); + frequency_capping_ = std::make_unique(client_mock_.get()); + exclusion_rule_ = std::make_unique( + frequency_capping_.get()); +} + + void OnAdsImplInitialize(const Result result) { + EXPECT_EQ(Result::SUCCESS, result); + } + + void TearDown() override { + // Code here will be called immediately after each test (right before the + // destructor) + } + + std::unique_ptr mock_ads_client_; + std::unique_ptr ads_; + + std::unique_ptr client_mock_; + std::unique_ptr frequency_capping_; + std::unique_ptr exclusion_rule_; + AdInfo ad_info_; +}; + +TEST_F(BraveAdsPerHourFrequencyCapTest, AdAllowedWhenNoAds) { + // Arrange + ad_info_.uuid = kTestAdUuid; + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsPerHourFrequencyCapTest, AdAllowedOverTheHour) { + // Arrange + ad_info_.uuid = kTestAdUuid; + // 1hr 1s in the past + client_mock_->GeneratePastAdHistoryFromNow(kTestAdUuid, + base::Time::kSecondsPerHour, 1); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsPerHourFrequencyCapTest, + AdExcludedWithPastAdsJustWithinTheHour) { + // Arrange + ad_info_.uuid = kTestAdUuid; + // 59m 59s + client_mock_->GeneratePastAdHistoryFromNow(kTestAdUuid, + base::Time::kSecondsPerHour - 1, 1); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_TRUE(is_ad_excluded); + EXPECT_EQ(exclusion_rule_->GetLastMessage(), "adUUID 9aea9a47-c6a0-4718-a0fa-706338bb2156 has exceeded the frequency capping for perHour"); // NOLINT +} + +TEST_F(BraveAdsPerHourFrequencyCapTest, AdExcludedWithPastAdWithinTheHour) { + // Arrange + ad_info_.uuid = kTestAdUuid; + client_mock_->GeneratePastAdHistoryFromNow(kTestAdUuid, 0, 1); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_TRUE(is_ad_excluded); + EXPECT_EQ(exclusion_rule_->GetLastMessage(), "adUUID 9aea9a47-c6a0-4718-a0fa-706338bb2156 has exceeded the frequency capping for perHour"); // NOLINT +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.cc new file mode 100644 index 000000000000..049e7d331eaf --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.cc @@ -0,0 +1,50 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/internal/time.h" +#include "bat/ads/internal/client.h" + +#include "bat/ads/ad_info.h" + +namespace ads { + +TotalMaxFrequencyCap::TotalMaxFrequencyCap( + const FrequencyCapping* const frequency_capping) + : frequency_capping_(frequency_capping) { +} + +TotalMaxFrequencyCap::~TotalMaxFrequencyCap() = default; + +bool TotalMaxFrequencyCap::ShouldExclude( + const AdInfo& ad) { + if (!DoesAdRespectMaximumCap(ad)) { + std::ostringstream string_stream; + string_stream << "creativeSetId " << ad.creative_set_id << + " has exceeded the frequency capping for totalMax"; + last_message_ = string_stream.str(); + return true; + } + return false; +} + +const std::string TotalMaxFrequencyCap::GetLastMessage() const { + return last_message_; +} + +bool TotalMaxFrequencyCap::DoesAdRespectMaximumCap( + const AdInfo& ad) const { + auto creative_set = frequency_capping_->GetCreativeSetHistoryForUuid( + ad.creative_set_id); + + if (creative_set.size() >= ad.total_max) { + return false; + } + + return true; +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.h b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.h new file mode 100644 index 000000000000..64b774edef01 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BAT_ADS_INTERNAL_MAXIMUM_FREQUENCY_CAP_H_ +#define BAT_ADS_INTERNAL_MAXIMUM_FREQUENCY_CAP_H_ + +#include + +#include "bat/ads/internal/frequency_capping/exclusion_rule.h" + +namespace ads { + +struct AdInfo; +class FrequencyCapping; + +class TotalMaxFrequencyCap : public ExclusionRule { + public: + TotalMaxFrequencyCap( + const FrequencyCapping* const frequency_capping); + + ~TotalMaxFrequencyCap() override; + + bool ShouldExclude( + const AdInfo& ad) override; + + const std::string GetLastMessage() const override; + + private: + const FrequencyCapping* const frequency_capping_; // NOT OWNED + + std::string last_message_; + + bool DoesAdRespectMaximumCap( + const AdInfo& ad) const; +}; + +} // namespace ads + +#endif // BAT_ADS_INTERNAL_MAXIMUM_FREQUENCY_CAP_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap_unittest.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap_unittest.cc new file mode 100644 index 000000000000..eade01217847 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap_unittest.cc @@ -0,0 +1,161 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include +#include + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/time/time.h" + +#include "bat/ads/internal/frequency_capping/exclusion_rule.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/internal/frequency_capping/exclusion_rules/total_max_frequency_cap.h" + +#include "bat/ads/internal/client_mock.h" +#include "bat/ads/internal/ads_client_mock.h" +#include "bat/ads/internal/ads_impl.h" +#include "bat/ads/ad_info.h" + +// npm run test -- brave_unit_tests --filter=Ads* + +using std::placeholders::_1; +using ::testing::_; +using ::testing::Invoke; + +namespace { + +const std::vector kTestCreativeSetIds = { + "654f10df-fbc4-4a92-8d43-2edf73734a60", + "465f10df-fbc4-4a92-8d43-4edf73734a60" +}; + +} // namespace + +namespace ads { + +class BraveAdsTotalMaxFrequencyCapTest : public ::testing::Test { + protected: + BraveAdsTotalMaxFrequencyCapTest() + : mock_ads_client_(std::make_unique()), + ads_(std::make_unique(mock_ads_client_.get())) { + // You can do set-up work for each test here + } + + ~BraveAdsTotalMaxFrequencyCapTest() override { + // You can do clean-up work that doesn't throw exceptions here + } + + // If the constructor and destructor are not enough for setting up and + // cleaning up each test, you can use the following methods + + void SetUp() override { + // Code here will be called immediately after the constructor (right before + // each test) + + auto callback = std::bind( + &BraveAdsTotalMaxFrequencyCapTest::OnAdsImplInitialize, this, _1); + ads_->Initialize(callback); + + client_mock_ = std::make_unique(ads_.get(), + mock_ads_client_.get()); + frequency_capping_ = std::make_unique(client_mock_.get()); + exclusion_rule_ = std::make_unique( + frequency_capping_.get()); + } + + void OnAdsImplInitialize(const Result result) { + EXPECT_EQ(Result::SUCCESS, result); + } + + void TearDown() override { + // Code here will be called immediately after each test (right before the + // destructor) + } + + std::unique_ptr mock_ads_client_; + std::unique_ptr ads_; + + std::unique_ptr client_mock_; + std::unique_ptr frequency_capping_; + std::unique_ptr exclusion_rule_; + AdInfo ad_info_; +}; + +TEST_F(BraveAdsTotalMaxFrequencyCapTest, AdAllowedWithNoAdHistory) { + // Arrange + ad_info_.creative_set_id = kTestCreativeSetIds.at(0); + ad_info_.total_max = 2; + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsTotalMaxFrequencyCapTest, AdAllowedWithMatchingAds) { + // Arrange + ad_info_.creative_set_id = kTestCreativeSetIds.at(0); + ad_info_.total_max = 2; + + client_mock_->GeneratePastCreativeSetHistoryFromNow(ad_info_.creative_set_id, + base::Time::kSecondsPerHour, 1); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsTotalMaxFrequencyCapTest, AdAllowedWithNonMatchingAds) { + // Arrange + client_mock_->GeneratePastCreativeSetHistoryFromNow(kTestCreativeSetIds.at(0), + base::Time::kSecondsPerHour, 5); + + ad_info_.creative_set_id = kTestCreativeSetIds.at(1); + ad_info_.total_max = 2; + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_FALSE(is_ad_excluded); +} + +TEST_F(BraveAdsTotalMaxFrequencyCapTest, AdExcludedWhenNoneAllowed) { + // Arrange + ad_info_.creative_set_id = kTestCreativeSetIds[0]; + ad_info_.total_max = 0; + + client_mock_->GeneratePastCreativeSetHistoryFromNow(ad_info_.creative_set_id, + base::Time::kSecondsPerHour, 5); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_TRUE(is_ad_excluded); + EXPECT_EQ(exclusion_rule_->GetLastMessage(), "creativeSetId 654f10df-fbc4-4a92-8d43-2edf73734a60 has exceeded the frequency capping for totalMax"); // NOLINT +} + +TEST_F(BraveAdsTotalMaxFrequencyCapTest, AdExcludedWhenMaximumReached) { + // Arrange + ad_info_.creative_set_id = kTestCreativeSetIds.at(0); + ad_info_.total_max = 5; + + client_mock_->GeneratePastCreativeSetHistoryFromNow(ad_info_.creative_set_id, + base::Time::kSecondsPerHour, 5); + + // Act + const bool is_ad_excluded = exclusion_rule_->ShouldExclude(ad_info_); + + // Assert + EXPECT_TRUE(is_ad_excluded); + EXPECT_EQ(exclusion_rule_->GetLastMessage(), "creativeSetId 654f10df-fbc4-4a92-8d43-2edf73734a60 has exceeded the frequency capping for totalMax"); // NOLINT +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/frequency_capping.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/frequency_capping.cc new file mode 100644 index 000000000000..a914805f8bac --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/frequency_capping.cc @@ -0,0 +1,92 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/ad_info.h" +#include "bat/ads/internal/client.h" +#include "bat/ads/internal/time.h" + +namespace ads { + +FrequencyCapping::FrequencyCapping( + const Client* const client) + : client_(client) { +} + +FrequencyCapping::~FrequencyCapping() = default; + +bool FrequencyCapping::DoesHistoryRespectCapForRollingTimeConstraint( + const std::deque history, + const uint64_t time_constraint_in_seconds, + const uint64_t cap) const { + uint64_t count = 0; + + auto now_in_seconds = Time::NowInSeconds(); + + for (const auto& timestamp_in_seconds : history) { + if (now_in_seconds - timestamp_in_seconds < time_constraint_in_seconds) { + count++; + } + } + + if (count < cap) { + return true; + } + + return false; +} + +std::deque FrequencyCapping::GetCreativeSetHistoryForUuid( + const std::string& uuid) const { + std::deque history; + + auto creative_set_history = client_->GetCreativeSetHistory(); + if (creative_set_history.find(uuid) != creative_set_history.end()) { + history = creative_set_history.at(uuid); + } + + return history; +} + +std::deque FrequencyCapping::GetAdsShownHistory() const { + std::deque history; + auto ads_history = client_->GetAdsShownHistory(); + + for (const auto& detail : ads_history) { + history.push_back(detail.timestamp_in_seconds); + } + + return history; +} + +std::deque FrequencyCapping::GetAdsHistoryForUuid( + const std::string& uuid) const { + std::deque history; + auto ads_history = client_->GetAdsShownHistory(); + + for (const auto& ad : ads_history) { + if (ad.ad_content.uuid != uuid) { + continue; + } + + history.push_back(ad.timestamp_in_seconds); + } + + return history; +} + +std::deque FrequencyCapping::GetCampaignForUuid( + const std::string& uuid) const { + std::deque history; + + auto campaign_history = client_->GetCampaignHistory(); + if (campaign_history.find(uuid) != campaign_history.end()) { + history = campaign_history.at(uuid); + } + + return history; +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/frequency_capping.h b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/frequency_capping.h new file mode 100644 index 000000000000..261919ecc566 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/frequency_capping.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BAT_ADS_INTERNAL_FREQUENCY_CAPPING_H_ +#define BAT_ADS_INTERNAL_FREQUENCY_CAPPING_H_ + +#include +#include +#include + +namespace ads { + +class Client; + +class FrequencyCapping { + public: + explicit FrequencyCapping( + const Client* const client); + + ~FrequencyCapping(); + + bool DoesHistoryRespectCapForRollingTimeConstraint( + const std::deque history, + const uint64_t time_constraint_in_seconds, + const uint64_t cap) const; + + std::deque GetCreativeSetHistoryForUuid( + const std::string& uuid) const; + + std::deque GetAdsShownHistory() const; + + std::deque GetAdsHistoryForUuid( + const std::string& uuid) const; + + std::deque GetCampaignForUuid( + const std::string& uuid) const; + + private: + const Client* const client_; // NOT OWNED +}; + +} // namespace ads + +#endif // BAT_ADS_INTERNAL_FREQUENCY_CAPPING_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rule.h b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rule.h new file mode 100644 index 000000000000..56b1c6a08330 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rule.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BAT_ADS_INTERNAL_PERMISSION_RULE_H_ +#define BAT_ADS_INTERNAL_PERMISSION_RULE_H_ + +#include + +namespace ads { + +class PermissionRule { + public: + virtual ~PermissionRule() = default; + virtual bool IsAllowed() = 0; + + virtual const std::string GetLastMessage() const = 0; +}; + +} // namespace ads + +#endif // BAT_ADS_INTERNAL_PERMISSION_RULE_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.cc new file mode 100644 index 000000000000..7665fe859445 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.cc @@ -0,0 +1,52 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/ads_client.h" +#include "bat/ads/internal/time.h" +#include "bat/ads/internal/client.h" + +#include "bat/ads/ad_info.h" + +namespace ads { + +AdsPerDayFrequencyCap::AdsPerDayFrequencyCap( + const AdsClient* const ads_client, + const FrequencyCapping* const frequency_capping) + : ads_client_(ads_client), + frequency_capping_(frequency_capping) { +} + +AdsPerDayFrequencyCap::~AdsPerDayFrequencyCap() = default; + +bool AdsPerDayFrequencyCap::IsAllowed() { + auto respects_day_limit = AreAdsPerDayBelowAllowedThreshold(); + + if (!respects_day_limit) { + last_message_ = "You have exceeded the allowed ads per day"; + } + + return respects_day_limit; +} + +const std::string AdsPerDayFrequencyCap::GetLastMessage() const { + return last_message_; +} + +bool AdsPerDayFrequencyCap::AreAdsPerDayBelowAllowedThreshold() const { + auto history = frequency_capping_->GetAdsShownHistory(); + + auto day_window = base::Time::kSecondsPerHour * base::Time::kHoursPerDay; + auto day_allowed = ads_client_->GetAdsPerDay(); + + auto respects_day_limit = + frequency_capping_->DoesHistoryRespectCapForRollingTimeConstraint( + history, day_window, day_allowed); + + return respects_day_limit; +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.h b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.h new file mode 100644 index 000000000000..e93c32f7796d --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BAT_ADS_INTERNAL_PER_DAY_LIMIT_H_ +#define BAT_ADS_INTERNAL_PER_DAY_LIMIT_H_ + +#include + +#include "bat/ads/internal/frequency_capping/permission_rule.h" + +namespace ads { + +struct AdInfo; +class AdsClient; +class FrequencyCapping; + +class AdsPerDayFrequencyCap : public PermissionRule { + public: + AdsPerDayFrequencyCap( + const AdsClient* const ads_client, + const FrequencyCapping* const frequency_capping); + + ~AdsPerDayFrequencyCap() override; + + bool IsAllowed() override; + + const std::string GetLastMessage() const override; + + private: + const AdsClient* const ads_client_; // NOT OWNED + const FrequencyCapping* const frequency_capping_; // NOT OWNED + + std::string last_message_; + + bool AreAdsPerDayBelowAllowedThreshold() const; +}; + +} // namespace ads + +#endif // BAT_ADS_INTERNAL_PER_DAY_LIMIT_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap_unittest.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap_unittest.cc new file mode 100644 index 000000000000..00ecece0ad76 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap_unittest.cc @@ -0,0 +1,146 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/time/time.h" + +#include "bat/ads/internal/frequency_capping/permission_rules/ads_per_day_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" + +#include "bat/ads/internal/client_mock.h" +#include "bat/ads/internal/ads_client_mock.h" +#include "bat/ads/internal/ads_impl.h" + +// npm run test -- brave_unit_tests --filter=Ads* + +using std::placeholders::_1; +using ::testing::_; +using ::testing::Invoke; + +namespace { + +const char kTestAdUuid[] = "9aea9a47-c6a0-4718-a0fa-706338bb2156"; + +} // namespace + +namespace ads { + +class BraveAdsAdsPerDayFrequencyCapTest : public ::testing::Test { + protected: + BraveAdsAdsPerDayFrequencyCapTest() + : mock_ads_client_(std::make_unique()), + ads_(std::make_unique(mock_ads_client_.get())) { + // You can do set-up work for each test here + } + + ~BraveAdsAdsPerDayFrequencyCapTest() override { + // You can do clean-up work that doesn't throw exceptions here + } + + // If the constructor and destructor are not enough for setting up and + // cleaning up each test, you can use the following methods + + void SetUp() override { + // Code here will be called immediately after the constructor (right before + // each test) + + auto callback = std::bind( + &BraveAdsAdsPerDayFrequencyCapTest::OnAdsImplInitialize, this, _1); + ads_->Initialize(callback); + + client_mock_ = std::make_unique(ads_.get(), + mock_ads_client_.get()); + frequency_capping_ = std::make_unique(client_mock_.get()); + per_day_limit_ = std::make_unique( + mock_ads_client_.get(), frequency_capping_.get()); + } + + void OnAdsImplInitialize(const Result result) { + EXPECT_EQ(Result::SUCCESS, result); + } + + void TearDown() override { + // Code here will be called immediately after each test (right before the + // destructor) + } + + std::unique_ptr mock_ads_client_; + std::unique_ptr ads_; + + std::unique_ptr client_mock_; + std::unique_ptr frequency_capping_; + std::unique_ptr per_day_limit_; +}; + +TEST_F(BraveAdsAdsPerDayFrequencyCapTest, + PerDayLimitRespectedWithNoAdHistory) { + // Arrange + ON_CALL(*mock_ads_client_, GetAdsPerDay()) + .WillByDefault(testing::Return(2)); + + // Act + const bool does_history_respect_ads_per_day_limit = + per_day_limit_->IsAllowed(); + + // Assert + EXPECT_TRUE(does_history_respect_ads_per_day_limit); +} + +TEST_F(BraveAdsAdsPerDayFrequencyCapTest, + PerDayLimitRespectedWithAdsBelowPerDayLimit) { + // Arrange + ON_CALL(*mock_ads_client_, GetAdsPerDay()) + .WillByDefault(testing::Return(2)); + + client_mock_->GeneratePastAdHistoryFromNow(kTestAdUuid, + base::Time::kSecondsPerHour, 1); + + // Act + const bool does_history_respect_ads_per_day_limit = + per_day_limit_->IsAllowed(); + + // Assert + EXPECT_TRUE(does_history_respect_ads_per_day_limit); +} + +TEST_F(BraveAdsAdsPerDayFrequencyCapTest, + PerDayLimitRespectedWithAdsAbovePerDayLimitButOlderThanADay) { + // Arrange + ON_CALL(*mock_ads_client_, GetAdsPerDay()) + .WillByDefault(testing::Return(2)); + + client_mock_->GeneratePastAdHistoryFromNow(kTestAdUuid, + base::Time::kSecondsPerHour * 24, 2); + + // Act + const bool does_history_respect_ads_per_day_limit = + per_day_limit_->IsAllowed(); + + // Assert + EXPECT_TRUE(does_history_respect_ads_per_day_limit); +} + +TEST_F(BraveAdsAdsPerDayFrequencyCapTest, + PerDayLimitNotRespectedWithAdsAbovePerDayLimit) { + // Arrange + ON_CALL(*mock_ads_client_, GetAdsPerDay()) + .WillByDefault(testing::Return(2)); + + client_mock_->GeneratePastAdHistoryFromNow(kTestAdUuid, + base::Time::kSecondsPerHour, 2); + + // Act + const bool does_history_respect_ads_per_day_limit = + per_day_limit_->IsAllowed(); + + // Assert + EXPECT_FALSE(does_history_respect_ads_per_day_limit); + EXPECT_EQ(per_day_limit_->GetLastMessage(), "You have exceeded the allowed ads per day"); // NOLINT +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.cc new file mode 100644 index 000000000000..a1228c3557c0 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.cc @@ -0,0 +1,59 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/ads_client.h" +#include "bat/ads/internal/time.h" +#include "bat/ads/internal/client.h" +#include "bat/ads/internal/ads_impl.h" + +#include "bat/ads/ad_info.h" + +namespace ads { + +AdsPerHourFrequencyCap::AdsPerHourFrequencyCap( + const AdsImpl* const ads, + const AdsClient* const ads_client, + const FrequencyCapping* const frequency_capping) + : ads_(ads), + ads_client_(ads_client), + frequency_capping_(frequency_capping) { +} + +AdsPerHourFrequencyCap::~AdsPerHourFrequencyCap() = default; + +bool AdsPerHourFrequencyCap::IsAllowed() { + if (ads_->IsMobile()) { + return true; + } + + auto history = frequency_capping_->GetAdsShownHistory(); + + auto respects_hour_limit = AreAdsPerHourBelowAllowedThreshold(history); + if (!respects_hour_limit) { + last_message_ = "You have exceeded the allowed ads per hour"; + return false; + } + return true; +} + +const std::string AdsPerHourFrequencyCap::GetLastMessage() const { + return last_message_; +} + +bool AdsPerHourFrequencyCap::AreAdsPerHourBelowAllowedThreshold( + const std::deque& history) const { + auto hour_window = base::Time::kSecondsPerHour; + auto hour_allowed = ads_client_->GetAdsPerHour(); + + auto respects_hour_limit = + frequency_capping_->DoesHistoryRespectCapForRollingTimeConstraint( + history, hour_window, hour_allowed); + + return respects_hour_limit; +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.h b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.h new file mode 100644 index 000000000000..8079c2da8313 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BAT_ADS_INTERNAL_PER_HOUR_LIMIT_FREQUENCY_CAP_H_ +#define BAT_ADS_INTERNAL_PER_HOUR_LIMIT_FREQUENCY_CAP_H_ + +#include +#include + +#include "bat/ads/internal/frequency_capping/permission_rule.h" + +namespace ads { + +struct AdInfo; +class AdsImpl; +class AdsClient; +class FrequencyCapping; + +class AdsPerHourFrequencyCap : public PermissionRule { + public: + AdsPerHourFrequencyCap( + const AdsImpl* const ads, + const AdsClient* const ads_client, + const FrequencyCapping* const frequency_capping); + + ~AdsPerHourFrequencyCap() override; + + bool IsAllowed() override; + + const std::string GetLastMessage() const override; + + private: + const AdsImpl* const ads_; // NOT OWNED + const AdsClient* const ads_client_; // NOT OWNED + const FrequencyCapping* const frequency_capping_; // NOT OWNED + + std::string last_message_; + + bool AreAdsPerHourBelowAllowedThreshold( + const std::deque& history) const; +}; + +} // namespace ads + +#endif // BAT_ADS_INTERNAL_PER_HOUR_LIMIT_FREQUENCY_CAP_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap_unittest.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap_unittest.cc new file mode 100644 index 000000000000..502ae3489dc1 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap_unittest.cc @@ -0,0 +1,146 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/time/time.h" + +#include "bat/ads/internal/frequency_capping/permission_rules/ads_per_hour_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" + +#include "bat/ads/internal/client_mock.h" +#include "bat/ads/internal/ads_client_mock.h" +#include "bat/ads/internal/ads_impl.h" + +// npm run test -- brave_unit_tests --filter=Ads* + +using std::placeholders::_1; +using ::testing::_; +using ::testing::Invoke; + +namespace { + +const char kTestAdUuid[] = "9aea9a47-c6a0-4718-a0fa-706338bb2156"; + +} // namespace + +namespace ads { + +class BraveAdsAdsPerHourFrequencyCapTest : public ::testing::Test { + protected: + BraveAdsAdsPerHourFrequencyCapTest() + : mock_ads_client_(std::make_unique()), + ads_(std::make_unique(mock_ads_client_.get())) { + // You can do set-up work for each test here + } + + ~BraveAdsAdsPerHourFrequencyCapTest() override { + // You can do clean-up work that doesn't throw exceptions here + } + + // If the constructor and destructor are not enough for setting up and + // cleaning up each test, you can use the following methods + + void SetUp() override { + // Code here will be called immediately after the constructor (right before + // each test) + + auto callback = std::bind( + &BraveAdsAdsPerHourFrequencyCapTest::OnAdsImplInitialize, this, _1); + ads_->Initialize(callback); + + client_mock_ = std::make_unique(ads_.get(), + mock_ads_client_.get()); + frequency_capping_ = std::make_unique( + client_mock_.get()); + per_hour_limit_ = std::make_unique( + ads_.get(), mock_ads_client_.get(), frequency_capping_.get()); + } + + void OnAdsImplInitialize(const Result result) { + EXPECT_EQ(Result::SUCCESS, result); + } + + void TearDown() override { + // Code here will be called immediately after each test (right before the + // destructor) + } + + std::unique_ptr mock_ads_client_; + std::unique_ptr ads_; + + std::unique_ptr client_mock_; + std::unique_ptr frequency_capping_; + std::unique_ptr per_hour_limit_; +}; + +TEST_F(BraveAdsAdsPerHourFrequencyCapTest, + PerHourLimitRespectedWithNoAdHistory) { + // Arrange + ON_CALL(*mock_ads_client_, GetAdsPerHour()) + .WillByDefault(testing::Return(2)); + + // Act + const bool does_history_respect_per_hour_limit = + per_hour_limit_->IsAllowed(); + + // Assert + EXPECT_TRUE(does_history_respect_per_hour_limit); +} + +TEST_F(BraveAdsAdsPerHourFrequencyCapTest, + PerHourLimitRespectedWithAdsBelowPerHourLimit) { + // Arrange + ON_CALL(*mock_ads_client_, GetAdsPerHour()) + .WillByDefault(testing::Return(2)); + + client_mock_->GeneratePastAdHistoryFromNow(kTestAdUuid, + base::Time::kSecondsPerHour - 1, 1); + + // Act + const bool does_history_respect_per_hour_limit = + per_hour_limit_->IsAllowed(); + + // Assert + EXPECT_TRUE(does_history_respect_per_hour_limit); +} + +TEST_F(BraveAdsAdsPerHourFrequencyCapTest, + PerHourLimitRespectedWithAdsAbovePerHourLimitButOlderThanAnHour) { + // Arrange + ON_CALL(*mock_ads_client_, GetAdsPerHour()) + .WillByDefault(testing::Return(2)); + + client_mock_->GeneratePastAdHistoryFromNow(kTestAdUuid, + base::Time::kSecondsPerHour, 2); + + // Act + const bool does_history_respect_per_hour_limit = + per_hour_limit_->IsAllowed(); + + // Assert + EXPECT_TRUE(does_history_respect_per_hour_limit); +} + +TEST_F(BraveAdsAdsPerHourFrequencyCapTest, + PerHourLimitNotRespectedWithAdsAbovePerHourLimit) { + // Arrange + ON_CALL(*mock_ads_client_, GetAdsPerHour()) + .WillByDefault(testing::Return(2)); + + client_mock_->GeneratePastAdHistoryFromNow(kTestAdUuid, 10 * 60, 2); + + // Act + const bool does_history_respect_per_hour_limit = + per_hour_limit_->IsAllowed(); + + // Assert + EXPECT_FALSE(does_history_respect_per_hour_limit); + EXPECT_EQ(per_hour_limit_->GetLastMessage(), "You have exceeded the allowed ads per hour"); // NOLINT +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.cc new file mode 100644 index 000000000000..dc2a2ec8b5e7 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.cc @@ -0,0 +1,62 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include "bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" +#include "bat/ads/ads_client.h" +#include "bat/ads/internal/time.h" +#include "bat/ads/internal/client.h" +#include "bat/ads/internal/ads_impl.h" + +#include "bat/ads/ad_info.h" + +namespace ads { + +MinimumWaitTimeFrequencyCap::MinimumWaitTimeFrequencyCap( + const AdsImpl* const ads, + const AdsClient* const ads_client, + const FrequencyCapping* const frequency_capping) + : ads_(ads), + ads_client_(ads_client), + frequency_capping_(frequency_capping) { +} + +MinimumWaitTimeFrequencyCap::~MinimumWaitTimeFrequencyCap() = default; + +bool MinimumWaitTimeFrequencyCap::IsAllowed() { + if (ads_->IsMobile()) { + return true; + } + + auto history = frequency_capping_->GetAdsShownHistory(); + + auto respects_minimum_wait_time = AreAdsAllowedAfterMinimumWaitTime(history); + if (!respects_minimum_wait_time) { + last_message_ = + "Ad cannot be shown as the minimum wait time has not passed"; + return false; + } + + return true; +} + +const std::string MinimumWaitTimeFrequencyCap::GetLastMessage() const { + return last_message_; +} + +bool MinimumWaitTimeFrequencyCap::AreAdsAllowedAfterMinimumWaitTime( + const std::deque& history) const { + auto hour_window = base::Time::kSecondsPerHour; + auto hour_allowed = ads_client_->GetAdsPerHour(); + auto minimum_wait_time = hour_window / hour_allowed; + + auto respects_minimum_wait_time = + frequency_capping_->DoesHistoryRespectCapForRollingTimeConstraint( + history, minimum_wait_time, 1); + + return respects_minimum_wait_time; +} + +} // namespace ads diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.h b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.h new file mode 100644 index 000000000000..3f8c6c936267 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BAT_ADS_INTERNAL_MINIMUM_WAIT_TIME_FREQUENCY_CAP_H_ +#define BAT_ADS_INTERNAL_MINIMUM_WAIT_TIME_FREQUENCY_CAP_H_ + +#include +#include + +#include "bat/ads/internal/frequency_capping/permission_rule.h" + +namespace ads { + +struct AdInfo; +class AdsImpl; +class AdsClient; +class FrequencyCapping; + +class MinimumWaitTimeFrequencyCap : public PermissionRule { + public: + MinimumWaitTimeFrequencyCap( + const AdsImpl* const ads, + const AdsClient* const ads_client, + const FrequencyCapping* const frequency_capping); + + ~MinimumWaitTimeFrequencyCap() override; + + bool IsAllowed() override; + + const std::string GetLastMessage() const override; + + private: + const AdsImpl* const ads_; // NOT OWNED + const AdsClient* const ads_client_; // NOT OWNED + const FrequencyCapping* const frequency_capping_; // NOT OWNED + + std::string last_message_; + + bool AreAdsAllowedAfterMinimumWaitTime( + const std::deque& history) const; +}; + +} // namespace ads + +#endif // BAT_ADS_INTERNAL_MINIMUM_WAIT_TIME_FREQUENCY_CAP_H_ diff --git a/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap_unittest.cc b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap_unittest.cc new file mode 100644 index 000000000000..65af54599c48 --- /dev/null +++ b/vendor/bat-native-ads/src/bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap_unittest.cc @@ -0,0 +1,129 @@ +/* Copyright (c) 2019 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 http://mozilla.org/MPL/2.0/. */ + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/time/time.h" + +#include "bat/ads/internal/frequency_capping/permission_rules/minimum_wait_time_frequency_cap.h" +#include "bat/ads/internal/frequency_capping/frequency_capping.h" + +#include "bat/ads/internal/client_mock.h" +#include "bat/ads/internal/ads_client_mock.h" +#include "bat/ads/internal/ads_impl.h" + +// npm run test -- brave_unit_tests --filter=Ads* + +using std::placeholders::_1; +using ::testing::_; +using ::testing::Invoke; + +namespace { + +const char kTestAdUuid[] = "9aea9a47-c6a0-4718-a0fa-706338bb2156"; + +} // namespace + +namespace ads { + +class BraveAdsMinimumWaitTimeFrequencyCapTest : public ::testing::Test { + protected: + BraveAdsMinimumWaitTimeFrequencyCapTest() + : mock_ads_client_(std::make_unique()), + ads_(std::make_unique(mock_ads_client_.get())) { + // You can do set-up work for each test here + } + + ~BraveAdsMinimumWaitTimeFrequencyCapTest() override { + // You can do clean-up work that doesn't throw exceptions here + } + + // If the constructor and destructor are not enough for setting up and + // cleaning up each test, you can use the following methods + + void SetUp() override { + // Code here will be called immediately after the constructor (right before + // each test) + + auto callback = std::bind( + &BraveAdsMinimumWaitTimeFrequencyCapTest::OnAdsImplInitialize, this, + _1); + ads_->Initialize(callback); + + client_mock_ = std::make_unique(ads_.get(), + mock_ads_client_.get()); + frequency_capping_ = std::make_unique( + client_mock_.get()); + minimum_wait_time_ = std::make_unique( + ads_.get(), mock_ads_client_.get(), frequency_capping_.get()); + } + + void OnAdsImplInitialize(const Result result) { + EXPECT_EQ(Result::SUCCESS, result); + } + + void TearDown() override { + // Code here will be called immediately after each test (right before the + // destructor) + } + + std::unique_ptr mock_ads_client_; + std::unique_ptr ads_; + + std::unique_ptr client_mock_; + std::unique_ptr frequency_capping_; + std::unique_ptr minimum_wait_time_; +}; + +TEST_F(BraveAdsMinimumWaitTimeFrequencyCapTest, + MinimumWaitTimeRespectedWithNoAdHistory) { + // Arrange + ON_CALL(*mock_ads_client_, GetAdsPerHour()) + .WillByDefault(testing::Return(2)); + + // Act + const bool does_history_respect_minimum_wait_time = + minimum_wait_time_->IsAllowed(); + + // Assert + EXPECT_TRUE(does_history_respect_minimum_wait_time); +} + +TEST_F(BraveAdsMinimumWaitTimeFrequencyCapTest, + MinimumWaitTimeRespectedWithAdOverMiniumWaitTime) { + // Arrange + ON_CALL(*mock_ads_client_, GetAdsPerHour()) + .WillByDefault(testing::Return(2)); + + client_mock_->GeneratePastAdHistoryFromNow(kTestAdUuid, 45 * 60, 1); + + // Act + const bool does_history_respect_minimum_wait_time = + minimum_wait_time_->IsAllowed(); + + // Assert + EXPECT_TRUE(does_history_respect_minimum_wait_time); +} + +TEST_F(BraveAdsMinimumWaitTimeFrequencyCapTest, + MinimumWaitTimeNotRespectedWithAdWithinMiniumWaitTime) { + // Arrange + ON_CALL(*mock_ads_client_, GetAdsPerHour()) + .WillByDefault(testing::Return(2)); + + client_mock_->GeneratePastAdHistoryFromNow(kTestAdUuid, 15 * 60, 1); + + // Act + const bool does_history_respect_minimum_wait_time = + minimum_wait_time_->IsAllowed(); + + // Assert + EXPECT_FALSE(does_history_respect_minimum_wait_time); + EXPECT_EQ(minimum_wait_time_->GetLastMessage(), "Ad cannot be shown as the minimum wait time has not passed"); // NOLINT +} + +} // namespace ads