Skip to content

Commit

Permalink
AdUp Technology bid adapter: optimize floor price detection (prebid#9332
Browse files Browse the repository at this point in the history
)
  • Loading branch information
SteffenAnders authored and JacobKlein26 committed Feb 8, 2023
1 parent eff1508 commit 7d5f870
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 62 deletions.
90 changes: 58 additions & 32 deletions modules/aduptechBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {deepAccess, getAdUnitSizes} from '../src/utils.js';
import {getAdUnitSizes, isArray, isBoolean, isEmpty, isFn, isPlainObject} from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER, NATIVE} from '../src/mediaTypes.js';
import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
Expand All @@ -23,7 +23,7 @@ export const internal = {
if (bidderRequest && bidderRequest.gdprConsent) {
return {
consentString: bidderRequest.gdprConsent.consentString,
consentRequired: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true
consentRequired: (isBoolean(bidderRequest.gdprConsent.gdprApplies)) ? bidderRequest.gdprConsent.gdprApplies : true
};
}

Expand Down Expand Up @@ -60,8 +60,26 @@ export const internal = {
*/
extractBannerConfig: (bidRequest) => {
const sizes = getAdUnitSizes(bidRequest);
if (Array.isArray(sizes) && sizes.length > 0) {
return { sizes: sizes };
if (isArray(sizes) && !isEmpty(sizes)) {
const banner = { sizes: sizes };

// try to add floor for each banner size
banner.sizes.forEach(size => {
const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size });
if (floor) {
size.push(floor.floor);
size.push(floor.currency);
}
});

// try to add default floor for banner
const floor = internal.getFloor(bidRequest, { mediaType: BANNER, size: '*' });
if (floor) {
banner.floorPrice = floor.floor;
banner.floorCurrency = floor.currency;
}

return banner;
}

return null;
Expand All @@ -74,8 +92,17 @@ export const internal = {
* @returns {null|Object.<string, *>}
*/
extractNativeConfig: (bidRequest) => {
if (bidRequest && deepAccess(bidRequest, 'mediaTypes.native')) {
return bidRequest.mediaTypes.native;
if (bidRequest?.mediaTypes?.native) {
const native = bidRequest.mediaTypes.native;

// try to add default floor for native
const floor = internal.getFloor(bidRequest, { mediaType: NATIVE, size: '*' });
if (floor) {
native.floorPrice = floor.floor;
native.floorCurrency = floor.currency;
}

return native;
}

return null;
Expand All @@ -96,27 +123,25 @@ export const internal = {
},

/**
* Extracts the floor price params from given bidRequest
* Try to get floor information via bidRequest.getFloor()
*
* @param {BidRequest} bidRequest
* @returns {undefined|float}
* @param {Object<string, *>} options
* @returns {null|Object.<string, *>}
*/
extractFloorPrice: (bidRequest) => {
let floorPrice;
if (bidRequest && bidRequest.params && bidRequest.params.floor) {
// if there is a manual floorPrice set
floorPrice = !isNaN(parseInt(bidRequest.params.floor)) ? bidRequest.params.floor : undefined;
}
if (typeof bidRequest.getFloor === 'function') {
// use prebid floor module
let floorInfo;
try {
floorInfo = bidRequest.getFloor();
} catch (e) {}
floorPrice = typeof floorInfo === 'object' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : floorPrice;
getFloor: (bidRequest, options) => {
if (!isFn(bidRequest?.getFloor)) {
return null;
}

return floorPrice;
try {
const floor = bidRequest.getFloor(options);
if (isPlainObject(floor) && !isNaN(floor.floor)) {
return floor;
}
} catch {}

return null;
},

/**
Expand All @@ -128,11 +153,11 @@ export const internal = {
groupBidRequestsByPublisher: (bidRequests) => {
const groupedBidRequests = {};

if (!bidRequests || bidRequests.length === 0) {
if (!bidRequests || isEmpty(bidRequests)) {
return groupedBidRequests;
}

bidRequests.forEach((bidRequest) => {
bidRequests.forEach(bidRequest => {
const publisher = internal.extractParams(bidRequest).publisher;
if (!publisher) {
return;
Expand Down Expand Up @@ -205,7 +230,7 @@ export const spec = {
const requests = [];

// stop here on invalid or empty data
if (!bidderRequest || !validBidRequests || validBidRequests.length === 0) {
if (!bidderRequest || !validBidRequests || isEmpty(validBidRequests)) {
return requests;
}

Expand Down Expand Up @@ -237,7 +262,7 @@ export const spec = {
}

// handle multiple bids per request
groupedBidRequests[publisher].forEach((bidRequest) => {
groupedBidRequests[publisher].forEach(bidRequest => {
const bid = {
bidId: bidRequest.bidId,
transactionId: bidRequest.transactionId,
Expand All @@ -257,10 +282,11 @@ export const spec = {
bid.native = nativeConfig;
}

// add floor price
const floorPrice = internal.extractFloorPrice(bidRequest);
if (floorPrice) {
bid.floorPrice = floorPrice;
// try to add default floor
const floor = internal.getFloor(bidRequest, { mediaType: '*', size: '*' });
if (floor) {
bid.floorPrice = floor.floor;
bid.floorCurrency = floor.currency;
}

request.data.imp.push(bid);
Expand All @@ -282,12 +308,12 @@ export const spec = {
const bidResponses = [];

// stop here on invalid or empty data
if (!response || !deepAccess(response, 'body.bids') || response.body.bids.length === 0) {
if (!response?.body?.bids || isEmpty(response.body.bids)) {
return bidResponses;
}

// parse multiple bids per response
response.body.bids.forEach((bid) => {
response.body.bids.forEach(bid => {
if (!bid || !bid.bid || !bid.creative) {
return;
}
Expand Down
131 changes: 101 additions & 30 deletions test/spec/modules/aduptechBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,54 @@ describe('AduptechBidAdapter', () => {
});
});

describe('getFloor', () => {
let bidRequest;

beforeEach(() => {
bidRequest = {
getFloor: sinon.stub()
};
});

it('should handle empty or invalid bidRequest', () => {
expect(internal.getFloor(null)).to.be.null;
expect(internal.getFloor({})).to.be.null;
expect(internal.getFloor({ getFloor: 'foo' })).to.be.null;
});

it('should detect floor via getFloor()', () => {
const result = {
floor: 1.11,
currency: 'USD'
};

const options = {
mediaType: BANNER,
size: '*'
}

bidRequest.getFloor.returns(result);

expect(internal.getFloor(bidRequest, options)).to.deep.equal(result);
expect(bidRequest.getFloor.calledOnceWith(options)).to.be.true;
});

it('should handle empty, invalid or faulty getFloor() results', () => {
bidRequest.getFloor
.onCall(0).returns({})
.onCall(1).returns({ floor: 'foo' })
.onCall(2).returns('bar')
.onCall(3).throws(new Error('baz'));

expect(internal.getFloor(bidRequest, {})).to.be.null;
expect(internal.getFloor(bidRequest, {})).to.be.null;
expect(internal.getFloor(bidRequest, {})).to.be.null;
expect(internal.getFloor(bidRequest, {})).to.be.null;

expect(bidRequest.getFloor.callCount).to.equal(4);
});
});

describe('groupBidRequestsByPublisher', () => {
it('should handle empty bidRequests', () => {
expect(internal.groupBidRequestsByPublisher(null)).to.deep.equal({});
Expand Down Expand Up @@ -541,34 +589,35 @@ describe('AduptechBidAdapter', () => {
}
};

const getFloorResponse = {
currency: 'USD',
floor: '1.23'
};

const validBidRequests = [
{
bidId: 'bidId1',
adUnitCode: 'adUnitCode1',
transactionId: 'transactionId1',
mediaTypes: {
banner: {
sizes: [[100, 200], [300, 400]]
}
},
params: {
publisher: 'publisher1',
placement: 'placement1'
const bidRequest = {
bidId: 'bidId1',
adUnitCode: 'adUnitCode1',
transactionId: 'transactionId1',
mediaTypes: {
banner: {
sizes: [[100, 200], [300, 400]]
},
getFloor: () => {
return getFloorResponse
native: {
image: {
required: true
},
}
}
];
},
params: {
publisher: 'publisher1',
placement: 'placement1'
},
getFloor: sinon.stub()
.onCall(0).returns({ floor: 1.11, currency: 'USD' })
.onCall(1).returns({ floor: 2.22, currency: 'EUR' })
.onCall(2).returns({ floor: 3.33, currency: 'USD' })
.onCall(3).returns({ floor: 4.44, currency: 'GBP' })
.onCall(4).returns({ floor: 5.55, currency: 'EUR' })
};

expect(spec.buildRequests(validBidRequests, bidderRequest)).to.deep.equal([
expect(spec.buildRequests([bidRequest], bidderRequest)).to.deep.equal([
{
url: internal.buildEndpointUrl(validBidRequests[0].params.publisher),
url: internal.buildEndpointUrl(bidRequest.params.publisher),
method: ENDPOINT_METHOD,
data: {
auctionId: bidderRequest.auctionId,
Expand All @@ -580,17 +629,39 @@ describe('AduptechBidAdapter', () => {
},
imp: [
{
bidId: validBidRequests[0].bidId,
transactionId: validBidRequests[0].transactionId,
adUnitCode: validBidRequests[0].adUnitCode,
params: validBidRequests[0].params,
banner: validBidRequests[0].mediaTypes.banner,
floorPrice: getFloorResponse.floor
bidId: bidRequest.bidId,
transactionId: bidRequest.transactionId,
adUnitCode: bidRequest.adUnitCode,
params: bidRequest.params,
banner: {
sizes: [
[100, 200, 1.11, 'USD'],
[300, 400, 2.22, 'EUR'],
],
floorPrice: 3.33,
floorCurrency: 'USD'
},
native: {
image: {
required: true
},
floorPrice: 4.44,
floorCurrency: 'GBP'
},
floorPrice: 5.55,
floorCurrency: 'EUR'
}
]
}
}
]);

expect(bidRequest.getFloor.callCount).to.equal(5);
expect(bidRequest.getFloor.getCall(0).calledWith({ mediaType: BANNER, size: bidRequest.mediaTypes.banner.sizes[0] })).to.be.true;
expect(bidRequest.getFloor.getCall(1).calledWith({ mediaType: BANNER, size: bidRequest.mediaTypes.banner.sizes[1] })).to.be.true;
expect(bidRequest.getFloor.getCall(2).calledWith({ mediaType: BANNER, size: '*' })).to.be.true;
expect(bidRequest.getFloor.getCall(3).calledWith({ mediaType: NATIVE, size: '*' })).to.be.true;
expect(bidRequest.getFloor.getCall(4).calledWith({ mediaType: '*', size: '*' })).to.be.true;
});
});

Expand Down

0 comments on commit 7d5f870

Please sign in to comment.