From 9f0bb65dc8ae415b6f9f445b118a3639771ebf5c Mon Sep 17 00:00:00 2001 From: markiian Date: Thu, 16 Oct 2025 16:24:33 +0300 Subject: [PATCH 1/3] Add test for video cache TTL enforcement for /vtrack endpoint --- .../model/config/AccountConfig.groovy | 1 + .../model/config/AccountVtrackConfig.groovy | 9 ++ .../service/PrebidServerService.groovy | 2 +- .../server/functional/tests/CacheSpec.groovy | 87 +++++++++++++++++++ .../server/functional/util/PBSUtils.groovy | 7 ++ 5 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 src/test/groovy/org/prebid/server/functional/model/config/AccountVtrackConfig.groovy diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy index 9dded674fee..83912535a57 100644 --- a/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountConfig.groovy @@ -26,6 +26,7 @@ class AccountConfig { AlternateBidderCodes alternateBidderCodes @JsonProperty("alternate_bidder_codes") AlternateBidderCodes alternateBidderCodesSnakeCase + AccountVtrackConfig vtrack static getDefaultAccountConfig() { new AccountConfig(status: AccountStatus.ACTIVE) diff --git a/src/test/groovy/org/prebid/server/functional/model/config/AccountVtrackConfig.groovy b/src/test/groovy/org/prebid/server/functional/model/config/AccountVtrackConfig.groovy new file mode 100644 index 00000000000..77397e20b4c --- /dev/null +++ b/src/test/groovy/org/prebid/server/functional/model/config/AccountVtrackConfig.groovy @@ -0,0 +1,9 @@ +package org.prebid.server.functional.model.config + +import groovy.transform.ToString + +@ToString(includeNames = true, ignoreNulls = true) +class AccountVtrackConfig { + + Integer ttl +} 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..499f54d0272 100644 --- a/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy +++ b/src/test/groovy/org/prebid/server/functional/service/PrebidServerService.groovy @@ -214,7 +214,7 @@ class PrebidServerService implements ObjectMapperWrapper { } PrebidCacheResponse sendVtrackRequest(VtrackRequest request, String account) { - def response = given(requestSpecification).queryParam("a", account) + def response = given(requestSpecification).queryParams(["a": account]) .body(request) .post(VTRACK_ENDPOINT) 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 b745ae53a1f..476926457c5 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy @@ -4,6 +4,7 @@ import org.prebid.server.functional.model.config.AccountAuctionConfig import org.prebid.server.functional.model.config.AccountCacheConfig import org.prebid.server.functional.model.config.AccountConfig import org.prebid.server.functional.model.config.AccountEventsConfig +import org.prebid.server.functional.model.config.AccountVtrackConfig import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.request.auction.Asset import org.prebid.server.functional.model.request.auction.BidRequest @@ -13,8 +14,10 @@ import org.prebid.server.functional.model.request.vtrack.VtrackRequest import org.prebid.server.functional.model.request.vtrack.xml.Vast import org.prebid.server.functional.model.response.auction.Adm 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.response.auction.ErrorType.CACHE import static org.prebid.server.functional.model.AccountStatus.ACTIVE import static org.prebid.server.functional.model.bidder.BidderName.GENERIC @@ -785,4 +788,88 @@ class CacheSpec extends BaseSpec { where: enabledCacheConcfig << [null, false, true] } + + def "PBS should failed VTrack request when sending request without account"() { + given: "Default VtrackRequest" + def creative = encodeXml(Vast.getDefaultVastModel(PBSUtils.randomString)) + def request = VtrackRequest.getDefaultVtrackRequest(creative) + + and: "Flush metrics" + flushMetrics(defaultPbsService) + + when: "PBS processes vtrack request" + defaultPbsService.sendVtrackRequest(request, null) + + then: "Request should fail with an error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == BAD_REQUEST.code() + assert exception.responseBody == "Account 'a' is required query parameter and can't be empty" + } + + def "PBS should use lowest tllSecond when account vtrack ttl is #accountTtl and request ttl second is #requestedTtl"() { + given: "Default VtrackRequest" + def creative = encodeXml(Vast.getDefaultVastModel(PBSUtils.randomString)) + def request = VtrackRequest.getDefaultVtrackRequest(creative).tap { + puts[0].ttlseconds = requestedTtl + } + + and: "Create and save vtrack in account" + def accountId = PBSUtils.randomNumber.toString() + def account = new Account().tap { + it.uuid = accountId + it.config = new AccountConfig().tap { + it.vtrack = new AccountVtrackConfig(ttl: accountTtl) + } + } + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(defaultPbsService) + + when: "PBS processes vtrack request" + defaultPbsService.sendVtrackRequest(request, accountId) + + then: "Pbs should emit creative_ttl.xml with lowest value" + def metrics = defaultPbsService.sendCollectedMetricsRequest() + def minTllSecond = PBSUtils.getMinValue(requestedTtl, accountTtl) + assert metrics[XML_CREATIVE_TTL_ACCOUNT_METRIC.formatted(accountId)] == minTllSecond + + where: + requestedTtl | accountTtl + null | null + null | PBSUtils.getRandomNumber(300, 1500) as Integer + PBSUtils.getRandomNumber(300, 1500) as Integer | null + PBSUtils.getRandomNumber(300, 1500) as Integer | PBSUtils.getRandomNumber(300, 1500) as Integer + PBSUtils.getRandomNumber(300, 1500) as Integer | PBSUtils.getRandomNegativeNumber(-300, -1500) as Integer + PBSUtils.getRandomNegativeNumber(-300, -1500) as Integer | PBSUtils.getRandomNumber(300, 1500) as Integer + PBSUtils.getRandomNegativeNumber(-300, -1500) as Integer | PBSUtils.getRandomNegativeNumber(-300, -1500) as Integer + } + + def "PBS should proceed request when account ttl and request ttl second are empty"() { + given: "Default VtrackRequest" + def creative = encodeXml(Vast.getDefaultVastModel(PBSUtils.randomString)) + def request = VtrackRequest.getDefaultVtrackRequest(creative).tap { + puts[0].ttlseconds = null + } + + and: "Create and save vtrack in account" + def accountId = PBSUtils.randomNumber.toString() + def account = new Account().tap { + it.uuid = accountId + it.config = new AccountConfig().tap { + it.vtrack = new AccountVtrackConfig(ttl: null) + } + } + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(defaultPbsService) + + when: "PBS processes vtrack request" + defaultPbsService.sendVtrackRequest(request, accountId) + + then: "Pbs shouldn't emit creative_ttl.xml" + def metrics = defaultPbsService.sendCollectedMetricsRequest() + assert !metrics[XML_CREATIVE_TTL_ACCOUNT_METRIC.formatted(accountId)] + } } 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 e1e7750ea05..03d4862c365 100644 --- a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy @@ -167,4 +167,11 @@ class PBSUtils implements ObjectMapperWrapper { return false } } + + static Integer getMinValue(Integer valueOne, Integer valueTwo) { + if (valueOne != null && valueTwo != null) { + return (valueOne > valueTwo) ? valueTwo : valueOne; + } + return (valueOne != null) ? valueOne : valueTwo; + } } From 9ed295549698dd0b5b14c991d0133e5dfc67cf15 Mon Sep 17 00:00:00 2001 From: markiian Date: Mon, 20 Oct 2025 12:59:09 +0300 Subject: [PATCH 2/3] Update after review, and add a tests case for vtrack --- .../server/functional/tests/CacheSpec.groovy | 56 +++++++++++++++---- .../server/functional/util/PBSUtils.groovy | 7 --- 2 files changed, 44 insertions(+), 19 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 476926457c5..8163976d566 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy @@ -610,7 +610,7 @@ class CacheSpec extends BaseSpec { assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 1 and: "Bid response targeting should contain value" - verifyAll (bidResponse?.seatbid[0]?.bid[0]?.ext?.prebid?.targeting as Map) { + 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 @@ -646,7 +646,7 @@ class CacheSpec extends BaseSpec { assert prebidCache.getRequestCount(bidRequest.imp[0].id) == 1 and: "Bid response targeting should contain value" - verifyAll (bidResponse.seatbid[0].bid[0].ext.prebid.targeting) { + 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 @@ -806,6 +806,41 @@ class CacheSpec extends BaseSpec { assert exception.responseBody == "Account 'a' is required query parameter and can't be empty" } + def "PBS shouldn't negative value in tllSecond when account vtrack ttl is #accountTtl and request ttl second is #requestedTtl"() { + given: "Default VtrackRequest" + def creative = encodeXml(Vast.getDefaultVastModel(PBSUtils.randomString)) + def request = VtrackRequest.getDefaultVtrackRequest(creative).tap { + puts[0].ttlseconds = requestedTtl + } + + and: "Create and save vtrack in account" + def accountId = PBSUtils.randomNumber.toString() + def account = new Account().tap { + it.uuid = accountId + it.config = new AccountConfig().tap { + it.vtrack = new AccountVtrackConfig(ttl: accountTtl) + } + } + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(defaultPbsService) + + when: "PBS processes vtrack request" + defaultPbsService.sendVtrackRequest(request, accountId) + + then: "Pbs should emit creative_ttl.xml with lowest value" + def metrics = defaultPbsService.sendCollectedMetricsRequest() + assert metrics[XML_CREATIVE_TTL_ACCOUNT_METRIC.formatted(accountId)] + == [requestedTtl, accountTtl].findAll({ it -> it > 0 }).min() + + where: + requestedTtl | accountTtl + PBSUtils.getRandomNumber(300, 1500) as Integer | PBSUtils.getRandomNegativeNumber(-1500, 300) as Integer + PBSUtils.getRandomNegativeNumber(-1500, 300) as Integer | PBSUtils.getRandomNumber(300, 1500) as Integer + PBSUtils.getRandomNegativeNumber(-1500, 300) as Integer | PBSUtils.getRandomNegativeNumber(-1500, 300) as Integer + } + def "PBS should use lowest tllSecond when account vtrack ttl is #accountTtl and request ttl second is #requestedTtl"() { given: "Default VtrackRequest" def creative = encodeXml(Vast.getDefaultVastModel(PBSUtils.randomString)) @@ -831,18 +866,15 @@ class CacheSpec extends BaseSpec { then: "Pbs should emit creative_ttl.xml with lowest value" def metrics = defaultPbsService.sendCollectedMetricsRequest() - def minTllSecond = PBSUtils.getMinValue(requestedTtl, accountTtl) - assert metrics[XML_CREATIVE_TTL_ACCOUNT_METRIC.formatted(accountId)] == minTllSecond + assert metrics[XML_CREATIVE_TTL_ACCOUNT_METRIC.formatted(accountId)] + == [requestedTtl, accountTtl].findAll().min() where: - requestedTtl | accountTtl - null | null - null | PBSUtils.getRandomNumber(300, 1500) as Integer - PBSUtils.getRandomNumber(300, 1500) as Integer | null - PBSUtils.getRandomNumber(300, 1500) as Integer | PBSUtils.getRandomNumber(300, 1500) as Integer - PBSUtils.getRandomNumber(300, 1500) as Integer | PBSUtils.getRandomNegativeNumber(-300, -1500) as Integer - PBSUtils.getRandomNegativeNumber(-300, -1500) as Integer | PBSUtils.getRandomNumber(300, 1500) as Integer - PBSUtils.getRandomNegativeNumber(-300, -1500) as Integer | PBSUtils.getRandomNegativeNumber(-300, -1500) as Integer + requestedTtl | accountTtl + null | null + null | PBSUtils.getRandomNumber(300, 1500) as Integer + PBSUtils.getRandomNumber(300, 1500) as Integer | null + PBSUtils.getRandomNumber(300, 1500) as Integer | PBSUtils.getRandomNumber(300, 1500) as Integer } def "PBS should proceed request when account ttl and request ttl second are empty"() { 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 03d4862c365..e1e7750ea05 100644 --- a/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy +++ b/src/test/groovy/org/prebid/server/functional/util/PBSUtils.groovy @@ -167,11 +167,4 @@ class PBSUtils implements ObjectMapperWrapper { return false } } - - static Integer getMinValue(Integer valueOne, Integer valueTwo) { - if (valueOne != null && valueTwo != null) { - return (valueOne > valueTwo) ? valueTwo : valueOne; - } - return (valueOne != null) ? valueOne : valueTwo; - } } From 8caf9df16b237d1bb5311ea56ade8cb1d15c0767 Mon Sep 17 00:00:00 2001 From: markiian Date: Mon, 20 Oct 2025 16:34:04 +0300 Subject: [PATCH 3/3] Update after review, and add a tests case for vtrack --- .../org/prebid/server/functional/tests/CacheSpec.groovy | 7 +++---- 1 file changed, 3 insertions(+), 4 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 8163976d566..7a36ea011c2 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/CacheSpec.groovy @@ -806,7 +806,7 @@ class CacheSpec extends BaseSpec { assert exception.responseBody == "Account 'a' is required query parameter and can't be empty" } - def "PBS shouldn't negative value in tllSecond when account vtrack ttl is #accountTtl and request ttl second is #requestedTtl"() { + def "PBS shouldn't use negative value in tllSecond when account vtrack ttl is #accountTtl and request ttl second is #requestedTtl"() { given: "Default VtrackRequest" def creative = encodeXml(Vast.getDefaultVastModel(PBSUtils.randomString)) def request = VtrackRequest.getDefaultVtrackRequest(creative).tap { @@ -832,7 +832,7 @@ class CacheSpec extends BaseSpec { then: "Pbs should emit creative_ttl.xml with lowest value" def metrics = defaultPbsService.sendCollectedMetricsRequest() assert metrics[XML_CREATIVE_TTL_ACCOUNT_METRIC.formatted(accountId)] - == [requestedTtl, accountTtl].findAll({ it -> it > 0 }).min() + == [requestedTtl, accountTtl].findAll { it -> it > 0 }.min() where: requestedTtl | accountTtl @@ -866,8 +866,7 @@ class CacheSpec extends BaseSpec { then: "Pbs should emit creative_ttl.xml with lowest value" def metrics = defaultPbsService.sendCollectedMetricsRequest() - assert metrics[XML_CREATIVE_TTL_ACCOUNT_METRIC.formatted(accountId)] - == [requestedTtl, accountTtl].findAll().min() + assert metrics[XML_CREATIVE_TTL_ACCOUNT_METRIC.formatted(accountId)] == [requestedTtl, accountTtl].min() where: requestedTtl | accountTtl