From 5a880fb9effe8f0aae8240740f353e74925b2218 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Tue, 14 Oct 2025 21:27:48 +0300 Subject: [PATCH 1/9] Tests: Support GET interface profiles --- .../request/get/GeneralGetRequest.groovy | 292 +++++++++ .../response/get/GeneralGetResponse.groovy | 11 + .../response/get/GeneralGetResponseExt.groovy | 14 + .../service/PrebidServerService.groovy | 25 +- .../functional/tests/ProfileSpec.groovy | 584 +++++++++++++++++- .../functional/tests/TargetingSpec.groovy | 12 +- 6 files changed, 918 insertions(+), 20 deletions(-) create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/get/GeneralGetResponse.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/model/response/get/GeneralGetResponseExt.groovy diff --git a/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy new file mode 100644 index 00000000000..6251dc5cf31 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy @@ -0,0 +1,292 @@ +package org.prebid.server.functional.model.request.get + +import com.fasterxml.jackson.annotation.JsonProperty +import org.prebid.server.functional.model.request.amp.ConsentType +import org.prebid.server.functional.model.request.auction.DebugCondition +import org.prebid.server.functional.util.PBSUtils + +import static org.prebid.server.functional.model.request.auction.DebugCondition.ENABLED + +class GeneralGetRequest { + + @JsonProperty("srid") + String storedRequestId + + @JsonProperty("tag_id") + String storedRequestIdLegacy + + @JsonProperty("pubid") + String accountId + + @JsonProperty("account") + String accountIdLegacy + + @JsonProperty("tmax") + Integer timeoutMax + + @JsonProperty("debug") + DebugCondition debug + + @JsonProperty("of") + String outputFormat + + @JsonProperty("om") + String outputModule + + @JsonProperty("rprof") + List requestProfiles + + @JsonProperty("iprof") + List impProfiles + + @JsonProperty("sarid") + String storedAuctionResponseId + + @JsonProperty("mimes") + List mimes + + @JsonProperty("w") + Integer width + + @JsonProperty("h") + Integer height + + @JsonProperty("ow") + Integer originalWidth + + @JsonProperty("oh") + Integer originalHeight + + @JsonProperty("sizes") + Object sizes + + @JsonProperty("ms") + Object sizesLegacy + + @JsonProperty("slot") + String slot + + @JsonProperty("mindur") + Integer minDuration + + @JsonProperty("maxdur") + Integer maxDuration + + @JsonProperty("api") + List api + + @JsonProperty("battr") + List battr + + @JsonProperty("delivery") + List delivery + + @JsonProperty("linearity") + Integer linearityMode + + @JsonProperty("minbr") + Integer minBitrate + + @JsonProperty("maxbr") + Integer maxBitrate + + @JsonProperty("maxex") + Integer maxExtended + + @JsonProperty("maxseq") + Integer maxSequence + + @JsonProperty("mincpms") + Integer minCpmPerSec + + @JsonProperty("poddur") + Integer podDuration + + @JsonProperty("podid") + Integer podId + + @JsonProperty("podseq") + Integer podSequence + + @JsonProperty("proto") + List proto + + @JsonProperty("rqddurs") + List requiredDurations + + @JsonProperty("seq") + Integer sequence + + @JsonProperty("slotinpod") + Integer slotInPod + + @JsonProperty("startdelay") + Integer startDelay + + @JsonProperty("skip") + Integer skip + + @JsonProperty("skipafter") + Integer skipAfter + + @JsonProperty("skipmin") + Integer skipMin + + @JsonProperty("pos") + Integer position + + @JsonProperty("stitched") + Integer stitched + + @JsonProperty("feed") + Integer feed + + @JsonProperty("nvol") + Integer normalizedVolume + + @JsonProperty("placement") + Integer placement + + @JsonProperty("plcmt") + Integer placementSubtype + + @JsonProperty("playbackend") + Integer playbackEndMode + + @JsonProperty("playbackmethod") + List playbackMethods + + @JsonProperty("boxingallowed") + Integer boxingAllowed + + @JsonProperty("btype") + List bannerTypes + + @JsonProperty("expdir") + List expandableDirections + + @JsonProperty("topframe") + Integer topFrame + + @JsonProperty("targeting") + String targeting + + @JsonProperty("consent") + String gppConsent + + @JsonProperty("gdpr_consent") + String gppConsentLegacy + + @JsonProperty("consent_string") + String gppConsentStringLegacy + + @JsonProperty("gdpr") + Integer gdpr + + @JsonProperty("privacy") + Integer privacy + + @JsonProperty("gdpr_applies") + String gdprApplies + + @JsonProperty("usp") + String usPrivacy + + @JsonProperty("addtl_consent") + String additionalConsent + + @JsonProperty("consent_type") + ConsentType consentType + + @JsonProperty("gpp_sid") + Integer gppSid + + @JsonProperty("coppa") + Integer coppaFlag + + @JsonProperty("gpc") + Integer globalPrivacyControl + + @JsonProperty("dnt") + Integer doNotTrack + + @JsonProperty("lmt") + Integer limitAdTracking + + @JsonProperty("bcat") + List blockedCategories + + @JsonProperty("badv") + List blockedAdvertisers + + @JsonProperty("page") + String page + + @JsonProperty("bundle") + String appBundle + + @JsonProperty("name") + String appName + + @JsonProperty("storeurl") + String storeUrl + + @JsonProperty("cgenre") + String contentGenre + + @JsonProperty("clang") + String contentLanguage + + @JsonProperty("crating") + String contentRating + + @JsonProperty("ccat") + Integer contentCategory + + @JsonProperty("ccattax") + List contentCategoryTaxonomy + + @JsonProperty("cseries") + String contentSeries + + @JsonProperty("rss_feed") + String contentSeriesAlias + + @JsonProperty("ctitle") + String contentTitle + + @JsonProperty("curl") + String contentUrl + + @JsonProperty("clivestream") + String contentLivestream + + @JsonProperty("ip") + String deviceIp + + @JsonProperty("ua") + String deviceUa + + @JsonProperty("dtype") + String deviceType + + @JsonProperty("ifa") + String deviceIfa + + @JsonProperty("ifat") + String deviceIfaType + + @JsonProperty("unknown") + String unknown + + @JsonProperty("unknown_alias") + String unknownAlias + + static GeneralGetRequest getDefault(String storedRequestId = PBSUtils.randomNumber) { + new GeneralGetRequest(storedRequestId: storedRequestId, debug: ENABLED) + } + + String resolveStoredRequestId() { + storedRequestId != null ? storedRequestId : storedRequestIdLegacy + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/get/GeneralGetResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/response/get/GeneralGetResponse.groovy new file mode 100644 index 00000000000..ec9e938bbda --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/get/GeneralGetResponse.groovy @@ -0,0 +1,11 @@ +package org.prebid.server.functional.model.response.get + +import groovy.transform.ToString +import org.prebid.server.functional.model.response.amp.AmpResponseExt + +@ToString(includeNames = true, ignoreNulls = true) +class GeneralGetResponse { + + Map targeting + GeneralGetResponseExt ext +} diff --git a/src/test/groovy/org/prebid/server/functional/model/response/get/GeneralGetResponseExt.groovy b/src/test/groovy/org/prebid/server/functional/model/response/get/GeneralGetResponseExt.groovy new file mode 100644 index 00000000000..619a6c4daf6 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/response/get/GeneralGetResponseExt.groovy @@ -0,0 +1,14 @@ +package org.prebid.server.functional.model.response.get + +import groovy.transform.ToString +import org.prebid.server.functional.model.response.BidderError +import org.prebid.server.functional.model.response.Debug +import org.prebid.server.functional.model.response.auction.ErrorType + +@ToString(includeNames = true, ignoreNulls = true) +class GeneralGetResponseExt { + + Debug debug + Map> errors + Map> warnings +} diff --git a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy index b9c173baa54..89afdf84707 100644 --- a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy +++ b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy @@ -13,6 +13,7 @@ import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.cookiesync.CookieSyncRequest import org.prebid.server.functional.model.request.event.EventRequest +import org.prebid.server.functional.model.request.get.GeneralGetRequest import org.prebid.server.functional.model.request.logging.httpinteraction.HttpInteractionRequest import org.prebid.server.functional.model.request.setuid.SetuidRequest import org.prebid.server.functional.model.request.vtrack.VtrackRequest @@ -24,6 +25,7 @@ import org.prebid.server.functional.model.response.biddersparams.BiddersParamsRe import org.prebid.server.functional.model.response.cookiesync.CookieSyncResponse import org.prebid.server.functional.model.response.cookiesync.RawCookieSyncResponse import org.prebid.server.functional.model.response.currencyrates.CurrencyRatesResponse +import org.prebid.server.functional.model.response.get.GeneralGetResponse import org.prebid.server.functional.model.response.getuids.GetuidResponse import org.prebid.server.functional.model.response.infobidders.BidderInfoResponse import org.prebid.server.functional.model.response.setuid.SetuidResponse @@ -45,6 +47,7 @@ import static java.time.ZoneOffset.UTC class PrebidServerService implements ObjectMapperWrapper { static final String AUCTION_ENDPOINT = "/openrtb2/auction" + static final String GENERAL_GET_ENDPOINT = "/openrtb2/auction" static final String AMP_ENDPOINT = "/openrtb2/amp" static final String COOKIE_SYNC_ENDPOINT = "/cookie_sync" static final String SET_UID_ENDPOINT = "/setuid" @@ -94,11 +97,11 @@ class PrebidServerService implements ObjectMapperWrapper { } } - AmpResponse sendAmpRequestWithAdditionalQueries(AmpRequest ampRequest, Map queries = [:]) { - def response = getAmp(ampRequest, [:], queries) + GeneralGetResponse sendGeneralGetRequest(GeneralGetRequest request, Map headers = [:]) { + def response = getAuction(request, headers) checkResponseStatusCode(response) - decode(response.body.asString(), AmpResponse) + decode(response.body.asString(), GeneralGetResponse) } AmpResponse sendAmpRequest(AmpRequest ampRequest, Map headers = [:]) { @@ -331,20 +334,22 @@ class PrebidServerService implements ObjectMapperWrapper { requestSpecification.post(COOKIE_SYNC_ENDPOINT) } - private Response getAmp(AmpRequest ampRequest, - Map headers = [:], - Map queries = [:]) { + private Response getAmp(AmpRequest ampRequest, Map headers = [:]) { def map = toMap(ampRequest) - if (!queries.isEmpty()) { - map.putAll(queries) - } - given(requestSpecification).headers(headers) .queryParams(map) .get(AMP_ENDPOINT) } + private Response getAuction(GeneralGetRequest request, Map headers = [:]) { + def map = toMap(request) + + given(requestSpecification).headers(headers) + .queryParams(map) + .get(GENERAL_GET_ENDPOINT) + } + private void checkResponseStatusCode(Response response, int statusCode = 200) { def responseStatusCode = response.statusCode if (responseStatusCode != statusCode) { diff --git a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy index c6d600d518c..672c3205456 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy @@ -19,6 +19,7 @@ import org.prebid.server.functional.model.request.auction.ImpExt import org.prebid.server.functional.model.request.auction.ImpExtPrebid import org.prebid.server.functional.model.request.auction.StoredAuctionResponse import org.prebid.server.functional.model.request.auction.StoredBidResponse +import org.prebid.server.functional.model.request.get.GeneralGetRequest import org.prebid.server.functional.model.request.profile.Profile import org.prebid.server.functional.model.request.profile.ImpProfile import org.prebid.server.functional.model.request.profile.ProfileMergePrecedence @@ -1530,7 +1531,7 @@ class ProfileSpec extends BaseSpec { pbsContainer.withCopyToContainer(Transferable.of(encodeYaml(accountsConfig)), SETTINGS_FILENAME) pbsContainer.start() - pbsWithStoredProfiles = new PrebidServerService(pbsContainer) + def pbsWithStoredProfiles = new PrebidServerService(pbsContainer) and: "BidRequest with profile" def requestWithProfile = BidRequest.getDefaultBidRequest().tap { @@ -1538,7 +1539,7 @@ class ProfileSpec extends BaseSpec { } when: "PBS processes auction request" - defaultPbsService.sendAuctionRequest(requestWithProfile) + pbsWithStoredProfiles.sendAuctionRequest(requestWithProfile) then: "PBs should throw error due to invalid profile config" def exception = thrown(PrebidServerException) @@ -1549,11 +1550,584 @@ class ProfileSpec extends BaseSpec { pbsContainer.stop() } + def "PBS should use request profile for general get request when profile included as parameter"() { + given: "Default profile in database" + def accountId = PBSUtils.randomNumber as String + def requestProfile = RequestProfile.getProfile(accountId) + profileRequestDao.save(StoredProfileRequest.getProfile(requestProfile)) + + and: "Default get request" + def getRequest = GeneralGetRequest.getDefault().tap { + it.requestProfiles = [requestProfile.id] + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(getRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.(getRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain data from profile" + verifyAll(bidder.getBidderRequest(request.id)) { + it.site.id == requestProfile.body.site.id + it.site.name == requestProfile.body.site.name + it.site.domain == requestProfile.body.site.domain + it.site.cat == requestProfile.body.site.cat + it.site.sectionCat == requestProfile.body.site.sectionCat + it.site.pageCat == requestProfile.body.site.pageCat + it.site.page == requestProfile.body.site.page + it.site.ref == requestProfile.body.site.ref + it.site.search == requestProfile.body.site.search + it.site.keywords == requestProfile.body.site.keywords + it.site.ext.data == requestProfile.body.site.ext.data + + it.device.didsha1 == requestProfile.body.device.didsha1 + it.device.didmd5 == requestProfile.body.device.didmd5 + it.device.dpidsha1 == requestProfile.body.device.dpidsha1 + it.device.ifa == requestProfile.body.device.ifa + it.device.macsha1 == requestProfile.body.device.macsha1 + it.device.macmd5 == requestProfile.body.device.macmd5 + it.device.dpidmd5 == requestProfile.body.device.dpidmd5 + } + } + + def "PBS should use imp profile for general get request when profile included as parameter"() { + given: "Default profile in database" + def accountId = PBSUtils.randomNumber as String + def impProfile = ImpProfile.getProfile(accountId) + profileImpDao.save(StoredProfileImp.getProfile(impProfile)) + + and: "Default get request" + def getRequest = GeneralGetRequest.getDefault().tap { + it.impProfiles = [impProfile.id] + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(getRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.sendGeneralGetRequest(getRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request imp should contain data from profile" + verifyAll(bidder.getBidderRequest(request.id).imp) { + it.id == [impProfile.body.id] + it.banner == [impProfile.body.banner] + } + } + + def "PBS should use request profile for general get request when profile included as parameter and exist in filesystem"() { + given: "Default get request" + def generalGetRequest = GeneralGetRequest.default.tap { + it.requestProfiles = [fileRequestProfile.id] + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(ACCOUNT_ID_FILE_STORAGE.toString()) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain data from profile" + verifyAll(bidder.getBidderRequest(request.id)) { + it.site.id == fileRequestProfile.body.site.id + it.site.name == fileRequestProfile.body.site.name + it.site.domain == fileRequestProfile.body.site.domain + it.site.cat == fileRequestProfile.body.site.cat + it.site.sectionCat == fileRequestProfile.body.site.sectionCat + it.site.pageCat == fileRequestProfile.body.site.pageCat + it.site.page == fileRequestProfile.body.site.page + it.site.ref == fileRequestProfile.body.site.ref + it.site.search == fileRequestProfile.body.site.search + it.site.keywords == fileRequestProfile.body.site.keywords + it.site.ext.data == fileRequestProfile.body.site.ext.data + + it.device.didsha1 == fileRequestProfile.body.device.didsha1 + it.device.didmd5 == fileRequestProfile.body.device.didmd5 + it.device.dpidsha1 == fileRequestProfile.body.device.dpidsha1 + it.device.ifa == fileRequestProfile.body.device.ifa + it.device.macsha1 == fileRequestProfile.body.device.macsha1 + it.device.macmd5 == fileRequestProfile.body.device.macmd5 + it.device.dpidmd5 == fileRequestProfile.body.device.dpidmd5 + } + } + + def "PBS should use imp profile for general get request when profile included as parameter and exist in filesystem"() { + given: "Default GeneralGetRequest" + def generalGetRequest = GeneralGetRequest.default.tap { + it.impProfiles = [fileImpProfile.id] + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(ACCOUNT_ID_FILE_STORAGE.toString()) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(fileImpProfile.id, request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request imp should contain data from profile" + verifyAll(bidder.getBidderRequest(request.id).imp) { + it.id == [fileImpProfile.body.id] + it.banner == [fileImpProfile.body.banner] + } + } + + def "PBS should emit error and metrics when request profile called from imp level for profile general get parameter"() { + given: "Default profile in database" + def accountId = PBSUtils.randomNumber as String + def requestProfile = RequestProfile.getProfile(accountId) + profileRequestDao.save(StoredProfileRequest.getProfile(requestProfile)) + + and: "Default GeneralGetRequest" + def generalGetRequest = GeneralGetRequest.default.tap { + it.impProfiles = [requestProfile.id] + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.sendGeneralGetRequest(generalGetRequest) + + then: "PBS should emit proper warning" + assert response.ext?.warnings[ErrorType.PREBID]*.code == [999] + assert response.ext?.warnings[ErrorType.PREBID]*.message == [NO_PROFILE_MESSAGE.formatted(requestProfile.id)] + + and: "Response should contain error" + assert !response.ext?.errors + + and: "Missing metric should increments" + def metrics = pbsWithStoredProfiles.sendCollectedMetricsRequest() + assert metrics[MISSING_ACCOUNT_PROFILE_METRIC.formatted(accountId)] == 1 + + and: "Bidder request should contain data from profile" + verifyAll(bidder.getBidderRequest(request.id)) { + it.site.id == request.site.id + it.site.name == request.site.name + it.site.domain == request.site.domain + it.site.cat == request.site.cat + it.site.sectionCat == request.site.sectionCat + it.site.pageCat == request.site.pageCat + it.site.page == request.site.page + it.site.ref == request.site.ref + it.site.search == request.site.search + it.site.keywords == request.site.keywords + it.site.ext.data == request.site.ext.data + + it.device.didsha1 == request.device.didsha1 + it.device.didmd5 == request.device.didmd5 + it.device.dpidsha1 == request.device.dpidsha1 + it.device.ifa == request.device.ifa + it.device.macsha1 == request.device.macsha1 + it.device.macmd5 == request.device.macmd5 + it.device.dpidmd5 == request.device.dpidmd5 + } + } + + def "PBS should emit error and metrics when imp profile called from request level for profile general get parameter"() { + given: "Default profile in database" + def accountId = PBSUtils.randomNumber as String + def impProfile = ImpProfile.getProfile(accountId) + profileImpDao.save(StoredProfileImp.getProfile(impProfile)) + + and: "Default GeneralGetRequest" + def generalGetRequest = GeneralGetRequest.default.tap { + it.requestProfiles = [impProfile.id] + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.sendGeneralGetRequest(generalGetRequest) + + then: "PBS should emit proper warning" + assert response.ext?.warnings[ErrorType.PREBID]*.code == [999] + assert response.ext?.warnings[ErrorType.PREBID]*.message == [NO_PROFILE_MESSAGE.formatted(impProfile.id)] + + and: "Response should contain error" + assert !response.ext?.errors + + and: "Missing metric should increments" + def metrics = pbsWithStoredProfiles.sendCollectedMetricsRequest() + assert metrics[MISSING_ACCOUNT_PROFILE_METRIC.formatted(accountId)] == 1 + + and: "Bidder request imp should contain data from original imp" + assert bidder.getBidderRequest(request.id).imp.banner == request.imp.banner + } + + def "PBS should emit error and metrics when request profile missing for profile general get parameter"() { + given: "Default GeneralGetRequest" + def requestProfile = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.requestProfiles = [requestProfile] + } + + and: "Default stored request" + def accountId = PBSUtils.randomNumber as String + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.sendGeneralGetRequest(generalGetRequest) + + then: "PBS should emit proper warning" + assert response.ext?.warnings[ErrorType.PREBID]*.code == [999] + assert response.ext?.warnings[ErrorType.PREBID]*.message == [NO_PROFILE_MESSAGE.formatted(requestProfile)] + + and: "Response should contain error" + assert !response.ext?.errors + + and: "Missing metric should increments" + def metrics = pbsWithStoredProfiles.sendCollectedMetricsRequest() + assert metrics[MISSING_ACCOUNT_PROFILE_METRIC.formatted(accountId)] == 1 + + and: "Bidder request should contain data from original stored request" + verifyAll(bidder.getBidderRequest(request.id)) { + it.site.id == request.site.id + it.site.name == request.site.name + it.site.domain == request.site.domain + it.site.cat == request.site.cat + it.site.sectionCat == request.site.sectionCat + it.site.pageCat == request.site.pageCat + it.site.page == request.site.page + it.site.ref == request.site.ref + it.site.search == request.site.search + it.site.keywords == request.site.keywords + it.site.ext.data == request.site.ext.data + + it.device.didsha1 == request.device.didsha1 + it.device.didmd5 == request.device.didmd5 + it.device.dpidsha1 == request.device.dpidsha1 + it.device.ifa == request.device.ifa + it.device.macsha1 == request.device.macsha1 + it.device.macmd5 == request.device.macmd5 + it.device.dpidmd5 == request.device.dpidmd5 + } + + and: "Bidder request imp should contain data from original imp" + assert bidder.getBidderRequest(request.id).imp.banner == request.imp.banner + + where: + requestParam | impParam + [PBSUtils.randomString] | null + null | [PBSUtils.randomString] + } + + def "PBS should emit error and metrics when imp profile missing for profile general get parameter"() { + given: "Default GeneralGetRequest" + def impProfile = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.impProfiles = [impProfile] + } + + and: "Default stored request" + def accountId = PBSUtils.randomNumber as String + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.sendGeneralGetRequest(generalGetRequest) + + then: "PBS should emit proper warning" + assert response.ext?.warnings[ErrorType.PREBID]*.code == [999] + assert response.ext?.warnings[ErrorType.PREBID]*.message == [NO_PROFILE_MESSAGE.formatted(impProfile.id)] + + and: "Response should contain error" + assert !response.ext?.errors + + and: "Missing metric should increments" + def metrics = pbsWithStoredProfiles.sendCollectedMetricsRequest() + assert metrics[MISSING_ACCOUNT_PROFILE_METRIC.formatted(accountId)] == 1 + + and: "Bidder request should contain data from original stored request" + verifyAll(bidder.getBidderRequest(request.id)) { + it.site.id == request.site.id + it.site.name == request.site.name + it.site.domain == request.site.domain + it.site.cat == request.site.cat + it.site.sectionCat == request.site.sectionCat + it.site.pageCat == request.site.pageCat + it.site.page == request.site.page + it.site.ref == request.site.ref + it.site.search == request.site.search + it.site.keywords == request.site.keywords + it.site.ext.data == request.site.ext.data + + it.device.didsha1 == request.device.didsha1 + it.device.didmd5 == request.device.didmd5 + it.device.dpidsha1 == request.device.dpidsha1 + it.device.ifa == request.device.ifa + it.device.macsha1 == request.device.macsha1 + it.device.macmd5 == request.device.macmd5 + it.device.dpidmd5 == request.device.dpidmd5 + } + + and: "Bidder request imp should contain data from original imp" + assert bidder.getBidderRequest(request.id).imp.banner == request.imp.banner + } + + def "PBS should prioritise profile for request and emit warning when profile included as parameter more than limit"() { + given: "Default profiles in database" + def accountId = PBSUtils.randomNumber as String + def profileSite = Site.rootFPDSite + def profileDevice = Device.default + def firstRequestProfile = RequestProfile.getProfile(accountId).tap { + it.body.site = profileSite + it.body.device = null + } + def secondRequestProfile = RequestProfile.getProfile(accountId).tap { + it.body.site = null + it.body.device = profileDevice + } + def impProfile = ImpProfile.getProfile(accountId, Imp.getDefaultImpression(VIDEO)) + profileRequestDao.save(StoredProfileRequest.getProfile(firstRequestProfile)) + profileRequestDao.save(StoredProfileRequest.getProfile(secondRequestProfile)) + profileImpDao.save(StoredProfileImp.getProfile(impProfile)) + + given: "Default GeneralGetRequest" + def generalGetRequest = GeneralGetRequest.default.tap { + it.requestProfiles = [firstRequestProfile, secondRequestProfile].id + it.impProfiles = [impProfile.id] + } + + and: "Default stored request" + def bidRequest = BidRequest.getDefaultBidRequest().tap { + setAccountId(accountId) + } + + and: "Flash metrics" + flushMetrics(pbsWithStoredProfiles) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), bidRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.sendGeneralGetRequest(generalGetRequest) + + then: "Response should contain error" + assert !response.ext?.errors + + and: "Missing metric should increments" + def metrics = pbsWithStoredProfiles.sendCollectedMetricsRequest() + assert metrics[LIMIT_EXCEEDED_ACCOUNT_PROFILE_METRIC.formatted(accountId)] == 1 + + and: "Bidder request should contain data from profile" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + verifyAll(bidderRequest) { + it.site.id == profileSite.id + it.site.name == profileSite.name + it.site.domain == profileSite.domain + it.site.cat == profileSite.cat + it.site.sectionCat == profileSite.sectionCat + it.site.pageCat == profileSite.pageCat + it.site.page == profileSite.page + it.site.ref == profileSite.ref + it.site.search == profileSite.search + it.site.keywords == profileSite.keywords + it.site.ext.data == profileSite.ext.data + + it.device.didsha1 == profileDevice.didsha1 + it.device.didmd5 == profileDevice.didmd5 + it.device.dpidsha1 == profileDevice.dpidsha1 + it.device.ifa == profileDevice.ifa + it.device.macsha1 == profileDevice.macsha1 + it.device.macmd5 == profileDevice.macmd5 + it.device.dpidmd5 == profileDevice.dpidmd5 + } + + and: "Bidder imp should contain original data from request" + assert verifyAll(bidderRequest.imp) { + it.banner == bidRequest.imp.banner + it.video == [null] + } + } + + def "PBS should override request profile from stored general get request when profile included as parameter"() { + given: "Default profile in database" + def accountId = PBSUtils.randomNumber as String + def requestProfile = RequestProfile.getProfile(accountId) + profileRequestDao.save(StoredProfileRequest.getProfile(requestProfile)) + + and: "Default GeneralGetRequest" + def generalGetRequest = GeneralGetRequest.default.tap { + it.requestProfiles = [requestProfile.id] + } + + and: "Stored request with profile" + def request = BidRequest.getDefaultBidRequest().tap { + it.ext.prebid.profileNames = [PBSUtils.randomString] + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.getStoredAuctionResponseId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain data from profile" + verifyAll(bidder.getBidderRequest(request.id)) { + it.site.id == requestProfile.body.site.id + it.site.name == requestProfile.body.site.name + it.site.domain == requestProfile.body.site.domain + it.site.cat == requestProfile.body.site.cat + it.site.sectionCat == requestProfile.body.site.sectionCat + it.site.pageCat == requestProfile.body.site.pageCat + it.site.page == requestProfile.body.site.page + it.site.ref == requestProfile.body.site.ref + it.site.search == requestProfile.body.site.search + it.site.keywords == requestProfile.body.site.keywords + it.site.ext.data == requestProfile.body.site.ext.data + + it.device.didsha1 == requestProfile.body.device.didsha1 + it.device.didmd5 == requestProfile.body.device.didmd5 + it.device.dpidsha1 == requestProfile.body.device.dpidsha1 + it.device.ifa == requestProfile.body.device.ifa + it.device.macsha1 == requestProfile.body.device.macsha1 + it.device.macmd5 == requestProfile.body.device.macmd5 + it.device.dpidmd5 == requestProfile.body.device.dpidmd5 + } + } + + def "PBS should override imp profile from stored general get request when profile included as parameter"() { + given: "Default profile in database" + def accountId = PBSUtils.randomNumber as String + def impProfile = ImpProfile.getProfile(accountId) + profileImpDao.save(StoredProfileImp.getProfile(impProfile)) + + given: "Default GeneralGetRequest" + def generalGetRequest = GeneralGetRequest.default.tap { + it.impProfiles = [impProfile.id] + } + + and: "Stored request with profile" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp.first.ext.prebid.profileNames = [PBSUtils.randomString] + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request imp should contain data from profile" + verifyAll(bidder.getBidderRequest(request.id).imp) { + it.id == [impProfile.body.id] + it.banner == [impProfile.body.banner] + } + } + + def "PBS should apply imp profile for all imps when profile included as parameter for with multi-imps general get request"() { + given: "Default profile in database" + def accountId = PBSUtils.randomNumber as String + def impProfile = ImpProfile.getProfile(accountId) + profileImpDao.save(StoredProfileImp.getProfile(impProfile)) + + and: "Default GeneralGetRequest" + def generalGetRequest = GeneralGetRequest.default.tap { + it.impProfiles = [impProfile.id] + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + addImp(Imp.defaultImpression) + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = pbsWithStoredProfiles.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request imp should contain data from profile" + verifyAll(bidder.getBidderRequest(request.id).imp) { + it.id == [impProfile.body.id] * 2 + it.banner == [impProfile.body.banner] * 2 + } + } + private static BidRequest getRequestWithProfiles(String accountId, List profiles) { BidRequest.getDefaultBidRequest().tap { - if (profiles.type.contains(ProfileType.IMP)) { - it.imp.first.ext.prebid.profileNames = profiles.findAll { it.type == ProfileType.IMP }*.id - } it.imp.first.ext.prebid.profileNames = profiles.findAll { it.type == ProfileType.IMP }*.id it.ext.prebid.profileNames = profiles.findAll { it.type == ProfileType.REQUEST }*.id setAccountId(accountId) diff --git a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy index d9d337b3c27..a7fb7db0b84 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/TargetingSpec.groovy @@ -1107,7 +1107,12 @@ class TargetingSpec extends BaseSpec { def "PBS amp should apply data from query to ext.prebid.amp.data"() { given: "Default AmpRequest" - def ampRequest = AmpRequest.defaultAmpRequest + def unknownValue = PBSUtils.randomString + def secondUnknownValue = PBSUtils.randomNumber + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.unknownField = unknownValue + it.secondUnknownField = secondUnknownValue + } and: "Bid request" def ampStoredRequest = BidRequest.defaultBidRequest @@ -1117,10 +1122,7 @@ class TargetingSpec extends BaseSpec { storedRequestDao.save(storedRequest) when: "PBS processes amp request" - def unknownValue = PBSUtils.randomString - def secondUnknownValue = PBSUtils.randomNumber - pbsWithDefaultTargetingLength.sendAmpRequestWithAdditionalQueries(ampRequest, ["unknown_field" : unknownValue, - "second_unknown_field": secondUnknownValue]) + pbsWithDefaultTargetingLength.sendAmpRequest(ampRequest) then: "Amp should contain data from query request" def bidderRequests = bidder.getBidderRequest(ampStoredRequest.id) From 1dd7157b0aa8d60e01d863794a94326c4088e777 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Fri, 17 Oct 2025 11:43:17 +0300 Subject: [PATCH 2/9] Add functional tests for /get endpoint parameters --- .../model/request/auction/DeviceExt.groovy | 3 + .../model/request/auction/Prebid.groovy | 2 + .../model/request/auction/Regs.groovy | 1 + .../request/get/GeneralGetRequest.groovy | 33 +- .../tests/GeneralGetInterfaceImpSpec.groovy | 1274 +++++++++++++++++ .../GeneralGetInterfaceRequestSpec.groovy | 794 ++++++++++ .../functional/tests/ProfileSpec.groovy | 6 +- .../GeneralGetInterfacePrivacySpec.groovy | 548 +++++++ .../privacy/GppSyncUserActivitiesSpec.groovy | 8 +- .../server/functional/util/PBSUtils.groovy | 4 + 10 files changed, 2653 insertions(+), 20 deletions(-) create mode 100644 src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy create mode 100644 src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy index 72337563b4b..2aba7a91d8d 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.model.request.auction +import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonValue import groovy.transform.ToString @@ -8,6 +9,8 @@ class DeviceExt { Atts atts String cdep + @JsonProperty("ifa_type") + String ifaType enum Atts { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy index 23b4e7f87a5..90ee496b3df 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Prebid.groovy @@ -49,6 +49,8 @@ class Prebid { List profileNames @JsonProperty("kvps") Map keyValuePairs + String outputFormat + String outputModule static class Channel { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Regs.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Regs.groovy index 6e647c16817..025d52c2760 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Regs.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Regs.groovy @@ -10,6 +10,7 @@ class Regs { Integer coppa Integer gdpr + Integer gpc String usPrivacy String gpp List gppSid diff --git a/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy index 6251dc5cf31..ed2f4bc6d16 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy @@ -3,6 +3,9 @@ package org.prebid.server.functional.model.request.get import com.fasterxml.jackson.annotation.JsonProperty import org.prebid.server.functional.model.request.amp.ConsentType import org.prebid.server.functional.model.request.auction.DebugCondition +import org.prebid.server.functional.model.request.auction.DeviceType +import org.prebid.server.functional.model.request.auction.VideoPlacementSubtypes +import org.prebid.server.functional.model.request.auction.VideoPlcmtSubtype import org.prebid.server.functional.util.PBSUtils import static org.prebid.server.functional.model.request.auction.DebugCondition.ENABLED @@ -82,7 +85,7 @@ class GeneralGetRequest { List delivery @JsonProperty("linearity") - Integer linearityMode + Integer linearity @JsonProperty("minbr") Integer minBitrate @@ -145,13 +148,13 @@ class GeneralGetRequest { Integer normalizedVolume @JsonProperty("placement") - Integer placement + VideoPlacementSubtypes placement @JsonProperty("plcmt") - Integer placementSubtype + VideoPlcmtSubtype placementSubtype @JsonProperty("playbackend") - Integer playbackEndMode + Integer playbackEnd @JsonProperty("playbackmethod") List playbackMethods @@ -172,19 +175,19 @@ class GeneralGetRequest { String targeting @JsonProperty("consent") - String gppConsent + String consent @JsonProperty("gdpr_consent") - String gppConsentLegacy + String consentLegacy @JsonProperty("consent_string") - String gppConsentStringLegacy + String consentStringLegacy @JsonProperty("gdpr") Integer gdpr @JsonProperty("privacy") - Integer privacy + Integer gdprPrivacy @JsonProperty("gdpr_applies") String gdprApplies @@ -199,10 +202,10 @@ class GeneralGetRequest { ConsentType consentType @JsonProperty("gpp_sid") - Integer gppSid + List gppSid @JsonProperty("coppa") - Integer coppaFlag + Integer coppa @JsonProperty("gpc") Integer globalPrivacyControl @@ -244,7 +247,7 @@ class GeneralGetRequest { Integer contentCategory @JsonProperty("ccattax") - List contentCategoryTaxonomy + List contentCategoryTaxonomy @JsonProperty("cseries") String contentSeries @@ -268,7 +271,7 @@ class GeneralGetRequest { String deviceUa @JsonProperty("dtype") - String deviceType + DeviceType deviceType @JsonProperty("ifa") String deviceIfa @@ -287,6 +290,10 @@ class GeneralGetRequest { } String resolveStoredRequestId() { - storedRequestId != null ? storedRequestId : storedRequestIdLegacy + storedRequestId ?: storedRequestIdLegacy + } + + String resolveAccountId() { + accountId ?: accountIdLegacy } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy new file mode 100644 index 00000000000..1770aa217db --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy @@ -0,0 +1,1274 @@ +package org.prebid.server.functional.tests + +import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.Imp +import org.prebid.server.functional.model.request.auction.VideoPlacementSubtypes +import org.prebid.server.functional.model.request.auction.VideoPlcmtSubtype +import org.prebid.server.functional.model.request.get.GeneralGetRequest +import org.prebid.server.functional.model.response.auction.BidResponse +import org.prebid.server.functional.model.response.auction.MediaType +import org.prebid.server.functional.util.PBSUtils + +class GeneralGetInterfaceImpSpec extends BaseSpec { + + def "PBS should apply mimes from general get request when it's specified"() { + given: "Default General get request" + def mimes = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.mimes = [mimes] + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain mimes from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.size() == 1 + assert bidderRequest.imp.first.getProperty(impMediaType.value).mimes == [mimes] + + where: + impMediaType << [ + MediaType.BANNER, + MediaType.VIDEO, + MediaType.AUDIO + ] + } + + def "PBS should apply width and height for banner imp from general get request when it's specified"() { + given: "Default General get request" + def width = PBSUtils.randomNumber + def height = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.width = width + it.height = height + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain width and height from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.size() == 1 + verifyAll(bidderRequest.imp.first.banner) { + it.width == width + it.height == height + it.format.width == [width] + it.format.height == [height] + } + } + + def "PBS should apply width and height for video imp from general get request when it's specified"() { + given: "Default General get request" + def width = PBSUtils.randomNumber + def height = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.width = width + it.height = height + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.VIDEO)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain width and height from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.size() == 1 + verifyAll(bidderRequest.imp.first.video) { + it.width == width + it.height == height + } + } + + def "PBS should apply ow and oh for banner imp from general get request when it's specified"() { + given: "Default General get request" + def width = PBSUtils.randomNumber + def height = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.originalWidth = width + it.originalHeight = height + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain width and height from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.size() == 1 + verifyAll(bidderRequest.imp.first.banner) { + it.width == width + it.height == height + it.format.width == [width] + it.format.height == [height] + } + } + + def "PBS should apply sizes banner imp from general get request when it's specified"() { + given: "Default General get request" + def width = PBSUtils.randomNumber + def height = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.sizes = ["${width}x${height}"] + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain width and height from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.size() == 1 + assert bidderRequest.imp.first.banner.format.width == [width] + assert bidderRequest.imp.first.banner.format.height == [height] + } + + def "PBS should apply sizes legacy banner imp from general get request when it's specified"() { + given: "Default General get request" + def width = PBSUtils.randomNumber + def height = PBSUtils.randomNumber + def widthSecond = PBSUtils.randomNumber + def heightSecond = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.sizesLegacy = ["${width}x${height}, ${widthSecond}x${heightSecond}"] + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain width and height from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.size() == 1 + assert bidderRequest.imp.first.banner.format.width == [width, widthSecond] + assert bidderRequest.imp.first.banner.format.height == [height, heightSecond] + } + + def "PBS should apply slot from height general get request when it's specified"() { + given: "Default General get request" + def slotParam = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.slot = [slotParam] + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain tagId from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.tagId == [slotParam] + } + + def "PBS should apply duration from general get request when it's specified"() { + given: "Default General get request" + def minDurationParam = PBSUtils.randomNumber + def maxDurationParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.minDuration = minDurationParam + it.maxDuration = maxDurationParam + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain mimes from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.size() == 1 + assert bidderRequest.imp.first.getProperty(impMediaType.value).minduration == [minDurationParam] + assert bidderRequest.imp.first.getProperty(impMediaType.value).maxduration == [maxDurationParam] + + where: + impMediaType << [ + MediaType.VIDEO, + MediaType.AUDIO + ] + } + + def "PBS should apply api from general get request when it's specified"() { + given: "Default General get request" + def apiParam = [PBSUtils.randomNumber, PBSUtils.randomNumber] + def generalGetRequest = GeneralGetRequest.default.tap { + it.api = apiParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain api from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).api == apiParam + + where: + impMediaType << [MediaType.BANNER, MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply battr from general get request when it's specified"() { + given: "Default General get request" + def battrParam = [PBSUtils.randomNumber] + def generalGetRequest = GeneralGetRequest.default.tap { + it.battr = battrParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain battr from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).battr == battrParam + + where: + impMediaType << [MediaType.BANNER, MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply delivery from general get request when it's specified"() { + given: "Default General get request" + def deliveryParam = [PBSUtils.randomNumber] + def generalGetRequest = GeneralGetRequest.default.tap { + it.delivery = deliveryParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain delivery from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).delivery == deliveryParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply linearity from general get request when it's specified"() { + given: "Default General get request" + def linearityParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.linearity = linearityParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.VIDEO)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain linearity from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.video.linearity == linearityParam + } + + def "PBS should apply minbr from general get request when it's specified"() { + given: "Default General get request" + def minBitrateParam = PBSUtils.randomNumber + def maxBitrateParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.minBitrate = minBitrateParam + it.maxBitrate = maxBitrateParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain minbr from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).minbitrate == minBitrateParam + assert bidderRequest.imp.first.getProperty(impMediaType.value).maxbitrate == maxBitrateParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply maxex from general get request when it's specified"() { + given: "Default General get request" + def maxexParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.maxExtended = maxexParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain maxex from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).maxextended == maxexParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply maxseq from general get request when it's specified"() { + given: "Default General get request" + def maxseqParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.maxSequence = maxseqParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain maxseq from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).maxseq == maxseqParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply minCpmPerSec from general get request when it's specified"() { + given: "Default General get request" + def mincpmsParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.minCpmPerSec = mincpmsParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain mincpms from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).mincpmpersec == mincpmsParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply poddur from general get request when it's specified"() { + given: "Default General get request" + def poddurParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.podDuration = poddurParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain poddur from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).poddur == poddurParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply podId from general get request when it's specified"() { + given: "Default General get request" + def podIdParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.podId = podIdParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain podid from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).podid == podIdParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply podseq from general get request when it's specified"() { + given: "Default General get request" + def podSequenceParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.podSequence = podSequenceParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain podseq from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).podseq == podSequenceParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply proto from general get request when it's specified"() { + given: "Default General get request" + def protoParam = [PBSUtils.randomNumber, PBSUtils.randomNumber] + def generalGetRequest = GeneralGetRequest.default.tap { + it.proto = protoParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain proto from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).protocols == protoParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply rqddurs from general get request when it's specified"() { + given: "Default General get request" + def requiredDurationsParam = [PBSUtils.randomNumber, PBSUtils.randomNumber] + def generalGetRequest = GeneralGetRequest.default.tap { + it.requiredDurations = requiredDurationsParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain rqddurs from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).rqddurs == requiredDurationsParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply sequence from general get request when it's specified"() { + given: "Default General get request" + def sequenceParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.sequence = sequenceParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain seq from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).sequence == sequenceParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply slotInPod from general get request when it's specified"() { + given: "Default General get request" + def slotInPodParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.slotInPod = slotInPodParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain slotinpod from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).slotinpod == slotInPodParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply startdelay from general get request when it's specified"() { + given: "Default General get request" + def startDelayParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.startDelay = startDelayParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain startdelay from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).startdelay == startDelayParam + + where: + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] + } + + def "PBS should apply skip from general get request when it's specified"() { + given: "Default General get request" + def skipParam = PBSUtils.randomNumber + def skipAfterParam = PBSUtils.randomNumber + def skipMinParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.skip = skipParam + it.skipAfter = skipAfter + it.skipMin = skipMinParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.VIDEO)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain skip from param" + def bidderRequest = bidder.getBidderRequest(request.id) + verifyAll(bidderRequest.imp.first.video) { + it.skip == skipParam + it.skipafter == skipAfterParam + it.skipmin == skipMinParam + } + } + + def "PBS should apply position from general get request when it's specified"() { + given: "Default General get request" + def positionParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.position = positionParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(impMediaType)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain pos from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.getProperty(impMediaType.value).pos == positionParam + + where: + impMediaType << [MediaType.BANNER, MediaType.VIDEO] + } + + def "PBS should apply stitched from general get request when it's specified"() { + given: "Default General get request" + def stitchedParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.stitched = stitchedParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.AUDIO)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain stitched from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.audio.stitched == stitchedParam + } + + def "PBS should apply feed from general get request when it's specified"() { + given: "Default General get request" + def feedParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.feed = feedParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.AUDIO)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain feed from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.audio.feed == feedParam + } + + def "PBS should apply normalizedVolume from general get request when it's specified"() { + given: "Default General get request" + def normalizedVolumeParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.normalizedVolume = normalizedVolumeParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.AUDIO)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain nvol from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.audio.nvol == normalizedVolumeParam + } + + def "PBS should apply placement from general get request when it's specified"() { + given: "Default General get request" + def placementParam = PBSUtils.getRandomEnum(VideoPlacementSubtypes) + def placementSubtypeParam = PBSUtils.getRandomEnum(VideoPlcmtSubtype) + def generalGetRequest = GeneralGetRequest.default.tap { + it.placement = placementParam + it.placementSubtype = placementSubtypeParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.VIDEO)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain placement from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.video.placement == placementParam + assert bidderRequest.imp.first.video.plcmt == placementSubtypeParam + } + + def "PBS should apply playbackendParam from general get request when it's specified"() { + given: "Default General get request" + def playbackEndParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.playbackEnd = playbackEndParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.VIDEO)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain boxingallowed from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.video.playbackend == playbackEndParam + } + + def "PBS should apply playbackMethodParam from general get request when it's specified"() { + given: "Default General get request" + def playbackMethodParam = [PBSUtils.randomNumber, PBSUtils.randomNumber] + def generalGetRequest = GeneralGetRequest.default.tap { + it.playbackMethods = playbackMethodParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.VIDEO)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain boxingallowed from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.video.playbackmethod == playbackMethodParam + } + + def "PBS should apply boxingAllowedParam from general get request when it's specified"() { + given: "Default General get request" + def boxingAllowedParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.boxingAllowed = boxingAllowedParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.VIDEO)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain boxingallowed from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.video.boxingallowed == boxingAllowedParam + } + + def "PBS should apply btype from general get request when it's specified"() { + given: "Default General get request" + def btypeParam = [PBSUtils.randomNumber, PBSUtils.randomNumber] + def generalGetRequest = GeneralGetRequest.default.tap { + it.bannerTypes = btypeParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain btype from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.banner.btype == btypeParam + } + + def "PBS should apply expandableDirections from general get request when it's specified"() { + given: "Default General get request" + def expandableDirectionsParam = [PBSUtils.randomNumber, PBSUtils.randomNumber] + def generalGetRequest = GeneralGetRequest.default.tap { + it.expandableDirections = expandableDirections + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain expdir from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.banner.expdir == expandableDirectionsParam + } + + def "PBS should apply topFrame from general get request when it's specified"() { + given: "Default General get request" + def topFrameParam = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.topFrame = topFrameParam + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain topFrame from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.first.banner.topframe == topFrameParam + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy new file mode 100644 index 00000000000..02c7b4e8786 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy @@ -0,0 +1,794 @@ +package org.prebid.server.functional.tests + +import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.Content +import org.prebid.server.functional.model.request.auction.DebugCondition +import org.prebid.server.functional.model.request.auction.Device +import org.prebid.server.functional.model.request.auction.DeviceType +import org.prebid.server.functional.model.request.auction.PublicCountryIp +import org.prebid.server.functional.model.request.get.GeneralGetRequest +import org.prebid.server.functional.model.response.auction.BidResponse +import org.prebid.server.functional.service.PrebidServerException +import org.prebid.server.functional.util.PBSUtils + +import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST +import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP +import static org.prebid.server.functional.model.request.auction.DistributionChannel.DOOH +import static org.prebid.server.functional.model.request.auction.DistributionChannel.SITE +import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID + +class GeneralGetInterfaceRequestSpec extends BaseSpec { + + def "PBS should process bid request from default general get request"() { + given: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should be valid" + assert bidder.getBidderRequest(request.id) + + where: + generalGetRequest << [ + new GeneralGetRequest(storedRequestId: PBSUtils.randomNumber), + new GeneralGetRequest(storedRequestIdLegacy: PBSUtils.randomNumber) + ] + } + + def "PBS should process bid request from default general get request "() { + given: "General get request without stored request param" + def generalGetRequest = new GeneralGetRequest() + + "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedRequestIdLegacy, request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Request should fail with an error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == BAD_REQUEST.code() + assert exception.responseBody == "replace" //TODO replace + } + + def "PBS should prioritise new storedRequest param over legacy when both presents"() { + given: "General get request with new and old stored request param" + def generalGetRequest = new GeneralGetRequest(storedRequestId: PBSUtils.randomNumber, + storedRequestIdLegacy: PBSUtils.randomNumber) + + "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedRequestIdLegacy, request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Request should fail with an error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == BAD_REQUEST.code() + assert exception.responseBody == "replace" //TODO replace + } + + def "PBS should apply accountId from general get request when it's specified"() { + given: "Default stored request" + def request = BidRequest.getDefaultBidRequest(distributionType) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain accountId from param" + assert bidder.getBidderRequest(request.id).accountId == generalGetRequest.resolveAccountId() + + where: + generalGetRequest | distributionType + GeneralGetRequest.default.tap { it.accountId = PBSUtils.randomNumber } | SITE + GeneralGetRequest.default.tap { it.accountId = PBSUtils.randomNumber } | APP + GeneralGetRequest.default.tap { it.accountId = PBSUtils.randomNumber } | DOOH + GeneralGetRequest.default.tap { it.accountIdLegacy = PBSUtils.randomNumber } | SITE + GeneralGetRequest.default.tap { it.accountIdLegacy = PBSUtils.randomNumber } | APP + GeneralGetRequest.default.tap { it.accountIdLegacy = PBSUtils.randomNumber } | DOOH + } + + def "PBS should prioritise new accountId param over legacy when both presents"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.default.tap { + it.accountId = PBSUtils.randomNumber + it.accountIdLegacy = PBSUtils.randomNumber + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain accountId from param" + assert bidder.getBidderRequest(request.id).accountId == generalGetRequest.accountId + } + + def "PBS should apply tmax from general get request when it's specified"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.default.tap { + it.timeoutMax = PBSUtils.randomNumber + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain tmax from param" + assert bidder.getBidderRequest(request.id).tmax == generalGetRequest.timeoutMax + } + + def "PBS shouldn't apply tmax from general get request when it's specified lower then 100"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.default.tap { + it.timeoutMax = tmax + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should contain warnings" + assert response.ext?.warnings[PREBID]*.message == ["replace"] //TODO replace + + and: "Response should not contain errors and warnings" + assert !response.ext?.errors + + and: "Bidder request should contain tmax from param" + assert bidder.getBidderRequest(request.id).tmax != generalGetRequest.timeoutMax + + where: + tmax << [PBSUtils.randomNegativeNumber, PBSUtils.getRandomNumber(0, 100)] + } + + def "PBS should apply debug from general get request when it's specified"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.default.tap { + it.debug = debugCondition + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain debug from param" + assert bidder.getBidderRequest(request.id).ext.prebid.debug == debugCondition + + where: + debugCondition << [DebugCondition.DISABLED, DebugCondition.ENABLED] + } + + def "PBS should apply outputformat from general get request when it's specified"() { + given: "Default General get request" + def outputFormat = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.outputFormat = outputFormat + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain outputformat from param" + assert bidder.getBidderRequest(request.id).ext.prebid.outputFormat == outputFormat + } + + def "PBS should apply outputmodule from general get request when it's specified"() { + given: "Default General get request" + def outputModule = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.outputModule = outputModule + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain outputformat from param" + assert bidder.getBidderRequest(request.id).ext.prebid.outputModule == outputModule + } + + def "PBS should apply storedAuctionResponse from general get request when it's specified"() { + given: "Default General get request" + def storedAuctionResponse = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.storedAuctionResponseId = storedAuctionResponse + } + + "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain storedAuctionResponse from param" + assert bidder.getBidderRequest(request.id).ext.prebid.storedAuctionResponse.id == storedAuctionResponse + } + + def "PBS should apply dnt from general get request when it's specified"() { + given: "Default General get request" + def dnt = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.doNotTrack = dnt + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain gpc from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.device.dnt == dnt + } + + def "PBS should apply lmt from general get request when it's specified"() { + given: "Default General get request" + def lmt = PBSUtils.randomNumber + def generalGetRequest = GeneralGetRequest.default.tap { + it.limitAdTracking = lmt + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain gpc from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.device.lmt == lmt + } + + def "PBS should apply bcat from general get request when it's specified"() { + given: "Default General get request" + def bcat = [PBSUtils.randomString] + def generalGetRequest = GeneralGetRequest.default.tap { + it.blockedCategories = bcat + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain bcat from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.bcat == bcat + } + + def "PBS should apply badv from general get request when it's specified"() { + given: "Default General get request" + def badv = [PBSUtils.randomString] + def generalGetRequest = GeneralGetRequest.default.tap { + it.blockedAdvertisers = badv + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain badv from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.badv == badv + } + + def "PBS should apply page from general get request when it's specified"() { + given: "Default General get request" + def page = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.page = page + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain page from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.site.page == page + } + + def "PBS should apply bundle from general app bundle request when it's specified"() { + given: "Default General get request" + def bundle = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.appBundle = bundle + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest(APP) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain app bundle from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.app.bundle == bundle + } + + def "PBS should apply name from general app name request when it's specified"() { + given: "Default General get request" + def name = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.appName = name + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest(APP) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain app name from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.app.name == name + } + + def "PBS should apply name from general app storeUrl request when it's specified"() { + given: "Default General get request" + def storeUrl = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.storeUrl = storeUrl + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest(APP) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain app storeUrl from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.app.storeUrl == storeUrl + } + + def "PBS should apply distribution data from general get request when it's specified"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.default.tap { + it.contentGenre = PBSUtils.randomString + it.contentLanguage = PBSUtils.randomString + it.contentRating = PBSUtils.randomString + it.contentCategory = PBSUtils.randomNumber + it.contentCategoryTaxonomy = [PBSUtils.randomNumber] + it.contentTitle = PBSUtils.randomString + it.contentUrl = PBSUtils.randomString + it.contentLivestream = PBSUtils.randomString + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest(distributionType) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain contentGenre from param" + verifyAll(bidder.getBidderRequest(request.id).getProperty(distributionType.value).content as Content) { + it.genre == generalGetRequest.contentGenre + it.language == generalGetRequest.contentLanguage + it.contentrating == generalGetRequest.contentRating + it.cat == [generalGetRequest.contentCategory.toString()] + it.cattax == generalGetRequest.contentCategoryTaxonomy // TODO discuss this issue + it.title == generalGetRequest.contentTitle + it.url == generalGetRequest.contentUrl + it.livestream == generalGetRequest.contentLivestream + } + + where: + distributionType << [SITE, APP, DOOH] + } + + def "PBS should apply series from general get request when it's specified"() { + given: "Default General get request" + def contentSeries = PBSUtils.randomString + def generalGetRequest = (rawGeneralGetRequest as GeneralGetRequest).tap { + it.storedAuctionResponseId = PBSUtils.randomString + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest(distributionType) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain contentGenre from param" + assert bidder.getBidderRequest(request.id).getProperty(distributionType.value).content.series == contentSeries + + where: + distributionType | rawGeneralGetRequest + SITE | { String series -> new GeneralGetRequest(contentSeries: series) } + APP | { String series -> new GeneralGetRequest(contentSeries: series) } + DOOH | { String series -> new GeneralGetRequest(contentSeries: series) } + + SITE | { String series -> new GeneralGetRequest(contentSeriesAlias: series) } + APP | { String series -> new GeneralGetRequest(contentSeriesAlias: series) } + DOOH | { String series -> new GeneralGetRequest(contentSeriesAlias: series) } + + SITE | { String series -> new GeneralGetRequest(contentSeries: series, contentSeriesAlias: PBSUtils.randomString) } + APP | { String series -> new GeneralGetRequest(contentSeries: series, contentSeriesAlias: PBSUtils.randomString) } + DOOH | { String series -> new GeneralGetRequest(contentSeries: series, contentSeriesAlias: PBSUtils.randomString) } + } + + def "PBS should apply device ip info from general get request when it's specified"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.default.tap { + it.deviceIp = deviceIp + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain device ip from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert getDeviceIp(bidderRequest.device) == deviceIp + + where: + deviceIp << [ + PBSUtils.getRandomEnum(PublicCountryIp).v4, + PBSUtils.getRandomEnum(PublicCountryIp).v6 + ] + } + + def "PBS should apply device params from general get request when it's specified"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.default.tap { + it.deviceUa = PBSUtils.randomString + it.deviceType = PBSUtils.getRandomEnum(DeviceType) + it.deviceIfa = PBSUtils.randomString + it.deviceIfaType = PBSUtils.randomString + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain device info from param" + def bidderRequest = bidder.getBidderRequest(request.id) + verifyAll(bidderRequest.device) { + it.ua == generalGetRequest.deviceUa + it.devicetype == generalGetRequest.deviceType + it.ifa == generalGetRequest.deviceIfa + it.ext.ifaType == generalGetRequest.deviceIfaType + } + } + + def "PBS should apply site page info from header of get request when it's specified"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.getDefault() + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def page = PBSUtils.randomString + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, ["Referer": page]) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain device ip from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.site.page == page + } + + def "PBS should apply site page info from #header header of get request when parameter is not specified"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.getDefault() + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def ua = PBSUtils.randomString + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, [header: ua]) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain device ip from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.device.ua == ua + + where: + header << ["User-Agent", "X-Device-User-Agent"] + } + + def "PBS should apply device ip info from #header header of get request when parameter is not specified"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.getDefault() + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, [(header): deviceIp]) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain device ip from headers" + def bidderRequest = bidder.getBidderRequest(request.id) + assert getDeviceIp(bidderRequest.device) == deviceIp + + where: + header | deviceIp + "X-Forwarded-For" | PBSUtils.getRandomEnum(PublicCountryIp).v4 + "X-Forwarded-For" | PBSUtils.getRandomEnum(PublicCountryIp).v6 + + "X-Device-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v4 + "X-Device-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v6 + + "X-Real-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v4 + "X-Real-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v6 + + "True-Client-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v4 + "True-Client-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v6 + } + + static String getDeviceIp(Device device) { + device.ip ?: device.ipv6 + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy index 672c3205456..4a9203edfe3 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy @@ -1693,7 +1693,7 @@ class ProfileSpec extends BaseSpec { } and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(fileImpProfile.id, request) + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) when: "PBS processes general get request" @@ -1945,7 +1945,7 @@ class ProfileSpec extends BaseSpec { profileRequestDao.save(StoredProfileRequest.getProfile(secondRequestProfile)) profileImpDao.save(StoredProfileImp.getProfile(impProfile)) - given: "Default GeneralGetRequest" + and: "Default GeneralGetRequest" def generalGetRequest = GeneralGetRequest.default.tap { it.requestProfiles = [firstRequestProfile, secondRequestProfile].id it.impProfiles = [impProfile.id] @@ -2062,7 +2062,7 @@ class ProfileSpec extends BaseSpec { def impProfile = ImpProfile.getProfile(accountId) profileImpDao.save(StoredProfileImp.getProfile(impProfile)) - given: "Default GeneralGetRequest" + and: "Default GeneralGetRequest" def generalGetRequest = GeneralGetRequest.default.tap { it.impProfiles = [impProfile.id] } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy new file mode 100644 index 00000000000..efb0122d65b --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy @@ -0,0 +1,548 @@ +package org.prebid.server.functional.tests.privacy + +import org.prebid.server.functional.model.config.AccountGdprConfig +import org.prebid.server.functional.model.config.PurposeConfig +import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.request.GppSectionId +import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.get.GeneralGetRequest +import org.prebid.server.functional.model.response.auction.BidResponse +import org.prebid.server.functional.util.PBSUtils +import org.prebid.server.functional.util.privacy.TcfConsent +import org.prebid.server.functional.util.privacy.gpp.UsNatV1Consent + +import static org.prebid.server.functional.model.config.Purpose.P2 +import static org.prebid.server.functional.model.config.PurposeEnforcement.BASIC +import static org.prebid.server.functional.model.config.PurposeEnforcement.NO +import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ADAPTER_DISALLOWED_COUNT +import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_REQUEST_DISALLOWED_COUNT +import static org.prebid.server.functional.model.request.amp.ConsentType.BOGUS +import static org.prebid.server.functional.model.request.amp.ConsentType.GPP +import static org.prebid.server.functional.model.request.amp.ConsentType.TCF_1 +import static org.prebid.server.functional.model.request.amp.ConsentType.TCF_2 +import static org.prebid.server.functional.model.request.amp.ConsentType.US_PRIVACY +import static org.prebid.server.functional.model.request.auction.ActivityType.FETCH_BIDS +import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID +import static org.prebid.server.functional.util.privacy.TcfConsent.GENERIC_VENDOR_ID +import static org.prebid.server.functional.util.privacy.TcfConsent.PurposeId.BASIC_ADS + +class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { + + def "PBS should apply gpp consent from general get request when it's specified"() { + given: "Default General get request" + def consentValue = PBSUtils.randomString + def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { + it.storedRequestId = PBSUtils.randomNumber + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain gpp info from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.user.consent == consentValue + + where: + consentGeneralRequest << + [ + { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + ] + } + + def "PBS should recognise consent from general get request as tcfv1 when consent type is tcf1"() { + given: "Default General get request" + def consentValue = PBSUtils.randomString + def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { + it.storedRequestId = PBSUtils.randomNumber + it.consentType = TCF_1 + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain gpp info from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.user.consent == consentValue + assert bidderRequest.regs.gpp == consentValue + + where: + consentGeneralRequest << + [ + { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + ] + } + + def "PBS should recognise consent from general get request as TCFv2 when consent type is tcf2"() { + given: "Default General get request" + def consentValue = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build().toString() + def accountId = PBSUtils.randomNumber.toString() + def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { + it.storedRequestId = PBSUtils.randomNumber + it.accountId = accountId + it.consentType = TCF_2 + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Save account config with requireConsent into DB" + def purposes = [(P2): new PurposeConfig(enforcePurpose: BASIC, enforceVendors: true)] + def accountGdprConfig = new AccountGdprConfig(purposes: purposes) + def account = getAccountWithGdpr(request.accountId, accountGdprConfig) + accountDao.save(account) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Generic bidderRequest should contain tcfv2 info" + def bidderRequest = response.ext.debug.resolvedRequest + verifyAll (bidderRequest) { + user.consent == consentValue + regs.gdpr == 1 + } + + and: "Shouldn't contain other privacy info" + verifyAll (bidderRequest.regs) { + !it.coppa + !it.gpc + !it.usPrivacy + !it.gpp + !it.gppSid + !it.ext + } + + and: "PBS should cansel request" + assert !bidder.getBidderRequests(request.id) + + then: "Metrics processed across activities should be updated" + def metrics = privacyPbsService.sendCollectedMetricsRequest() + assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(request, FETCH_BIDS)] == 1 + assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(request, FETCH_BIDS)] == 1 + + where: + consentGeneralRequest << + [ + { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + ] + } + + def "PBS should recognise consent from general get request as us_privacy when consent type is us_privacy"() { + given: "Default General get request" + def consentValue = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build().toString() + def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { + it.storedRequestId = PBSUtils.randomNumber + it.consentType = US_PRIVACY + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Generic bidderRequest should contain tcfv2 info" + def bidderRequest = bidder.getBidderRequest(request.id) + verifyAll (bidderRequest) { + user.consent == consentValue + regs.usPrivacy == consentValue + regs.gdpr == 1 + } + + and: "Shouldn't contain other privacy info" + verifyAll (bidderRequest.regs) { + !it.coppa + !it.gpc + !it.gpp + !it.gppSid + !it.ext + } + + where: + consentGeneralRequest << + [ + { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + ] + } + + def "PBS should recognise consent from general get request as gpp when consent type is gpp"() { + given: "Default General get request" + def consentValue = new UsNatV1Consent.Builder().build().toString() + def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { + it.storedRequestId = PBSUtils.randomNumber + it.consentType = GPP + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Generic bidderRequest should contain tcfv2 info" + def bidderRequest = bidder.getBidderRequest(request.id) + verifyAll (bidderRequest) { + user.consent == consentValue + regs.gpp == consentValue + regs.gdpr == 1 + } + + and: "Shouldn't contain other privacy info" + verifyAll (bidderRequest.regs) { + !it.coppa + !it.gpc + !it.gpp + !it.gppSid + !it.ext + } + + where: + consentGeneralRequest << + [ + { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + ] + } + + def "PBS should emit error when consent type is invalid"() { + given: "Default General get request" + def consentValue = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build().toString() + def accountId = PBSUtils.randomNumber.toString() + def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { + it.storedRequestId = PBSUtils.randomNumber + it.accountId = accountId + it.consentType = BOGUS + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should contain error" + assert response.ext?.errors[PREBID]*.code == [999] + assert response.ext?.errors[PREBID]*.message == ["Invalid consent_type param passed"] + + where: + consentGeneralRequest << + [ + { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + ] + } + + def "PBS should apply gdpr from general get request when it's specified"() { + given: "Default General get request" + def gdprValue = PBSUtils.randomBinary + def accountId = PBSUtils.randomNumber + def generalGetRequest = (consentGeneralRequest(gdprValue) as GeneralGetRequest).tap { + it.storedRequestId = PBSUtils.randomNumber + it.accountId = accountId + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(accountId as String) + } + + and: "Save account config with requireConsent into DB" + def purposes = [(P2): new PurposeConfig(enforcePurpose: NO, enforceVendors: false)] + def accountGdprConfig = new AccountGdprConfig(purposes: purposes) + def account = getAccountWithGdpr(accountId as String, accountGdprConfig) + accountDao.save(account) + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain gpp info from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.regs.gdpr == gdprValue + + where: + consentGeneralRequest << [ + { Integer gdpr -> new GeneralGetRequest(gdpr: gdpr) }, + { Integer gdpr -> new GeneralGetRequest(gdprPrivacy: gdpr) }, + { Integer gdpr -> new GeneralGetRequest(gdprApplies: gdpr) }, + { Integer gdpr -> new GeneralGetRequest(consent: gdpr, consentLegacy: PBSUtils.randomBinary) }, + { Integer gdpr -> new GeneralGetRequest(consent: gdpr, consentStringLegacy: PBSUtils.randomBinary) }, + { Integer gdpr -> new GeneralGetRequest(consentLegacy: gdpr, consentStringLegacy: PBSUtils.randomBinary) }, + ] + } + + def "PBS should apply usp from general get request when it's specified"() { + given: "Default General get request" + def usp = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.usPrivacy = usp + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain usPrivacy from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.regs.usPrivacy == usp + } + + def "PBS should apply addtl_consent from general get request when it's specified"() { + given: "Default General get request" + def addtlConsent = PBSUtils.randomString + def generalGetRequest = GeneralGetRequest.default.tap { + it.additionalConsent = addtlConsent + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain addtlConsent from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.user.ext.consentedProvidersSettings.consentedProviders == addtlConsent + } + + def "PBS should apply gpp_sid from general get request when it's specified"() { + given: "Default General get request" + def gppSids = [PBSUtils.getRandomEnum(GppSectionId.class)].intValue + def generalGetRequest = GeneralGetRequest.default.tap { + it.gppSid = gppSids + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain gpp_sid from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.regs.gppSid == gppSids + } + + def "PBS should apply coppa from general get request when it's specified"() { + given: "Default General get request" + def coppa = PBSUtils.randomBinary + def generalGetRequest = GeneralGetRequest.default.tap { + it.coppa = coppa + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain coppa from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.regs.coppa == coppa + } + + def "PBS should apply gpc from general get request when it's specified"() { + given: "Default General get request" + def gpc = PBSUtils.randomBinary + def generalGetRequest = GeneralGetRequest.default.tap { + it.globalPrivacyControl = gpc + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain gpc from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.regs.gpc == gpc + } +} diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy index 2303bfc9e5f..e902a69b838 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy @@ -2017,7 +2017,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { when: "PBS processes cookie sync request with header" def response = prebidServerService - .sendCookieSyncRequest(cookieSyncRequest, ["X-Forwarded-For": "209.232.44.21"]) + .sendCookieSyncRequest(cookieSyncRequest, ["X-Forwarded-For": USA_IP.v4]) then: "Response should contain bidders userSync.urls" assert response.getBidderUserSync(GENERIC).userSync.url @@ -2076,7 +2076,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { when: "PBS processes set uid request with header" def response = prebidServerService - .sendSetUidRequest(setuidRequest, uidsCookie, ["X-Forwarded-For": "209.232.44.21"]) + .sendSetUidRequest(setuidRequest, uidsCookie, ["X-Forwarded-For": USA_IP.v4]) then: "Response should contain uids cookie" assert response.uidsCookie @@ -2133,7 +2133,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { when: "PBS processes cookie sync request with header" def response = prebidServerService - .sendCookieSyncRequest(cookieSyncRequest, ["X-Forwarded-For": "209.232.44.21"]) + .sendCookieSyncRequest(cookieSyncRequest, ["X-Forwarded-For": USA_IP.v4]) then: "Response should not contain any URLs for bidders" assert !response.bidderStatus.userSync.url @@ -2190,7 +2190,7 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { accountDao.save(account) when: "PBS processes set uid request" - prebidServerService.sendSetUidRequest(setuidRequest, uidsCookie, ["X-Forwarded-For": "209.232.44.21"]) + prebidServerService.sendSetUidRequest(setuidRequest, uidsCookie, ["X-Forwarded-For": USA_IP.v4]) then: "Request should fail with error" def exception = thrown(PrebidServerException) diff --git a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy index 748071dd10f..78780a478d9 100644 --- a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy @@ -23,6 +23,10 @@ class PBSUtils implements ObjectMapperWrapper { new Random().nextInt(upperBound - min) + min } + static int getRandomBinary() { + return new Random().nextInt(2) + } + static int getRandomNumberWithExclusion(int excludedValue, int min = 0, int max = MAX_VALUE) { def value = getRandomNumber(min, max) value == excludedValue ? getRandomNumberWithExclusion(excludedValue, min, max) : value From f6543e24a95042fa9e1429d1fb85faf2b108ea08 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Thu, 13 Nov 2025 13:02:28 +0200 Subject: [PATCH 3/9] update after review --- .../model/request/auction/Imp.groovy | 5 + .../request/get/GeneralGetRequest.groovy | 20 +- .../tests/GeneralGetInterfaceImpSpec.groovy | 288 +++++++++++++----- .../GeneralGetInterfaceRequestSpec.groovy | 90 ++---- .../GeneralGetInterfacePrivacySpec.groovy | 49 --- 5 files changed, 239 insertions(+), 213 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy index 13c97a36ba4..9104bffff1d 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy @@ -110,4 +110,9 @@ class Imp { (audio ? AUDIO : null) ].findAll { it } } + + Object getSingleMediaTypeData() { + return banner ?: video ?: nativeObj ?: audio + } + } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy index ed2f4bc6d16..0dcb317bf8a 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy @@ -27,7 +27,6 @@ class GeneralGetRequest { @JsonProperty("tmax") Integer timeoutMax - @JsonProperty("debug") DebugCondition debug @JsonProperty("of") @@ -45,7 +44,6 @@ class GeneralGetRequest { @JsonProperty("sarid") String storedAuctionResponseId - @JsonProperty("mimes") List mimes @JsonProperty("w") @@ -60,13 +58,11 @@ class GeneralGetRequest { @JsonProperty("oh") Integer originalHeight - @JsonProperty("sizes") Object sizes @JsonProperty("ms") Object sizesLegacy - @JsonProperty("slot") String slot @JsonProperty("mindur") @@ -75,16 +71,13 @@ class GeneralGetRequest { @JsonProperty("maxdur") Integer maxDuration - @JsonProperty("api") List api @JsonProperty("battr") - List battr + List blockAttributes - @JsonProperty("delivery") List delivery - @JsonProperty("linearity") Integer linearity @JsonProperty("minbr") @@ -112,7 +105,7 @@ class GeneralGetRequest { Integer podSequence @JsonProperty("proto") - List proto + List protocols @JsonProperty("rqddurs") List requiredDurations @@ -126,7 +119,6 @@ class GeneralGetRequest { @JsonProperty("startdelay") Integer startDelay - @JsonProperty("skip") Integer skip @JsonProperty("skipafter") @@ -141,13 +133,11 @@ class GeneralGetRequest { @JsonProperty("stitched") Integer stitched - @JsonProperty("feed") Integer feed @JsonProperty("nvol") Integer normalizedVolume - @JsonProperty("placement") VideoPlacementSubtypes placement @JsonProperty("plcmt") @@ -171,10 +161,8 @@ class GeneralGetRequest { @JsonProperty("topframe") Integer topFrame - @JsonProperty("targeting") String targeting - @JsonProperty("consent") String consent @JsonProperty("gdpr_consent") @@ -183,7 +171,6 @@ class GeneralGetRequest { @JsonProperty("consent_string") String consentStringLegacy - @JsonProperty("gdpr") Integer gdpr @JsonProperty("privacy") @@ -204,7 +191,6 @@ class GeneralGetRequest { @JsonProperty("gpp_sid") List gppSid - @JsonProperty("coppa") Integer coppa @JsonProperty("gpc") @@ -222,7 +208,6 @@ class GeneralGetRequest { @JsonProperty("badv") List blockedAdvertisers - @JsonProperty("page") String page @JsonProperty("bundle") @@ -279,7 +264,6 @@ class GeneralGetRequest { @JsonProperty("ifat") String deviceIfaType - @JsonProperty("unknown") String unknown @JsonProperty("unknown_alias") diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy index 1770aa217db..30e06a1f745 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy @@ -10,6 +10,9 @@ import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.model.response.auction.MediaType import org.prebid.server.functional.util.PBSUtils +import static org.prebid.server.functional.model.response.auction.ErrorType.GENERIC +import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID + class GeneralGetInterfaceImpSpec extends BaseSpec { def "PBS should apply mimes from general get request when it's specified"() { @@ -19,7 +22,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { it.mimes = [mimes] } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { it.imp = [Imp.getDefaultImpression(impMediaType)] } @@ -42,29 +45,21 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain mimes from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.size() == 1 - assert bidderRequest.imp.first.getProperty(impMediaType.value).mimes == [mimes] + assert bidderRequest.imp.first.singleMediaTypeData.mimes == [mimes] where: - impMediaType << [ - MediaType.BANNER, - MediaType.VIDEO, - MediaType.AUDIO - ] + impMediaType << [MediaType.BANNER, MediaType.VIDEO, MediaType.AUDIO] } - def "PBS should apply width and height for banner imp from general get request when it's specified"() { + def "PBS should remove invalid mimes from general get request when it's specified"() { given: "Default General get request" - def width = PBSUtils.randomNumber - def height = PBSUtils.randomNumber + def validMemis = PBSUtils.randomString def generalGetRequest = GeneralGetRequest.default.tap { - it.width = width - it.height = height + it.mimes = (invalidMimes + validMemis) } - "Default stored request" - def request = BidRequest.getDefaultBidRequest().tap { - it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] - } + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) @@ -77,6 +72,40 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + + and: "Response should contain warning" + assert response.ext?.warnings[PREBID]*.code == [999] + assert response.ext?.warnings[PREBID]*.message == ['some message'] //TODO replace + + and: "Bidder request should contain mimes from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.size() == 1 + assert bidderRequest.imp.first.singleMediaTypeData.mimes == [validMemis] + + where: + invalidMimes << [[null], [''], [PBSUtils.randomNumber.toString()]] + } + + def "PBS should apply width and height for banner imp from general get request when it's specified"() { + given: "Default General get request" + def width = PBSUtils.randomNumber + def height = PBSUtils.randomNumber + def generalGetRequest = bannerGeneralRequest(height, width) as GeneralGetRequest + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + then: "Response should not contain errors and warnings" assert !response.ext?.errors assert !response.ext?.warnings @@ -90,29 +119,70 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { it.format.width == [width] it.format.height == [height] } + + where: + bannerGeneralRequest << + [ + { Integer h, Integer w -> new GeneralGetRequest(height: h, width: w) }, + { Integer h, Integer w -> new GeneralGetRequest(originalHeight: h, originalWidth: w) }, + { Integer h, Integer w -> new GeneralGetRequest(height: h, width: w, + originalHeight: PBSUtils.randomNumber, originalWidth: PBSUtils.randomNumber) } + ] } - def "PBS should apply width and height for video imp from general get request when it's specified"() { + def "PBS should unsuccessfully pass and throw error due to validation banner.format{w.h} when banner.format width and height is invalid"() { given: "Default General get request" - def width = PBSUtils.randomNumber - def height = PBSUtils.randomNumber def generalGetRequest = GeneralGetRequest.default.tap { - it.width = width - it.height = height + it.width = bannerFormatWidth + it.height = bannerFormatHeight } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { - it.imp = [Imp.getDefaultImpression(MediaType.VIDEO)] + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] } and: "Save storedRequest into DB" def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Bid response should contain error" + assert response.ext?.errors[GENERIC]*.code == [400] + assert response.ext?.errors[GENERIC]*.message[0] == "Invalid request format: " + + "request.imp[0].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements" + + and: "PBs shouldn't perform a bidder request due to stored bid response" + assert !bidder.getBidderRequests(request.id) + + where: + bannerFormatWidth | bannerFormatHeight + 0 | 0 + 0 | null + 0 | PBSUtils.randomNegativeNumber + null | null + null | PBSUtils.randomNegativeNumber + PBSUtils.randomNegativeNumber | PBSUtils.randomNegativeNumber + } + + def "PBS should apply width and height for banner imp from general get request when banner width and height are square dimensions"() { + given: "Default General get request" + def side = PBSUtils.getRandomNumber(0, 10) + def generalGetRequest = GeneralGetRequest.default.tap { + it.width = side + it.height = side + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -124,24 +194,26 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain width and height from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.size() == 1 - verifyAll(bidderRequest.imp.first.video) { - it.width == width - it.height == height + verifyAll(bidderRequest.imp.first.banner) { + it.width == side + it.height == side + it.format.width == [side] + it.format.height == [side] } } - def "PBS should apply ow and oh for banner imp from general get request when it's specified"() { + def "PBS should apply width and height for video imp from general get request when it's specified"() { given: "Default General get request" def width = PBSUtils.randomNumber def height = PBSUtils.randomNumber def generalGetRequest = GeneralGetRequest.default.tap { - it.originalWidth = width - it.originalHeight = height + it.width = width + it.height = height } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { - it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + it.imp = [Imp.getDefaultImpression(MediaType.VIDEO)] } and: "Save storedRequest into DB" @@ -162,11 +234,9 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain width and height from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.size() == 1 - verifyAll(bidderRequest.imp.first.banner) { + verifyAll(bidderRequest.imp.first.video) { it.width == width it.height == height - it.format.width == [width] - it.format.height == [height] } } @@ -178,7 +248,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { it.sizes = ["${width}x${height}"] } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] } @@ -187,10 +257,6 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -215,7 +281,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { it.sizesLegacy = ["${width}x${height}, ${widthSecond}x${heightSecond}"] } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] } @@ -224,10 +290,6 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -242,6 +304,80 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { assert bidderRequest.imp.first.banner.format.height == [height, heightSecond] } + def "PBS should unsuccessfully pass and throw error due to validation banner sizes when banner.format width and height is invalid"() { + given: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Bid response should contain error" + assert response.ext?.errors[GENERIC]*.code == [400] + assert response.ext?.errors[GENERIC]*.message[0] == "Invalid request format: " + + "request.imp[0].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements" + + and: "PBs shouldn't perform a bidder request due to stored bid response" + assert !bidder.getBidderRequests(request.id) + + where: + generalGetRequest << [ + GeneralGetRequest.default.tap { sizes = ["0x0"]}, + GeneralGetRequest.default.tap { sizes = ["0x"]}, + GeneralGetRequest.default.tap { sizes = ["x"]}, + GeneralGetRequest.default.tap { sizes = ["0"]}, + GeneralGetRequest.default.tap { sizes = ["0x${PBSUtils.randomNegativeNumber}"]}, + GeneralGetRequest.default.tap { sizes = [""]}, + GeneralGetRequest.default.tap { sizes = [" "]}, + GeneralGetRequest.default.tap { sizes = [null]}, + GeneralGetRequest.default.tap { sizes = ["x${PBSUtils.randomNegativeNumber}"]}, + GeneralGetRequest.default.tap { sizes = ["${PBSUtils.randomNegativeNumber}x"]}, + GeneralGetRequest.default.tap { sizes = ["${PBSUtils.randomNegativeNumber}x${PBSUtils.randomNegativeNumber}"]}, + ] + } + + def "PBS should apply sizes banner imp from general get request when banner width and height are square dimensions"() { + given: "Default General get request" + def side = PBSUtils.getRandomNumber(0, 10) + def generalGetRequest = bannerGeneralRequest(side) as GeneralGetRequest + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain width and height from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.size() == 1 + assert bidderRequest.imp.first.banner.format.width == [side] + assert bidderRequest.imp.first.banner.format.height == [side] + + where: + bannerGeneralRequest << + [ + { Integer requestSide -> new GeneralGetRequest(sizes: ["${requestSide}x${requestSide}"]) }, + { Integer requestSide -> new GeneralGetRequest(sizesLegacy: ["${requestSide}x${requestSide}"]) }, + { Integer requestSide -> new GeneralGetRequest(sizes: ["${requestSide}x${requestSide}"], + sizesLegacy: ["${PBSUtils.randomNumber}x${PBSUtils.randomNumber}"]) } + ] + } + def "PBS should apply slot from height general get request when it's specified"() { given: "Default General get request" def slotParam = PBSUtils.randomString @@ -249,17 +385,13 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { it.slot = [slotParam] } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -281,7 +413,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { it.maxDuration = maxDurationParam } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { it.imp = [Imp.getDefaultImpression(impMediaType)] } @@ -304,8 +436,8 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain mimes from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.size() == 1 - assert bidderRequest.imp.first.getProperty(impMediaType.value).minduration == [minDurationParam] - assert bidderRequest.imp.first.getProperty(impMediaType.value).maxduration == [maxDurationParam] + assert bidderRequest.imp.first.singleMediaTypeData.minduration == [minDurationParam] + assert bidderRequest.imp.first.singleMediaTypeData.maxduration == [maxDurationParam] where: impMediaType << [ @@ -343,7 +475,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain api from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).api == apiParam + assert bidderRequest.imp.first.singleMediaTypeData.api == apiParam where: impMediaType << [MediaType.BANNER, MediaType.VIDEO, MediaType.AUDIO] @@ -353,7 +485,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { given: "Default General get request" def battrParam = [PBSUtils.randomNumber] def generalGetRequest = GeneralGetRequest.default.tap { - it.battr = battrParam + it.blockAttributes = battrParam } and: "Default stored request" @@ -378,7 +510,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain battr from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).battr == battrParam + assert bidderRequest.imp.first.singleMediaTypeData.battr == battrParam where: impMediaType << [MediaType.BANNER, MediaType.VIDEO, MediaType.AUDIO] @@ -413,7 +545,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain delivery from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).delivery == deliveryParam + assert bidderRequest.imp.first.singleMediaTypeData.delivery == deliveryParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -482,8 +614,8 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain minbr from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).minbitrate == minBitrateParam - assert bidderRequest.imp.first.getProperty(impMediaType.value).maxbitrate == maxBitrateParam + assert bidderRequest.imp.first.singleMediaTypeData.minbitrate == minBitrateParam + assert bidderRequest.imp.first.singleMediaTypeData.maxbitrate == maxBitrateParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -518,7 +650,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain maxex from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).maxextended == maxexParam + assert bidderRequest.imp.first.singleMediaTypeData.maxextended == maxexParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -553,7 +685,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain maxseq from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).maxseq == maxseqParam + assert bidderRequest.imp.first.singleMediaTypeData.maxseq == maxseqParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -588,7 +720,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain mincpms from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).mincpmpersec == mincpmsParam + assert bidderRequest.imp.first.singleMediaTypeData.mincpmpersec == mincpmsParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -623,7 +755,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain poddur from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).poddur == poddurParam + assert bidderRequest.imp.first.singleMediaTypeData.poddur == poddurParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -658,7 +790,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain podid from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).podid == podIdParam + assert bidderRequest.imp.first.singleMediaTypeData.podid == podIdParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -693,7 +825,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain podseq from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).podseq == podSequenceParam + assert bidderRequest.imp.first.singleMediaTypeData.podseq == podSequenceParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -703,7 +835,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { given: "Default General get request" def protoParam = [PBSUtils.randomNumber, PBSUtils.randomNumber] def generalGetRequest = GeneralGetRequest.default.tap { - it.proto = protoParam + it.protocols = protoParam } and: "Default stored request" @@ -728,7 +860,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain proto from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).protocols == protoParam + assert bidderRequest.imp.first.singleMediaTypeData.protocols == protoParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -763,7 +895,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain rqddurs from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).rqddurs == requiredDurationsParam + assert bidderRequest.imp.first.singleMediaTypeData.rqddurs == requiredDurationsParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -798,7 +930,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain seq from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).sequence == sequenceParam + assert bidderRequest.imp.first.singleMediaTypeData.sequence == sequenceParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -833,7 +965,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain slotinpod from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).slotinpod == slotInPodParam + assert bidderRequest.imp.first.singleMediaTypeData.slotinpod == slotInPodParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -868,7 +1000,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain startdelay from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).startdelay == startDelayParam + assert bidderRequest.imp.first.singleMediaTypeData.startdelay == startDelayParam where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -943,7 +1075,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain pos from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.getProperty(impMediaType.value).pos == positionParam + assert bidderRequest.imp.first.singleMediaTypeData.pos == positionParam where: impMediaType << [MediaType.BANNER, MediaType.VIDEO] @@ -1192,10 +1324,6 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -1256,10 +1384,6 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy index 02c7b4e8786..0446ed8bf33 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy @@ -1,5 +1,6 @@ package org.prebid.server.functional.tests +import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Content @@ -8,7 +9,6 @@ import org.prebid.server.functional.model.request.auction.Device import org.prebid.server.functional.model.request.auction.DeviceType import org.prebid.server.functional.model.request.auction.PublicCountryIp import org.prebid.server.functional.model.request.get.GeneralGetRequest -import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils @@ -49,7 +49,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { given: "General get request without stored request param" def generalGetRequest = new GeneralGetRequest() - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" @@ -70,7 +70,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def generalGetRequest = new GeneralGetRequest(storedRequestId: PBSUtils.randomNumber, storedRequestIdLegacy: PBSUtils.randomNumber) - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" @@ -121,7 +121,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { it.accountIdLegacy = PBSUtils.randomNumber } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" @@ -145,7 +145,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { it.timeoutMax = PBSUtils.randomNumber } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" @@ -169,7 +169,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { it.timeoutMax = tmax } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" @@ -198,7 +198,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { it.debug = debugCondition } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" @@ -226,7 +226,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { it.outputFormat = outputFormat } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" @@ -251,7 +251,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { it.outputModule = outputModule } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" @@ -276,7 +276,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { it.storedAuctionResponseId = storedAuctionResponse } - "Default stored request" + and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" @@ -308,10 +308,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -338,10 +334,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -368,10 +360,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -398,10 +386,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -428,10 +412,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -458,10 +438,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -488,10 +464,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -518,10 +490,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -562,7 +530,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.warnings and: "Bidder request should contain contentGenre from param" - verifyAll(bidder.getBidderRequest(request.id).getProperty(distributionType.value).content as Content) { + verifyAll(getRequestContent(bidder.getBidderRequest(request.id))) { it.genre == generalGetRequest.contentGenre it.language == generalGetRequest.contentLanguage it.contentrating == generalGetRequest.contentRating @@ -599,7 +567,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.warnings and: "Bidder request should contain contentGenre from param" - assert bidder.getBidderRequest(request.id).getProperty(distributionType.value).content.series == contentSeries + assert getRequestContent(bidder.getBidderRequest(request.id)).series == contentSeries where: distributionType | rawGeneralGetRequest @@ -629,10 +597,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -667,10 +631,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -699,10 +659,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def page = PBSUtils.randomString def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, ["Referer": page]) @@ -727,10 +683,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def ua = PBSUtils.randomString def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, [header: ua]) @@ -758,10 +710,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, [(header): deviceIp]) @@ -791,4 +739,18 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { static String getDeviceIp(Device device) { device.ip ?: device.ipv6 } + + static Content getRequestContent(BidderRequest bidderRequest) { + def distributionChannels = bidderRequest.getRequestDistributionChannels() + + if (distributionChannels.contains(SITE)) { + return bidderRequest.site.content + } + + if (distributionChannels.contains(APP)) { + return bidderRequest.app.content + } + + return bidderRequest.dooh.content + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy index efb0122d65b..f6bc6b97d9f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy @@ -6,7 +6,6 @@ import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.GppSectionId import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.get.GeneralGetRequest -import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.TcfConsent import org.prebid.server.functional.util.privacy.gpp.UsNatV1Consent @@ -42,10 +41,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) @@ -84,10 +79,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) @@ -140,10 +131,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def account = getAccountWithGdpr(request.accountId, accountGdprConfig) accountDao.save(account) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) @@ -206,10 +193,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) @@ -261,10 +244,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) @@ -323,10 +302,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) @@ -370,10 +345,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) @@ -410,10 +381,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) @@ -440,10 +407,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) @@ -470,10 +433,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) @@ -500,10 +459,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) @@ -530,10 +485,6 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) - and: "Default bid response" - def bidResponse = BidResponse.getDefaultBidResponse(request) - bidder.setResponse(request.id, bidResponse) - when: "PBS processes general get request" def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) From 5126e00d661eea12dc1289c2318fff3251657c3f Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Tue, 18 Nov 2025 15:03:15 +0200 Subject: [PATCH 4/9] update get interface tests --- .../model/request/auction/Device.groovy | 2 + .../model/request/auction/DeviceExt.groovy | 2 + .../model/request/auction/Imp.groovy | 1 + .../request/auction/PublicCountryIp.groovy | 6 + .../auction/StoredAuctionResponse.groovy | 2 + .../request/get/GeneralGetRequest.groovy | 1 + .../service/PrebidServerService.groovy | 2 +- .../tests/GeneralGetInterfaceImpSpec.groovy | 268 ++++++++++++++++-- .../GeneralGetInterfaceRequestSpec.groovy | 99 ++++++- .../GeneralGetInterfacePrivacySpec.groovy | 46 +++ 10 files changed, 403 insertions(+), 26 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Device.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Device.groovy index 60ef8fa7884..44514ccba13 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Device.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Device.groovy @@ -1,8 +1,10 @@ package org.prebid.server.functional.model.request.auction +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.util.PBSUtils +@EqualsAndHashCode @ToString(includeNames = true, ignoreNulls = true) class Device { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy index 2aba7a91d8d..efd8da65aa5 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/DeviceExt.groovy @@ -2,8 +2,10 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonValue +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString +@EqualsAndHashCode @ToString class DeviceExt { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy index 9104bffff1d..93a29c52e1b 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Imp.groovy @@ -111,6 +111,7 @@ class Imp { ].findAll { it } } + @JsonIgnore Object getSingleMediaTypeData() { return banner ?: video ?: nativeObj ?: audio } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy index 9bdb94f007d..69194afd4b4 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy @@ -1,5 +1,7 @@ package org.prebid.server.functional.model.request.auction +import inet.ipaddr.IPAddressString + enum PublicCountryIp { USA_IP("209.232.44.21", "d646:2414:17b2:f371:9b62:f176:b4c0:51cd"), @@ -14,4 +16,8 @@ enum PublicCountryIp { this.v4 = v4 this.v6 = ipV6 } + + String getMaskedIPv6() { + new IPAddressString(this.v6).toAddress().mask(new IPAddressString("::/64").toAddress().getNetworkMask()).toCanonicalString() + } } diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredAuctionResponse.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredAuctionResponse.groovy index cde5f2268de..d6a3a98dd82 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredAuctionResponse.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/StoredAuctionResponse.groovy @@ -1,9 +1,11 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.annotation.JsonProperty +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.prebid.server.functional.model.response.auction.SeatBid +@EqualsAndHashCode @ToString(includeNames = true, ignoreNulls = true) class StoredAuctionResponse { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy index 0dcb317bf8a..206bf9ae891 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy @@ -163,6 +163,7 @@ class GeneralGetRequest { String targeting + @JsonProperty("tcfc") String consent @JsonProperty("gdpr_consent") diff --git a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy index a85124f54db..a8c5aa061d8 100644 --- a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy +++ b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy @@ -48,7 +48,7 @@ import static java.time.ZoneOffset.UTC class PrebidServerService implements ObjectMapperWrapper { static final String AUCTION_ENDPOINT = "/openrtb2/auction" - static final String GENERAL_GET_ENDPOINT = "/openrtb2/auction" + static final String GENERAL_GET_ENDPOINT = "/openrtb2/get" static final String AMP_ENDPOINT = "/openrtb2/amp" static final String COOKIE_SYNC_ENDPOINT = "/cookie_sync" static final String SET_UID_ENDPOINT = "/setuid" diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy index 30e06a1f745..66898faed44 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy @@ -2,6 +2,7 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.Format import org.prebid.server.functional.model.request.auction.Imp import org.prebid.server.functional.model.request.auction.VideoPlacementSubtypes import org.prebid.server.functional.model.request.auction.VideoPlcmtSubtype @@ -125,8 +126,10 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { [ { Integer h, Integer w -> new GeneralGetRequest(height: h, width: w) }, { Integer h, Integer w -> new GeneralGetRequest(originalHeight: h, originalWidth: w) }, - { Integer h, Integer w -> new GeneralGetRequest(height: h, width: w, - originalHeight: PBSUtils.randomNumber, originalWidth: PBSUtils.randomNumber) } + { Integer h, Integer w -> + new GeneralGetRequest(height: h, width: w, + originalHeight: PBSUtils.randomNumber, originalWidth: PBSUtils.randomNumber) + } ] } @@ -327,17 +330,17 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { where: generalGetRequest << [ - GeneralGetRequest.default.tap { sizes = ["0x0"]}, - GeneralGetRequest.default.tap { sizes = ["0x"]}, - GeneralGetRequest.default.tap { sizes = ["x"]}, - GeneralGetRequest.default.tap { sizes = ["0"]}, - GeneralGetRequest.default.tap { sizes = ["0x${PBSUtils.randomNegativeNumber}"]}, - GeneralGetRequest.default.tap { sizes = [""]}, - GeneralGetRequest.default.tap { sizes = [" "]}, - GeneralGetRequest.default.tap { sizes = [null]}, - GeneralGetRequest.default.tap { sizes = ["x${PBSUtils.randomNegativeNumber}"]}, - GeneralGetRequest.default.tap { sizes = ["${PBSUtils.randomNegativeNumber}x"]}, - GeneralGetRequest.default.tap { sizes = ["${PBSUtils.randomNegativeNumber}x${PBSUtils.randomNegativeNumber}"]}, + GeneralGetRequest.default.tap { sizes = ["0x0"] }, + GeneralGetRequest.default.tap { sizes = ["0x"] }, + GeneralGetRequest.default.tap { sizes = ["x"] }, + GeneralGetRequest.default.tap { sizes = ["0"] }, + GeneralGetRequest.default.tap { sizes = ["0x${PBSUtils.randomNegativeNumber}"] }, + GeneralGetRequest.default.tap { sizes = [""] }, + GeneralGetRequest.default.tap { sizes = [" "] }, + GeneralGetRequest.default.tap { sizes = [null] }, + GeneralGetRequest.default.tap { sizes = ["x${PBSUtils.randomNegativeNumber}"] }, + GeneralGetRequest.default.tap { sizes = ["${PBSUtils.randomNegativeNumber}x"] }, + GeneralGetRequest.default.tap { sizes = ["${PBSUtils.randomNegativeNumber}x${PBSUtils.randomNegativeNumber}"] }, ] } @@ -373,8 +376,10 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { [ { Integer requestSide -> new GeneralGetRequest(sizes: ["${requestSide}x${requestSide}"]) }, { Integer requestSide -> new GeneralGetRequest(sizesLegacy: ["${requestSide}x${requestSide}"]) }, - { Integer requestSide -> new GeneralGetRequest(sizes: ["${requestSide}x${requestSide}"], - sizesLegacy: ["${PBSUtils.randomNumber}x${PBSUtils.randomNumber}"]) } + { Integer requestSide -> + new GeneralGetRequest(sizes: ["${requestSide}x${requestSide}"], + sizesLegacy: ["${PBSUtils.randomNumber}x${PBSUtils.randomNumber}"]) + } ] } @@ -440,10 +445,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { assert bidderRequest.imp.first.singleMediaTypeData.maxduration == [maxDurationParam] where: - impMediaType << [ - MediaType.VIDEO, - MediaType.AUDIO - ] + impMediaType << [MediaType.VIDEO, MediaType.AUDIO] } def "PBS should apply api from general get request when it's specified"() { @@ -583,7 +585,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { assert bidderRequest.imp.first.video.linearity == linearityParam } - def "PBS should apply minbr from general get request when it's specified"() { + def "PBS should apply minbr and maxbr from general get request when it's specified"() { given: "Default General get request" def minBitrateParam = PBSUtils.randomNumber def maxBitrateParam = PBSUtils.randomNumber @@ -1239,7 +1241,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain boxingallowed from param" + and: "Bidder request should contain playbackmethod from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.first.video.playbackend == playbackEndParam } @@ -1271,7 +1273,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain boxingallowed from param" + and: "Bidder request should contain playbackmethod from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.first.video.playbackmethod == playbackMethodParam } @@ -1395,4 +1397,226 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.first.banner.topframe == topFrameParam } + + def "PBS should use original values for banner imp when it's not specified in get request"() { + given: "Default General get request" + def storedRequestId = PBSUtils.randomString + def generalGetRequest = new GeneralGetRequest(storedRequestId: storedRequestId) + + and: "Default stored request" + def bannerImp = Imp.getDefaultImpression(MediaType.BANNER).tap { + it.banner.mimes = [PBSUtils.randomString] + it.banner.width = PBSUtils.randomNumber + it.banner.height = PBSUtils.randomNumber + it.banner.format = [new Format(width: PBSUtils.randomNumber, height: PBSUtils.randomNumber)] + it.banner.api = [PBSUtils.randomNumber] + it.banner.battr = [PBSUtils.randomNumber] + it.banner.pos = PBSUtils.randomNumber + it.banner.btype = [PBSUtils.randomNumber] + it.banner.expdir = [PBSUtils.randomNumber] + it.banner.topframe = PBSUtils.randomNumber + } + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [bannerImp] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain imp data from original request" + verifyAll(bidder.getBidderRequest(request.id).imp.first.banner) { + it.mimes = bannerImp.banner.mimes + it.width = bannerImp.banner.width + it.height = bannerImp.banner.height + it.format = bannerImp.banner.format + it.api = bannerImp.banner.api + it.battr = bannerImp.banner.battr + it.pos = bannerImp.banner.pos + it.btype = bannerImp.banner.btype + it.expdir = bannerImp.banner.expdir + it.topframe = bannerImp.banner.topframe + } + } + + def "PBS should use original values for video imp when it's not specified in get request"() { + given: "Default General get request" + def storedRequestId = PBSUtils.randomString + def generalGetRequest = new GeneralGetRequest(storedRequestId: storedRequestId) + + and: "Default stored request" + def videoImp = Imp.getDefaultImpression(MediaType.VIDEO).tap { + it.video.mimes = [PBSUtils.randomString] + it.video.width = PBSUtils.randomNumber + it.video.height = PBSUtils.randomNumber + it.video.minduration = PBSUtils.randomNumber + it.video.maxduration = PBSUtils.randomNumber + it.video.api = [PBSUtils.randomNumber] + it.video.battr = [PBSUtils.randomNumber] + it.video.delivery = [PBSUtils.randomNumber] + it.video.linearity = PBSUtils.randomNumber + it.video.minbitrate = PBSUtils.randomNumber + it.video.maxbitrate = PBSUtils.randomNumber + it.video.maxextended = PBSUtils.randomNumber + it.video.maxseq = PBSUtils.randomNumber + it.video.mincpmpersec = PBSUtils.randomNumber + it.video.poddur = PBSUtils.randomNumber + it.video.podid = PBSUtils.randomNumber + it.video.podseq = PBSUtils.randomNumber + it.video.protocols = [PBSUtils.randomNumber] + it.video.rqddurs = [PBSUtils.randomNumber] + it.video.sequence = PBSUtils.randomNumber + it.video.slotinpod = PBSUtils.randomNumber + it.video.startdelay = PBSUtils.randomNumber + it.video.skip = PBSUtils.randomNumber + it.video.skipafter = PBSUtils.randomNumber + it.video.skipmin = PBSUtils.randomNumber + it.video.pos = PBSUtils.randomNumber + it.video.placement = PBSUtils.getRandomEnum(VideoPlacementSubtypes) + it.video.plcmt = PBSUtils.getRandomEnum(VideoPlcmtSubtype) + it.video.playbackend = PBSUtils.randomNumber + it.video.playbackmethod = [PBSUtils.randomNumber] + it.video.boxingallowed = PBSUtils.randomNumber + } + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [videoImp] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain imp data from original request" + verifyAll(bidder.getBidderRequest(request.id).imp.first.video) { + it.mimes == videoImp.video.mimes + it.width == videoImp.video.width + it.height == videoImp.video.height + it.minduration == videoImp.video.minduration + it.maxduration == videoImp.video.maxduration + it.api == videoImp.video.api + it.battr == videoImp.video.battr + it.delivery == videoImp.video.delivery + it.linearity == videoImp.video.linearity + it.minbitrate == videoImp.video.minbitrate + it.maxbitrate == videoImp.video.maxbitrate + it.maxextended == videoImp.video.maxextended + it.maxseq == videoImp.video.maxseq + it.mincpmpersec == videoImp.video.mincpmpersec + it.poddur == videoImp.video.poddur + it.podid == videoImp.video.podid + it.podseq == videoImp.video.podseq + it.protocols == videoImp.video.protocols + it.rqddurs == videoImp.video.rqddurs + it.sequence == videoImp.video.sequence + it.slotinpod == videoImp.video.slotinpod + it.startdelay == videoImp.video.startdelay + it.skip == videoImp.video.skip + it.skipafter == videoImp.video.skipafter + it.skipmin == videoImp.video.skipmin + it.pos == videoImp.video.pos + it.placement == videoImp.video.placement + it.plcmt == videoImp.video.plcmt + it.playbackend == videoImp.video.playbackend + it.playbackmethod == videoImp.video.playbackmethod + it.boxingallowed == videoImp.video.boxingallowed + } + } + + def "PBS should use original values for audio imp when it's not specified in get request"() { + given: "Default General get request" + def storedRequestId = PBSUtils.randomString + def generalGetRequest = new GeneralGetRequest(storedRequestId: storedRequestId) + + and: "Default stored request" + def audioImp = Imp.getDefaultImpression(MediaType.AUDIO).tap { + it.audio.mimes = [PBSUtils.randomString] + it.audio.minduration = PBSUtils.randomNumber + it.audio.maxduration = PBSUtils.randomNumber + it.audio.api = [PBSUtils.randomNumber] + it.audio.battr = [PBSUtils.randomNumber] + it.audio.delivery = [PBSUtils.randomNumber] + it.audio.minbitrate = PBSUtils.randomNumber + it.audio.maxbitrate = PBSUtils.randomNumber + it.audio.maxextended = PBSUtils.randomNumber + it.audio.maxseq = PBSUtils.randomNumber + it.audio.mincpmpersec = PBSUtils.randomNumber + it.audio.poddur = PBSUtils.randomNumber + it.audio.podid = PBSUtils.randomNumber + it.audio.podseq = PBSUtils.randomNumber + it.audio.protocols = [PBSUtils.randomNumber] + it.audio.rqddurs = [PBSUtils.randomNumber] + it.audio.sequence = PBSUtils.randomNumber + it.audio.slotinpod = PBSUtils.randomNumber + it.audio.startdelay = PBSUtils.randomNumber + it.audio.stitched = PBSUtils.randomNumber + it.audio.feed = PBSUtils.randomNumber + it.audio.nvol = PBSUtils.randomNumber + } + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [audioImp] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Default bid response" + def bidResponse = BidResponse.getDefaultBidResponse(request) + bidder.setResponse(request.id, bidResponse) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain imp data from original request" + verifyAll(bidder.getBidderRequest(request.id).imp.first.audio) { + it.mimes == audioImp.audio.mimes + it.minduration == audioImp.audio.minduration + it.maxduration == audioImp.audio.maxduration + it.api == audioImp.audio.api + it.battr == audioImp.audio.battr + it.delivery == audioImp.audio.delivery + it.minbitrate == audioImp.audio.minbitrate + it.maxbitrate == audioImp.audio.maxbitrate + it.maxextended == audioImp.audio.maxextended + it.maxseq == audioImp.audio.maxseq + it.mincpmpersec == audioImp.audio.mincpmpersec + it.poddur == audioImp.audio.poddur + it.podid == audioImp.audio.podid + it.podseq == audioImp.audio.podseq + it.protocols == audioImp.audio.protocols + it.rqddurs == audioImp.audio.rqddurs + it.sequence == audioImp.audio.sequence + it.slotinpod == audioImp.audio.slotinpod + it.startdelay == audioImp.audio.startdelay + it.stitched == audioImp.audio.stitched + it.feed == audioImp.audio.feed + it.nvol == audioImp.audio.nvol + } + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy index 0446ed8bf33..270f7854808 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy @@ -6,8 +6,10 @@ import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Content import org.prebid.server.functional.model.request.auction.DebugCondition 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.DeviceType import org.prebid.server.functional.model.request.auction.PublicCountryIp +import org.prebid.server.functional.model.request.auction.StoredAuctionResponse import org.prebid.server.functional.model.request.get.GeneralGetRequest import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils @@ -45,7 +47,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { ] } - def "PBS should process bid request from default general get request "() { + def "PBS should response with error when process bid request is not specified in general get request"() { given: "General get request without stored request param" def generalGetRequest = new GeneralGetRequest() @@ -736,6 +738,99 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { "True-Client-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v6 } + + def "PBS should use original values from stored request when it's not specified in get request"() { + given: "Default General get request" + def storedRequestId = PBSUtils.randomString + def generalGetRequest = new GeneralGetRequest(storedRequestId: storedRequestId) + + and: "Default stored request" + def bidRequestDevice = new Device().tap { + it.dnt = PBSUtils.randomNumber + it.lmt = PBSUtils.randomNumber + it.ip = PBSUtils.getRandomEnum(PublicCountryIp).v4 + it.ipv6 = PBSUtils.getRandomEnum(PublicCountryIp).maskedIPv6 + it.ua = PBSUtils.randomString + it.devicetype = PBSUtils.getRandomEnum(DeviceType) + it.ifa = PBSUtils.randomString + it.ext = new DeviceExt(ifaType: PBSUtils.randomString) + } + + def request = BidRequest.getDefaultBidRequest().tap { + it.tmax = PBSUtils.getRandomNumber(1000, 5000) + it.bcat = [PBSUtils.randomString] + it.badv = [PBSUtils.randomString] + it.device = bidRequestDevice + + it.ext.prebid.debug = PBSUtils.getRandomEnum(DebugCondition) + it.ext.prebid.outputFormat = PBSUtils.randomString + it.ext.prebid.outputModule = PBSUtils.randomString + it.ext.prebid.storedAuctionResponse = new StoredAuctionResponse(id: PBSUtils.randomNumber) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedAuctionResponseId, request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain device ip from headers" + verifyAll(bidder.getBidderRequest(request.id)) { + it.bcat == request.bcat + it.badv == request.badv + it.device == bidRequestDevice + + it.ext.prebid.debug == request.ext.prebid.debug + it.ext.prebid.outputFormat == request.ext.prebid.outputFormat + it.ext.prebid.outputModule == request.ext.prebid.outputModule + it.ext.prebid.storedAuctionResponse == request.ext.prebid.storedAuctionResponse + } + } + + def "PBS should use original content values from stored #channel request when it's not specified in get request"() { + given: "Default General get request" + def storedRequestId = PBSUtils.randomString + def generalGetRequest = new GeneralGetRequest(storedRequestId: storedRequestId) + + and: "Default stored request" + def content = Content.getDefaultContent().tap { + it.genre = PBSUtils.randomString + it.language = PBSUtils.randomString + it.contentrating = PBSUtils.randomString + it.cat = [PBSUtils.randomString] + it.cattax = PBSUtils.randomNumber + it.series = PBSUtils.randomString + it.title = PBSUtils.randomString + it.url = PBSUtils.randomString + it.livestream = PBSUtils.randomNumber + } + def request = BidRequest.getDefaultBidRequest(channel).tap { + it.getProperty(channel.value).content = content + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedAuctionResponseId, request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain device ip from headers" + assert getRequestContent(bidder.getBidderRequest(request.id)) == content + + where: + channel << [SITE, APP, DOOH] + } + static String getDeviceIp(Device device) { device.ip ?: device.ipv6 } @@ -746,11 +841,9 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { if (distributionChannels.contains(SITE)) { return bidderRequest.site.content } - if (distributionChannels.contains(APP)) { return bidderRequest.app.content } - return bidderRequest.dooh.content } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy index f6bc6b97d9f..53465e4fe45 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy @@ -5,6 +5,11 @@ import org.prebid.server.functional.model.config.PurposeConfig import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.GppSectionId import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.ConsentedProvidersSettings +import org.prebid.server.functional.model.request.auction.Regs +import org.prebid.server.functional.model.request.auction.RegsExt +import org.prebid.server.functional.model.request.auction.User +import org.prebid.server.functional.model.request.auction.UserExt import org.prebid.server.functional.model.request.get.GeneralGetRequest import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.TcfConsent @@ -496,4 +501,45 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.regs.gpc == gpc } + + def "PBS should use original values from stored request when it's not specified in get request"() { + given: "Default General get request" + def storedRequestId = PBSUtils.randomString + def generalGetRequest = new GeneralGetRequest(storedRequestId: storedRequestId) + + and: "Default stored request" + def userForRequest = User.defaultUser.tap { + it.consent = PBSUtils.randomString + it.ext = new UserExt(consentedProvidersSettings: new ConsentedProvidersSettings(consentedProviders: PBSUtils.randomString)) + } + def regsForRequest = new Regs().tap { + it.gdpr = 0 // for preventing bidder block + it.gpp = PBSUtils.randomString + it.usPrivacy = PBSUtils.randomString + it.gppSid = [PBSUtils.randomNumber] + it.ext = new RegsExt(gpc: PBSUtils.randomNumber) + it.coppa = PBSUtils.randomNumber + } + def request = BidRequest.getDefaultBidRequest().tap { + user = userForRequest + regs = regsForRequest + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain privacy data from original request" + verifyAll (bidder.getBidderRequest(request.id)) { + it.user == userForRequest + it.regs == regsForRequest + } + } } From bb42e749601240c641a6bbd9cdc690dd17e74517 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Mon, 24 Nov 2025 19:01:52 +0200 Subject: [PATCH 5/9] update tests --- .../get/CommaSeparatedListSerializer.groovy | 14 ++ .../request/get/GeneralGetRequest.groovy | 22 +- .../service/PrebidServerService.groovy | 4 +- .../tests/GeneralGetInterfaceImpSpec.groovy | 208 +++++++++++------- .../GeneralGetInterfaceRequestSpec.groovy | 131 +++++++---- .../server/functional/util/PBSUtils.groovy | 4 + 6 files changed, 253 insertions(+), 130 deletions(-) create mode 100644 src/test/groovy/org/prebid/server/functional/model/request/get/CommaSeparatedListSerializer.groovy diff --git a/src/test/groovy/org/prebid/server/functional/model/request/get/CommaSeparatedListSerializer.groovy b/src/test/groovy/org/prebid/server/functional/model/request/get/CommaSeparatedListSerializer.groovy new file mode 100644 index 00000000000..72189391ec5 --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/request/get/CommaSeparatedListSerializer.groovy @@ -0,0 +1,14 @@ +package org.prebid.server.functional.model.request.get + +import com.fasterxml.jackson.core.JsonGenerator +import com.fasterxml.jackson.databind.JsonSerializer +import com.fasterxml.jackson.databind.SerializerProvider + +class CommaSeparatedListSerializer extends JsonSerializer> { + + @Override + void serialize(List value, JsonGenerator generator, SerializerProvider serializers) throws IOException { + String result = value.join(',') + generator.writeString(result) + } +} diff --git a/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy index 206bf9ae891..f0bbaf799d5 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy @@ -1,6 +1,7 @@ package org.prebid.server.functional.model.request.get import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.annotation.JsonSerialize import org.prebid.server.functional.model.request.amp.ConsentType import org.prebid.server.functional.model.request.auction.DebugCondition import org.prebid.server.functional.model.request.auction.DeviceType @@ -36,9 +37,11 @@ class GeneralGetRequest { String outputModule @JsonProperty("rprof") + @JsonSerialize(using = CommaSeparatedListSerializer) List requestProfiles @JsonProperty("iprof") + @JsonSerialize(using = CommaSeparatedListSerializer) List impProfiles @JsonProperty("sarid") @@ -53,10 +56,10 @@ class GeneralGetRequest { Integer height @JsonProperty("ow") - Integer originalWidth + Integer overrideWidth @JsonProperty("oh") - Integer originalHeight + Integer overrideHeight Object sizes @@ -71,11 +74,14 @@ class GeneralGetRequest { @JsonProperty("maxdur") Integer maxDuration + @JsonSerialize(using = CommaSeparatedListSerializer) List api @JsonProperty("battr") + @JsonSerialize(using = CommaSeparatedListSerializer) List blockAttributes + @JsonSerialize(using = CommaSeparatedListSerializer) List delivery Integer linearity @@ -105,9 +111,11 @@ class GeneralGetRequest { Integer podSequence @JsonProperty("proto") + @JsonSerialize(using = CommaSeparatedListSerializer) List protocols @JsonProperty("rqddurs") + @JsonSerialize(using = CommaSeparatedListSerializer) List requiredDurations @JsonProperty("seq") @@ -147,15 +155,18 @@ class GeneralGetRequest { Integer playbackEnd @JsonProperty("playbackmethod") + @JsonSerialize(using = CommaSeparatedListSerializer) List playbackMethods @JsonProperty("boxingallowed") Integer boxingAllowed @JsonProperty("btype") + @JsonSerialize(using = CommaSeparatedListSerializer) List bannerTypes @JsonProperty("expdir") + @JsonSerialize(using = CommaSeparatedListSerializer) List expandableDirections @JsonProperty("topframe") @@ -190,6 +201,7 @@ class GeneralGetRequest { ConsentType consentType @JsonProperty("gpp_sid") + @JsonSerialize(using = CommaSeparatedListSerializer) List gppSid Integer coppa @@ -204,9 +216,11 @@ class GeneralGetRequest { Integer limitAdTracking @JsonProperty("bcat") + @JsonSerialize(using = CommaSeparatedListSerializer) List blockedCategories @JsonProperty("badv") + @JsonSerialize(using = CommaSeparatedListSerializer) List blockedAdvertisers String page @@ -233,7 +247,7 @@ class GeneralGetRequest { Integer contentCategory @JsonProperty("ccattax") - List contentCategoryTaxonomy + Integer contentCategoryTaxonomy @JsonProperty("cseries") String contentSeries @@ -248,7 +262,7 @@ class GeneralGetRequest { String contentUrl @JsonProperty("clivestream") - String contentLivestream + Integer contentLivestream @JsonProperty("ip") String deviceIp diff --git a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy index a8c5aa061d8..45ce0627cc7 100644 --- a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy +++ b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy @@ -98,11 +98,11 @@ class PrebidServerService implements ObjectMapperWrapper { } } - GeneralGetResponse sendGeneralGetRequest(GeneralGetRequest request, Map headers = [:]) { + BidResponse sendGeneralGetRequest(GeneralGetRequest request, Map headers = [:]) { def response = getAuction(request, headers) checkResponseStatusCode(response) - decode(response.body.asString(), GeneralGetResponse) + decode(response.body.asString(), BidResponse) } AmpResponse sendAmpRequest(AmpRequest ampRequest, Map headers = [:]) { diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy index 66898faed44..e0e5df71cbe 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy @@ -9,9 +9,10 @@ import org.prebid.server.functional.model.request.auction.VideoPlcmtSubtype import org.prebid.server.functional.model.request.get.GeneralGetRequest import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.model.response.auction.MediaType +import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils +import spock.lang.PendingFeature -import static org.prebid.server.functional.model.response.auction.ErrorType.GENERIC import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID class GeneralGetInterfaceImpSpec extends BaseSpec { @@ -52,6 +53,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { impMediaType << [MediaType.BANNER, MediaType.VIDEO, MediaType.AUDIO] } + @PendingFeature def "PBS should remove invalid mimes from general get request when it's specified"() { given: "Default General get request" def validMemis = PBSUtils.randomString @@ -86,14 +88,16 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { assert bidderRequest.imp.first.singleMediaTypeData.mimes == [validMemis] where: - invalidMimes << [[null], [''], [PBSUtils.randomNumber.toString()]] + invalidMimes << [[''], [PBSUtils.randomNumber.toString()]] } def "PBS should apply width and height for banner imp from general get request when it's specified"() { given: "Default General get request" def width = PBSUtils.randomNumber def height = PBSUtils.randomNumber - def generalGetRequest = bannerGeneralRequest(height, width) as GeneralGetRequest + def generalGetRequest = (bannerGeneralRequest(height, width) as GeneralGetRequest).tap { + it.storedRequestId = PBSUtils.randomNumber + } and: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { @@ -125,15 +129,15 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { bannerGeneralRequest << [ { Integer h, Integer w -> new GeneralGetRequest(height: h, width: w) }, - { Integer h, Integer w -> new GeneralGetRequest(originalHeight: h, originalWidth: w) }, + { Integer h, Integer w -> new GeneralGetRequest(overrideHeight: h, overrideWidth: w) }, { Integer h, Integer w -> - new GeneralGetRequest(height: h, width: w, - originalHeight: PBSUtils.randomNumber, originalWidth: PBSUtils.randomNumber) + new GeneralGetRequest(height: PBSUtils.randomNumber, width: PBSUtils.randomNumber, + overrideHeight: h, overrideWidth: w) } ] } - def "PBS should unsuccessfully pass and throw error due to validation banner.format{w.h} when banner.format width and height is invalid"() { + def "PBS should unsuccessfully pass and throw error due to validation banner.{w.h} when values {w.h} is null"() { given: "Default General get request" def generalGetRequest = GeneralGetRequest.default.tap { it.width = bannerFormatWidth @@ -142,7 +146,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { - it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + it.imp = [Imp.getDefaultImpression(MediaType.BANNER).tap { it.banner.format = null }] } and: "Save storedRequest into DB" @@ -150,29 +154,54 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { storedRequestDao.save(storedRequest) when: "PBS processes general get request" - def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) + defaultPbsService.sendGeneralGetRequest(generalGetRequest) - then: "Bid response should contain error" - assert response.ext?.errors[GENERIC]*.code == [400] - assert response.ext?.errors[GENERIC]*.message[0] == "Invalid request format: " + - "request.imp[0].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements" - - and: "PBs shouldn't perform a bidder request due to stored bid response" - assert !bidder.getBidderRequests(request.id) + then: "PBs should throw error due to invalid request" + def exception = thrown(PrebidServerException) + assert exception.statusCode == 400 + assert exception.responseBody == 'Invalid request format: request.imp[0].banner has no sizes. Define "w" and "h", or include "format" elements' where: bannerFormatWidth | bannerFormatHeight - 0 | 0 0 | null - 0 | PBSUtils.randomNegativeNumber null | null null | PBSUtils.randomNegativeNumber + } + + def "PBS should unsuccessfully pass and throw error due to validation banner.{w.h} when values {w.h} not null"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.default.tap { + it.width = bannerFormatWidth + it.height = bannerFormatHeight + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER).tap { it.banner.format = null }] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "PBs should throw error due to invalid request" + def exception = thrown(PrebidServerException) + assert exception.statusCode == 400 + assert exception.responseBody == 'Invalid request format: request.imp[0].banner must define a valid "h" and "w" properties' + + where: + bannerFormatWidth | bannerFormatHeight + 0 | 0 + 0 | PBSUtils.randomNegativeNumber PBSUtils.randomNegativeNumber | PBSUtils.randomNegativeNumber } def "PBS should apply width and height for banner imp from general get request when banner width and height are square dimensions"() { given: "Default General get request" - def side = PBSUtils.getRandomNumber(0, 10) + def side = PBSUtils.getRandomNumber(1, 10) def generalGetRequest = GeneralGetRequest.default.tap { it.width = side it.height = side @@ -248,7 +277,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { def width = PBSUtils.randomNumber def height = PBSUtils.randomNumber def generalGetRequest = GeneralGetRequest.default.tap { - it.sizes = ["${width}x${height}"] + it.sizes = ["${width}x${height}".toString()] } and: "Default stored request" @@ -281,7 +310,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { def widthSecond = PBSUtils.randomNumber def heightSecond = PBSUtils.randomNumber def generalGetRequest = GeneralGetRequest.default.tap { - it.sizesLegacy = ["${width}x${height}, ${widthSecond}x${heightSecond}"] + it.sizesLegacy = ["${width}x${height},${widthSecond}x${heightSecond}".toString()] } and: "Default stored request" @@ -307,8 +336,14 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { assert bidderRequest.imp.first.banner.format.height == [height, heightSecond] } - def "PBS should unsuccessfully pass and throw error due to validation banner sizes when banner.format width and height is invalid"() { - given: "Default stored request" + def "PBS should apply sizes banner imp from general get request when banner width and height are square dimensions"() { + given: "Default General get request" + def side = PBSUtils.getRandomNumber(1, 10) + def generalGetRequest = (bannerGeneralRequest(side) as GeneralGetRequest).tap { + it.storedRequestId = PBSUtils.randomNumber + } + + and: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] } @@ -320,36 +355,30 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) - then: "Bid response should contain error" - assert response.ext?.errors[GENERIC]*.code == [400] - assert response.ext?.errors[GENERIC]*.message[0] == "Invalid request format: " + - "request.imp[0].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements" + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings - and: "PBs shouldn't perform a bidder request due to stored bid response" - assert !bidder.getBidderRequests(request.id) + and: "Bidder request should contain width and height from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.imp.size() == 1 + assert bidderRequest.imp.first.banner.format.width == [side] + assert bidderRequest.imp.first.banner.format.height == [side] where: - generalGetRequest << [ - GeneralGetRequest.default.tap { sizes = ["0x0"] }, - GeneralGetRequest.default.tap { sizes = ["0x"] }, - GeneralGetRequest.default.tap { sizes = ["x"] }, - GeneralGetRequest.default.tap { sizes = ["0"] }, - GeneralGetRequest.default.tap { sizes = ["0x${PBSUtils.randomNegativeNumber}"] }, - GeneralGetRequest.default.tap { sizes = [""] }, - GeneralGetRequest.default.tap { sizes = [" "] }, - GeneralGetRequest.default.tap { sizes = [null] }, - GeneralGetRequest.default.tap { sizes = ["x${PBSUtils.randomNegativeNumber}"] }, - GeneralGetRequest.default.tap { sizes = ["${PBSUtils.randomNegativeNumber}x"] }, - GeneralGetRequest.default.tap { sizes = ["${PBSUtils.randomNegativeNumber}x${PBSUtils.randomNegativeNumber}"] }, - ] + bannerGeneralRequest << + [ + { Integer requestSide -> new GeneralGetRequest(sizes: ["${requestSide}x${requestSide}".toString()]) }, + { Integer requestSide -> new GeneralGetRequest(sizesLegacy: ["${requestSide}x${requestSide}".toString()]) }, + { Integer requestSide -> + new GeneralGetRequest(sizes: ["${requestSide}x${requestSide}".toString()], + sizesLegacy: ["${PBSUtils.randomNumber}x${PBSUtils.randomNumber}".toString()]) + } + ] } - def "PBS should apply sizes banner imp from general get request when banner width and height are square dimensions"() { - given: "Default General get request" - def side = PBSUtils.getRandomNumber(0, 10) - def generalGetRequest = bannerGeneralRequest(side) as GeneralGetRequest - - and: "Default stored request" + def "PBS should ignore sizes banner imp from general get request when it's specified partially"() { + given: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] } @@ -368,26 +397,54 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain width and height from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.size() == 1 - assert bidderRequest.imp.first.banner.format.width == [side] - assert bidderRequest.imp.first.banner.format.height == [side] + assert bidderRequest.imp.banner.format.width == request.imp.banner.format.width + assert bidderRequest.imp.banner.format.height == request.imp.banner.format.height where: - bannerGeneralRequest << - [ - { Integer requestSide -> new GeneralGetRequest(sizes: ["${requestSide}x${requestSide}"]) }, - { Integer requestSide -> new GeneralGetRequest(sizesLegacy: ["${requestSide}x${requestSide}"]) }, - { Integer requestSide -> - new GeneralGetRequest(sizes: ["${requestSide}x${requestSide}"], - sizesLegacy: ["${PBSUtils.randomNumber}x${PBSUtils.randomNumber}"]) - } - ] + generalGetRequest << [ + GeneralGetRequest.default.tap { sizes = ['0x'] }, + GeneralGetRequest.default.tap { sizes = ['x'] }, + GeneralGetRequest.default.tap { sizes = ['0'] }, + GeneralGetRequest.default.tap { sizes = [''] }, + GeneralGetRequest.default.tap { sizes = [' '] }, + GeneralGetRequest.default.tap { sizes = [null] }, + GeneralGetRequest.default.tap { sizes = ["${PBSUtils.randomNegativeNumber}x".toString()] }, + ] + } + + def "PBS should unsuccessfully pass and throw error due to validation banner sizes when sizes width and height is invalid"() { + given: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + it.imp = [Imp.getDefaultImpression(MediaType.BANNER)] + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "PBs should throw error due to invalid request" + def exception = thrown(PrebidServerException) + assert exception.statusCode == 400 + assert exception.responseBody == 'Invalid request format: request.imp[0].banner.format[0] should define *either* ' + + '{w, h} (for static size requirements) *or* {wmin, wratio, hratio} (for flexible sizes) to be non-zero positive' + + where: + generalGetRequest << [ + GeneralGetRequest.default.tap { sizes = ['0x0'] }, + GeneralGetRequest.default.tap { sizes = ["0x${PBSUtils.randomNegativeNumber}".toString()] }, + GeneralGetRequest.default.tap { sizes = ["x${PBSUtils.randomNegativeNumber}".toString()] }, + GeneralGetRequest.default.tap { sizes = ["${PBSUtils.randomNegativeNumber}x${PBSUtils.randomNegativeNumber}".toString()] }, + ] } def "PBS should apply slot from height general get request when it's specified"() { given: "Default General get request" def slotParam = PBSUtils.randomString def generalGetRequest = GeneralGetRequest.default.tap { - it.slot = [slotParam] + it.slot = slotParam } and: "Default stored request" @@ -406,7 +463,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain tagId from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.tagId == [slotParam] + assert bidderRequest.imp.tagId.flatten() == [slotParam] } def "PBS should apply duration from general get request when it's specified"() { @@ -440,9 +497,8 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain mimes from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.size() == 1 - assert bidderRequest.imp.first.singleMediaTypeData.minduration == [minDurationParam] - assert bidderRequest.imp.first.singleMediaTypeData.maxduration == [maxDurationParam] + assert bidderRequest.imp.singleMediaTypeData.minduration == [minDurationParam] + assert bidderRequest.imp.singleMediaTypeData.maxduration == [maxDurationParam] where: impMediaType << [MediaType.VIDEO, MediaType.AUDIO] @@ -1015,7 +1071,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { def skipMinParam = PBSUtils.randomNumber def generalGetRequest = GeneralGetRequest.default.tap { it.skip = skipParam - it.skipAfter = skipAfter + it.skipAfter = skipAfterParam it.skipMin = skipMinParam } @@ -1342,7 +1398,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { given: "Default General get request" def expandableDirectionsParam = [PBSUtils.randomNumber, PBSUtils.randomNumber] def generalGetRequest = GeneralGetRequest.default.tap { - it.expandableDirections = expandableDirections + it.expandableDirections = expandableDirectionsParam } and: "Default stored request" @@ -1437,16 +1493,16 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain imp data from original request" verifyAll(bidder.getBidderRequest(request.id).imp.first.banner) { - it.mimes = bannerImp.banner.mimes - it.width = bannerImp.banner.width - it.height = bannerImp.banner.height - it.format = bannerImp.banner.format - it.api = bannerImp.banner.api - it.battr = bannerImp.banner.battr - it.pos = bannerImp.banner.pos - it.btype = bannerImp.banner.btype - it.expdir = bannerImp.banner.expdir - it.topframe = bannerImp.banner.topframe + it.mimes == bannerImp.banner.mimes + it.width == bannerImp.banner.width + it.height == bannerImp.banner.height + it.format == bannerImp.banner.format + it.api == bannerImp.banner.api + it.battr == bannerImp.banner.battr + it.pos == bannerImp.banner.pos + it.btype == bannerImp.banner.btype + it.expdir == bannerImp.banner.expdir + it.topframe == bannerImp.banner.topframe } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy index 270f7854808..ea341c66b53 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy @@ -2,6 +2,7 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.bidderspecific.BidderRequest import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.db.StoredResponse import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Content import org.prebid.server.functional.model.request.auction.DebugCondition @@ -11,8 +12,10 @@ import org.prebid.server.functional.model.request.auction.DeviceType import org.prebid.server.functional.model.request.auction.PublicCountryIp import org.prebid.server.functional.model.request.auction.StoredAuctionResponse import org.prebid.server.functional.model.request.get.GeneralGetRequest +import org.prebid.server.functional.model.response.auction.SeatBid import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils +import spock.lang.PendingFeature import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST import static org.prebid.server.functional.model.request.auction.DistributionChannel.APP @@ -47,37 +50,32 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { ] } - def "PBS should response with error when process bid request is not specified in general get request"() { - given: "General get request without stored request param" - def generalGetRequest = new GeneralGetRequest() + def "PBS should prioritise new storedRequest param over legacy when both presents"() { + given: "General get request with new and old stored request param" + def generalGetRequest = new GeneralGetRequest(storedRequestId: PBSUtils.randomNumber, + storedRequestIdLegacy: PBSUtils.randomNumber) and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedRequestIdLegacy, request) + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedRequestId, request) storedRequestDao.save(storedRequest) when: "PBS processes general get request" - defaultPbsService.sendGeneralGetRequest(generalGetRequest) - - then: "Request should fail with an error" - def exception = thrown(PrebidServerException) - assert exception.statusCode == BAD_REQUEST.code() - assert exception.responseBody == "replace" //TODO replace - } + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) - def "PBS should prioritise new storedRequest param over legacy when both presents"() { - given: "General get request with new and old stored request param" - def generalGetRequest = new GeneralGetRequest(storedRequestId: PBSUtils.randomNumber, - storedRequestIdLegacy: PBSUtils.randomNumber) + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings - and: "Default stored request" - def request = BidRequest.getDefaultBidRequest() + and: "Bidder request should be valid" + assert bidder.getBidderRequest(request.id) + } - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedRequestIdLegacy, request) - storedRequestDao.save(storedRequest) + def "PBS should response with error when process bid request is not specified in general get request"() { + given: "General get request without stored request param" + def generalGetRequest = new GeneralGetRequest() when: "PBS processes general get request" defaultPbsService.sendGeneralGetRequest(generalGetRequest) @@ -85,12 +83,14 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { then: "Request should fail with an error" def exception = thrown(PrebidServerException) assert exception.statusCode == BAD_REQUEST.code() - assert exception.responseBody == "replace" //TODO replace + assert exception.responseBody == "Invalid request format: Request require the stored request id." } def "PBS should apply accountId from general get request when it's specified"() { given: "Default stored request" - def request = BidRequest.getDefaultBidRequest(distributionType) + def request = BidRequest.getDefaultBidRequest(distributionType).tap { + setAccountId(generalGetRequest.resolveAccountId()) + } and: "Save storedRequest into DB" def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) @@ -124,7 +124,9 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { } and: "Default stored request" - def request = BidRequest.getDefaultBidRequest() + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(generalGetRequest.accountId) + } and: "Save storedRequest into DB" def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) @@ -144,7 +146,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def "PBS should apply tmax from general get request when it's specified"() { given: "Default General get request" def generalGetRequest = GeneralGetRequest.default.tap { - it.timeoutMax = PBSUtils.randomNumber + it.timeoutMax = PBSUtils.getRandomNumber(3000, 5000) } and: "Default stored request" @@ -162,13 +164,14 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.warnings and: "Bidder request should contain tmax from param" - assert bidder.getBidderRequest(request.id).tmax == generalGetRequest.timeoutMax + assert PBSUtils.isApproximatelyEqual(bidder.getBidderRequest(request.id).tmax as Integer, generalGetRequest.timeoutMax) } + @PendingFeature def "PBS shouldn't apply tmax from general get request when it's specified lower then 100"() { given: "Default General get request" def generalGetRequest = GeneralGetRequest.default.tap { - it.timeoutMax = tmax + it.timeoutMax = PBSUtils.getRandomNumber(0, 100) } and: "Default stored request" @@ -189,9 +192,28 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { and: "Bidder request should contain tmax from param" assert bidder.getBidderRequest(request.id).tmax != generalGetRequest.timeoutMax + } - where: - tmax << [PBSUtils.randomNegativeNumber, PBSUtils.getRandomNumber(0, 100)] + def "PBS shouldn't apply tmax from general get request when it's specified as negative"() { + given: "Default General get request" + def generalGetRequest = GeneralGetRequest.default.tap { + it.timeoutMax = PBSUtils.randomNegativeNumber + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "PBs should throw error due to invalid request" + def exception = thrown(PrebidServerException) + assert exception.statusCode == 500 + assert exception.responseBody == 'Critical error while running the auction: Start time and timeout must be positive' } def "PBS should apply debug from general get request when it's specified"() { @@ -273,27 +295,40 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def "PBS should apply storedAuctionResponse from general get request when it's specified"() { given: "Default General get request" - def storedAuctionResponse = PBSUtils.randomString + def storedAuctionResponseId = PBSUtils.randomString def generalGetRequest = GeneralGetRequest.default.tap { - it.storedAuctionResponseId = storedAuctionResponse + it.storedAuctionResponseId = storedAuctionResponseId } and: "Default stored request" def request = BidRequest.getDefaultBidRequest() and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedRequestId, request) storedRequestDao.save(storedRequest) + and: "Stored response in DB" + def storedAuctionResponse = SeatBid.getStoredResponse(request) + def storedResponse = new StoredResponse(responseId: storedAuctionResponseId, + storedAuctionResponse: storedAuctionResponse) + storedResponseDao.save(storedResponse) + when: "PBS processes general get request" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) - then: "Response should not contain errors and warnings" + then: "Response should contain same stored auction response as requested" + assert response.seatbid == [storedAuctionResponse] + + and: "PBs should emit warning" + assert response.ext?.warnings[PREBID]*.code == [999] + assert response.ext?.warnings[PREBID]*.message == + ["no auction. response defined by storedauctionresponse" as String] + + and: "Response should not contain errors" assert !response.ext?.errors - assert !response.ext?.warnings - and: "Bidder request should contain storedAuctionResponse from param" - assert bidder.getBidderRequest(request.id).ext.prebid.storedAuctionResponse.id == storedAuctionResponse + and: "PBS not send request to bidder" + assert bidder.getRequestCount(request.id) == 0 } def "PBS should apply dnt from general get request when it's specified"() { @@ -511,10 +546,10 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { it.contentLanguage = PBSUtils.randomString it.contentRating = PBSUtils.randomString it.contentCategory = PBSUtils.randomNumber - it.contentCategoryTaxonomy = [PBSUtils.randomNumber] + it.contentCategoryTaxonomy = PBSUtils.randomNumber it.contentTitle = PBSUtils.randomString it.contentUrl = PBSUtils.randomString - it.contentLivestream = PBSUtils.randomString + it.contentLivestream = PBSUtils.randomNumber } and: "Default stored request" @@ -550,8 +585,8 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def "PBS should apply series from general get request when it's specified"() { given: "Default General get request" def contentSeries = PBSUtils.randomString - def generalGetRequest = (rawGeneralGetRequest as GeneralGetRequest).tap { - it.storedAuctionResponseId = PBSUtils.randomString + def generalGetRequest = (rawGeneralGetRequest(contentSeries) as GeneralGetRequest).tap { + it.storedRequestId = PBSUtils.randomString } and: "Default stored request" @@ -613,7 +648,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { where: deviceIp << [ PBSUtils.getRandomEnum(PublicCountryIp).v4, - PBSUtils.getRandomEnum(PublicCountryIp).v6 + PBSUtils.getRandomEnum(PublicCountryIp).maskedIPv6 ] } @@ -663,7 +698,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { when: "PBS processes general get request" def page = PBSUtils.randomString - def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, ["Referer": page]) + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, ['Referer': page]) then: "Response should not contain errors and warnings" assert !response.ext?.errors @@ -687,7 +722,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { when: "PBS processes general get request" def ua = PBSUtils.randomString - def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, [header: ua]) + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, [(header): ua]) then: "Response should not contain errors and warnings" assert !response.ext?.errors @@ -726,16 +761,16 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { where: header | deviceIp "X-Forwarded-For" | PBSUtils.getRandomEnum(PublicCountryIp).v4 - "X-Forwarded-For" | PBSUtils.getRandomEnum(PublicCountryIp).v6 + "X-Forwarded-For" | PBSUtils.getRandomEnum(PublicCountryIp).maskedIPv6 "X-Device-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v4 - "X-Device-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v6 + "X-Device-IP" | PBSUtils.getRandomEnum(PublicCountryIp).maskedIPv6 "X-Real-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v4 - "X-Real-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v6 + "X-Real-IP" | PBSUtils.getRandomEnum(PublicCountryIp).maskedIPv6 "True-Client-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v4 - "True-Client-IP" | PBSUtils.getRandomEnum(PublicCountryIp).v6 + "True-Client-IP" | PBSUtils.getRandomEnum(PublicCountryIp).maskedIPv6 } @@ -769,7 +804,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { } and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedAuctionResponseId, request) + def storedRequest = StoredRequest.getStoredRequest(storedRequestId, request) storedRequestDao.save(storedRequest) when: "PBS processes general get request" @@ -814,7 +849,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { } and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedAuctionResponseId, request) + def storedRequest = StoredRequest.getStoredRequest(storedRequestId, request) storedRequestDao.save(storedRequest) when: "PBS processes general get request" diff --git a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy index 78780a478d9..85abf5a07a9 100644 --- a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy @@ -179,4 +179,8 @@ class PBSUtils implements ObjectMapperWrapper { return false } } + + static Boolean isApproximatelyEqual(Integer actual, Integer expected, Integer tolerance = 100) { + Math.abs(actual - expected) <= tolerance + } } From 25e4ff6e66eea9d7de3c4040a4a911ab59d91496 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Mon, 24 Nov 2025 20:04:44 +0200 Subject: [PATCH 6/9] update tests --- .../tests/GeneralGetInterfaceRequestSpec.groovy | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy index ea341c66b53..efd82f920ef 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy @@ -690,14 +690,17 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { def generalGetRequest = GeneralGetRequest.getDefault() and: "Default stored request" - def request = BidRequest.getDefaultBidRequest() + def request = BidRequest.getDefaultBidRequest().tap { + site.page = null + site.publisher.domain = PBSUtils.randomString + } and: "Save storedRequest into DB" def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) storedRequestDao.save(storedRequest) when: "PBS processes general get request" - def page = PBSUtils.randomString + def page = "http://${PBSUtils.randomString}" def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, ['Referer': page]) then: "Response should not contain errors and warnings" @@ -722,7 +725,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { when: "PBS processes general get request" def ua = PBSUtils.randomString - def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, [(header): ua]) + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest, ['User-Agent': '', (header): ua]) // remove original user-agent then: "Response should not contain errors and warnings" assert !response.ext?.errors @@ -773,7 +776,6 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { "True-Client-IP" | PBSUtils.getRandomEnum(PublicCountryIp).maskedIPv6 } - def "PBS should use original values from stored request when it's not specified in get request"() { given: "Default General get request" def storedRequestId = PBSUtils.randomString From c88a5a2d08a82b072f7847dfb91822dc5a7207bf Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Tue, 25 Nov 2025 20:26:19 +0200 Subject: [PATCH 7/9] update privacy tests --- .../auction/ConsentedProvidersSettings.groovy | 2 + .../model/request/auction/Regs.groovy | 2 + .../model/request/auction/RegsExt.groovy | 2 + .../request/get/GeneralGetRequest.groovy | 26 +- .../GeneralGetInterfacePrivacySpec.groovy | 225 ++++++++++++------ 5 files changed, 169 insertions(+), 88 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/ConsentedProvidersSettings.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/ConsentedProvidersSettings.groovy index aa7bd511cb2..778bb27a277 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/ConsentedProvidersSettings.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/ConsentedProvidersSettings.groovy @@ -2,8 +2,10 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString +@EqualsAndHashCode @ToString(includeNames = true, ignoreNulls = true) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) class ConsentedProvidersSettings { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Regs.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Regs.groovy index 025d52c2760..8ffc689be32 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Regs.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Regs.groovy @@ -2,8 +2,10 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString +@EqualsAndHashCode @ToString(includeNames = true, ignoreNulls = true) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) class Regs { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy index d7e9cb2242e..fe4d4c851e4 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy @@ -2,8 +2,10 @@ package org.prebid.server.functional.model.request.auction import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.databind.annotation.JsonNaming +import groovy.transform.EqualsAndHashCode import groovy.transform.ToString +@EqualsAndHashCode @ToString(includeNames = true, ignoreNulls = true) @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy) class RegsExt { diff --git a/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy index f0bbaf799d5..e0395b091cd 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/get/GeneralGetRequest.groovy @@ -175,24 +175,18 @@ class GeneralGetRequest { String targeting @JsonProperty("tcfc") - String consent + String tcfConsent @JsonProperty("gdpr_consent") - String consentLegacy + String generalConsent @JsonProperty("consent_string") - String consentStringLegacy + String generalConsentString Integer gdpr - @JsonProperty("privacy") - Integer gdprPrivacy - @JsonProperty("gdpr_applies") - String gdprApplies - - @JsonProperty("usp") - String usPrivacy + Boolean gdprApplies @JsonProperty("addtl_consent") String additionalConsent @@ -200,21 +194,27 @@ class GeneralGetRequest { @JsonProperty("consent_type") ConsentType consentType - @JsonProperty("gpp_sid") + @JsonProperty("gppc") + String gpp + + @JsonProperty("gpps") @JsonSerialize(using = CommaSeparatedListSerializer) List gppSid - Integer coppa - @JsonProperty("gpc") Integer globalPrivacyControl + Integer coppa + @JsonProperty("dnt") Integer doNotTrack @JsonProperty("lmt") Integer limitAdTracking + @JsonProperty("usp") + String usPrivacy + @JsonProperty("bcat") @JsonSerialize(using = CommaSeparatedListSerializer) List blockedCategories diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy index 53465e4fe45..c2aefdd376e 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy @@ -6,6 +6,7 @@ import org.prebid.server.functional.model.db.StoredRequest import org.prebid.server.functional.model.request.GppSectionId import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.ConsentedProvidersSettings +import org.prebid.server.functional.model.request.auction.DebugCondition import org.prebid.server.functional.model.request.auction.Regs import org.prebid.server.functional.model.request.auction.RegsExt import org.prebid.server.functional.model.request.auction.User @@ -14,12 +15,15 @@ import org.prebid.server.functional.model.request.get.GeneralGetRequest import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.TcfConsent import org.prebid.server.functional.util.privacy.gpp.UsNatV1Consent +import spock.lang.PendingFeature import static org.prebid.server.functional.model.config.Purpose.P2 import static org.prebid.server.functional.model.config.PurposeEnforcement.BASIC import static org.prebid.server.functional.model.config.PurposeEnforcement.NO import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ADAPTER_DISALLOWED_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_REQUEST_DISALLOWED_COUNT +import static org.prebid.server.functional.model.request.GppSectionId.HEADER_V1 +import static org.prebid.server.functional.model.request.GppSectionId.TCF_EU_V2 import static org.prebid.server.functional.model.request.amp.ConsentType.BOGUS import static org.prebid.server.functional.model.request.amp.ConsentType.GPP import static org.prebid.server.functional.model.request.amp.ConsentType.TCF_1 @@ -32,9 +36,14 @@ import static org.prebid.server.functional.util.privacy.TcfConsent.PurposeId.BAS class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { + String DEFAULT_COPPA = '1YYY' + def "PBS should apply gpp consent from general get request when it's specified"() { given: "Default General get request" - def consentValue = PBSUtils.randomString + def consentValue = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build().toString() def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { it.storedRequestId = PBSUtils.randomNumber } @@ -60,18 +69,18 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { where: consentGeneralRequest << [ - { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent, generalConsent: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent, generalConsentString: PBSUtils.randomString) }, ] } def "PBS should recognise consent from general get request as tcfv1 when consent type is tcf1"() { given: "Default General get request" - def consentValue = PBSUtils.randomString + def consentValue = new TcfConsent.Builder() + .setPurposesLITransparency(BASIC_ADS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build().toString() def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { it.storedRequestId = PBSUtils.randomNumber it.consentType = TCF_1 @@ -94,31 +103,26 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { and: "Bidder request should contain gpp info from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.user.consent == consentValue - assert bidderRequest.regs.gpp == consentValue where: consentGeneralRequest << [ - { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(generalConsent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(generalConsentString: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(generalConsentString: gppConsent, generalConsent: PBSUtils.randomString) } ] } def "PBS should recognise consent from general get request as TCFv2 when consent type is tcf2"() { given: "Default General get request" - def consentValue = new TcfConsent.Builder() - .setPurposesLITransparency(BASIC_ADS) - .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) - .build().toString() + def consentValue = new TcfConsent.Builder().build().toString() def accountId = PBSUtils.randomNumber.toString() def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { it.storedRequestId = PBSUtils.randomNumber it.accountId = accountId + it.debug = DebugCondition.ENABLED it.consentType = TCF_2 + it.gdpr = 1 } and: "Default stored request" @@ -156,8 +160,8 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { !it.gpc !it.usPrivacy !it.gpp - !it.gppSid - !it.ext + + it.ext == new RegsExt() } and: "PBS should cansel request" @@ -171,22 +175,15 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { where: consentGeneralRequest << [ - { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent, generalConsent: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent, generalConsentString: PBSUtils.randomString) } ] } def "PBS should recognise consent from general get request as us_privacy when consent type is us_privacy"() { given: "Default General get request" - def consentValue = new TcfConsent.Builder() - .setPurposesLITransparency(BASIC_ADS) - .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) - .build().toString() - def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { + def generalGetRequest = (consentGeneralRequest(DEFAULT_COPPA) as GeneralGetRequest).tap { it.storedRequestId = PBSUtils.randomNumber it.consentType = US_PRIVACY } @@ -208,9 +205,8 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { and: "Generic bidderRequest should contain tcfv2 info" def bidderRequest = bidder.getBidderRequest(request.id) verifyAll (bidderRequest) { - user.consent == consentValue - regs.usPrivacy == consentValue - regs.gdpr == 1 + regs.usPrivacy == DEFAULT_COPPA + regs.ext == new RegsExt() } and: "Shouldn't contain other privacy info" @@ -219,18 +215,14 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { !it.gpc !it.gpp !it.gppSid - !it.ext } where: consentGeneralRequest << [ - { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(generalConsent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(generalConsentString: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(generalConsentString: gppConsent, generalConsent: PBSUtils.randomString) } ] } @@ -259,38 +251,30 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { and: "Generic bidderRequest should contain tcfv2 info" def bidderRequest = bidder.getBidderRequest(request.id) verifyAll (bidderRequest) { - user.consent == consentValue regs.gpp == consentValue - regs.gdpr == 1 } and: "Shouldn't contain other privacy info" verifyAll (bidderRequest.regs) { !it.coppa !it.gpc - !it.gpp !it.gppSid - !it.ext + it.ext == new RegsExt() } where: consentGeneralRequest << [ - { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(generalConsent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(generalConsentString: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(generalConsentString: gppConsent, generalConsent: PBSUtils.randomString) } ] } + @PendingFeature def "PBS should emit error when consent type is invalid"() { given: "Default General get request" - def consentValue = new TcfConsent.Builder() - .setPurposesLITransparency(BASIC_ADS) - .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) - .build().toString() + def consentValue = PBSUtils.randomString def accountId = PBSUtils.randomNumber.toString() def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { it.storedRequestId = PBSUtils.randomNumber @@ -317,12 +301,9 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { where: consentGeneralRequest << [ - { String gppConsent -> new GeneralGetRequest(consent: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consentStringLegacy: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consent: gppConsent, consentStringLegacy: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(consentLegacy: gppConsent, consentStringLegacy: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(generalConsent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(generalConsentString: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(generalConsentString: gppConsent, generalConsent: PBSUtils.randomString) } ] } @@ -364,17 +345,14 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { where: consentGeneralRequest << [ { Integer gdpr -> new GeneralGetRequest(gdpr: gdpr) }, - { Integer gdpr -> new GeneralGetRequest(gdprPrivacy: gdpr) }, { Integer gdpr -> new GeneralGetRequest(gdprApplies: gdpr) }, - { Integer gdpr -> new GeneralGetRequest(consent: gdpr, consentLegacy: PBSUtils.randomBinary) }, - { Integer gdpr -> new GeneralGetRequest(consent: gdpr, consentStringLegacy: PBSUtils.randomBinary) }, - { Integer gdpr -> new GeneralGetRequest(consentLegacy: gdpr, consentStringLegacy: PBSUtils.randomBinary) }, + { Integer gdpr -> new GeneralGetRequest(gdpr: gdpr, gdprApplies: PBSUtils.randomBoolean) }, ] } def "PBS should apply usp from general get request when it's specified"() { given: "Default General get request" - def usp = PBSUtils.randomString + def usp = DEFAULT_COPPA def generalGetRequest = GeneralGetRequest.default.tap { it.usPrivacy = usp } @@ -424,9 +402,35 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { assert bidderRequest.user.ext.consentedProvidersSettings.consentedProviders == addtlConsent } - def "PBS should apply gpp_sid from general get request when it's specified"() { + def "PBS should apply gpp from general get request when it's specified"() { + given: "Default General get request" + def gppConsent = SIMPLE_GPC_DISALLOW_LOGIC.toString() + def generalGetRequest = GeneralGetRequest.default.tap { + it.gpp = gppConsent + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest() + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain addtlConsent from param" + def bidderRequest = bidder.getBidderRequest(request.id) + assert bidderRequest.regs.gpp == gppConsent + } + + def "PBS should apply gpps from general get request when it's specified"() { given: "Default General get request" - def gppSids = [PBSUtils.getRandomEnum(GppSectionId.class)].intValue + def gppSids = [PBSUtils.getRandomEnum(GppSectionId.class, [TCF_EU_V2, HEADER_V1])].intValue def generalGetRequest = GeneralGetRequest.default.tap { it.gppSid = gppSids } @@ -499,7 +503,7 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { and: "Bidder request should contain gpc from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.regs.gpc == gpc + assert bidderRequest.regs.ext.gpc as Integer == gpc } def "PBS should use original values from stored request when it's not specified in get request"() { @@ -509,16 +513,16 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { and: "Default stored request" def userForRequest = User.defaultUser.tap { - it.consent = PBSUtils.randomString + it.consent = new TcfConsent.Builder().build().toString() it.ext = new UserExt(consentedProvidersSettings: new ConsentedProvidersSettings(consentedProviders: PBSUtils.randomString)) } def regsForRequest = new Regs().tap { it.gdpr = 0 // for preventing bidder block - it.gpp = PBSUtils.randomString - it.usPrivacy = PBSUtils.randomString + it.gpp = SIMPLE_GPC_DISALLOW_LOGIC + it.usPrivacy = DEFAULT_COPPA it.gppSid = [PBSUtils.randomNumber] it.ext = new RegsExt(gpc: PBSUtils.randomNumber) - it.coppa = PBSUtils.randomNumber + it.coppa = 0 } def request = BidRequest.getDefaultBidRequest().tap { user = userForRequest @@ -538,8 +542,79 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { and: "Bidder request should contain privacy data from original request" verifyAll (bidder.getBidderRequest(request.id)) { - it.user == userForRequest + it.user.consent == userForRequest.consent + it.user.ext.consentedProvidersSettings == userForRequest.ext.consentedProvidersSettings it.regs == regsForRequest } } + + def "PBS should properly process privacy functionality when it's required"() { + given: "Default General get request" + def consentValue = new TcfConsent.Builder().build().toString() + def accountId = PBSUtils.randomNumber.toString() + def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { + it.storedRequestId = PBSUtils.randomNumber + it.accountId = accountId + it.debug = DebugCondition.ENABLED + it.consentType = TCF_2 + it.gdpr = 1 + } + + and: "Default stored request" + def request = BidRequest.getDefaultBidRequest().tap { + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + and: "Save account config with requireConsent into DB" + def purposes = [(P2): new PurposeConfig(enforcePurpose: BASIC, enforceVendors: true)] + def accountGdprConfig = new AccountGdprConfig(purposes: purposes) + def account = getAccountWithGdpr(request.accountId, accountGdprConfig) + accountDao.save(account) + + when: "PBS processes general get request" + def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Generic bidderRequest should contain tcfv2 info" + def bidderRequest = response.ext.debug.resolvedRequest + verifyAll (bidderRequest) { + user.consent == consentValue + regs.gdpr == 1 + } + + and: "Shouldn't contain other privacy info" + verifyAll (bidderRequest.regs) { + !it.coppa + !it.gpc + !it.usPrivacy + !it.gpp + it.ext == new RegsExt() + } + + and: "PBS should cansel request" + assert !bidder.getBidderRequests(request.id) + + then: "Metrics processed across activities should be updated" + def metrics = privacyPbsService.sendCollectedMetricsRequest() + assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(request, FETCH_BIDS)] == 1 + assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(request, FETCH_BIDS)] == 1 + + where: + consentGeneralRequest << + [ + { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(generalConsent: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(generalConsentString: gppConsent) }, + { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent, generalConsent: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent, generalConsentString: PBSUtils.randomString) }, + { String gppConsent -> new GeneralGetRequest(generalConsentString: gppConsent, generalConsent: PBSUtils.randomString) } + ] + } } From 6ba1166083c9175fe9123d8c4ad2623a1b2d4e68 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Wed, 26 Nov 2025 12:02:38 +0200 Subject: [PATCH 8/9] Increase test coverage --- .../tests/GeneralGetInterfaceImpSpec.groovy | 59 ++++++++++ .../functional/tests/ProfileSpec.groovy | 106 +++++++++--------- 2 files changed, 109 insertions(+), 56 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy index e0e5df71cbe..67b93b45ab0 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy @@ -1,6 +1,7 @@ package org.prebid.server.functional.tests import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.request.amp.Targeting import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Format import org.prebid.server.functional.model.request.auction.Imp @@ -13,6 +14,8 @@ import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils import spock.lang.PendingFeature +import java.nio.charset.StandardCharsets + import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID class GeneralGetInterfaceImpSpec extends BaseSpec { @@ -1675,4 +1678,60 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { it.nvol == audioImp.audio.nvol } } + + def "PBS get request should move targeting key to imp.ext.data"() { + given: "Create targeting" + def targeting = new Targeting().tap { + any = PBSUtils.randomString + } + + and: "Encode Targeting to String" + def encodeTargeting = URLEncoder.encode(encode(targeting), StandardCharsets.UTF_8) + + and: "Amp request with targeting" + def generalGetRequest = GeneralGetRequest.default.tap { + it.targeting = encodeTargeting + } + + and: "Default BidRequest" + def bidRequest = BidRequest.defaultBidRequest + + and: "Create and save stored request into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedRequestId, bidRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "Amp response should contain value from targeting in imp.ext.data" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp[0].ext.data.any == targeting.any + } + + def "PBS should throw exception when general get request linked to stored request with several imps"() { + given: "Stored request with several imps" + def request = BidRequest.getDefaultBidRequest().tap { + addImp(Imp.defaultImpression) + setAccountId(accountId) + } + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + storedRequestDao.save(storedRequest) + + when: "PBS processes general get request" + defaultPbsService.sendGeneralGetRequest(generalGetRequest) + + then: "PBs should throw error due to invalid request" + def exception = thrown(PrebidServerException) + assert exception.statusCode == 400 + assert exception.responseBody == "data for tag_id '${generalGetRequest.resolveStoredRequestId()}' includes '${request.imp.size()}'" + + " imp elements. Only one is allowed" + + where: + generalGetRequest << [ + new GeneralGetRequest(storedRequestId: PBSUtils.randomNumber), + new GeneralGetRequest(storedRequestIdLegacy: PBSUtils.randomNumber) + ] + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy index 4a9203edfe3..545beb0f04c 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy @@ -1519,7 +1519,7 @@ class ProfileSpec extends BaseSpec { ] } - def "PBS should throw exception when profiles are not configured for filesystem and request contain profileId"() { + def "PBS should process request when profiles are not configured for filesystem and request contain profileId"() { given: "PBS with profiles.fail-on-unknown config" def config = FILESYSTEM_CONFIG + PROFILES_CONFIG + ['settings.filesystem.profiles-dir': null] pbsContainer = new PrebidServerContainer(config) @@ -1527,6 +1527,7 @@ class ProfileSpec extends BaseSpec { pbsContainer.withFolder(IMPS_PATH) pbsContainer.withFolder(RESPONSES_PATH) pbsContainer.withFolder(CATEGORIES_PATH) + pbsContainer.withCopyToContainer(Transferable.of(encode(fileRequestProfileWithEmptyMerge)), "$PROFILES_PATH/${fileRequestProfileWithEmptyMerge.fileName}") def accountsConfig = new FileSystemAccountsConfig(accounts: [new AccountConfig(id: ACCOUNT_ID_FILE_STORAGE, status: ACTIVE)]) pbsContainer.withCopyToContainer(Transferable.of(encodeYaml(accountsConfig)), SETTINGS_FILENAME) @@ -1534,17 +1535,45 @@ class ProfileSpec extends BaseSpec { def pbsWithStoredProfiles = new PrebidServerService(pbsContainer) and: "BidRequest with profile" - def requestWithProfile = BidRequest.getDefaultBidRequest().tap { - it.ext.prebid.profileNames = [PBSUtils.randomString] - } + def requestWithProfile = getRequestWithProfiles(ACCOUNT_ID_FILE_STORAGE.toString(), [fileRequestProfileWithEmptyMerge]).tap { + it.device = Device.default + } as BidRequest when: "PBS processes auction request" - pbsWithStoredProfiles.sendAuctionRequest(requestWithProfile) + def response = pbsWithStoredProfiles.sendAuctionRequest(requestWithProfile) - then: "PBs should throw error due to invalid profile config" - def exception = thrown(PrebidServerException) - assert exception.statusCode == 400 - assert exception.responseBody == INVALID_REQUEST_PREFIX + CONFIG_ERROR_MESSAGE + then: "PBS should emit proper warning" + assert response.ext?.warnings[ErrorType.PREBID]*.code == [999] + assert response.ext?.warnings[ErrorType.PREBID]*.message == [NO_REQUEST_PROFILE_MESSAGE.formatted(fileRequestProfileWithEmptyMerge.id)] + + and: "Response should contain error" + assert !response.ext?.errors + + and: "Missing metric should increments" + def metrics = pbsWithStoredProfiles.sendCollectedMetricsRequest() + assert metrics[MISSING_ACCOUNT_PROFILE_METRIC.formatted(ACCOUNT_ID_FILE_STORAGE)] == 1 + + and: "Bidder request should contain data from profile" + verifyAll(bidder.getBidderRequest(requestWithProfile.id)) { + it.site.id == requestWithProfile.site.id + it.site.name == requestWithProfile.site.name + it.site.domain == requestWithProfile.site.domain + it.site.cat == requestWithProfile.site.cat + it.site.sectionCat == requestWithProfile.site.sectionCat + it.site.pageCat == requestWithProfile.site.pageCat + it.site.page == requestWithProfile.site.page + it.site.ref == requestWithProfile.site.ref + it.site.search == requestWithProfile.site.search + it.site.keywords == requestWithProfile.site.keywords + + it.device.didsha1 == requestWithProfile.device.didsha1 + it.device.didmd5 == requestWithProfile.device.didmd5 + it.device.dpidsha1 == requestWithProfile.device.dpidsha1 + it.device.ifa == requestWithProfile.device.ifa + it.device.macsha1 == requestWithProfile.device.macsha1 + it.device.macmd5 == requestWithProfile.device.macmd5 + it.device.dpidmd5 == requestWithProfile.device.dpidmd5 + } cleanup: "Stop and remove pbs container" pbsContainer.stop() @@ -1563,15 +1592,16 @@ class ProfileSpec extends BaseSpec { and: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { + it.device = Device.default setAccountId(accountId) } and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(getRequest.resolveStoredRequestId(), request) + def storedRequest = StoredRequest.getStoredRequest(getRequest.storedRequestId, request) storedRequestDao.save(storedRequest) when: "PBS processes general get request" - def response = pbsWithStoredProfiles.(getRequest) + def response = pbsWithStoredProfiles.sendGeneralGetRequest(getRequest) then: "Response should not contain errors and warnings" assert !response.ext?.errors @@ -1589,7 +1619,6 @@ class ProfileSpec extends BaseSpec { it.site.ref == requestProfile.body.site.ref it.site.search == requestProfile.body.site.search it.site.keywords == requestProfile.body.site.keywords - it.site.ext.data == requestProfile.body.site.ext.data it.device.didsha1 == requestProfile.body.device.didsha1 it.device.didmd5 == requestProfile.body.device.didmd5 @@ -1723,6 +1752,7 @@ class ProfileSpec extends BaseSpec { and: "Default stored request" def request = BidRequest.getDefaultBidRequest().tap { + it.device = Device.default setAccountId(accountId) } @@ -1756,7 +1786,6 @@ class ProfileSpec extends BaseSpec { it.site.ref == request.site.ref it.site.search == request.site.search it.site.keywords == request.site.keywords - it.site.ext.data == request.site.ext.data it.device.didsha1 == request.device.didsha1 it.device.didmd5 == request.device.didmd5 @@ -1816,11 +1845,12 @@ class ProfileSpec extends BaseSpec { and: "Default stored request" def accountId = PBSUtils.randomNumber as String def request = BidRequest.getDefaultBidRequest().tap { + it.device = Device.default setAccountId(accountId) } and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedRequestId, request) storedRequestDao.save(storedRequest) when: "PBS processes general get request" @@ -1828,7 +1858,7 @@ class ProfileSpec extends BaseSpec { then: "PBS should emit proper warning" assert response.ext?.warnings[ErrorType.PREBID]*.code == [999] - assert response.ext?.warnings[ErrorType.PREBID]*.message == [NO_PROFILE_MESSAGE.formatted(requestProfile)] + assert response.ext?.warnings[ErrorType.PREBID]*.message == [NO_REQUEST_PROFILE_MESSAGE.formatted(requestProfile)] and: "Response should contain error" assert !response.ext?.errors @@ -1849,7 +1879,6 @@ class ProfileSpec extends BaseSpec { it.site.ref == request.site.ref it.site.search == request.site.search it.site.keywords == request.site.keywords - it.site.ext.data == request.site.ext.data it.device.didsha1 == request.device.didsha1 it.device.didmd5 == request.device.didmd5 @@ -1871,14 +1900,15 @@ class ProfileSpec extends BaseSpec { def "PBS should emit error and metrics when imp profile missing for profile general get parameter"() { given: "Default GeneralGetRequest" - def impProfile = PBSUtils.randomString + def impProfileId = PBSUtils.randomString def generalGetRequest = GeneralGetRequest.default.tap { - it.impProfiles = [impProfile] + it.impProfiles = [impProfileId] } and: "Default stored request" def accountId = PBSUtils.randomNumber as String def request = BidRequest.getDefaultBidRequest().tap { + it.device = Device.default setAccountId(accountId) } @@ -1891,7 +1921,7 @@ class ProfileSpec extends BaseSpec { then: "PBS should emit proper warning" assert response.ext?.warnings[ErrorType.PREBID]*.code == [999] - assert response.ext?.warnings[ErrorType.PREBID]*.message == [NO_PROFILE_MESSAGE.formatted(impProfile.id)] + assert response.ext?.warnings[ErrorType.PREBID]*.message == [NO_IMP_PROFILE_MESSAGE.formatted(impProfileId)] and: "Response should contain error" assert !response.ext?.errors @@ -1912,7 +1942,6 @@ class ProfileSpec extends BaseSpec { it.site.ref == request.site.ref it.site.search == request.site.search it.site.keywords == request.site.keywords - it.site.ext.data == request.site.ext.data it.device.didsha1 == request.device.didsha1 it.device.didmd5 == request.device.didmd5 @@ -2022,7 +2051,7 @@ class ProfileSpec extends BaseSpec { } and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.getStoredAuctionResponseId(), request) + def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedRequestId, request) storedRequestDao.save(storedRequest) when: "PBS processes general get request" @@ -2091,41 +2120,6 @@ class ProfileSpec extends BaseSpec { } } - def "PBS should apply imp profile for all imps when profile included as parameter for with multi-imps general get request"() { - given: "Default profile in database" - def accountId = PBSUtils.randomNumber as String - def impProfile = ImpProfile.getProfile(accountId) - profileImpDao.save(StoredProfileImp.getProfile(impProfile)) - - and: "Default GeneralGetRequest" - def generalGetRequest = GeneralGetRequest.default.tap { - it.impProfiles = [impProfile.id] - } - - and: "Default stored request" - def request = BidRequest.getDefaultBidRequest().tap { - addImp(Imp.defaultImpression) - setAccountId(accountId) - } - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) - storedRequestDao.save(storedRequest) - - when: "PBS processes general get request" - def response = pbsWithStoredProfiles.sendGeneralGetRequest(generalGetRequest) - - then: "Response should not contain errors and warnings" - assert !response.ext?.errors - assert !response.ext?.warnings - - and: "Bidder request imp should contain data from profile" - verifyAll(bidder.getBidderRequest(request.id).imp) { - it.id == [impProfile.body.id] * 2 - it.banner == [impProfile.body.banner] * 2 - } - } - private static BidRequest getRequestWithProfiles(String accountId, List profiles) { BidRequest.getDefaultBidRequest().tap { it.imp.first.ext.prebid.profileNames = profiles.findAll { it.type == ProfileType.IMP }*.id From 5056fdd6a30558c8a38b247aa4105f3311e1d79e Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Thu, 4 Dec 2025 20:07:21 +0200 Subject: [PATCH 9/9] update after review --- .../model/request/auction/Banner.groovy | 3 +- .../functional/tests/BidderParamsSpec.groovy | 26 ++-- .../tests/GeneralGetInterfaceImpSpec.groovy | 55 ++++---- .../GeneralGetInterfaceRequestSpec.groovy | 30 ++--- .../functional/tests/ProfileSpec.groovy | 2 +- .../GeneralGetInterfacePrivacySpec.groovy | 120 ++++-------------- 6 files changed, 90 insertions(+), 146 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Banner.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Banner.groovy index 97461b79d78..ce75803a3bb 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Banner.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Banner.groovy @@ -18,7 +18,8 @@ class Banner { Integer pos List mimes Integer topframe - List expdir + @JsonProperty("expdir") + List expandableDirections List api String id Integer vcm diff --git a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy index 220585f008f..0c0702b84bd 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/BidderParamsSpec.groovy @@ -1017,7 +1017,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain seatBid" assert response.seatbid.bid.flatten().size() == 1 - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequest(bidRequest.id) and: "Response shouldn't contain error" @@ -1058,7 +1058,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain seatBid" assert response.seatbid.bid.flatten().size() == 1 - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequest(bidRequest.id) and: "Response shouldn't contain error" @@ -1094,7 +1094,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain seatBid" assert response.seatbid.bid.flatten().size() == 1 - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequest(bidRequest.id) and: "Response shouldn't contain error" @@ -1182,7 +1182,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain seatBid" assert response.seatbid.bid.flatten().size() == 1 - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequest(bidRequest.id) and: "Response shouldn't contain error" @@ -1390,7 +1390,7 @@ class BidderParamsSpec extends BaseSpec { then: "Response should contain adapter code" assert response.seatbid.bid.ext.prebid.meta.adapterCode.flatten() == [BidderName.GENERIC] - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequest(bidRequest.id) } @@ -1424,7 +1424,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain repose millis with corresponding bidder" assert response.ext.responsetimemillis.containsKey(ALIAS.value) - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequests(bidRequest.id) } @@ -1462,7 +1462,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain repose millis with corresponding bidder" assert response.ext.responsetimemillis.containsKey(ALIAS.value) - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequests(bidRequest.id) cleanup: "Stop and remove pbs container" @@ -1515,7 +1515,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain repose millis with corresponding bidder" assert response.ext.responsetimemillis.containsKey(ALIAS.value) - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequests(bidRequest.id) cleanup: "Stop and remove pbs container" @@ -1552,7 +1552,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain repose millis with corresponding bidder" assert response.ext.responsetimemillis.containsKey(ALIAS_UPPER_CASE.value) - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequests(bidRequest.id) } @@ -1612,7 +1612,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain repose millis with corresponding bidder" assert response.ext.responsetimemillis.containsKey(ALIAS_UPPER_CASE.value) - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequests(bidRequest.id) } @@ -1652,7 +1652,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain repose millis with corresponding bidder" assert response.ext.responsetimemillis.containsKey(ALIAS.value) - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequests(bidRequest.id) cleanup: "Stop and remove pbs container" @@ -1695,7 +1695,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain repose millis with corresponding bidder" assert response.ext.responsetimemillis.containsKey(ALIAS.value) - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequests(bidRequest.id) cleanup: "Stop and remove pbs container" @@ -1735,7 +1735,7 @@ class BidderParamsSpec extends BaseSpec { and: "Response should contain repose millis with corresponding bidder" assert response.ext.responsetimemillis.containsKey(AMX.value) - and: "Bidder request should be valid" + and: "PBS should preform bidder request" assert bidder.getBidderRequests(bidRequest.id) cleanup: "Stop and remove pbs container" diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy index 67b93b45ab0..7c38590340d 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceImpSpec.groovy @@ -15,6 +15,7 @@ import org.prebid.server.functional.util.PBSUtils import spock.lang.PendingFeature import java.nio.charset.StandardCharsets +import java.time.Instant import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID @@ -59,9 +60,9 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { @PendingFeature def "PBS should remove invalid mimes from general get request when it's specified"() { given: "Default General get request" - def validMemis = PBSUtils.randomString + def validMimes = PBSUtils.randomString def generalGetRequest = GeneralGetRequest.default.tap { - it.mimes = (invalidMimes + validMemis) + it.mimes = (invalidMimes + validMimes) } and: "Default stored request" @@ -88,7 +89,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain mimes from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.size() == 1 - assert bidderRequest.imp.first.singleMediaTypeData.mimes == [validMemis] + assert bidderRequest.imp.first.singleMediaTypeData.mimes == [validMimes] where: invalidMimes << [[''], [PBSUtils.randomNumber.toString()]] @@ -159,7 +160,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { when: "PBS processes general get request" defaultPbsService.sendGeneralGetRequest(generalGetRequest) - then: "PBs should throw error due to invalid request" + then: "PBS should throw error due to invalid request" def exception = thrown(PrebidServerException) assert exception.statusCode == 400 assert exception.responseBody == 'Invalid request format: request.imp[0].banner has no sizes. Define "w" and "h", or include "format" elements' @@ -190,7 +191,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { when: "PBS processes general get request" defaultPbsService.sendGeneralGetRequest(generalGetRequest) - then: "PBs should throw error due to invalid request" + then: "PBS should throw error due to invalid request" def exception = thrown(PrebidServerException) assert exception.statusCode == 400 assert exception.responseBody == 'Invalid request format: request.imp[0].banner must define a valid "h" and "w" properties' @@ -397,7 +398,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain width and height from param" + and: "Bidder request should contain width and height from original stored request" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.size() == 1 assert bidderRequest.imp.banner.format.width == request.imp.banner.format.width @@ -428,7 +429,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { when: "PBS processes general get request" defaultPbsService.sendGeneralGetRequest(generalGetRequest) - then: "PBs should throw error due to invalid request" + then: "PBS should throw error due to invalid request" def exception = thrown(PrebidServerException) assert exception.statusCode == 400 assert exception.responseBody == 'Invalid request format: request.imp[0].banner.format[0] should define *either* ' + @@ -443,7 +444,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { ] } - def "PBS should apply slot from height general get request when it's specified"() { + def "PBS should apply slot from general get request when it's specified"() { given: "Default General get request" def slotParam = PBSUtils.randomString def generalGetRequest = GeneralGetRequest.default.tap { @@ -498,7 +499,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain mimes from param" + and: "Bidder request should contain duration from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.singleMediaTypeData.minduration == [minDurationParam] assert bidderRequest.imp.singleMediaTypeData.maxduration == [maxDurationParam] @@ -1300,7 +1301,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain playbackmethod from param" + and: "Bidder request should contain playbackend from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.imp.first.video.playbackend == playbackEndParam } @@ -1426,7 +1427,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { and: "Bidder request should contain expdir from param" def bidderRequest = bidder.getBidderRequest(request.id) - assert bidderRequest.imp.first.banner.expdir == expandableDirectionsParam + assert bidderRequest.imp.first.banner.expandableDirections == expandableDirectionsParam } def "PBS should apply topFrame from general get request when it's specified"() { @@ -1472,7 +1473,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { it.banner.battr = [PBSUtils.randomNumber] it.banner.pos = PBSUtils.randomNumber it.banner.btype = [PBSUtils.randomNumber] - it.banner.expdir = [PBSUtils.randomNumber] + it.banner.expandableDirections = [PBSUtils.randomNumber] it.banner.topframe = PBSUtils.randomNumber } def request = BidRequest.getDefaultBidRequest().tap { @@ -1504,7 +1505,7 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { it.battr == bannerImp.banner.battr it.pos == bannerImp.banner.pos it.btype == bannerImp.banner.btype - it.expdir == bannerImp.banner.expdir + it.expandableDirections == bannerImp.banner.expandableDirections it.topframe == bannerImp.banner.topframe } } @@ -1700,19 +1701,22 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.storedRequestId, bidRequest) storedRequestDao.save(storedRequest) - when: "PBS processes amp request" + when: "PBS processes general get request" defaultPbsService.sendGeneralGetRequest(generalGetRequest) - then: "Amp response should contain value from targeting in imp.ext.data" + then: "General get response should contain value from targeting in imp.ext.data" def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert bidderRequest.imp[0].ext.data.any == targeting.any } def "PBS should throw exception when general get request linked to stored request with several imps"() { - given: "Stored request with several imps" + given: "Start time" + def startTime = Instant.now() + + and: "Stored request with several imps" def request = BidRequest.getDefaultBidRequest().tap { addImp(Imp.defaultImpression) - setAccountId(accountId) + setAccountId(generalGetRequest.resolveStoredRequestId()) } and: "Save storedRequest into DB" @@ -1720,13 +1724,18 @@ class GeneralGetInterfaceImpSpec extends BaseSpec { storedRequestDao.save(storedRequest) when: "PBS processes general get request" - defaultPbsService.sendGeneralGetRequest(generalGetRequest) + def response = defaultPbsService.sendGeneralGetRequest(generalGetRequest) - then: "PBs should throw error due to invalid request" - def exception = thrown(PrebidServerException) - assert exception.statusCode == 400 - assert exception.responseBody == "data for tag_id '${generalGetRequest.resolveStoredRequestId()}' includes '${request.imp.size()}'" + - " imp elements. Only one is allowed" + then: "Response should not contain errors and warnings" + assert !response.ext?.errors + assert !response.ext?.warnings + + and: "Bidder request should contain only first imp data" + assert bidder.getBidderRequest(request.id).imp.id == [request.imp.first.id] + + and: "PBS log should contain message" + def logs = defaultPbsService.getLogsByTime(startTime) + assert getLogsByText(logs, '').size() == 1 //TODO add logs where: generalGetRequest << [ diff --git a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy index efd82f920ef..19c855e7dde 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/GeneralGetInterfaceRequestSpec.groovy @@ -40,7 +40,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should be valid" + and: "PBS should perform bidder request" assert bidder.getBidderRequest(request.id) where: @@ -69,11 +69,11 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should be valid" + and: "PBS should perform bidder request" assert bidder.getBidderRequest(request.id) } - def "PBS should response with error when process bid request is not specified in general get request"() { + def "PBS should respond with error when process bid request is not specified in general get request"() { given: "General get request without stored request param" def generalGetRequest = new GeneralGetRequest() @@ -168,7 +168,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { } @PendingFeature - def "PBS shouldn't apply tmax from general get request when it's specified lower then 100"() { + def "PBS shouldn't apply tmax from general get request when it's specified lower than 100"() { given: "Default General get request" def generalGetRequest = GeneralGetRequest.default.tap { it.timeoutMax = PBSUtils.getRandomNumber(0, 100) @@ -210,7 +210,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { when: "PBS processes general get request" defaultPbsService.sendGeneralGetRequest(generalGetRequest) - then: "PBs should throw error due to invalid request" + then: "PBS should throw error due to invalid request" def exception = thrown(PrebidServerException) assert exception.statusCode == 500 assert exception.responseBody == 'Critical error while running the auction: Start time and timeout must be positive' @@ -289,7 +289,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain outputformat from param" + and: "Bidder request should contain outputModule from param" assert bidder.getBidderRequest(request.id).ext.prebid.outputModule == outputModule } @@ -319,7 +319,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { then: "Response should contain same stored auction response as requested" assert response.seatbid == [storedAuctionResponse] - and: "PBs should emit warning" + and: "PBS should emit warning" assert response.ext?.warnings[PREBID]*.code == [999] assert response.ext?.warnings[PREBID]*.message == ["no auction. response defined by storedauctionresponse" as String] @@ -352,7 +352,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain gpc from param" + and: "Bidder request should contain dnt from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.device.dnt == dnt } @@ -378,7 +378,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain gpc from param" + and: "Bidder request should contain lmt from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.device.lmt == lmt } @@ -572,7 +572,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { it.language == generalGetRequest.contentLanguage it.contentrating == generalGetRequest.contentRating it.cat == [generalGetRequest.contentCategory.toString()] - it.cattax == generalGetRequest.contentCategoryTaxonomy // TODO discuss this issue + it.cattax == generalGetRequest.contentCategoryTaxonomy it.title == generalGetRequest.contentTitle it.url == generalGetRequest.contentUrl it.livestream == generalGetRequest.contentLivestream @@ -603,7 +603,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain contentGenre from param" + and: "Bidder request should contain contentSeries from param" assert getRequestContent(bidder.getBidderRequest(request.id)).series == contentSeries where: @@ -707,12 +707,12 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain device ip from param" + and: "Bidder request should contain site page from header" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.site.page == page } - def "PBS should apply site page info from #header header of get request when parameter is not specified"() { + def "PBS should apply user agent info from #header header of get request when parameter is not specified"() { given: "Default General get request" def generalGetRequest = GeneralGetRequest.getDefault() @@ -731,7 +731,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain device ip from param" + and: "Bidder request should contain device ua from header" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.device.ua == ua @@ -861,7 +861,7 @@ class GeneralGetInterfaceRequestSpec extends BaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain device ip from headers" + and: "Bidder request should contain content data from headers" assert getRequestContent(bidder.getBidderRequest(request.id)) == content where: diff --git a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy index 545beb0f04c..6ae3c70c29b 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/ProfileSpec.groovy @@ -1519,7 +1519,7 @@ class ProfileSpec extends BaseSpec { ] } - def "PBS should process request when profiles are not configured for filesystem and request contain profileId"() { + def "PBS should process request with warning when profiles are not configured for filesystem and request contain profileId"() { given: "PBS with profiles.fail-on-unknown config" def config = FILESYSTEM_CONFIG + PROFILES_CONFIG + ['settings.filesystem.profiles-dir': null] pbsContainer = new PrebidServerContainer(config) diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy index c2aefdd376e..c9ebf7a2ac1 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GeneralGetInterfacePrivacySpec.groovy @@ -13,6 +13,7 @@ import org.prebid.server.functional.model.request.auction.User import org.prebid.server.functional.model.request.auction.UserExt import org.prebid.server.functional.model.request.get.GeneralGetRequest import org.prebid.server.functional.util.PBSUtils +import org.prebid.server.functional.util.privacy.CcpaConsent import org.prebid.server.functional.util.privacy.TcfConsent import org.prebid.server.functional.util.privacy.gpp.UsNatV1Consent import spock.lang.PendingFeature @@ -36,8 +37,6 @@ import static org.prebid.server.functional.util.privacy.TcfConsent.PurposeId.BAS class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { - String DEFAULT_COPPA = '1YYY' - def "PBS should apply gpp consent from general get request when it's specified"() { given: "Default General get request" def consentValue = new TcfConsent.Builder() @@ -62,16 +61,16 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain gpp info from param" + and: "Bidder request should contain user consent info from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.user.consent == consentValue where: consentGeneralRequest << [ - { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent, generalConsent: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent, generalConsentString: PBSUtils.randomString) }, + { String tcfConsent -> new GeneralGetRequest(tcfConsent: tcfConsent) }, + { String tcfConsent -> new GeneralGetRequest(tcfConsent: tcfConsent, generalConsent: PBSUtils.randomString) }, + { String tcfConsent -> new GeneralGetRequest(tcfConsent: tcfConsent, generalConsentString: PBSUtils.randomString) }, ] } @@ -100,7 +99,7 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain gpp info from param" + and: "Bidder request should contain user consent info from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.user.consent == consentValue @@ -113,77 +112,9 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { ] } - def "PBS should recognise consent from general get request as TCFv2 when consent type is tcf2"() { - given: "Default General get request" - def consentValue = new TcfConsent.Builder().build().toString() - def accountId = PBSUtils.randomNumber.toString() - def generalGetRequest = (consentGeneralRequest(consentValue) as GeneralGetRequest).tap { - it.storedRequestId = PBSUtils.randomNumber - it.accountId = accountId - it.debug = DebugCondition.ENABLED - it.consentType = TCF_2 - it.gdpr = 1 - } - - and: "Default stored request" - def request = BidRequest.getDefaultBidRequest().tap { - setAccountId(accountId) - } - - and: "Save storedRequest into DB" - def storedRequest = StoredRequest.getStoredRequest(generalGetRequest.resolveStoredRequestId(), request) - storedRequestDao.save(storedRequest) - - and: "Save account config with requireConsent into DB" - def purposes = [(P2): new PurposeConfig(enforcePurpose: BASIC, enforceVendors: true)] - def accountGdprConfig = new AccountGdprConfig(purposes: purposes) - def account = getAccountWithGdpr(request.accountId, accountGdprConfig) - accountDao.save(account) - - when: "PBS processes general get request" - def response = privacyPbsService.sendGeneralGetRequest(generalGetRequest) - - then: "Response should not contain errors and warnings" - assert !response.ext?.errors - assert !response.ext?.warnings - - and: "Generic bidderRequest should contain tcfv2 info" - def bidderRequest = response.ext.debug.resolvedRequest - verifyAll (bidderRequest) { - user.consent == consentValue - regs.gdpr == 1 - } - - and: "Shouldn't contain other privacy info" - verifyAll (bidderRequest.regs) { - !it.coppa - !it.gpc - !it.usPrivacy - !it.gpp - - it.ext == new RegsExt() - } - - and: "PBS should cansel request" - assert !bidder.getBidderRequests(request.id) - - then: "Metrics processed across activities should be updated" - def metrics = privacyPbsService.sendCollectedMetricsRequest() - assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(request, FETCH_BIDS)] == 1 - assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(request, FETCH_BIDS)] == 1 - - where: - consentGeneralRequest << - [ - { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent) }, - { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent, generalConsent: PBSUtils.randomString) }, - { String gppConsent -> new GeneralGetRequest(tcfConsent: gppConsent, generalConsentString: PBSUtils.randomString) } - ] - } - def "PBS should recognise consent from general get request as us_privacy when consent type is us_privacy"() { given: "Default General get request" - def generalGetRequest = (consentGeneralRequest(DEFAULT_COPPA) as GeneralGetRequest).tap { + def generalGetRequest = (consentGeneralRequest(new CcpaConsent().getConsentString()) as GeneralGetRequest).tap { it.storedRequestId = PBSUtils.randomNumber it.consentType = US_PRIVACY } @@ -202,10 +133,10 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Generic bidderRequest should contain tcfv2 info" + and: "Generic bidderRequest should contain us privacy info" def bidderRequest = bidder.getBidderRequest(request.id) verifyAll (bidderRequest) { - regs.usPrivacy == DEFAULT_COPPA + regs.usPrivacy == new CcpaConsent().getConsentString() regs.ext == new RegsExt() } @@ -248,20 +179,21 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Generic bidderRequest should contain tcfv2 info" + and: "Generic bidderRequest should contain gpp info" def bidderRequest = bidder.getBidderRequest(request.id) - verifyAll (bidderRequest) { - regs.gpp == consentValue - } + assert bidderRequest.regs.gpp == consentValue + and: "Shouldn't contain other privacy info" verifyAll (bidderRequest.regs) { !it.coppa !it.gpc !it.gppSid - it.ext == new RegsExt() } + and: "Shouldn't contain ext privacy info" + assert bidderRequest.regs.ext == new RegsExt() + where: consentGeneralRequest << [ @@ -338,7 +270,7 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain gpp info from param" + and: "Bidder request should contain user consent info from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.regs.gdpr == gdprValue @@ -352,7 +284,7 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def "PBS should apply usp from general get request when it's specified"() { given: "Default General get request" - def usp = DEFAULT_COPPA + def usp = new CcpaConsent().getConsentString() def generalGetRequest = GeneralGetRequest.default.tap { it.usPrivacy = usp } @@ -423,12 +355,12 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { assert !response.ext?.errors assert !response.ext?.warnings - and: "Bidder request should contain addtlConsent from param" + and: "Bidder request should contain regs.gpp from param" def bidderRequest = bidder.getBidderRequest(request.id) assert bidderRequest.regs.gpp == gppConsent } - def "PBS should apply gpps from general get request when it's specified"() { + def "PBS should apply gpp section ids from general get request when it's specified"() { given: "Default General get request" def gppSids = [PBSUtils.getRandomEnum(GppSectionId.class, [TCF_EU_V2, HEADER_V1])].intValue def generalGetRequest = GeneralGetRequest.default.tap { @@ -519,7 +451,7 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { def regsForRequest = new Regs().tap { it.gdpr = 0 // for preventing bidder block it.gpp = SIMPLE_GPC_DISALLOW_LOGIC - it.usPrivacy = DEFAULT_COPPA + it.usPrivacy = new CcpaConsent().getConsentString() it.gppSid = [PBSUtils.randomNumber] it.ext = new RegsExt(gpc: PBSUtils.randomNumber) it.coppa = 0 @@ -585,8 +517,8 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { and: "Generic bidderRequest should contain tcfv2 info" def bidderRequest = response.ext.debug.resolvedRequest verifyAll (bidderRequest) { - user.consent == consentValue - regs.gdpr == 1 + it.user.consent == consentValue + it.regs.gdpr == 1 } and: "Shouldn't contain other privacy info" @@ -595,13 +527,15 @@ class GeneralGetInterfacePrivacySpec extends PrivacyBaseSpec { !it.gpc !it.usPrivacy !it.gpp - it.ext == new RegsExt() } - and: "PBS should cansel request" + and: "Shouldn't contain ext privacy info" + assert bidderRequest.regs.ext == new RegsExt() + + and: "PBS should cancel request" assert !bidder.getBidderRequests(request.id) - then: "Metrics processed across activities should be updated" + and: "General Get Request with TCF_2 type correctly updates privacy enforcement metrics" def metrics = privacyPbsService.sendCollectedMetricsRequest() assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(request, FETCH_BIDS)] == 1 assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(request, FETCH_BIDS)] == 1