Skip to content

Commit

Permalink
Update how key-values are passed into ad server
Browse files Browse the repository at this point in the history
  • Loading branch information
antosarho committed Jun 8, 2024
1 parent cb91a7c commit 2bca149
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 27 deletions.
22 changes: 11 additions & 11 deletions modules/adnuntiusBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
import { isStr, deepAccess } from '../src/utils.js';
import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray} from '../src/utils.js';
import { config } from '../src/config.js';
import { getStorageManager } from '../src/storageManager.js';

Expand All @@ -19,10 +19,6 @@ const METADATA_KEY = 'adn.metaData';
const METADATA_KEY_SEPARATOR = '@@@';

export const misc = {
getUnixTimestamp: function (addDays, asMinutes) {
const multiplication = addDays / (asMinutes ? 1440 : 1);
return Date.now() + (addDays && addDays > 0 ? (1000 * 60 * 60 * 24 * multiplication) : 0);
}
};

const storageTool = (function () {
Expand Down Expand Up @@ -50,11 +46,11 @@ const storageTool = (function () {
if (datum.key === 'voidAuIds' && Array.isArray(datum.value)) {
return true;
}
return datum.key && datum.value && datum.exp && datum.exp > misc.getUnixTimestamp() && (!network || network === datum.network);
return datum.key && datum.value && datum.exp && datum.exp > getUnixTimestampFromNow() && (!network || network === datum.network);
}) : [];
const voidAuIdsEntry = filteredEntries.find(entry => entry.key === 'voidAuIds');
if (voidAuIdsEntry) {
const now = misc.getUnixTimestamp();
const now = getUnixTimestampFromNow();
voidAuIdsEntry.value = voidAuIdsEntry.value.filter(voidAuId => voidAuId.auId && voidAuId.exp > now);
if (!voidAuIdsEntry.value.length) {
filteredEntries = filteredEntries.filter(entry => entry.key !== 'voidAuIds');
Expand All @@ -73,7 +69,7 @@ const storageTool = (function () {
const notNewExistingAuIds = currentVoidAuIds.filter(auIdObj => {
return newAuIds.indexOf(auIdObj.value) < -1;
}) || [];
const oneDayFromNow = misc.getUnixTimestamp(1);
const oneDayFromNow = getUnixTimestampFromNow(1);
const apiIdsArray = newAuIds.map(auId => {
return { exp: oneDayFromNow, auId: auId };
}) || [];
Expand All @@ -86,7 +82,7 @@ const storageTool = (function () {
if (key !== 'voidAuIds') {
metaAsObj[key + METADATA_KEY_SEPARATOR + network] = {
value: apiRespMetadata[key],
exp: misc.getUnixTimestamp(100),
exp: getUnixTimestampFromNow(100),
network: network
}
}
Expand Down Expand Up @@ -201,10 +197,14 @@ const targetingTool = (function() {
},
mergeKvsFromOrtb: function(bidTargeting, bidderRequest) {
const kv = getKvsFromOrtb(bidderRequest || {});
if (!kv) {
if (isEmpty(kv)) {
return;
}
bidTargeting.kv = {...kv, ...bidTargeting.kv};
if (bidTargeting.kv && !Array.isArray(bidTargeting.kv)) {
bidTargeting.kv = convertObjectToArray(bidTargeting.kv);
}
bidTargeting.kv = bidTargeting.kv || [];
bidTargeting.kv = bidTargeting.kv.concat(convertObjectToArray(kv));
}
}
})();
Expand Down
31 changes: 31 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,37 @@ export function memoize(fn, key = function (arg) { return arg; }) {
return memoized;
}

/**
* Returns a Unix timestamp for given time value and unit.
* @param timeValue numeric value, defaults to 0 (which means now)
* @param timeUnit defaults to days (or 'd'), use 'm' for minutes. Any parameter that isn't 'd' or 'm' will return Date.now().
* @returns {number}
*/
export function getUnixTimestampFromNow(timeValue = 0, timeUnit = 'd') {
const acceptableUnits = ['m', 'd'];
if (acceptableUnits.indexOf(timeUnit) < 0) {
return Date.now();
}
const multiplication = timeValue / (timeUnit === 'm' ? 1440 : 1);
return Date.now() + (timeValue && timeValue > 0 ? (1000 * 60 * 60 * 24 * multiplication) : 0);
}

/**
* Converts given object with enumerable properties into an array, so {key: 1, anotherKey: 'fred', third: ['fred']} is turned
* into [{key: 1}, {anotherKey: 'fred'}, {third: ['fred']}]
* @param {Object} obj the object
* @returns {Array}
*/
export function convertObjectToArray(obj) {
const convertedArray = [];
for (const prop in obj) {
const arrElement = {};
arrElement[prop] = obj[prop];
convertedArray.push(arrElement);
}
return convertedArray;
}

/**
* Sets dataset attributes on a script
* @param {HTMLScriptElement} script
Expand Down
115 changes: 99 additions & 16 deletions test/spec/modules/adnuntiusBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { config } from 'src/config.js';
import * as utils from 'src/utils.js';
import { getStorageManager } from 'src/storageManager.js';
import { getGlobal } from '../../../src/prebidGlobal';
import {getUnixTimestampFromNow} from "src/utils.js";

describe('adnuntiusBidAdapter', function () {
const URL = 'https://ads.adnuntius.delivery/i?tzo=';
const EURO_URL = 'https://europe.delivery.adnuntius.com/i?tzo=';
const usi = utils.generateUUID()

const meta = [{ key: 'valueless' }, { value: 'keyless' }, { key: 'voidAuIds' }, { key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: misc.getUnixTimestamp() }, { exp: misc.getUnixTimestamp(1) }] }, { key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: misc.getUnixTimestamp(1) }, { key: 'valid', value: 'also-valid', exp: misc.getUnixTimestamp(1) }, { key: 'expired', value: 'fwefew', exp: misc.getUnixTimestamp() }, { key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp(), network: 'adnuntius' }, { key: 'usi', value: usi, exp: misc.getUnixTimestamp(100), network: 'adnuntius' }, { key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp() }]
const meta = [{ key: 'valueless' }, { value: 'keyless' }, { key: 'voidAuIds' }, { key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: getUnixTimestampFromNow() }, { exp: getUnixTimestampFromNow(1) }] }, { key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: getUnixTimestampFromNow(1) }, { key: 'valid', value: 'also-valid', exp: getUnixTimestampFromNow(1) }, { key: 'expired', value: 'fwefew', exp: getUnixTimestampFromNow() }, { key: 'usi', value: 'should be skipped because timestamp', exp: getUnixTimestampFromNow(), network: 'adnuntius' }, { key: 'usi', value: usi, exp: getUnixTimestampFromNow(100), network: 'adnuntius' }, { key: 'usi', value: 'should be skipped because timestamp', exp: getUnixTimestampFromNow() }]
let storage;

// need this to make the restore work correctly -- something to do with stubbing static prototype methods
Expand Down Expand Up @@ -505,7 +506,7 @@ describe('adnuntiusBidAdapter', function () {
});

it('Test request changes for voided au ids', function () {
storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{ key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: misc.getUnixTimestamp(1) }, { auId: '0000000000000023', exp: misc.getUnixTimestamp(1) }] }]));
storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{ key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: getUnixTimestampFromNow(1) }, { auId: '0000000000000023', exp: getUnixTimestampFromNow(1) }] }]));
const bRequests = bidderRequests.concat([{
bidId: 'adn-11118b6bc',
bidder: 'adnuntius',
Expand Down Expand Up @@ -592,14 +593,20 @@ describe('adnuntiusBidAdapter', function () {
delete bidderRequests[0].params.targeting;
});

function countMatches(actualArray, expectedValue) {
return actualArray.filter(val => {
return JSON.stringify(val) === JSON.stringify(expectedValue);
}).length;
}

it('should pass site data ext as key values to ad server', function () {
const ortb2 = {
site: {
ext: {
data: {
'12345': 'true',
'45678': 'true',
'9090': 'should-be-overwritten'
'9090': 'should-be-retained'
}
}
}
Expand All @@ -614,26 +621,102 @@ describe('adnuntiusBidAdapter', function () {
expect(request.length).to.equal(1);
expect(request[0]).to.have.property('url')
const data = JSON.parse(request[0].data);
expect(data.adUnits[0].kv['12345']).to.equal('true');
expect(data.adUnits[0].kv['45678']).to.equal('true');
expect(data.adUnits[0].kv['9090'][0]).to.equal('take it over');
expect(data.adUnits[0].kv['merge'][0]).to.equal('this');
expect(countMatches(data.adUnits[0].kv, {'9090': ['take it over']})).to.equal(1);
expect(countMatches(data.adUnits[0].kv, {'merge': ['this']})).to.equal(1);
expect(countMatches(data.adUnits[0].kv, {'9090': 'should-be-retained'})).to.equal(1);
expect(countMatches(data.adUnits[0].kv, {'45678': 'true'})).to.equal(1);
expect(countMatches(data.adUnits[0].kv, {'12345': 'true'})).to.equal(1);
expect(data.adUnits[0].kv.length).to.equal(5);

delete bidderRequests[0].params.targeting;
});

it('should skip passing site data ext if missing', function () {
it('should pass site data ext as key values to ad server with targeting in different format', function () {
const ortb2 = {
site: {
ext: {
data: {
'12345': 'true',
'45678': 'true',
'9090': 'should-be-retained'
}
}
}
};
bidderRequests[0].params.targeting = {
kv: [
{'merge': ['this']},
{'9090': ['take it over']}
]
};
const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 }));
expect(request.length).to.equal(1);
expect(request[0]).to.have.property('url')
const data = JSON.parse(request[0].data);
expect(countMatches(data.adUnits[0].kv, {'9090': ['take it over']})).to.equal(1);
expect(countMatches(data.adUnits[0].kv, {'merge': ['this']})).to.equal(1);
expect(countMatches(data.adUnits[0].kv, {'9090': 'should-be-retained'})).to.equal(1);
expect(countMatches(data.adUnits[0].kv, {'45678': 'true'})).to.equal(1);
expect(countMatches(data.adUnits[0].kv, {'12345': 'true'})).to.equal(1);
expect(data.adUnits[0].kv.length).to.equal(5);

delete bidderRequests[0].params.targeting;
});

it('should pass site data ext as key values to ad server even if no kv targeting specified in params.targeting', function () {
const ortb2 = {
site: {
ext: {
data: {
'12345': 'true',
'45678': 'true',
'9090': 'should-be-retained'
}
}
}
};
const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 }));
expect(request.length).to.equal(1);
expect(request[0]).to.have.property('url')
const data = JSON.parse(request[0].data);
expect(countMatches(data.adUnits[0].kv, {'9090': 'should-be-retained'})).to.equal(1);
expect(countMatches(data.adUnits[0].kv, {'45678': 'true'})).to.equal(1);
expect(countMatches(data.adUnits[0].kv, {'12345': 'true'})).to.equal(1);
expect(data.adUnits[0].kv.length).to.equal(3);

delete bidderRequests[0].params.targeting;
});

it('should skip passing site ext if missing', function () {
const ortb2 = {
site: {
ext: {
}
}
};

delete bidderRequests[0].params.targeting;
const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 }));
expect(request.length).to.equal(1);
expect(request[0]).to.have.property('url');
const data = JSON.parse(request[0].data);
expect(data.adUnits[0]).to.not.have.property('kv');
});

it('should skip passing site ext data if missing', function () {
const ortb2 = {
site: {
ext: {
data: {}
}
}
};

delete bidderRequests[0].params.targeting;
const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 }));
expect(request.length).to.equal(1);
expect(request[0]).to.have.property('url');
const data = JSON.parse(request[0].data);
expect(data.adUnits[0]).to.not.have.property('kv');
});

Expand Down Expand Up @@ -985,30 +1068,30 @@ describe('adnuntiusBidAdapter', function () {
const usiEntry = results.find(entry => entry.key === 'usi' && entry.network === 'some-network-id');
expect(usiEntry.key).to.equal('usi');
expect(usiEntry.value).to.equal('from-api-server dude');
expect(usiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90));
expect(usiEntry.exp).to.be.greaterThan(getUnixTimestampFromNow(90));
expect(usiEntry.network).to.equal('some-network-id')

const voidAuIdsEntry = results.find(entry => entry.key === 'voidAuIds');
expect(voidAuIdsEntry.key).to.equal('voidAuIds');
expect(voidAuIdsEntry.exp).to.equal(undefined);
expect(voidAuIdsEntry.value[0].auId).to.equal('00000000000abcde');
expect(voidAuIdsEntry.value[0].exp).to.be.greaterThan(misc.getUnixTimestamp());
expect(voidAuIdsEntry.value[0].exp).to.be.lessThan(misc.getUnixTimestamp(2));
expect(voidAuIdsEntry.value[0].exp).to.be.greaterThan(getUnixTimestampFromNow());
expect(voidAuIdsEntry.value[0].exp).to.be.lessThan(getUnixTimestampFromNow(2));
expect(voidAuIdsEntry.value[1].auId).to.equal('00000000000fffff');
expect(voidAuIdsEntry.value[1].exp).to.be.greaterThan(misc.getUnixTimestamp());
expect(voidAuIdsEntry.value[1].exp).to.be.lessThan(misc.getUnixTimestamp(2));
expect(voidAuIdsEntry.value[1].exp).to.be.greaterThan(getUnixTimestampFromNow());
expect(voidAuIdsEntry.value[1].exp).to.be.lessThan(getUnixTimestampFromNow(2));

const validEntry = results.find(entry => entry.key === 'valid');
expect(validEntry.key).to.equal('valid');
expect(validEntry.value).to.equal('also-valid');
expect(validEntry.exp).to.be.greaterThan(misc.getUnixTimestamp());
expect(validEntry.exp).to.be.lessThan(misc.getUnixTimestamp(2));
expect(validEntry.exp).to.be.greaterThan(getUnixTimestampFromNow());
expect(validEntry.exp).to.be.lessThan(getUnixTimestampFromNow(2));

const randomApiEntry = results.find(entry => entry.key === 'randomApiKey');
expect(randomApiEntry.key).to.equal('randomApiKey');
expect(randomApiEntry.value).to.equal('randomApiValue');
expect(randomApiEntry.network).to.equal('some-network-id');
expect(randomApiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90));
expect(randomApiEntry.exp).to.be.greaterThan(getUnixTimestampFromNow(90));
});

it('should not process valid response when passed alt bidder that is an adndeal', function () {
Expand Down
38 changes: 38 additions & 0 deletions test/spec/utils_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,44 @@ describe('Utils', function () {
});
});

describe('getUnixTimestampFromNow', () => {
it('correctly obtains unix timestamp', () => {
const nowValue = new Date('2024-01-01').valueOf();
sinon.stub(Date, 'now').returns(nowValue);
let val = utils.getUnixTimestampFromNow();
expect(val).equal(nowValue);

val = utils.getUnixTimestampFromNow(1);
expect(val).equal(nowValue + (1000 * 60 * 60 * 24));

val = utils.getUnixTimestampFromNow(1, 'd');
expect(val).equal(nowValue + (1000 * 60 * 60 * 24));

val = utils.getUnixTimestampFromNow(1, 'm');
expect(val).equal(nowValue + (1000 * 60 * 60 * 24 / 1440));

val = utils.getUnixTimestampFromNow(2, 'm');
expect(val).equal(nowValue + (1000 * 60 * 60 * 24 * 2 / 1440));

// any value that isn't 'm' or 'd' gets treated as Date.now();
val = utils.getUnixTimestampFromNow(10, 'o');
expect(val).equal(nowValue);
});
});

describe('convertObjectToArray', () => {
it('correctly converts object to array', () => {
const obj = {key: 1, anotherKey: 'fred', third: ['fred'], fourth: {sub: {obj: 'test'}}};
const array = utils.convertObjectToArray(obj);

expect(JSON.stringify(array[0])).equal(JSON.stringify({'key': 1}))
expect(JSON.stringify(array[1])).equal(JSON.stringify({'anotherKey': 'fred'}))
expect(JSON.stringify(array[2])).equal(JSON.stringify({'third': ['fred']}))
expect(JSON.stringify(array[3])).equal(JSON.stringify({'fourth': {sub: {obj: 'test'}}}));
expect(array.length).to.equal(4);
});
});

describe('setScriptAttributes', () => {
it('correctly adds attributes from an object', () => {
const script = document.createElement('script'),
Expand Down

0 comments on commit 2bca149

Please sign in to comment.