From 4b71b130f8185d7b6ffbf1c0a4d56199c4612d5d Mon Sep 17 00:00:00 2001 From: markiian Date: Tue, 3 Jun 2025 16:46:41 +0300 Subject: [PATCH 1/2] Add test for "Cache endpoint split for response" --- .../server/functional/tests/CacheSpec.groovy | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy index e145e5a972f..e4a62c2cefe 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy @@ -14,8 +14,10 @@ import org.prebid.server.functional.model.response.auction.Adm import org.prebid.server.functional.model.response.auction.BidResponse import org.prebid.server.functional.util.PBSUtils +import static org.prebid.server.functional.model.response.auction.ErrorType.CACHE import static org.prebid.server.functional.model.response.auction.MediaType.BANNER import static org.prebid.server.functional.model.response.auction.MediaType.VIDEO +import static org.prebid.server.functional.testcontainers.Dependencies.getNetworkServiceContainer class CacheSpec extends BaseSpec { @@ -33,6 +35,11 @@ class CacheSpec extends BaseSpec { private static final String JSON_CREATIVE_SIZE_GLOBAL_METRIC = "prebid_cache.creative_size.json" private static final String CACHE_REQUEST_OK_GLOBAL_METRIC = "prebid_cache.requests.ok" + private static final String CACHE_PATH = "/${PBSUtils.randomString}".toString() + private static final String CACHE_HOST = "${PBSUtils.randomString}:${PBSUtils.getRandomNumber(0, 65535)}".toString() + private static final String INTERNAL_CACHE_PATH = "/cache".toString() + private static final String INTERNAL_CACHE_SCHEME = "http".toString() + def "PBS should update prebid_cache.creative_size.xml metric when xml creative is received"() { given: "Current value of metric prebid_cache.requests.ok" def initialValue = getCurrentMetricValue(defaultPbsService, CACHE_REQUEST_OK_GLOBAL_METRIC) @@ -492,4 +499,143 @@ class CacheSpec extends BaseSpec { PBSUtils.getRandomCase(" inline ") | " ${PBSUtils.getRandomCase(" impression ")} $PBSUtils.randomNumber " " inline ${PBSUtils.getRandomString()} " | " ImpreSSion " } + + def "PBS shouldn't cache bids when targeting is specified and config cache is invalid"() { + given: "Pbs config with cache" + def INVALID_PREBID_CACHE_CONFIG = ["cache.path" : CACHE_PATH, + "cache.scheme": "http", + "cache.host" : CACHE_HOST] + def pbsService = pbsServiceFactory.getService(INVALID_PREBID_CACHE_CONFIG) + + and: "Default BidRequest with cache, targeting" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.enableCache() + it.ext.prebid.targeting = new Targeting() + } + + when: "PBS processes auction request" + def bidResponse = pbsService.sendAuctionRequest(bidRequest) + + then: "PBS shouldn't call PBC" + assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 0 + + and: "Bid response targeting should contain value" + def cacheTargeting = bidResponse?.seatbid[0]?.bid[0]?.ext?.prebid?.targeting?.findAll { it -> it.key.startsWith("hb_cache") } + assert cacheTargeting.isEmpty() + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(INVALID_PREBID_CACHE_CONFIG) + } + + def "PBS should cache bids and emit error when targeting is specified and config cache is valid and internal is invalid"() { + given: "Pbs config with cache" + def INVALID_PREBID_CACHE_CONFIG = ["cache.internal.path" : CACHE_PATH, + "cache.internal.scheme": "http", + "cache.internal.host" : CACHE_HOST] + def pbsService = pbsServiceFactory.getService(INVALID_PREBID_CACHE_CONFIG) + + and: "Default BidRequest with cache, targeting" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.enableCache() + it.ext.prebid.targeting = new Targeting() + } + + when: "PBS processes auction request" + def bidResponse = pbsService.sendAuctionRequest(bidRequest) + + then: "PBS should call PBC" + assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 0 + + and: "Seat bid shouldn't be discarded" + assert !bidResponse.seatbid.isEmpty() + + and: "Bid response targeting should contain value" + def cacheTargeting = bidResponse.seatbid[0].bid[0].ext.prebid.targeting.findAll { it -> it.key.startsWith("hb_cache") } + assert cacheTargeting.isEmpty() + + and: "Debug should contain http call with empty response body" + def cacheCall = bidResponse.ext.debug.httpcalls['cache'][0] + assert cacheCall.responseBody == null + assert cacheCall.uri == "${INTERNAL_CACHE_SCHEME}://${networkServiceContainer.hostAndPort + INTERNAL_CACHE_PATH}" + + then: "Response should contain error" + assert bidResponse.ext?.errors[CACHE]*.code == [999] + assert bidResponse.ext?.errors[CACHE]*.message[0].startsWith("Failed to resolve ") + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(INVALID_PREBID_CACHE_CONFIG) + } + + def "PBS should cache bids when targeting is specified and config cache is invalid and internal cache config valid"() { + given: "Pbs config with cache" + def INVALID_PREBID_CACHE_CONFIG = ["cache.path" : CACHE_PATH, + "cache.scheme": "http", + "cache.host" : CACHE_HOST,] + def VALID_INTERNAL_CACHE_CONFIG = ["cache.internal.scheme": INTERNAL_CACHE_SCHEME, + "cache.internal.host" : "$networkServiceContainer.hostAndPort".toString(), + "cache.internal.path" : INTERNAL_CACHE_PATH,] + def pbsService = pbsServiceFactory.getService(INVALID_PREBID_CACHE_CONFIG + VALID_INTERNAL_CACHE_CONFIG) + + and: "Default BidRequest with cache, targeting" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.enableCache() + it.ext.prebid.targeting = new Targeting() + } + + when: "PBS processes auction request" + def bidResponse = pbsService.sendAuctionRequest(bidRequest) + + then: "PBS shouldn't call PBC" + assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 1 + + and: "Bid response targeting should contain value" + def cacheTargeting = bidResponse.seatbid[0].bid[0].ext.prebid.targeting.findAll { it -> it.key.startsWith("hb_cache") } + assert cacheTargeting.size() == 6 + assert cacheTargeting["hb_cache_path"] == CACHE_PATH + assert cacheTargeting["hb_cache_host"] == CACHE_HOST + assert cacheTargeting["hb_cache_path_generi"] == CACHE_PATH + assert cacheTargeting["hb_cache_host_generi"] == CACHE_HOST + + and: "Debug should contain http call" + assert bidResponse.ext.debug.httpcalls['cache'][0].uri == + "${INTERNAL_CACHE_SCHEME}://${CACHE_HOST + CACHE_PATH}" + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(INVALID_PREBID_CACHE_CONFIG + VALID_INTERNAL_CACHE_CONFIG) + } + + def "PBS should cache bids when targeting is specified and config cache and internal cache config valid"() { + given: "Pbs config with cache" + def VALID_INTERNAL_CACHE_CONFIG = ["cache.internal.scheme": INTERNAL_CACHE_SCHEME, + "cache.internal.host" : "$networkServiceContainer.hostAndPort".toString(), + "cache.internal.path" : INTERNAL_CACHE_PATH] + def pbsService = pbsServiceFactory.getService(VALID_INTERNAL_CACHE_CONFIG) + + and: "Default BidRequest with cache, targeting" + def bidRequest = BidRequest.defaultBidRequest.tap { + it.enableCache() + it.ext.prebid.targeting = new Targeting() + } + + when: "PBS processes auction request" + def bidResponse = pbsService.sendAuctionRequest(bidRequest) + + then: "PBS should call PBC" + assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 1 + + and: "Bid response targeting should contain value" + def cacheTargeting = bidResponse.seatbid[0].bid[0].ext.prebid.targeting.findAll { it -> it.key.startsWith("hb_cache") } + assert cacheTargeting.size() == 6 + assert cacheTargeting["hb_cache_path"] == INTERNAL_CACHE_PATH + assert cacheTargeting["hb_cache_host"] == networkServiceContainer.hostAndPort.toString() + assert cacheTargeting["hb_cache_path_generi"] == INTERNAL_CACHE_PATH + assert cacheTargeting["hb_cache_host_generi"] == networkServiceContainer.hostAndPort.toString() + + and: "Debug should contain http call" + assert bidResponse.ext.debug.httpcalls['cache'][0].uri == + "${INTERNAL_CACHE_SCHEME}://${networkServiceContainer.hostAndPort + INTERNAL_CACHE_PATH}" + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(VALID_INTERNAL_CACHE_CONFIG) + } } From bd7fd16e8e99bbd32b809000b4749a19c91d4780 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Mon, 9 Jun 2025 18:39:45 +0300 Subject: [PATCH 2/2] update after review --- .../server/functional/tests/CacheSpec.groovy | 70 ++++++++++--------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy index e4a62c2cefe..2352e333c16 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy @@ -24,6 +24,7 @@ class CacheSpec extends BaseSpec { private static final String PBS_API_HEADER = 'x-pbc-api-key' private static final Integer MAX_DATACENTER_REGION_LENGTH = 4 private static final Integer DEFAULT_UUID_LENGTH = 36 + private static final Integer TARGETING_PARAM_NAME_MAX_LENGTH = 20 private static final String XML_CREATIVE_SIZE_ACCOUNT_METRIC = "account.%s.prebid_cache.creative_size.xml" private static final String JSON_CREATIVE_SIZE_ACCOUNT_METRIC = "account.%s.prebid_cache.creative_size.json" @@ -37,8 +38,9 @@ class CacheSpec extends BaseSpec { private static final String CACHE_PATH = "/${PBSUtils.randomString}".toString() private static final String CACHE_HOST = "${PBSUtils.randomString}:${PBSUtils.getRandomNumber(0, 65535)}".toString() - private static final String INTERNAL_CACHE_PATH = "/cache".toString() - private static final String INTERNAL_CACHE_SCHEME = "http".toString() + private static final String INTERNAL_CACHE_PATH = '/cache' + private static final String HTTP_SCHEME = 'http' + private static final String HTTPS_SCHEME = 'https' def "PBS should update prebid_cache.creative_size.xml metric when xml creative is received"() { given: "Current value of metric prebid_cache.requests.ok" @@ -503,25 +505,27 @@ class CacheSpec extends BaseSpec { def "PBS shouldn't cache bids when targeting is specified and config cache is invalid"() { given: "Pbs config with cache" def INVALID_PREBID_CACHE_CONFIG = ["cache.path" : CACHE_PATH, - "cache.scheme": "http", + "cache.scheme": HTTP_SCHEME, "cache.host" : CACHE_HOST] def pbsService = pbsServiceFactory.getService(INVALID_PREBID_CACHE_CONFIG) and: "Default BidRequest with cache, targeting" def bidRequest = BidRequest.defaultBidRequest.tap { it.enableCache() - it.ext.prebid.targeting = new Targeting() } when: "PBS processes auction request" def bidResponse = pbsService.sendAuctionRequest(bidRequest) - then: "PBS shouldn't call PBC" - assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 0 + then: "Response should contain error" + assert bidResponse.ext?.errors[CACHE]*.code == [999] + assert bidResponse.ext?.errors[CACHE]*.message[0] == ("Failed to resolve '${CACHE_HOST.tokenize(":")[0]}' [A(1)]") and: "Bid response targeting should contain value" - def cacheTargeting = bidResponse?.seatbid[0]?.bid[0]?.ext?.prebid?.targeting?.findAll { it -> it.key.startsWith("hb_cache") } - assert cacheTargeting.isEmpty() + assert bidResponse.seatbid[0].bid[0].ext.prebid.targeting.findAll { it.key.startsWith("hb_cache") }.isEmpty() + + and: "PBS shouldn't call PBC" + assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 0 cleanup: "Stop and remove pbs container" pbsServiceFactory.removeContainer(INVALID_PREBID_CACHE_CONFIG) @@ -530,14 +534,13 @@ class CacheSpec extends BaseSpec { def "PBS should cache bids and emit error when targeting is specified and config cache is valid and internal is invalid"() { given: "Pbs config with cache" def INVALID_PREBID_CACHE_CONFIG = ["cache.internal.path" : CACHE_PATH, - "cache.internal.scheme": "http", + "cache.internal.scheme": HTTP_SCHEME, "cache.internal.host" : CACHE_HOST] def pbsService = pbsServiceFactory.getService(INVALID_PREBID_CACHE_CONFIG) and: "Default BidRequest with cache, targeting" def bidRequest = BidRequest.defaultBidRequest.tap { it.enableCache() - it.ext.prebid.targeting = new Targeting() } when: "PBS processes auction request" @@ -550,17 +553,16 @@ class CacheSpec extends BaseSpec { assert !bidResponse.seatbid.isEmpty() and: "Bid response targeting should contain value" - def cacheTargeting = bidResponse.seatbid[0].bid[0].ext.prebid.targeting.findAll { it -> it.key.startsWith("hb_cache") } - assert cacheTargeting.isEmpty() + assert bidResponse.seatbid[0].bid[0].ext.prebid.targeting.findAll { it.key.startsWith("hb_cache") }.isEmpty() and: "Debug should contain http call with empty response body" def cacheCall = bidResponse.ext.debug.httpcalls['cache'][0] assert cacheCall.responseBody == null - assert cacheCall.uri == "${INTERNAL_CACHE_SCHEME}://${networkServiceContainer.hostAndPort + INTERNAL_CACHE_PATH}" + assert cacheCall.uri == "${HTTP_SCHEME}://${networkServiceContainer.hostAndPort + INTERNAL_CACHE_PATH}" then: "Response should contain error" assert bidResponse.ext?.errors[CACHE]*.code == [999] - assert bidResponse.ext?.errors[CACHE]*.message[0].startsWith("Failed to resolve ") + assert bidResponse.ext?.errors[CACHE]*.message[0] == ("Failed to resolve '${CACHE_HOST.tokenize(":")[0]}' [A(1)]") cleanup: "Stop and remove pbs container" pbsServiceFactory.removeContainer(INVALID_PREBID_CACHE_CONFIG) @@ -569,9 +571,9 @@ class CacheSpec extends BaseSpec { def "PBS should cache bids when targeting is specified and config cache is invalid and internal cache config valid"() { given: "Pbs config with cache" def INVALID_PREBID_CACHE_CONFIG = ["cache.path" : CACHE_PATH, - "cache.scheme": "http", + "cache.scheme": HTTPS_SCHEME, "cache.host" : CACHE_HOST,] - def VALID_INTERNAL_CACHE_CONFIG = ["cache.internal.scheme": INTERNAL_CACHE_SCHEME, + def VALID_INTERNAL_CACHE_CONFIG = ["cache.internal.scheme": HTTP_SCHEME, "cache.internal.host" : "$networkServiceContainer.hostAndPort".toString(), "cache.internal.path" : INTERNAL_CACHE_PATH,] def pbsService = pbsServiceFactory.getService(INVALID_PREBID_CACHE_CONFIG + VALID_INTERNAL_CACHE_CONFIG) @@ -579,7 +581,6 @@ class CacheSpec extends BaseSpec { and: "Default BidRequest with cache, targeting" def bidRequest = BidRequest.defaultBidRequest.tap { it.enableCache() - it.ext.prebid.targeting = new Targeting() } when: "PBS processes auction request" @@ -589,16 +590,18 @@ class CacheSpec extends BaseSpec { assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 1 and: "Bid response targeting should contain value" - def cacheTargeting = bidResponse.seatbid[0].bid[0].ext.prebid.targeting.findAll { it -> it.key.startsWith("hb_cache") } - assert cacheTargeting.size() == 6 - assert cacheTargeting["hb_cache_path"] == CACHE_PATH - assert cacheTargeting["hb_cache_host"] == CACHE_HOST - assert cacheTargeting["hb_cache_path_generi"] == CACHE_PATH - assert cacheTargeting["hb_cache_host_generi"] == CACHE_HOST + verifyAll (bidResponse?.seatbid[0]?.bid[0]?.ext?.prebid?.targeting as Map) { + it.get("hb_cache_id") + it.get("hb_cache_id_generic") + it.get("hb_cache_path") == CACHE_PATH + it.get("hb_cache_host") == CACHE_HOST + it.get("hb_cache_path_generic".substring(0, TARGETING_PARAM_NAME_MAX_LENGTH)) == CACHE_PATH + it.get("hb_cache_host_generic".substring(0, TARGETING_PARAM_NAME_MAX_LENGTH)) == CACHE_HOST + } and: "Debug should contain http call" assert bidResponse.ext.debug.httpcalls['cache'][0].uri == - "${INTERNAL_CACHE_SCHEME}://${CACHE_HOST + CACHE_PATH}" + "${HTTPS_SCHEME}://${CACHE_HOST + CACHE_PATH}" cleanup: "Stop and remove pbs container" pbsServiceFactory.removeContainer(INVALID_PREBID_CACHE_CONFIG + VALID_INTERNAL_CACHE_CONFIG) @@ -606,7 +609,7 @@ class CacheSpec extends BaseSpec { def "PBS should cache bids when targeting is specified and config cache and internal cache config valid"() { given: "Pbs config with cache" - def VALID_INTERNAL_CACHE_CONFIG = ["cache.internal.scheme": INTERNAL_CACHE_SCHEME, + def VALID_INTERNAL_CACHE_CONFIG = ["cache.internal.scheme": HTTP_SCHEME, "cache.internal.host" : "$networkServiceContainer.hostAndPort".toString(), "cache.internal.path" : INTERNAL_CACHE_PATH] def pbsService = pbsServiceFactory.getService(VALID_INTERNAL_CACHE_CONFIG) @@ -614,7 +617,6 @@ class CacheSpec extends BaseSpec { and: "Default BidRequest with cache, targeting" def bidRequest = BidRequest.defaultBidRequest.tap { it.enableCache() - it.ext.prebid.targeting = new Targeting() } when: "PBS processes auction request" @@ -624,16 +626,18 @@ class CacheSpec extends BaseSpec { assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 1 and: "Bid response targeting should contain value" - def cacheTargeting = bidResponse.seatbid[0].bid[0].ext.prebid.targeting.findAll { it -> it.key.startsWith("hb_cache") } - assert cacheTargeting.size() == 6 - assert cacheTargeting["hb_cache_path"] == INTERNAL_CACHE_PATH - assert cacheTargeting["hb_cache_host"] == networkServiceContainer.hostAndPort.toString() - assert cacheTargeting["hb_cache_path_generi"] == INTERNAL_CACHE_PATH - assert cacheTargeting["hb_cache_host_generi"] == networkServiceContainer.hostAndPort.toString() + verifyAll (bidResponse.seatbid[0].bid[0].ext.prebid.targeting) { + it.get("hb_cache_id") + it.get("hb_cache_id_generic") + it.get("hb_cache_path") == INTERNAL_CACHE_PATH + it.get("hb_cache_host") == networkServiceContainer.hostAndPort.toString() + it.get("hb_cache_path_generic".substring(0, TARGETING_PARAM_NAME_MAX_LENGTH)) == INTERNAL_CACHE_PATH + it.get("hb_cache_host_generic".substring(0, TARGETING_PARAM_NAME_MAX_LENGTH)) == networkServiceContainer.hostAndPort.toString() + } and: "Debug should contain http call" assert bidResponse.ext.debug.httpcalls['cache'][0].uri == - "${INTERNAL_CACHE_SCHEME}://${networkServiceContainer.hostAndPort + INTERNAL_CACHE_PATH}" + "${HTTP_SCHEME}://${networkServiceContainer.hostAndPort + INTERNAL_CACHE_PATH}" cleanup: "Stop and remove pbs container" pbsServiceFactory.removeContainer(VALID_INTERNAL_CACHE_CONFIG)