Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 3 additions & 29 deletions modules/openxBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,37 +162,11 @@ function isBidRequestValid(bidRequest) {
}

function buildRequests(bidRequests, bidderRequest) {
Comment on lines 163 to 164
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simplified request consolidation now uses only the first bid request's parameters for request-level fields (platform, delDomain, coppa, doNotTrack, response_template_name, test - see lines 60-78 of the converter configuration). If different bid requests specify different values for these parameters, only the first request's values will be used. Consider validating that all bid requests have consistent request-level parameters, or document this limitation so publishers understand these parameters must be consistent across all ad units in an auction.

Suggested change
function buildRequests(bidRequests, bidderRequest) {
/**
* Validate that all bidRequests use consistent values for request-level parameters.
* The OpenRTB converter uses only the first bidRequest's values for these fields.
* This function logs a warning when inconsistencies are detected so publishers are
* aware that parameters must be consistent across all ad units in an auction.
*
* @param {Array} bidRequests
*/
function validateRequestLevelParamsConsistency(bidRequests) {
if (!Array.isArray(bidRequests) || bidRequests.length < 2) {
return;
}
const fields = ['platform', 'delDomain', 'coppa', 'doNotTrack', 'response_template_name', 'test'];
const baseRequest = bidRequests[0] || {};
const baseParams = baseRequest.params || {};
for (let i = 1; i < bidRequests.length; i++) {
const currentRequest = bidRequests[i] || {};
const currentParams = currentRequest.params || {};
fields.forEach(field => {
if (baseParams[field] !== currentParams[field]) {
utils.logWarn(
`OpenX: inconsistent request-level parameter "${field}" between ad units. ` +
'The value from the first ad unit will be used for the OpenRTB request.'
);
}
});
}
}
function buildRequests(bidRequests, bidderRequest) {
validateRequestLevelParamsConsistency(bidRequests);

Copilot uses AI. Check for mistakes.
const videoRequests = bidRequests.filter(bidRequest => isVideoBidRequest(bidRequest));
const bannerAndNativeRequests = bidRequests.filter(bidRequest => isBannerBidRequest(bidRequest) || isNativeBidRequest(bidRequest))
// In case of multi-format bids remove `video` from mediaTypes as for video a separate bid request is built
.map(bid => ({...bid, mediaTypes: {...bid.mediaTypes, video: undefined}}));

const requests = bannerAndNativeRequests.length ? [createRequest(bannerAndNativeRequests, bidderRequest, null)] : [];
videoRequests.forEach(bid => {
requests.push(createRequest([bid], bidderRequest, VIDEO));
});
return requests;
}

function createRequest(bidRequests, bidderRequest, mediaType) {
return {
return [{
method: 'POST',
url: config.getConfig('openxOrtbUrl') || REQUEST_URL,
data: converter.toORTB({bidRequests, bidderRequest, context: {mediaType}})
}
}

function isVideoBidRequest(bidRequest) {
return utils.deepAccess(bidRequest, 'mediaTypes.video');
}

function isNativeBidRequest(bidRequest) {
return utils.deepAccess(bidRequest, 'mediaTypes.native');
}

function isBannerBidRequest(bidRequest) {
const isNotVideoOrNativeBid = !isVideoBidRequest(bidRequest) && !isNativeBidRequest(bidRequest)
return utils.deepAccess(bidRequest, 'mediaTypes.banner') || isNotVideoOrNativeBid;
data: converter.toORTB({bidRequests, bidderRequest})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep mediaType context when converting video requests

Removing the context.mediaType argument from converter.toORTB means response parsing can no longer fall back to a known media type for video requests. In setResponseMediaType, if a bid response lacks seatbid.bid[].mtype (common in ORTB 2.5-style responses), the converter throws and the bid is skipped; this path was previously avoided by sending video requests with context.mediaType = 'video'.

Useful? React with 👍 / 👎.

}];
}

function interpretResponse(resp, req) {
Expand Down
99 changes: 69 additions & 30 deletions test/spec/modules/openxBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,16 +416,12 @@ describe('OpenxRtbAdapter', function () {
playerSize: [640, 480]
};
const requests = spec.buildRequests([multiformat], mockBidderRequest);
expect(requests).to.have.length(2);
expect(requests).to.have.length(1);
expect(requests[0].data.imp).to.have.length(1);
expect(requests[0].data.imp[0].banner).to.exist;
expect(requests[0].data.imp[0].video).to.not.exist;
expect(requests[0].data.imp[0].native).to.not.exist;
expect(requests[1].data.imp).to.have.length(1);
expect(requests[1].data.imp[0].banner).to.not.exist;
expect(requests[1].data.imp[0].native).to.not.exist;
if (FEATURES.VIDEO) {
expect(requests[1].data.imp[0].video).to.exist;
expect(requests[0].data.imp[0].video).to.exist;
}
})

Expand Down Expand Up @@ -454,21 +450,33 @@ describe('OpenxRtbAdapter', function () {
sizes: [[300, 250]]
}
const requests = spec.buildRequests([multiformat], mockBidderRequest);
expect(requests).to.have.length(2);
expect(requests).to.have.length(1);
expect(requests[0].data.imp).to.have.length(1);
expect(requests[0].data.imp[0].banner).to.exist;
expect(requests[0].data.imp[0].video).to.not.exist;
if (FEATURES.NATIVE) {
expect(requests[0].data.imp[0].native).to.exist;
}
expect(requests[1].data.imp).to.have.length(1);
expect(requests[1].data.imp[0].banner).to.not.exist;
expect(requests[1].data.imp[0].native).to.not.exist;
if (FEATURES.VIDEO) {
expect(requests[1].data.imp[0].video).to.exist;
expect(requests[0].data.imp[0].video).to.exist;
}
})

it('should send banner, video and native bids in a single HTTP request', function () {
const allBids = [...bidRequestsWithMediaTypes, nativeBidRequest];
const request = spec.buildRequests(allBids, mockBidderRequest);
expect(request).to.have.length(1);
expect(request[0].data.imp).to.have.length(3);
expect(request[0].data.imp[0].banner).to.exist;
if (FEATURES.VIDEO) {
const videoImp = request[0].data.imp.find(imp => imp.id === bidRequestsWithMediaTypes[1].bidId);
expect(videoImp.video).to.exist;
}
if (FEATURES.NATIVE) {
const nativeImp = request[0].data.imp.find(imp => imp.id === nativeBidRequest.bidId);
expect(nativeImp.native).to.exist;
}
});

it('should send bid request to openx url via POST', function () {
const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest);
expect(request[0].url).to.equal(REQUEST_URL);
Expand All @@ -482,19 +490,26 @@ describe('OpenxRtbAdapter', function () {
expect(request[0].data.ext.platformId).to.be.undefined;
});

it('should send platform id, if available', function () {
it('should send platform id from first bid, if available', function () {
bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00';
bidRequestsWithMediaTypes[1].params.platform = '51ca3159-abc2-4035-8e00-fe26eaa09397';

const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest);
expect(request.length).to.equal(1);
expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform);
expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[1].params.platform);
});

it('should send delDomain from first bid when bids have different delDomains', function () {
bidRequestsWithMediaTypes[0].params.delDomain = 'domain-a.test';
bidRequestsWithMediaTypes[1].params.delDomain = 'domain-b.test';
const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest);
expect(request[0].data.ext.delDomain).to.equal('domain-a.test');
});

it('should send openx adunit codes', function () {
const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest);
expect(request[0].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[0].params.unit);
expect(request[1].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[1].params.unit);
expect(request[0].data.imp[1].tagid).to.equal(bidRequestsWithMediaTypes[1].params.unit);
});

it('should send out custom params on bids that have customParams specified', function () {
Expand Down Expand Up @@ -563,7 +578,7 @@ describe('OpenxRtbAdapter', function () {
expect(request[0].data.imp[0].bidfloorcur).to.equal('USD');
});

it('should send not send floors', function () {
it('should not send floors', function () {
adServerCurrencyStub.returns('EUR');
const bidRequest = Object.assign({},
bidRequestsWithMediaTypes[0],
Expand Down Expand Up @@ -904,7 +919,6 @@ describe('OpenxRtbAdapter', function () {
it('should send a signal to specify that US Privacy applies to this request', async function () {
const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest));
expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN');
expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN');
});

it('should not send the regs object, when consent string is undefined', async function () {
Expand Down Expand Up @@ -946,29 +960,25 @@ describe('OpenxRtbAdapter', function () {
bidderRequest.bids = bidRequests;
const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest));
expect(request[0].data.regs.ext.gdpr).to.equal(1);
expect(request[1].data.regs.ext.gdpr).to.equal(1);
});

it('should send the consent string', async function () {
bidderRequest.bids = bidRequests;
const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest));
expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString);
expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString);
});

it('should send the addtlConsent string', async function () {
bidderRequest.bids = bidRequests;
const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest));
expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent);
expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent);
});

it('should send a signal to specify that GDPR does not apply to this request', async function () {
bidderRequest.gdprConsent.gdprApplies = false;
bidderRequest.bids = bidRequests;
const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest));
expect(request[0].data.regs.ext.gdpr).to.equal(0);
expect(request[1].data.regs.ext.gdpr).to.equal(0);
});

it('when GDPR application is undefined, should not send a signal to specify whether GDPR applies to this request, ' +
Expand All @@ -978,15 +988,14 @@ describe('OpenxRtbAdapter', function () {
const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest));
expect(request[0].data.regs?.ext?.gdpr).to.not.be.ok;
expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString);
expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString);
});

it('when consent string is undefined, should not send the consent string, ', async function () {
delete bidderRequest.gdprConsent.consentString;
bidderRequest.bids = bidRequests;
const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest));
expect(request[0].data.imp[0].ext.consent).to.equal(undefined);
expect(request[1].data.imp[0].ext.consent).to.equal(undefined);
expect(request[0].data.imp[1].ext.consent).to.equal(undefined);
});
});

Expand All @@ -1002,8 +1011,6 @@ describe('OpenxRtbAdapter', function () {
const request = spec.buildRequests(bidRequests, bidderRequest);
expect(request[0].data.regs.gpp).to.equal('test-gpp-string');
expect(request[0].data.regs.gpp_sid).to.deep.equal([6]);
expect(request[1].data.regs.gpp).to.equal('test-gpp-string');
expect(request[1].data.regs.gpp_sid).to.deep.equal([6]);
});

it('should not send GPP string and GPP section IDs in bid request when not available', async function () {
Expand All @@ -1014,8 +1021,6 @@ describe('OpenxRtbAdapter', function () {
const request = spec.buildRequests(bidRequests, bidderRequest);
expect(request[0].data.regs.gpp).to.not.exist;
expect(request[0].data.regs.gpp_sid).to.not.exist;
expect(request[1].data.regs.gpp).to.not.exist;
expect(request[1].data.regs.gpp_sid).to.not.exist;
});
});
});
Expand Down Expand Up @@ -1251,7 +1256,7 @@ describe('OpenxRtbAdapter', function () {
context('video', function () {
it('should send bid request with a mediaTypes specified with video type', function () {
const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest);
expect(request[1].data.imp[0]).to.have.any.keys(VIDEO);
expect(request[0].data.imp[1]).to.have.any.keys(VIDEO);
});

it('Update imp.video with OpenRTB options from mimeTypes and params', function() {
Expand Down Expand Up @@ -1288,8 +1293,8 @@ describe('OpenxRtbAdapter', function () {
h: 250
};
const requests = spec.buildRequests([bid01], bidderRequest);
expect(requests).to.have.lengthOf(2);
expect(requests[1].data.imp[0].video).to.deep.equal(expected);
expect(requests).to.have.lengthOf(1);
expect(requests[0].data.imp[0].video).to.deep.equal(expected);
});
});
}
Expand Down Expand Up @@ -1573,6 +1578,7 @@ describe('OpenxRtbAdapter', function () {
crid: 'test-creative-id',
dealid: 'test-deal-id',
adm: '<VAST version="4.0"><Ad></Ad></VAST>',
mtype: 2,
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR title indicates this is a "WIP" (Work In Progress). However, the code changes appear complete with proper test coverage. Please update the PR title to remove "WIP" if this is ready for review, or clarify what remaining work is needed before this can be merged.

Copilot uses AI. Check for mistakes.
}]
}],
cur: 'AUS'
Expand Down Expand Up @@ -1693,6 +1699,39 @@ describe('OpenxRtbAdapter', function () {
});
}

context('when multiple bid requests (banner + video) are in one request and both bids are returned', function() {
it('should correctly return both banner and video bids', function () {
const bidRequestConfigs = [
{
bidder: 'openx', params: { unit: '1', delDomain: 'test-del-domain' },
adUnitCode: 'au-1', mediaTypes: { banner: { sizes: [[300, 250]] } },
bidId: 'bid-id-banner', bidderRequestId: 'br-1', auctionId: 'a-1'
},
{
bidder: 'openx', params: { unit: '2', delDomain: 'test-del-domain' },
adUnitCode: 'au-2', mediaTypes: { video: { playerSize: [640, 480] } },
bidId: 'bid-id-video', bidderRequestId: 'br-1', auctionId: 'a-1'
}
];
const bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0];
const bidResponse = {
seatbid: [{
bid: [
{ impid: 'bid-id-banner', price: 1, adm: '<div>ad</div>', mtype: 1 },
{ impid: 'bid-id-video', price: 2, adm: '<VAST/>', mtype: 2 }
]
}],
cur: 'USD'
};
const result = spec.interpretResponse({ body: bidResponse }, bidRequest);
expect(result.bids).to.have.length(2);
expect(result.bids[0].mediaType).to.equal(BANNER);
expect(result.bids[1].mediaType).to.equal(VIDEO);
expect(result.bids[0].requestId).to.equal('bid-id-banner');
expect(result.bids[1].requestId).to.equal('bid-id-video');
});
});

if (FEATURES.NATIVE) {
context('when native request and the response is a native', function() {
beforeEach(function () {
Expand Down