Skip to content

Commit

Permalink
RTB House Bid Adapter: add DSA support (#11097)
Browse files Browse the repository at this point in the history
* RTB House adapter: add DSA support

* RTB House: add DSA support with extended field control
  • Loading branch information
piotrj-rtbh authored Feb 15, 2024
1 parent 13cbc7c commit 9379982
Show file tree
Hide file tree
Showing 2 changed files with 255 additions and 13 deletions.
51 changes: 49 additions & 2 deletions modules/rtbhouseBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {deepAccess, isArray, logError, logInfo, mergeDeep} from '../src/utils.js';
import {deepAccess, deepClone, isArray, logError, logInfo, mergeDeep, isEmpty, isPlainObject, isNumber, isStr} from '../src/utils.js';
import {getOrigin} from '../libraries/getOrigin/index.js';
import {BANNER, NATIVE} from '../src/mediaTypes.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
Expand All @@ -18,6 +18,12 @@ const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE];
const TTL = 55;
const GVLID = 16;

const DSA_ATTRIBUTES = [
{ name: 'dsarequired', 'min': 0, 'max': 3 },
{ name: 'pubrender', 'min': 0, 'max': 2 },
{ name: 'datatopub', 'min': 0, 'max': 2 }
];

// Codes defined by OpenRTB Native Ads 1.1 specification
export const OPENRTB = {
NATIVE: {
Expand Down Expand Up @@ -95,6 +101,17 @@ export const spec = {
}
});

const dsa = deepAccess(ortb2Params, 'regs.ext.dsa');
if (validateDSA(dsa)) {
mergeDeep(request, {
regs: {
ext: {
dsa
}
}
});
}

let computedEndpointUrl = ENDPOINT_URL;

if (bidderRequest.fledgeEnabled) {
Expand Down Expand Up @@ -133,7 +150,13 @@ export const spec = {
} else {
interpretedBid = interpretBannerBid(serverBid);
}
if (serverBid.ext) interpretedBid.ext = serverBid.ext;

if (serverBid.ext) {
interpretedBid.ext = deepClone(serverBid.ext);
if (serverBid.ext.dsa) {
interpretedBid.meta = Object.assign({}, interpretedBid.meta, { dsa: serverBid.ext.dsa });
}
}

bids.push(interpretedBid);
});
Expand Down Expand Up @@ -518,3 +541,27 @@ function interpretNativeAd(adm) {
});
return result;
}

/**
* https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/dsa_transparency.md
*
* @param {object} dsa
* @returns {boolean} whether dsa object contains valid attributes values
*/
function validateDSA(dsa) {
if (isEmpty(dsa) || !isPlainObject(dsa)) return false;

return DSA_ATTRIBUTES.reduce((prev, attr) => {
const dsaEntry = dsa[attr.name];
return prev && (
!dsa.hasOwnProperty(attr.name) ||
(isNumber(dsaEntry) && dsaEntry >= attr.min && dsaEntry <= attr.max)
)
}, true) &&
(!dsa.hasOwnProperty('transparency') ||
(isArray(dsa.transparency) && dsa.transparency.every(
v => isPlainObject(v) && isStr(v.domain) && v.domain && isArray(v.dsaparams) &&
v.dsaparams.every(x => isNumber(x))
))
)
}
217 changes: 206 additions & 11 deletions test/spec/modules/rtbhouseBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect } from 'chai';
import { OPENRTB, spec } from 'modules/rtbhouseBidAdapter.js';
import { newBidder } from 'src/adapters/bidderFactory.js';
import { config } from 'src/config.js';
import { mergeDeep } from '../../../src/utils';

describe('RTBHouseAdapter', () => {
const adapter = newBidder(spec);
Expand Down Expand Up @@ -304,6 +305,152 @@ describe('RTBHouseAdapter', () => {
expect(data.user).to.nested.include({'ext.data': 'some user data'});
});

context('DSA', () => {
const validDSAObject = {
'dsarequired': 3,
'pubrender': 0,
'datatopub': 2,
'transparency': [
{
'domain': 'platform1domain.com',
'dsaparams': [1]
},
{
'domain': 'SSP2domain.com',
'dsaparams': [1, 2]
}
]
};
const invalidDSAObjects = [
-1,
0,
'',
'x',
true,
[],
[1],
{},
{
'dsarequired': -1
},
{
'pubrender': -1
},
{
'datatopub': -1
},
{
'dsarequired': 4
},
{
'pubrender': 3
},
{
'datatopub': 3
},
{
'dsarequired': '1'
},
{
'pubrender': '1'
},
{
'datatopub': '1'
},
{
'transparency': '1'
},
{
'transparency': 2
},
{
'transparency': [
1, 2
]
},
{
'transparency': [
{
domain: '',
dsaparams: []
}
]
},
{
'transparency': [
{
domain: 'x',
dsaparams: null
}
]
},
{
'transparency': [
{
domain: 'x',
dsaparams: [1, '2']
}
]
},
];
let bidRequest;

beforeEach(() => {
bidRequest = Object.assign([], bidRequests);
});

it('should add dsa information to the request via bidderRequest.ortb2.regs.ext.dsa', function () {
const localBidderRequest = {
...bidderRequest,
ortb2: {
regs: {
ext: {
dsa: validDSAObject
}
}
}
};

const request = spec.buildRequests(bidRequest, localBidderRequest);
const data = JSON.parse(request.data);

expect(data).to.have.nested.property('regs.ext.dsa');
expect(data.regs.ext.dsa.dsarequired).to.equal(3);
expect(data.regs.ext.dsa.pubrender).to.equal(0);
expect(data.regs.ext.dsa.datatopub).to.equal(2);
expect(data.regs.ext.dsa.transparency).to.deep.equal([
{
'domain': 'platform1domain.com',
'dsaparams': [1]
},
{
'domain': 'SSP2domain.com',
'dsaparams': [1, 2]
}
]);
});

invalidDSAObjects.forEach((invalidDSA, index) => {
it(`should not add dsa information to the request via bidderRequest.ortb2.regs.ext.dsa; test# ${index}`, function () {
const localBidderRequest = {
...bidderRequest,
ortb2: {
regs: {
ext: {
dsa: invalidDSA
}
}
}
};

const request = spec.buildRequests(bidRequest, localBidderRequest);
const data = JSON.parse(request.data);

expect(data).to.not.have.nested.property('regs.ext.dsa');
});
});
});

context('FLEDGE', function() {
afterEach(function () {
config.resetConfig();
Expand Down Expand Up @@ -563,17 +710,20 @@ describe('RTBHouseAdapter', () => {
});

describe('interpretResponse', function () {
let response = [{
'id': 'bidder_imp_identifier',
'impid': '552b8922e28f27',
'price': 0.5,
'adid': 'Ad_Identifier',
'adm': '<!-- test creative -->',
'adomain': ['rtbhouse.com'],
'cid': 'Ad_Identifier',
'w': 300,
'h': 250
}];
let response;
beforeEach(() => {
response = [{
'id': 'bidder_imp_identifier',
'impid': '552b8922e28f27',
'price': 0.5,
'adid': 'Ad_Identifier',
'adm': '<!-- test creative -->',
'adomain': ['rtbhouse.com'],
'cid': 'Ad_Identifier',
'w': 300,
'h': 250
}];
});

let fledgeResponse = {
'id': 'bid-identifier',
Expand Down Expand Up @@ -638,6 +788,51 @@ describe('RTBHouseAdapter', () => {
});
});

context('when the response contains DSA object', function () {
it('should get correct bid response', function () {
const dsa = {
'dsa': {
'behalf': 'Advertiser',
'paid': 'Advertiser',
'transparency': [{
'domain': 'dsp1domain.com',
'dsaparams': [1, 2]
}],
'adrender': 1
}
};
mergeDeep(response[0], { ext: dsa });

const expectedResponse = [
{
'requestId': '552b8922e28f27',
'cpm': 0.5,
'creativeId': 29681110,
'width': 300,
'height': 250,
'ad': '<!-- test creative -->',
'mediaType': 'banner',
'currency': 'USD',
'ttl': 300,
'meta': {
'advertiserDomains': ['rtbhouse.com'],
...dsa
},
'netRevenue': true,
ext: { ...dsa }
}
];
let bidderRequest;
let result = spec.interpretResponse({body: response}, {bidderRequest});

expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0]));
expect(result[0]).to.have.nested.property('meta.dsa');
expect(result[0]).to.have.nested.property('ext.dsa');
expect(result[0].meta.dsa).to.deep.equal(expectedResponse[0].meta.dsa);
expect(result[0].ext.dsa).to.deep.equal(expectedResponse[0].meta.dsa);
});
});

describe('native', () => {
const adm = {
native: {
Expand Down

0 comments on commit 9379982

Please sign in to comment.