diff --git a/docs/application-settings.md b/docs/application-settings.md index 7a4a72f38d1..bf89c1c3d83 100644 --- a/docs/application-settings.md +++ b/docs/application-settings.md @@ -12,6 +12,7 @@ There are two ways to configure application settings: database and file. This do - `auction.truncate-target-attr` - Maximum targeting attributes size. Values between 1 and 255. - `auction.default-integration` - Default integration to assume. - `auction.debug-allow` - enables debug output in the auction response. Default `true`. +- `auction.impression-limit` - a max number of impressions allowed for the auction, impressions that exceed this limit will be dropped, 0 means no limit. - `auction.bid-validations.banner-creative-max-size` - Overrides creative max size validation for banners. Valid values are: - "skip": don't do anything about creative max size for this publisher diff --git a/src/main/java/org/prebid/server/auction/requestfactory/AmpRequestFactory.java b/src/main/java/org/prebid/server/auction/requestfactory/AmpRequestFactory.java index 6d2ac3ee72d..b8fcd6d6301 100644 --- a/src/main/java/org/prebid/server/auction/requestfactory/AmpRequestFactory.java +++ b/src/main/java/org/prebid/server/auction/requestfactory/AmpRequestFactory.java @@ -414,6 +414,10 @@ private Future updateBidRequest(AuctionContext auctionContext) { .map(bidRequest -> overrideParameters(bidRequest, httpRequest, auctionContext.getPrebidErrors())) .map(bidRequest -> paramsResolver.resolve(bidRequest, auctionContext, ENDPOINT, true)) .map(bidRequest -> ortb2RequestFactory.removeEmptyEids(bidRequest, auctionContext.getDebugWarnings())) + .compose(resolvedBidRequest -> ortb2RequestFactory.limitImpressions( + account, + resolvedBidRequest, + auctionContext.getDebugWarnings())) .compose(resolvedBidRequest -> ortb2RequestFactory.validateRequest( account, resolvedBidRequest, diff --git a/src/main/java/org/prebid/server/auction/requestfactory/AuctionRequestFactory.java b/src/main/java/org/prebid/server/auction/requestfactory/AuctionRequestFactory.java index 628ea212fd8..13df5f4df82 100644 --- a/src/main/java/org/prebid/server/auction/requestfactory/AuctionRequestFactory.java +++ b/src/main/java/org/prebid/server/auction/requestfactory/AuctionRequestFactory.java @@ -241,6 +241,7 @@ private Future updateAndValidateBidRequest(AuctionContext auctionCon return storedRequestProcessor.processAuctionRequest(account.getId(), auctionContext.getBidRequest()) .compose(auctionStoredResult -> updateBidRequest(auctionStoredResult, auctionContext)) + .compose(bidRequest -> ortb2RequestFactory.limitImpressions(account, bidRequest, debugWarnings)) .compose(bidRequest -> ortb2RequestFactory.validateRequest( account, bidRequest, httpRequest, auctionContext.getDebugContext(), debugWarnings)) .map(interstitialProcessor::process); diff --git a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java index 5a122d5a64e..d50b35d3717 100644 --- a/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java +++ b/src/main/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactory.java @@ -6,6 +6,7 @@ import com.iab.openrtb.request.Dooh; import com.iab.openrtb.request.Eid; import com.iab.openrtb.request.Geo; +import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Publisher; import com.iab.openrtb.request.Regs; import com.iab.openrtb.request.Site; @@ -192,6 +193,23 @@ public Future activityInfrastructureFrom(AuctionContext auctionContext.getDebugContext().getTraceLevel())); } + public Future limitImpressions(Account account, BidRequest bidRequest, List warnings) { + final List imps = bidRequest.getImp(); + final int impsLimit = Optional.ofNullable(account) + .map(Account::getAuction) + .map(AccountAuctionConfig::getImpressionLimit) + .orElse(0); + + if (impsLimit > 0 && imps.size() > impsLimit) { + metrics.updateImpsDroppedMetric(imps.size() - impsLimit); + warnings.add(("Only first %d impressions were kept due to the limit, " + + "all the subsequent impressions have been dropped for the auction").formatted(impsLimit)); + return Future.succeededFuture(bidRequest.toBuilder().imp(imps.subList(0, impsLimit)).build()); + } + + return Future.succeededFuture(bidRequest); + } + public Future validateRequest(Account account, BidRequest bidRequest, HttpRequestContext httpRequestContext, diff --git a/src/main/java/org/prebid/server/auction/requestfactory/VideoRequestFactory.java b/src/main/java/org/prebid/server/auction/requestfactory/VideoRequestFactory.java index 2e41fd97a35..811db804fb9 100644 --- a/src/main/java/org/prebid/server/auction/requestfactory/VideoRequestFactory.java +++ b/src/main/java/org/prebid/server/auction/requestfactory/VideoRequestFactory.java @@ -119,6 +119,12 @@ public Future> fromRequest(RoutingContext routingC .map(auctionContext -> auctionContext.with(debugResolver.debugContextFrom(auctionContext))) + .compose(auctionContext -> ortb2RequestFactory.limitImpressions( + auctionContext.getAccount(), + auctionContext.getBidRequest(), + auctionContext.getDebugWarnings()) + .map(auctionContext::with)) + .compose(auctionContext -> ortb2RequestFactory.validateRequest( auctionContext.getAccount(), auctionContext.getBidRequest(), diff --git a/src/main/java/org/prebid/server/metric/MetricName.java b/src/main/java/org/prebid/server/metric/MetricName.java index 84bb68f1c67..ab32c446226 100644 --- a/src/main/java/org/prebid/server/metric/MetricName.java +++ b/src/main/java/org/prebid/server/metric/MetricName.java @@ -31,6 +31,7 @@ public enum MetricName { request_time, prices, imps_requested, + imps_dropped, imps_banner, imps_video, imps_native, diff --git a/src/main/java/org/prebid/server/metric/Metrics.java b/src/main/java/org/prebid/server/metric/Metrics.java index 00295decad3..9010a7018c5 100644 --- a/src/main/java/org/prebid/server/metric/Metrics.java +++ b/src/main/java/org/prebid/server/metric/Metrics.java @@ -182,6 +182,10 @@ public void updateAppAndNoCookieAndImpsRequestedMetrics(boolean isApp, boolean l incCounter(MetricName.imps_requested, numImps); } + public void updateImpsDroppedMetric(int numImps) { + incCounter(MetricName.imps_dropped, numImps); + } + public void updateImpTypesMetrics(List imps) { final Map mediaTypeToCount = imps.stream() diff --git a/src/main/java/org/prebid/server/settings/model/AccountAuctionConfig.java b/src/main/java/org/prebid/server/settings/model/AccountAuctionConfig.java index e41f005df54..397047bf240 100644 --- a/src/main/java/org/prebid/server/settings/model/AccountAuctionConfig.java +++ b/src/main/java/org/prebid/server/settings/model/AccountAuctionConfig.java @@ -60,4 +60,7 @@ public class AccountAuctionConfig { AccountCacheConfig cache; AccountBidRankingConfig ranking; + + @JsonAlias("impression-limit") + Integer impressionLimit; } diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy index bf49ce7c874..21a60bef192 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountAuctionConfig.groovy @@ -35,6 +35,7 @@ class AccountAuctionConfig { @JsonProperty("bidadjustments") BidAdjustment bidAdjustments BidRounding bidRounding + Integer impressionLimit @JsonProperty("price_granularity") PriceGranularityType priceGranularitySnakeCase @@ -54,4 +55,7 @@ class AccountAuctionConfig { AccountPriceFloorsConfig priceFloorsSnakeCase @JsonProperty("bid_rounding") BidRounding bidRoundingSnakeCase + @JsonProperty("impression_limit") + Integer impressionLimitSnakeCase + } diff --git a/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy index a9bc17dfe1d..1506e2e0a4d 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy @@ -9,6 +9,7 @@ import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Device import org.prebid.server.functional.model.request.auction.DeviceExt +import org.prebid.server.functional.model.request.auction.Imp import org.prebid.server.functional.model.request.auction.PrebidStoredRequest import org.prebid.server.functional.model.request.auction.Renderer import org.prebid.server.functional.model.request.auction.RendererData @@ -47,15 +48,18 @@ class AuctionSpec extends BaseSpec { private static final Integer DEFAULT_TIMEOUT = getRandomTimeout() private static final Integer MIN_BID_ID_LENGTH = 17 private static final Integer DEFAULT_UUID_LENGTH = 36 - private static final Map PBS_CONFIG = ["auction.biddertmax.max" : MAX_TIMEOUT as String, - "auction.default-timeout-ms": DEFAULT_TIMEOUT as String] private static final Map GENERIC_CONFIG = [ "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()] - @Shared PrebidServerService prebidServerService = pbsServiceFactory.getService(PBS_CONFIG) + private static final String IMPS_REQUESTED_METRIC = 'imps_requested' + private static final String IMPS_DROPPED_METRIC = 'imps_dropped' + private static final Integer IMP_LIMIT = 1 + private static final Map PBS_CONFIG = ["auction.biddertmax.max" : MAX_TIMEOUT as String, + "auction.default-timeout-ms": DEFAULT_TIMEOUT as String] + def "PBS should return version in response header for auction request for #description"() { when: "PBS processes auction request" def response = defaultPbsService.sendAuctionRequestRaw(bidRequest) @@ -721,4 +725,163 @@ class AuctionSpec extends BaseSpec { cleanup: "Stop and remove pbs container" pbsServiceFactory.removeContainer(pbsConfig) } + + def "PBS should drop extra impressions with warnings when number of impressions exceeds impression-limit"() { + given: "Bid request with multiple imps" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp.add(Imp.getDefaultImpression()) + } + + and: "Account in the DB with impression limit config" + def accountConfig = new AccountConfig(auction: accountAuctionConfig) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(defaultPbsService) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain seatNonBid" + assert !response?.ext?.seatnonbid + + and: "PBS should emit an warning" + assert response.ext?.warnings[PREBID]*.code == [999] + assert response.ext?.warnings[PREBID]*.message == + ["Only first $IMP_LIMIT impressions were kept due to the limit, " + + "all the subsequent impressions have been dropped for the auction" as String] + + and: "PBS shouldn't emit an error" + assert !response.ext?.errors + + and: "Metrics for imps should be updated" + def metrics = defaultPbsService.sendCollectedMetricsRequest() + assert metrics[IMPS_DROPPED_METRIC] == bidRequest.imp.size() - IMP_LIMIT + assert metrics[IMPS_REQUESTED_METRIC] == IMP_LIMIT + + and: "Response should contain seat bid" + assert response.seatbid[0].bid.size() == IMP_LIMIT + + and: "Bidder request should contain imps according to limit" + assert bidder.getBidderRequest(bidRequest.id).imp.size() == IMP_LIMIT + + where: + accountAuctionConfig << [ + new AccountAuctionConfig(impressionLimit: IMP_LIMIT), + new AccountAuctionConfig(impressionLimitSnakeCase: IMP_LIMIT) + ] + } + + def "PBS shouldn't drop extra impressions when number of impressions equal to impression-limit"() { + given: "Bid request with multiple imps" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp.add(Imp.getDefaultImpression()) + } + + and: "Account in the DB with impression limit config" + def accountConfig = new AccountConfig(auction: new AccountAuctionConfig(impressionLimit: bidRequest.imp.size())) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(defaultPbsService) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain seatNonBid" + assert !response?.ext?.seatnonbid + + and: "Response shouldn't contain warnings and error" + assert !response.ext?.warnings + assert !response.ext?.errors + + and: "Metrics for imps requested should be updated" + def metrics = defaultPbsService.sendCollectedMetricsRequest() + assert metrics[IMPS_REQUESTED_METRIC] == bidRequest.imp.size() + assert !metrics[IMPS_DROPPED_METRIC] + + and: "Response should contain seat bid" + assert response.seatbid[0].bid.size() == bidRequest.imp.size() + + and: "Bidder request should contain originals imps" + assert bidder.getBidderRequest(bidRequest.id).imp.size() == bidRequest.imp.size() + } + + def "PBS shouldn't drop extra impressions when number of impressions less than or equal to impression-limit"() { + given: "Bid request with multiple imps" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp.add(Imp.getDefaultImpression()) + } + + and: "Account in the DB with impression limit config" + def impressionLimit = bidRequest.imp.size() + 1 + def accountConfig = new AccountConfig(auction: new AccountAuctionConfig(impressionLimit: impressionLimit)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(defaultPbsService) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain seatNonBid" + assert !response?.ext?.seatnonbid + + and: "Response shouldn't contain warnings and error" + assert !response.ext?.warnings + assert !response.ext?.errors + + and: "Metrics for imps requested should be updated" + def metrics = defaultPbsService.sendCollectedMetricsRequest() + assert metrics[IMPS_REQUESTED_METRIC] == bidRequest.imp.size() + assert !metrics[IMPS_DROPPED_METRIC] + + and: "Response should contain seat bid" + assert response.seatbid[0].bid.size() == bidRequest.imp.size() + + and: "Bidder request should contain originals imps" + assert bidder.getBidderRequest(bidRequest.id).imp.size() == bidRequest.imp.size() + } + + def "PBS shouldn't drop extra impressions when impression-limit set to #impressionLimit"() { + given: "Bid request with multiple imps" + def bidRequest = BidRequest.defaultBidRequest.tap { + imp.add(Imp.getDefaultImpression()) + } + + and: "Account in the DB with impression limit config" + def accountConfig = new AccountConfig(auction: new AccountAuctionConfig(impressionLimit: impressionLimit)) + def account = new Account(uuid: bidRequest.getAccountId(), config: accountConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(defaultPbsService) + + when: "PBS processes auction request" + def response = defaultPbsService.sendAuctionRequest(bidRequest) + + then: "Response should contain seatNonBid" + assert !response?.ext?.seatnonbid + + and: "Response shouldn't contain warnings and error" + assert !response.ext?.warnings + assert !response.ext?.errors + + and: "Metrics for imps requested should be updated" + def metrics = defaultPbsService.sendCollectedMetricsRequest() + assert metrics[IMPS_REQUESTED_METRIC] == bidRequest.imp.size() + assert !metrics[IMPS_DROPPED_METRIC] + + and: "Response should contain seat bid" + assert response.seatbid[0].bid.size() == bidRequest.imp.size() + + and: "Bidder request should contain originals imps" + assert bidder.getBidderRequest(bidRequest.id).imp.size() == bidRequest.imp.size() + + where: + impressionLimit << [null, PBSUtils.randomNegativeNumber, 0] + } } diff --git a/src/test/java/org/prebid/server/auction/requestfactory/AmpRequestFactoryTest.java b/src/test/java/org/prebid/server/auction/requestfactory/AmpRequestFactoryTest.java index fe4c72b89ca..b521d8623d6 100644 --- a/src/test/java/org/prebid/server/auction/requestfactory/AmpRequestFactoryTest.java +++ b/src/test/java/org/prebid/server/auction/requestfactory/AmpRequestFactoryTest.java @@ -1757,6 +1757,8 @@ private void givenBidRequest( given(ortb2ImplicitParametersResolver.resolve(any(), any(), any(), anyBoolean())).willAnswer( answerWithFirstArgument()); + given(ortb2RequestFactory.limitImpressions(any(), any(), any())) + .willAnswer(invocation -> Future.succeededFuture((BidRequest) invocation.getArgument(1))); given(ortb2RequestFactory.validateRequest(any(), any(), any(), any(), any())) .willAnswer(invocation -> Future.succeededFuture((BidRequest) invocation.getArgument(1))); diff --git a/src/test/java/org/prebid/server/auction/requestfactory/AuctionRequestFactoryTest.java b/src/test/java/org/prebid/server/auction/requestfactory/AuctionRequestFactoryTest.java index 19e414e80c9..7f07e0107e7 100644 --- a/src/test/java/org/prebid/server/auction/requestfactory/AuctionRequestFactoryTest.java +++ b/src/test/java/org/prebid/server/auction/requestfactory/AuctionRequestFactoryTest.java @@ -167,6 +167,8 @@ public void setUp() { given(ortb2RequestFactory.executeRawAuctionRequestHooks(any())) .willAnswer(invocation -> Future.succeededFuture( ((AuctionContext) invocation.getArgument(0)).getBidRequest())); + given(ortb2RequestFactory.limitImpressions(any(), any(), any())) + .willAnswer(invocationOnMock -> Future.succeededFuture(invocationOnMock.getArgument(1))); given(ortb2RequestFactory.validateRequest(any(), any(), any(), any(), any())) .willAnswer(invocationOnMock -> Future.succeededFuture((BidRequest) invocationOnMock.getArgument(1))); given(ortb2RequestFactory.removeEmptyEids(any(), any())) diff --git a/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java b/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java index d5c2cb5bea3..778916127c7 100644 --- a/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java +++ b/src/test/java/org/prebid/server/auction/requestfactory/Ortb2RequestFactoryTest.java @@ -7,6 +7,7 @@ import com.iab.openrtb.request.Dooh; import com.iab.openrtb.request.Eid; import com.iab.openrtb.request.Geo; +import com.iab.openrtb.request.Imp; import com.iab.openrtb.request.Publisher; import com.iab.openrtb.request.Regs; import com.iab.openrtb.request.Site; @@ -83,6 +84,7 @@ import java.util.List; import java.util.function.UnaryOperator; +import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; @@ -90,12 +92,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.prebid.server.assertion.FutureAssertion.assertThat; @@ -1708,6 +1712,70 @@ public void removeEmptyEidsShouldRemoveEmptyUidsOnly() { "removed EID source3 due to empty ID"); } + @Test + public void validateShouldDropImpressionsOverAccountLimitAndReturnWarning() { + // given + final Imp imp1 = Imp.builder().id("1").build(); + final Imp imp2 = Imp.builder().id("2").build(); + final BidRequest bidRequest = givenBidRequest(request -> request.imp(asList(imp1, imp2))); + final List warning = new ArrayList<>(); + + final Account givenAccount = Account.builder() + .id(ACCOUNT_ID) + .auction(AccountAuctionConfig.builder().impressionLimit(1).build()) + .build(); + + // when + final BidRequest result = target.limitImpressions(givenAccount, bidRequest, warning).result(); + + // then + assertThat(warning).hasSize(1) + .containsOnly("Only first 1 impressions were kept due to the limit, " + + "all the subsequent impressions have been dropped for the auction"); + + verify(metrics).updateImpsDroppedMetric(1); + assertThat(result.getImp()).containsOnly(imp1); + } + + @Test + public void validateShouldNotDropImpressionsReturnWarningWhenAccountLimitIsSetToZero() { + // given + final Imp imp1 = Imp.builder().id("1").build(); + final Imp imp2 = Imp.builder().id("2").build(); + final BidRequest bidRequest = givenBidRequest(request -> request.imp(asList(imp1, imp2))); + final List warning = new ArrayList<>(); + + final Account givenAccount = Account.builder() + .id(ACCOUNT_ID) + .auction(AccountAuctionConfig.builder().impressionLimit(0).build()) + .build(); + + // when + final BidRequest result = target.limitImpressions(givenAccount, bidRequest, warning).result(); + + // then + assertThat(warning).isEmpty(); + assertThat(result).isEqualTo(bidRequest); + verify(metrics, never()).updateImpsDroppedMetric(anyInt()); + } + + @Test + public void validateShouldNotDropImpressionsReturnWarningWhenAccountLimitIsNotSet() { + // given + final Imp imp1 = Imp.builder().id("1").build(); + final Imp imp2 = Imp.builder().id("2").build(); + final BidRequest bidRequest = givenBidRequest(request -> request.imp(asList(imp1, imp2))); + final List warning = new ArrayList<>(); + + // when + final BidRequest result = target.limitImpressions(Account.empty(ACCOUNT_ID), bidRequest, warning).result(); + + // then + assertThat(warning).isEmpty(); + assertThat(result).isEqualTo(bidRequest); + verify(metrics, never()).updateImpsDroppedMetric(anyInt()); + } + private void givenTarget(int timeoutAdjustmentFactor) { target = new Ortb2RequestFactory( timeoutAdjustmentFactor, diff --git a/src/test/java/org/prebid/server/auction/requestfactory/VideoRequestFactoryTest.java b/src/test/java/org/prebid/server/auction/requestfactory/VideoRequestFactoryTest.java index ca01531b02e..ad154e06b49 100644 --- a/src/test/java/org/prebid/server/auction/requestfactory/VideoRequestFactoryTest.java +++ b/src/test/java/org/prebid/server/auction/requestfactory/VideoRequestFactoryTest.java @@ -408,6 +408,8 @@ private void givenBidRequest(BidRequest bidRequest, List podErrors) { .build()); given(ortb2RequestFactory.fetchAccountWithoutStoredRequestLookup(any())).willReturn(Future.succeededFuture()); + given(ortb2RequestFactory.limitImpressions(any(), any(), any())) + .willAnswer(invocationOnMock -> Future.succeededFuture(invocationOnMock.getArgument(1))); given(ortb2RequestFactory.validateRequest(any(), any(), any(), any(), any())) .willAnswer(invocation -> Future.succeededFuture((BidRequest) invocation.getArgument(1))); diff --git a/src/test/java/org/prebid/server/metric/MetricsTest.java b/src/test/java/org/prebid/server/metric/MetricsTest.java index 243ef33e66d..73e9b902168 100644 --- a/src/test/java/org/prebid/server/metric/MetricsTest.java +++ b/src/test/java/org/prebid/server/metric/MetricsTest.java @@ -332,6 +332,15 @@ public void updateAppAndNoCookieAndImpsRequestedMetricsShouldIncrementMetrics() assertThat(metricRegistry.counter("imps_requested").getCount()).isEqualTo(4); } + @Test + public void updateImpsDroppedMetricShouldIncrementMetrics() { + // when + metrics.updateImpsDroppedMetric(2); + + // then + assertThat(metricRegistry.counter("imps_dropped").getCount()).isEqualTo(2); + } + @Test public void updateDebugRequestsMetricsShouldIncrementMetrics() { // when