diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/container/NetworkServiceContainer.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/container/NetworkServiceContainer.groovy index 53faa7165fa..8022f2e8dcc 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/container/NetworkServiceContainer.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/container/NetworkServiceContainer.groovy @@ -8,6 +8,9 @@ class NetworkServiceContainer extends MockServerContainer { NetworkServiceContainer(String version) { super(DockerImageName.parse("mockserver/mockserver:mockserver-$version")) + def aliasWithTopLevelDomain = "${getNetworkAliases().first()}.com".toString() + withCreateContainerCmdModifier { it.withHostName(aliasWithTopLevelDomain) } + setNetworkAliases([aliasWithTopLevelDomain]) } String getHostAndPort() { diff --git a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/HttpSettings.groovy b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/HttpSettings.groovy index de271b4123a..5af648b2bc0 100644 --- a/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/HttpSettings.groovy +++ b/src/test/groovy/org/prebid/server/functional/testcontainers/scaffolding/HttpSettings.groovy @@ -1,13 +1,21 @@ package org.prebid.server.functional.testcontainers.scaffolding +import org.mockserver.matchers.Times +import org.mockserver.model.Header import org.mockserver.model.HttpRequest +import org.mockserver.model.HttpStatusCode +import org.prebid.server.functional.model.ResponseModel import org.testcontainers.containers.MockServerContainer import static org.mockserver.model.HttpRequest.request +import static org.mockserver.model.HttpResponse.response +import static org.mockserver.model.HttpStatusCode.OK_200 +import static org.mockserver.model.MediaType.APPLICATION_JSON class HttpSettings extends NetworkScaffolding { private static final String ENDPOINT = "/stored-requests" + private static final String RFC_ENDPOINT = "/stored-requests-rfc" private static final String AMP_ENDPOINT = "/amp-stored-requests" HttpSettings(MockServerContainer mockServerContainer) { @@ -27,12 +35,47 @@ class HttpSettings extends NetworkScaffolding { @Override void setResponse() { + } + + protected HttpRequest getRfcRequest(String accountId) { + request().withPath(RFC_ENDPOINT) + .withQueryStringParameter("account-id", accountId) + } + + + void setRfcResponse(String value, + ResponseModel responseModel, + HttpStatusCode statusCode = OK_200, + Map headers = [:]) { + def responseHeaders = headers.collect { new Header(it.key, it.value) } + def mockResponse = encode(responseModel) + mockServerClient.when(getRfcRequest(value), Times.unlimited()) + .respond(response().withStatusCode(statusCode.code()) + .withBody(mockResponse, APPLICATION_JSON) + .withHeaders(responseHeaders)) + } + int getRfcRequestCount(String value) { + mockServerClient.retrieveRecordedRequests(getRfcRequest(value)) + .size() } @Override void reset() { super.reset(ENDPOINT) + super.reset(RFC_ENDPOINT) super.reset(AMP_ENDPOINT) } + + static String getEndpoint() { + return ENDPOINT + } + + static String getAmpEndpoint() { + return AMP_ENDPOINT + } + + static String getRfcEndpoint() { + return RFC_ENDPOINT + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/HttpSettingsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/HttpSettingsSpec.groovy index 2c6d1556a81..01f60ac2808 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/HttpSettingsSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/HttpSettingsSpec.groovy @@ -15,7 +15,6 @@ import org.prebid.server.functional.testcontainers.PbsConfig import org.prebid.server.functional.testcontainers.scaffolding.HttpSettings import org.prebid.server.functional.util.PBSUtils import org.prebid.server.util.ResourceUtil -import spock.lang.Shared import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.testcontainers.Dependencies.networkServiceContainer @@ -23,11 +22,25 @@ import static org.prebid.server.functional.testcontainers.Dependencies.networkSe class HttpSettingsSpec extends BaseSpec { // Check that PBS actually applied account config only possible by relying on side effects. - @Shared - HttpSettings httpSettings = new HttpSettings(networkServiceContainer) + static PrebidServerService prebidServerService + static PrebidServerService prebidServerServiceWithRfc - @Shared - PrebidServerService prebidServerService = pbsServiceFactory.getService(PbsConfig.httpSettingsConfig) + private static final HttpSettings httpSettings = new HttpSettings(networkServiceContainer) + private static final Map PBS_CONFIG_WITH_RFC = new HashMap<>(PbsConfig.httpSettingsConfig) + + ['settings.http.endpoint': "${networkServiceContainer.rootUri}${HttpSettings.rfcEndpoint}".toString(), + 'settings.http.rfc3986-compatible': 'true'] + + def setupSpec() { + prebidServerService = pbsServiceFactory.getService(PbsConfig.httpSettingsConfig) + prebidServerServiceWithRfc = pbsServiceFactory.getService(PBS_CONFIG_WITH_RFC) + bidder.setResponse() + vendorList.setResponse() + } + + def cleanupSpec() { + prebidServerService = pbsServiceFactory.removeContainer(PbsConfig.httpSettingsConfig) + prebidServerService = pbsServiceFactory.removeContainer(PBS_CONFIG_WITH_RFC) + } def "PBS should take account information from http data source on auction request"() { given: "Get basic BidRequest with generic bidder and set gdpr = 1" @@ -35,8 +48,8 @@ class HttpSettingsSpec extends BaseSpec { bidRequest.regs.gdpr = 1 and: "Prepare default account response with gdpr = 0" - def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(bidRequest?.site?.publisher?.id) - httpSettings.setResponse(bidRequest?.site?.publisher?.id, httpSettingsResponse) + def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(bidRequest.accountId) + httpSettings.setResponse(bidRequest.accountId, httpSettingsResponse) when: "PBS processes auction request" def response = prebidServerService.sendAuctionRequest(bidRequest) @@ -51,7 +64,32 @@ class HttpSettingsSpec extends BaseSpec { assert bidder.getRequestCount(bidRequest.id) == 1 and: "There should be only one account request" - assert httpSettings.getRequestCount(bidRequest?.site?.publisher?.id) == 1 + assert httpSettings.getRequestCount(bidRequest.accountId) == 1 + } + + def "PBS should take account information from http data source on auction request when rfc3986 enabled"() { + given: "Get basic BidRequest with generic bidder and set gdpr = 1" + def bidRequest = BidRequest.defaultBidRequest + bidRequest.regs.gdpr = 1 + + and: "Prepare default account response with gdpr = 0" + def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(bidRequest.accountId) + httpSettings.setRfcResponse(bidRequest.accountId, httpSettingsResponse) + + when: "PBS processes auction request" + def response = prebidServerServiceWithRfc.sendAuctionRequest(bidRequest) + + then: "Response should contain basic fields" + assert response.id + assert response.seatbid?.size() == 1 + assert response.seatbid.first().seat == GENERIC + assert response.seatbid?.first()?.bid?.size() == 1 + + and: "There should be only one call to bidder" + assert bidder.getRequestCount(bidRequest.id) == 1 + + and: "There should be only one account request" + assert httpSettings.getRfcRequestCount(bidRequest.accountId) == 1 } def "PBS should take account information from http data source on AMP request"() { @@ -84,6 +122,36 @@ class HttpSettingsSpec extends BaseSpec { assert !response.ext?.debug?.httpcalls?.isEmpty() } + def "PBS should take account information from http data source on AMP request when rfc3986 enabled"() { + given: "Default AmpRequest" + def ampRequest = AmpRequest.defaultAmpRequest + + and: "Get basic stored request and set gdpr = 1" + def ampStoredRequest = BidRequest.defaultBidRequest + ampStoredRequest.site.publisher.id = ampRequest.account + ampStoredRequest.regs.gdpr = 1 + + and: "Save storedRequest into DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Prepare default account response with gdpr = 0" + def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(ampRequest.account.toString()) + httpSettings.setRfcResponse(ampRequest.account.toString(), httpSettingsResponse) + + when: "PBS processes amp request" + def response = prebidServerServiceWithRfc.sendAmpRequest(ampRequest) + + then: "Response should contain httpcalls" + assert !response.ext?.debug?.httpcalls?.isEmpty() + + and: "There should be only one account request" + assert httpSettings.getRfcRequestCount(ampRequest.account.toString()) == 1 + + then: "Response should contain targeting" + assert !response.ext?.debug?.httpcalls?.isEmpty() + } + def "PBS should take account information from http data source on event request"() { given: "Default EventRequest" def eventRequest = EventRequest.defaultEventRequest @@ -103,6 +171,25 @@ class HttpSettingsSpec extends BaseSpec { assert httpSettings.getRequestCount(eventRequest.accountId.toString()) == 1 } + def "PBS should take account information from http data source on event request when rfc3986 enabled"() { + given: "Default EventRequest" + def eventRequest = EventRequest.defaultEventRequest + + and: "Prepare default account response" + def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(eventRequest.accountId.toString()) + httpSettings.setRfcResponse(eventRequest.accountId.toString(), httpSettingsResponse) + + when: "PBS processes event request" + def responseBody = prebidServerServiceWithRfc.sendEventRequest(eventRequest) + + then: "Event response should contain and corresponding content-type" + assert responseBody == + ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png") + + and: "There should be only one account request" + assert httpSettings.getRfcRequestCount(eventRequest.accountId.toString()) == 1 + } + def "PBS should take account information from http data source on setuid request"() { given: "Pbs config with adapters.generic.usersync.redirect.*" def pbsConfig = PbsConfig.httpSettingsConfig + @@ -137,6 +224,42 @@ class HttpSettingsSpec extends BaseSpec { pbsServiceFactory.removeContainer(pbsConfig) } + def "PBS should take account information from http data source on setuid request when rfc3986 enabled"() { + given: "Pbs config with adapters.generic.usersync.redirect.*" + def pbsConfig = new HashMap<>(PbsConfig.httpSettingsConfig) + + ['settings.http.endpoint': "${networkServiceContainer.rootUri}${HttpSettings.rfcEndpoint}".toString(), + 'settings.http.rfc3986-compatible': 'true', + 'adapters.generic.usersync.redirect.url' : "$networkServiceContainer.rootUri/generic-usersync&redir={{redirect_url}}".toString(), + 'adapters.generic.usersync.redirect.support-cors' : 'false', + 'adapters.generic.usersync.redirect.format-override': 'blank'] + def prebidServerService = pbsServiceFactory.getService(pbsConfig) + + and: "Get default SetuidRequest and set account, gdpr=1 " + def request = SetuidRequest.defaultSetuidRequest + request.gdpr = 1 + request.account = PBSUtils.randomNumber.toString() + def uidsCookie = UidsCookie.defaultUidsCookie + + and: "Prepare default account response" + def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(request.account) + httpSettings.setRfcResponse(request.account, httpSettingsResponse) + + when: "PBS processes setuid request" + def response = prebidServerService.sendSetUidRequest(request, uidsCookie) + + then: "Response should contain tempUIDs cookie" + assert !response.uidsCookie.uids + assert response.uidsCookie.tempUIDs + assert response.responseBody == + ResourceUtil.readByteArrayFromClassPath("org/prebid/server/functional/tracking-pixel.png") + + and: "There should be only one account request" + assert httpSettings.getRfcRequestCount(request.account) == 1 + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + def "PBS should take account information from http data source on vtrack request"() { given: "Default VtrackRequest" String payload = PBSUtils.randomString @@ -162,6 +285,31 @@ class HttpSettingsSpec extends BaseSpec { assert prebidCacheRequest.contains("/event?t=imp&b=${request.puts[0].bidid}&a=$accountId&bidder=${request.puts[0].bidder}") } + def "PBS should take account information from http data source on vtrack request when rfc3986 enabled"() { + given: "Default VtrackRequest" + String payload = PBSUtils.randomString + def request = VtrackRequest.getDefaultVtrackRequest(encodeXml(Vast.getDefaultVastModel(payload))) + def accountId = PBSUtils.randomNumber.toString() + + and: "Prepare default account response" + def httpSettingsResponse = HttpAccountsResponse.getDefaultHttpAccountsResponse(accountId) + httpSettings.setRfcResponse(accountId, httpSettingsResponse) + + when: "PBS processes vtrack request" + def response = prebidServerServiceWithRfc.sendVtrackRequest(request, accountId) + + then: "Response should contain uid" + assert response.responses[0]?.uuid + + and: "There should be only one account request and pbc request" + assert httpSettings.getRfcRequestCount(accountId.toString()) == 1 + assert prebidCache.getXmlRequestCount(payload) == 1 + + and: "VastXml that was send to PrebidCache must contain event url" + def prebidCacheRequest = prebidCache.getXmlRecordedRequestsBody(payload)[0] + assert prebidCacheRequest.contains("/event?t=imp&b=${request.puts[0].bidid}&a=$accountId&bidder=${request.puts[0].bidder}") + } + def "PBS should return error if account settings isn't found"() { given: "Default EventRequest" def eventRequest = EventRequest.defaultEventRequest @@ -174,4 +322,17 @@ class HttpSettingsSpec extends BaseSpec { assert exception.statusCode == 401 assert exception.responseBody.contains("Account '$eventRequest.accountId' doesn't support events") } + + def "PBS should return error if account settings isn't found when rfc3986 enabled"() { + given: "Default EventRequest" + def eventRequest = EventRequest.defaultEventRequest + + when: "PBS processes event request" + prebidServerServiceWithRfc.sendEventRequest(eventRequest) + + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == 401 + assert exception.responseBody.contains("Account '$eventRequest.accountId' doesn't support events") + } }