From 507e4f3379ccb81911d7dbef008fde4dec2a173e Mon Sep 17 00:00:00 2001 From: digital-matter Date: Wed, 4 Sep 2024 17:33:31 +0300 Subject: [PATCH 1/2] Digital Matter Bid Adapter: Refactor Adapter --- modules/digitalMatterBidAdapter.js | 215 ++++++- modules/digitalMatterBidAdapter.md | 68 +- .../modules/digitalMatterBidAdapter_spec.js | 593 ++++++------------ 3 files changed, 428 insertions(+), 448 deletions(-) diff --git a/modules/digitalMatterBidAdapter.js b/modules/digitalMatterBidAdapter.js index e6af0a8f108..da64a133e57 100644 --- a/modules/digitalMatterBidAdapter.js +++ b/modules/digitalMatterBidAdapter.js @@ -1,23 +1,208 @@ -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {deepAccess, deepSetValue, getDNT, inIframe, logWarn, parseSizesInput} from '../src/utils.js'; +import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { - buildRequests, - getUserSyncs, - interpretResponse, - isBidRequestValid -} from '../libraries/xeUtils/bidderUtils.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; -const BIDDER_CODE = 'digitalmatter'; -const ENDPOINT = 'https://prebid.di-change.live'; +const BIDDER_CODE = 'digitalMatter'; +const ENDPOINT_URL = 'https://adx.digitalmatter.services/' export const spec = { code: BIDDER_CODE, - aliases: ['dichange', 'digitalmatter'], - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid, - buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), - interpretResponse, - getUserSyncs + supportedMediaTypes: [BANNER], + aliases: [], + bidParameters: ['accountId', 'siteId'], + isBidRequestValid: function (bid) { + if (typeof bid.params !== 'object') { + return false; + } + if (!hasBannerMediaType(bid)) { + logWarn('Invalid bid request: missing required mediaType - banner'); + return false; + } + + return !!(bid.params.accountId && bid.params.siteId) + }, + buildRequests: function (validBidRequests, bidderRequest) { + const common = bidderRequest.ortb2 || {}; + const site = common.site; + const tid = common?.source?.tid; + const {user} = common || {}; + + if (!site.page) { + site.page = bidderRequest.refererInfo.page; + } + + const device = getDevice(common.device); + const schain = getByKey(validBidRequests, 'schain'); + const eids = getByKey(validBidRequests, 'userIdAsEids'); + const currency = config.getConfig('currency') + const cur = currency && [currency]; + + const imp = validBidRequests.map((bid, id) => { + const {accountId, siteId} = bid.params; + const bannerParams = deepAccess(bid, 'mediaTypes.banner'); + const position = deepAccess(bid, 'mediaTypes.banner.pos') ?? 0; + + return { + id: bid.adUnitCode, + bidId: bid.bidId, + accountId: accountId, + adUnitCode: bid.adUnitCode, + siteId: siteId, + banner: { + pos: position, + topframe: inIframe() ? 0 : 1, + format: bannerParams.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1] + })) + }, + sizes: parseSizesInput(bannerParams.sizes), + }; + }); + + const ext = { + prebid: { + targeting: { + includewinners: true, + includebidderkeys: false + } + } + }; + + const payload = { + id: bidderRequest.bidderRequestId, + tid, + site, + device, + user, + cur, + imp, + test: config.getConfig('debug') ? 1 : 0, + tmax: bidderRequest.timeout, + start: bidderRequest.auctionStart, + ext + }; + + if (schain) { + deepSetValue(payload, 'source.ext.schain', schain); + } + + if (eids) { + deepSetValue(payload, 'user.ext.eids', eids); + } + + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(payload, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); + } + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL + 'openrtb2/auction', + data: payloadString, + }; + }, + interpretResponse: function (serverResponse) { + const body = serverResponse.body || serverResponse; + const {cur} = body; + const bids = []; + + if (body && body.bids && Array.isArray(body.bids)) { + body.bids.forEach(bidItem => { + const bid = { + requestId: bidItem.bidid, + adomain: bidItem.adomain, + cpm: bidItem.cpm, + currency: cur, + netRevenue: true, + ttl: bidItem.ttl || 300, + creativeId: bidItem.creativeid, + width: bidItem.width, + height: bidItem.height, + dealId: bidItem.dealid, + ad: bidItem.ad, + meta: bidItem.meta, + }; + + bids.push(bid); + }); + } + + return bids + }, + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + if (usersSynced) { + return []; + } + + const userSyncs = []; + + function checkGppStatus(gppConsent) { + if (gppConsent && Array.isArray(gppConsent.applicableSections)) { + return gppConsent.applicableSections.every(sec => typeof sec === 'number' && sec <= 5); + } + return true; + } + + if (hasPurpose1Consent(gdprConsent) && checkGppStatus(gppConsent)) { + responses.forEach(response => { + if (response.body.ext && response.body.ext.usersync) { + try { + const userSync = response.body.ext.usersync; + + userSync.forEach((element) => { + let url = element.url; + let type = element.type; + + if (url) { + if ((type === 'image' || type === 'redirect') && syncOptions.pixelEnabled) { + userSyncs.push({type: 'image', url: url}); + } else if (type === 'iframe' && syncOptions.iframeEnabled) { + userSyncs.push({type: 'iframe', url: url}); + } + } + }) + } catch (e) { + // + } + } + }); + } + + return userSyncs; + } +} + +let usersSynced = false; + +function hasBannerMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} + +function getDevice(data) { + let dnt = data.dnt; + if (!dnt) { + dnt = getDNT() ? 1 : 0; + } + return { + w: data.w || window.innerWidth, + h: data.h || window.innerHeight, + ua: data.ua || navigator.userAgent, + dnt: dnt, + language: data.language || navigator.language, + } +} + +function getByKey(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i], key); + if (result) { + return result; + } + } } registerBidder(spec); diff --git a/modules/digitalMatterBidAdapter.md b/modules/digitalMatterBidAdapter.md index 1b8426497e9..19c0ecd631a 100644 --- a/modules/digitalMatterBidAdapter.md +++ b/modules/digitalMatterBidAdapter.md @@ -1,50 +1,38 @@ # Overview ``` -Module Name: Digital Matter Bidder Adapter -Module Type: Digital Matter Bidder Adapter -Maintainer: di-change@digitalmatter.ai +Module Name: Digital Matter Bid Adapter +Module Type: Digital Matter Bid Adapter +Maintainer: prebid@digitalmatter.ai ``` -# Test Parameters -``` +# Description + +Module that connects to Digital Matter demand sources + +# Banner Test Parameters + +```js var adUnits = [ - { - code: 'test-banner', - mediaTypes: { - banner: { - sizes: [[300, 250]], - } - }, - bids: [ - { - bidder: 'digitalmatter', - params: { - env: 'digitalmatter', - pid: '40', - ext: {} - } - } + { + code: "test-banner", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] ] + } }, - { - code: 'test-video', - sizes: [ [ 640, 480 ] ], - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'instream', - skipppable: true - } - }, - bids: [{ - bidder: 'digitalmatter', - params: { - env: 'digitalmatter', - pid: '40', - ext: {} - } - }] - } + bids: [ + { + bidder: "digitalMatter", + params: { + accountId: "1_demo_1", // string, required + siteId: "1-demo-1" // string, required + } + } + ] + } ]; ``` diff --git a/test/spec/modules/digitalMatterBidAdapter_spec.js b/test/spec/modules/digitalMatterBidAdapter_spec.js index deb31a9da02..87056588cd9 100644 --- a/test/spec/modules/digitalMatterBidAdapter_spec.js +++ b/test/spec/modules/digitalMatterBidAdapter_spec.js @@ -1,89 +1,65 @@ -import {expect} from 'chai'; -import {config} from 'src/config.js'; -import {spec} from 'modules/digitalMatterBidAdapter.js'; -import {deepClone} from 'src/utils'; -import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; - -const ENDPOINT = 'https://prebid.di-change.live'; - -const defaultRequest = { - adUnitCode: 'test', - bidId: '1', - requestId: 'qwerty', - ortb2: { - source: { - tid: 'auctionId' +import {assert, expect} from 'chai'; +import {spec} from 'modules/digitalMatterBidAdapter'; +import {config} from '../../../src/config'; +import {deepClone} from '../../../src/utils'; + +const bid = { + 'adUnitCode': 'adUnitCode', + 'bidId': 'bidId', + 'bidder': 'digitalMatter', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] } }, - ortb2Imp: { - ext: { - tid: 'tr1', - } - }, - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [300, 200] - ] - } - }, - bidder: 'digitalmatter', - params: { - env: 'digitalmatter', - pid: '40', - ext: {} - }, - bidRequestsCount: 1 -}; - -const defaultRequestVideo = deepClone(defaultRequest); -defaultRequestVideo.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'instream', - skipppable: true + 'params': { + 'accountId': '1_demo_1', + 'siteId': '1-demo-1' } }; - -const videoBidderRequest = { - bidderCode: 'digitalmatter', - bids: [{mediaTypes: {video: {}}, bidId: 'qwerty'}] -}; - -const displayBidderRequest = { - bidderCode: 'digitalmatter', - bids: [{bidId: 'qwerty'}] +const bidderRequest = { + ortb2: { + source: { + tid: 'tid-string' + }, + regs: { + ext: { + gdpr: 1 + } + }, + site: { + domain: 'publisher.domain.com', + publisher: { + domain: 'publisher.domain.com' + }, + page: 'https://publisher.domain.com/test.html' + }, + device: { + w: 100, + h: 100, + dnt: 0, + ua: navigator.userAgent, + language: 'en' + } + } }; -describe('digitalmatterBidAdapter', () => { +describe('Digital Matter BidAdapter', function () { describe('isBidRequestValid', function () { - it('should return false when request params is missing', function () { - const invalidRequest = deepClone(defaultRequest); - delete invalidRequest.params; - expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); - }); - - it('should return false when required env param is missing', function () { - const invalidRequest = deepClone(defaultRequest); - delete invalidRequest.params.env; - expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + it('should return true when all required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return false when required pid param is missing', function () { - const invalidRequest = deepClone(defaultRequest); - delete invalidRequest.params.pid; - expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + it('should return false when required params are not passed', function () { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); - it('should return false when video.playerSize is missing', function () { - const invalidRequest = deepClone(defaultRequestVideo); - delete invalidRequest.mediaTypes.video.playerSize; - expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); - }); - - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(defaultRequest)).to.equal(true); + it('should return false when media type banner is missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.mediaTypes.banner; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -91,368 +67,199 @@ describe('digitalmatterBidAdapter', () => { beforeEach(function () { config.resetConfig(); }); - it('should send request with correct structure', function () { - const request = spec.buildRequests([defaultRequest], {}); - expect(request.method).to.equal('POST'); - expect(request.url).to.equal(ENDPOINT + '/bid'); - expect(request.options).to.have.property('contentType').and.to.equal('application/json'); - expect(request).to.have.property('data'); + let request = spec.buildRequests([bid], bidderRequest); + + assert.equal(request.method, 'POST'); + assert.equal(request.url, 'https://adx.digitalmatter.services/openrtb2/auction'); + assert.equal(request.options, undefined); + assert.ok(request.data); }); - it('should build basic request structure', function () { - const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; - expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); - expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); - expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); - expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); - expect(request).to.have.property('bc').and.to.equal(1); - expect(request).to.have.property('floor').and.to.equal(null); - expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); - expect(request).to.have.property('gdprApplies').and.to.equal(0); - expect(request).to.have.property('consentString').and.to.equal(''); - expect(request).to.have.property('userEids').and.to.deep.equal([]); - expect(request).to.have.property('usPrivacy').and.to.equal(''); - expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); - expect(request).to.have.property('ext').and.to.deep.equal({}); - expect(request).to.have.property('env').and.to.deep.equal({ - env: 'digitalmatter', - pid: '40' - }); - expect(request).to.have.property('device').and.to.deep.equal({ - ua: navigator.userAgent, - lang: navigator.language + it('should have default request structure', function () { + let keys = 'tid,site,device,imp,test,ext'.split(','); + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + let data = Object.keys(request); + + assert.deepEqual(keys, data); + }); + + it('should send info about device', function () { + config.setConfig({ + device: {w: 1920, h: 1080} }); + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + + assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.device.w, 100); + assert.equal(request.device.h, 100); }); - it('should build request with schain', function () { - const schainRequest = deepClone(defaultRequest); - schainRequest.schain = { - validation: 'strict', - config: { - ver: '1.0' - } - }; - const request = JSON.parse(spec.buildRequests([schainRequest], {}).data)[0]; - expect(request).to.have.property('schain').and.to.deep.equal({ - validation: 'strict', - config: { - ver: '1.0' - } + it('should send info about the site', function () { + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + + assert.deepEqual(request.site, { + domain: 'publisher.domain.com', + publisher: { + domain: 'publisher.domain.com' + }, + page: 'https://publisher.domain.com/test.html' }); }); - it('should build request with location', function () { - const bidderRequest = { - refererInfo: { - page: 'page', - location: 'location', - domain: 'domain', - ref: 'ref', - isAmp: false - } - }; - const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; - expect(request).to.have.property('location'); - const location = request.location; - expect(location).to.have.property('page').and.to.equal('page'); - expect(location).to.have.property('location').and.to.equal('location'); - expect(location).to.have.property('domain').and.to.equal('domain'); - expect(location).to.have.property('ref').and.to.equal('ref'); - expect(location).to.have.property('isAmp').and.to.equal(false); + it('should send currency if defined', function () { + config.setConfig({currency: {adServerCurrency: 'EUR'}}); + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + + assert.deepEqual(request.cur, [{adServerCurrency: 'EUR'}]); }); - it('should build request with ortb2 info', function () { - const ortb2Request = deepClone(defaultRequest); - ortb2Request.ortb2 = { - site: { - name: 'name' + it('should pass supply chain object', function () { + let validBidRequests = { + ...bid, + schain: { + validation: 'strict', + config: { + ver: '1.0' + } } }; - const request = JSON.parse(spec.buildRequests([ortb2Request], {}).data)[0]; - expect(request).to.have.property('ortb2').and.to.deep.equal({ - site: { - name: 'name' + + let request = JSON.parse(spec.buildRequests([validBidRequests], bidderRequest).data); + assert.deepEqual(request.source.ext.schain, { + validation: 'strict', + config: { + ver: '1.0' } }); }); - it('should build request with ortb2Imp info', function () { - const ortb2ImpRequest = deepClone(defaultRequest); - ortb2ImpRequest.ortb2Imp = { - ext: { - data: { - pbadslot: 'home1', - adUnitSpecificAttribute: '1' + it('should pass extended ids if exists', function () { + let validBidRequests = { + ...bid, + userIdAsEids: [ + { + source: 'adserver.org', + uids: [ + { + id: 'TTD_ID_FROM_USER_ID_MODULE', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + } + ] } - } + ] }; - const request = JSON.parse(spec.buildRequests([ortb2ImpRequest], {}).data)[0]; - expect(request).to.have.property('ortb2Imp').and.to.deep.equal({ - ext: { - data: { - pbadslot: 'home1', - adUnitSpecificAttribute: '1' - } - } - }); - }); - it('should build request with valid bidfloor', function () { - const bfRequest = deepClone(defaultRequest); - bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); - const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0]; - expect(request).to.have.property('floor').and.to.equal(5); + let request = JSON.parse(spec.buildRequests([validBidRequests], bidderRequest).data); + + assert.deepEqual(request.user.ext.eids, validBidRequests.userIdAsEids); }); - it('should build request with gdpr consent data if applies', function () { - const bidderRequest = { + it('should pass gdpr consent data if gdprApplies', function () { + let consentedBidderRequest = { + ...bidderRequest, gdprConsent: { gdprApplies: true, - consentString: 'qwerty' + consentString: 'consentDataString' } }; - const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; - expect(request).to.have.property('gdprApplies').and.equals(1); - expect(request).to.have.property('consentString').and.equals('qwerty'); - }); - it('should build request with usp consent data if applies', function () { - const bidderRequest = { - uspConsent: '1YA-' - }; - const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; - expect(request).to.have.property('usPrivacy').and.equals('1YA-'); - }); - - it('should build request with extended ids', function () { - const idRequest = deepClone(defaultRequest); - idRequest.userIdAsEids = [ - {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, - {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} - ]; - const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; - expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); - }); - - it('should build request with video', function () { - const request = JSON.parse(spec.buildRequests([defaultRequestVideo], {}).data)[0]; - expect(request).to.have.property('video').and.to.deep.equal({ - playerSize: [640, 480], - context: 'instream', - skipppable: true - }); - expect(request).to.have.property('sizes').and.to.deep.equal(['640x480']); + let request = JSON.parse(spec.buildRequests([bid], consentedBidderRequest).data); + assert.equal(request.user.ext.consent, consentedBidderRequest.gdprConsent.consentString); + assert.equal(request.regs.ext.gdpr, consentedBidderRequest.gdprConsent.gdprApplies); + assert.equal(typeof request.regs.ext.gdpr, 'number'); }); }); describe('interpretResponse', function () { - it('should return empty bids', function () { - const serverResponse = { - body: { - data: null - } - }; - - const invalidResponse = spec.interpretResponse(serverResponse, {}); - expect(invalidResponse).to.be.an('array').that.is.empty; + it('should return empty array if no body in response', function () { + assert.ok(spec.interpretResponse([])); }); - it('should interpret valid response', function () { - const serverResponse = { - body: { - data: [{ - requestId: 'qwerty', - cpm: 1, - currency: 'USD', - width: 300, - height: 250, - ttl: 600, - meta: { - advertiserDomains: ['digitalmatter'] - }, - ext: { - pixels: [ - ['iframe', 'surl1'], - ['image', 'surl2'], - ] - } - }] + it('should return array with bids if response not empty', function () { + const firstResponse = { + id: 'id_1', + impid: 'impId_1', + bidid: 'bidId_1', + adunitcode: 'adUnitCode_1', + cpm: 0.10, + ad: '

ad', + adomain: [ + 'advertiser.org' + ], + width: 970, + height: 250, + creativeid: 'creativeId_1', + meta: { + advertiserDomains: [ + 'advertiser.org' + ] } }; - - const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); - const bid = validResponse[0]; - expect(validResponse).to.be.an('array').that.is.not.empty; - expect(bid.requestId).to.equal('qwerty'); - expect(bid.cpm).to.equal(1); - expect(bid.currency).to.equal('USD'); - expect(bid.width).to.equal(300); - expect(bid.height).to.equal(250); - expect(bid.ttl).to.equal(600); - expect(bid.meta).to.deep.equal({advertiserDomains: ['digitalmatter']}); - }); - - it('should interpret valid banner response', function () { - const serverResponse = { - body: { - data: [{ - requestId: 'qwerty', - cpm: 1, - currency: 'USD', - width: 300, - height: 250, - ttl: 600, - mediaType: 'banner', - creativeId: 'demo-banner', - ad: 'ad', - meta: {} - }] + const secondResponse = { + 'id': 'id_2', + 'impid': 'impId_2', + 'bidid': 'bidId_2', + 'adunitcode': 'adUnitCode_2', + 'cpm': 0.11, + 'ad': '

ad', + 'adomain': [ + 'advertiser.org' + ], + 'width': 970, + 'height': 250, + 'creativeid': 'creativeId_2', + 'meta': { + 'advertiserDomains': [ + 'advertiser.org' + ] } }; + const currency = 'EUR'; - const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); - const bid = validResponseBanner[0]; - expect(validResponseBanner).to.be.an('array').that.is.not.empty; - expect(bid.mediaType).to.equal('banner'); - expect(bid.creativeId).to.equal('demo-banner'); - expect(bid.ad).to.equal('ad'); - }); - - it('should interpret valid video response', function () { - const serverResponse = { + const bids = spec.interpretResponse({ body: { - data: [{ - requestId: 'qwerty', - cpm: 1, - currency: 'USD', - width: 600, - height: 480, - ttl: 600, - mediaType: 'video', - creativeId: 'demo-video', - ad: 'vast-xml', - meta: {} - }] + id: 'randomId', + cur: currency, + bids: [ + firstResponse, + secondResponse + ] } - }; + }); - const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: videoBidderRequest}); - const bid = validResponseBanner[0]; - expect(validResponseBanner).to.be.an('array').that.is.not.empty; - expect(bid.mediaType).to.equal('video'); - expect(bid.creativeId).to.equal('demo-video'); - expect(bid.ad).to.equal('vast-xml'); + assert.ok(bids); + assert.deepEqual(bids[0].requestId, firstResponse.bidid); + assert.deepEqual(bids[0].cpm, firstResponse.cpm); + assert.deepEqual(bids[0].creativeId, firstResponse.creativeid); + assert.deepEqual(bids[0].ttl, 300); + assert.deepEqual(bids[0].netRevenue, true); + assert.deepEqual(bids[0].currency, currency); + assert.deepEqual(bids[0].width, firstResponse.width); + assert.deepEqual(bids[0].height, firstResponse.height); + assert.deepEqual(bids[0].dealId, undefined); + assert.deepEqual(bids[0].meta.advertiserDomains, [ 'advertiser.org' ]); + + assert.deepEqual(bids[1].requestId, secondResponse.bidid); + assert.deepEqual(bids[1].cpm, secondResponse.cpm); + assert.deepEqual(bids[1].creativeId, secondResponse.creativeid); + assert.deepEqual(bids[1].ttl, 300); + assert.deepEqual(bids[1].netRevenue, true); + assert.deepEqual(bids[1].currency, currency); + assert.deepEqual(bids[1].width, secondResponse.width); + assert.deepEqual(bids[1].height, secondResponse.height); + assert.deepEqual(bids[1].dealId, undefined); + assert.deepEqual(bids[1].meta.advertiserDomains, [ 'advertiser.org' ]); }); }); describe('getUserSyncs', function () { - it('shoukd handle no params', function () { - const opts = spec.getUserSyncs({}, []); - expect(opts).to.be.an('array').that.is.empty; - }); - - it('should return empty if sync is not allowed', function () { - const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); - expect(opts).to.be.an('array').that.is.empty; - }); - - it('should allow iframe sync', function () { - const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [{ - body: { - data: [{ - requestId: 'qwerty', - ext: { - pixels: [ - ['iframe', 'surl1?a=b'], - ['image', 'surl2?a=b'], - ] - } - }] - } - }]); - expect(opts.length).to.equal(1); - expect(opts[0].type).to.equal('iframe'); - expect(opts[0].url).to.equal('surl1?a=b&us_privacy=&gdpr=0&gdpr_consent='); - }); - - it('should allow pixel sync', function () { - const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ - body: { - data: [{ - requestId: 'qwerty', - ext: { - pixels: [ - ['iframe', 'surl1?a=b'], - ['image', 'surl2?a=b'], - ] - } - }] - } - }]); - expect(opts.length).to.equal(1); - expect(opts[0].type).to.equal('image'); - expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=0&gdpr_consent='); - }); - - it('should allow pixel sync and parse consent params', function () { - const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ - body: { - data: [{ - requestId: 'qwerty', - ext: { - pixels: [ - ['iframe', 'surl1?a=b'], - ['image', 'surl2?a=b'], - ] - } - }] - } - }], { - gdprApplies: 1, - consentString: '1YA-' - }); - expect(opts.length).to.equal(1); - expect(opts[0].type).to.equal('image'); - expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=1&gdpr_consent=1YA-'); - }); - }); - - describe('getBidFloor', function () { - it('should return null when getFloor is not a function', () => { - const bid = {getFloor: 2}; - const result = getBidFloor(bid); - expect(result).to.be.null; - }); - - it('should return null when getFloor doesnt return an object', () => { - const bid = {getFloor: () => 2}; - const result = getBidFloor(bid); - expect(result).to.be.null; - }); - - it('should return null when floor is not a number', () => { - const bid = { - getFloor: () => ({floor: 'string', currency: 'USD'}) - }; - const result = getBidFloor(bid); - expect(result).to.be.null; - }); - - it('should return null when currency is not USD', () => { - const bid = { - getFloor: () => ({floor: 5, currency: 'EUR'}) - }; - const result = getBidFloor(bid); - expect(result).to.be.null; - }); - - it('should return floor value when everything is correct', () => { - const bid = { - getFloor: () => ({floor: 5, currency: 'USD'}) - }; - const result = getBidFloor(bid); - expect(result).to.equal(5); + it('handle empty array (e.g. timeout)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); }); }); -}) +}); From ac5fcc52f77dc476eb20c098130be6f9e79290fc Mon Sep 17 00:00:00 2001 From: digital-matter Date: Tue, 17 Sep 2024 09:18:31 +0300 Subject: [PATCH 2/2] Digital Matter Bid Adapter: add aliases --- modules/digitalMatterBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/digitalMatterBidAdapter.js b/modules/digitalMatterBidAdapter.js index da64a133e57..c6663434d1e 100644 --- a/modules/digitalMatterBidAdapter.js +++ b/modules/digitalMatterBidAdapter.js @@ -10,7 +10,7 @@ const ENDPOINT_URL = 'https://adx.digitalmatter.services/' export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], - aliases: [], + aliases: ['dichange', 'digitalmatter'], bidParameters: ['accountId', 'siteId'], isBidRequestValid: function (bid) { if (typeof bid.params !== 'object') {