diff --git a/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy index f9b9688d4da..e5e9649d82b 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/AuctionSpec.groovy @@ -42,14 +42,17 @@ import static org.prebid.server.functional.util.SystemProperties.PBS_VERSION class AuctionSpec extends BaseSpec { private static final String USER_SYNC_URL = "$networkServiceContainer.rootUri/generic-usersync" - private static final boolean CORS_SUPPORT = false + private static final Boolean CORS_SUPPORT = false private static final UserSyncInfo.Type USER_SYNC_TYPE = REDIRECT - private static final int DEFAULT_TIMEOUT = getRandomTimeout() + private static final Integer DEFAULT_TIMEOUT = getRandomTimeout() + private static final Integer MIN_BID_ID_LENGTH = 17 + private static final Integer DEFAULT_UUID_LENGTH = 36 private static final Map PBS_CONFIG = ["auction.biddertmax.max" : MAX_TIMEOUT as String, "auction.default-timeout-ms": DEFAULT_TIMEOUT as String] private static final Map GENERIC_CONFIG = [ "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()] + @Shared PrebidServerService prebidServerService = pbsServiceFactory.getService(PBS_CONFIG) @@ -591,4 +594,131 @@ class AuctionSpec extends BaseSpec { def bidderRequest = bidder.getBidderRequest(bidRequest.id) assert !bidderRequest?.device?.ext?.cdep } + + def "PBS should override short bid.id with random uuid when enforce-random-bid-id is enabled"() { + given: "PBS with enabled generate-bid-id" + def pbsConfig = ['auction.enforce-random-bid-id': 'true'] + def pbsService = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + enableEvents() + } + + and: "Default bid response" + def originalBidId = PBSUtils.getRandomString(PBSUtils.getRandomNumber(1, MIN_BID_ID_LENGTH - 1)) + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid.first.bid.first.id = originalBidId + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Save account in DB" + def account = new Account(uuid: bidRequest.accountId, eventsEnabled: true) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Should include imp from original request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.id.sort() == bidRequest.imp.id.sort() + + and: "Bid response should contain changed bid.id for wins event" + def bidIds = response.seatbid.bid.id.flatten() + def bidResponseEvents = response.seatbid.first.bid.first.ext.prebid.events + assert bidResponseEvents.win.contains("win&b=${bidIds.first}") + assert bidResponseEvents.imp.contains("imp&b=${bidIds.first}") + + and: "BidResponse should contain different bid.id" + assert bidIds.sort() != [originalBidId] + + and: "BidResponse should contain generated UUID" + assert PBSUtils.isUUID(response.seatbid.first.bid.first.id) + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS shouldn't override short bid.id when enforce-random-bid-id in default or disabled"() { + given: "PBS with disabled generate-bid-id" + def pbsConfig = ["auction.enforce-random-bid-id": enforceRandomBidId] + def pbsService = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + enableEvents() + } + + and: "Default bid response" + def originalBidId = PBSUtils.getRandomString(PBSUtils.getRandomNumber(0, MIN_BID_ID_LENGTH)) + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid.first.bid.first.id = originalBidId + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Save account in DB" + def account = new Account(uuid: bidRequest.accountId, eventsEnabled: true) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Should include imp from original request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.id.sort() == bidRequest.imp.id.sort() + + and: "Bid response should contain changed bid.id for wins event" + def bidResponseEvents = response.seatbid.first.bid.first.ext.prebid.events + assert bidResponseEvents.win.contains("win&b=${originalBidId}") + assert bidResponseEvents.imp.contains("imp&b=${originalBidId}") + + and: "BidResponse should contain original bid.id" + assert response.seatbid.bid.id.flatten().sort() == [originalBidId] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + + where: + enforceRandomBidId << [null, 'false'] + } + + def "PBS shouldn't override long enough bid.id with random uuid when enforce-random-bid-id is enabled"() { + given: "PBS with enabled generate-bid-id" + def pbsConfig = ['auction.enforce-random-bid-id': 'true'] + def pbsService = pbsServiceFactory.getService(pbsConfig) + + and: "Default bid request" + def bidRequest = BidRequest.defaultBidRequest.tap { + enableEvents() + } + + and: "Default bid response" + def originalBidId = PBSUtils.getRandomString(PBSUtils.getRandomNumber(MIN_BID_ID_LENGTH, DEFAULT_UUID_LENGTH)) + def bidResponse = BidResponse.getDefaultBidResponse(bidRequest).tap { + seatbid.first.bid.first.id = originalBidId + } + bidder.setResponse(bidRequest.id, bidResponse) + + and: "Save account in DB" + def account = new Account(uuid: bidRequest.accountId, eventsEnabled: true) + accountDao.save(account) + + when: "PBS processes auction request" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Should include imp from original request" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + assert bidderRequest.imp.id.sort() == bidRequest.imp.id.sort() + + and: "Bid response should contain changed bid.id for wins event" + def bidResponseEvents = response.seatbid.first.bid.first.ext.prebid.events + assert bidResponseEvents.win.contains("win&b=${originalBidId}") + assert bidResponseEvents.imp.contains("imp&b=${originalBidId}") + + and: "BidResponse should contain original bid.id" + assert response.seatbid.bid.id.flatten().sort() == [originalBidId] + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } } 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 c82e96268c6..e1e7750ea05 100644 --- a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy @@ -155,4 +155,16 @@ class PBSUtils implements ObjectMapperWrapper { def version = versionParts.join('.') return (version >= minVersion && version <= maxVersion) ? version : getRandomVersion(minVersion, maxVersion) } + + static Boolean isUUID(String str) { + if (str == null) { + return false + } + try { + UUID.fromString(str) + return true + } catch (IllegalArgumentException e) { + return false + } + } }