• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

prebid / Prebid.js / 21444465511

28 Jan 2026 03:32PM UTC coverage: 96.22% (+0.003%) from 96.217%
21444465511

push

github

web-flow
Taboola support extra signals (#14299)

* add deferredBilling support using onBidBillable

* update burl setting

* support nurl firing logic

* add extra signals to taboola request

* add extra ad signals

* fix missing semicolon

* use Prebid's built-in counters

* updated detectBot logic

41716 of 51289 branches covered (81.34%)

216 of 221 new or added lines in 2 files covered. (97.74%)

130 existing lines in 9 files now uncovered.

209165 of 217382 relevant lines covered (96.22%)

71.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.62
/test/spec/unit/pbjs_api_spec.js
1
import {
2
  createBidReceived,
3
  getAdServerTargeting,
4
  getAdUnits,
5
  getBidRequests,
6
  getBidResponses,
7
  getBidResponsesFromAPI,
8
  getTargetingKeys,
9
  getTargetingKeysBidLandscape
10
} from 'test/fixtures/fixtures.js';
11
import {auctionManager, newAuctionManager} from 'src/auctionManager.js';
12
import {filters, newTargeting, targeting} from 'src/targeting.js';
13
import {config as configObj} from 'src/config.js';
14
import * as ajaxLib from 'src/ajax.js';
15
import * as auctionModule from 'src/auction.js';
16
import {resetAuctionState} from 'src/auction.js';
17
import {registerBidder} from 'src/adapters/bidderFactory.js';
18
import * as pbjsModule from 'src/prebid.js';
19
import pbjs, {resetQueSetup, startAuction} from 'src/prebid.js';
20
import {hook} from '../../../src/hook.js';
21
import {reset as resetDebugging} from '../../../src/debugging.js';
22
import {stubAuctionIndex} from '../../helpers/indexStub.js';
23
import {createBid} from '../../../src/bidfactory.js';
24
import {enrichFPD} from '../../../src/fpd/enrichment.js';
25
import {mockFpdEnrichments} from '../../helpers/fpd.js';
26
import {deepAccess, deepSetValue, generateUUID} from '../../../src/utils.js';
27
import {getCreativeRenderer} from '../../../src/creativeRenderers.js';
28
import {BID_STATUS, EVENTS, GRANULARITY_OPTIONS, PB_LOCATOR, TARGETING_KEYS} from 'src/constants.js';
29
import {getBidToRender} from '../../../src/adRendering.js';
30
import {getGlobal} from '../../../src/prebidGlobal.js';
31

32
var assert = require('chai').assert;
1✔
33
var expect = require('chai').expect;
1✔
34

35
var utils = require('src/utils');
1✔
36
var adapterManager = require('src/adapterManager').default;
1✔
37
var events = require('src/events');
1✔
38

39
// These bid adapters are required to be loaded for the following tests to work
40
require('modules/appnexusBidAdapter');
1✔
41

42
var config = require('test/fixtures/config.json');
1✔
43

44
var adUnits = getAdUnits();
1✔
45
var adUnitCodes = getAdUnits().map(unit => unit.code);
2✔
46
var bidsBackHandler = function() {};
1✔
47
const timeout = 2000;
1✔
48
const auctionId = generateUUID();
1✔
49
let auction;
50

51
function resetAuction() {
52
  if (auction == null) {
95✔
53
    auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout: timeout, labels: undefined, auctionId: auctionId});
1✔
54
  }
55
  pbjs.setConfig({ enableSendAllBids: false });
95✔
56
  auction.getBidRequests = getBidRequests;
95✔
57
  auction.getBidsReceived = getBidResponses;
95✔
58
  auction.getAdUnits = getAdUnits;
95✔
59
  auction.getAuctionStatus = function() { return auctionModule.AUCTION_COMPLETED }
95✔
60
}
61

62
var Slot = function Slot(elementId, pathId) {
1✔
63
  var slot = {
30✔
64
    targeting: {},
65
    getSlotElementId: function getSlotElementId() {
66
      return elementId;
51✔
67
    },
68

69
    getAdUnitPath: function getAdUnitPath() {
70
      return pathId;
46✔
71
    },
72

73
    setTargeting: function setTargeting(key, value) {
74
      this.targeting[key] = Array.isArray(value) ? value : [value];
901✔
75
    },
76

77
    getTargeting: function getTargeting(key) {
78
      return this.targeting[key] || [];
258!
79
    },
80

81
    getTargetingKeys: function getTargetingKeys() {
82
      return Object.getOwnPropertyNames(this.targeting);
6✔
83
    },
84

85
    clearTargeting: function clearTargeting() {
86
      this.targeting = {};
×
87
      return this;
×
88
    },
89

90
    updateTargetingFromMap: function updateTargetingFromMap(targetingMap) {
91
      Object.keys(targetingMap).forEach(key => this.setTargeting(key, targetingMap[key]))
889✔
92
    }
93
  };
94
  slot.spySetTargeting = sinon.spy(slot, 'setTargeting');
30✔
95
  slot.spyGetSlotElementId = sinon.spy(slot, 'getSlotElementId');
30✔
96
  return slot;
30✔
97
};
98

99
var createSlotArray = function createSlotArray() {
1✔
100
  return [
6✔
101
    new Slot(config.adUnitElementIDs[0], config.adUnitCodes[0]),
102
    new Slot(config.adUnitElementIDs[1], config.adUnitCodes[1]),
103
    new Slot(config.adUnitElementIDs[2], config.adUnitCodes[2])
104
  ];
105
};
106

107
var createSlotArrayScenario2 = function createSlotArrayScenario2() {
1✔
108
  var slot1 = new Slot(config.adUnitElementIDs[0], config.adUnitCodes[0]);
6✔
109
  slot1.setTargeting('pos1', '750x350');
6✔
110
  var slot2 = new Slot(config.adUnitElementIDs[1], config.adUnitCodes[0]);
6✔
111
  slot2.setTargeting('pos1', '750x350');
6✔
112
  return [
6✔
113
    slot1,
114
    slot2
115
  ];
116
};
117

118
window.googletag = {
1✔
119
  _slots: [],
120
  _targeting: {},
121
  pubads: function () {
122
    var self = this;
170✔
123
    return {
170✔
124
      getSlots: function () {
125
        return self._slots;
70✔
126
      },
127

128
      setSlots: function (slots) {
129
        self._slots = slots;
12✔
130
      },
131

132
      setTargeting: function(key, arrayOfValues) {
133
        self._targeting[key] = Array.isArray(arrayOfValues) ? arrayOfValues : [arrayOfValues];
×
134
      },
135

136
      getTargeting: function(key) {
137
        return self._targeting[key] || [];
×
138
      },
139

140
      getTargetingKeys: function() {
141
        return Object.getOwnPropertyNames(self._targeting);
×
142
      },
143

144
      clearTargeting: function() {
145
        self._targeting = {};
×
146
      }
147
    };
148
  }
149
};
150

151
var createTagAST = function() {
1✔
152
  var tags = {};
1✔
153
  tags[config.adUnitCodes[0]] = {
1✔
154
    keywords: {}
155
  };
156
  return tags;
1✔
157
};
158

159
window.apntag = {
1✔
160
  keywords: [],
161
  tags: createTagAST(),
162
  setKeywords: function(key, params, options) {
163
    var self = this;
25✔
164
    if (!self.tags.hasOwnProperty(key)) {
25!
165
      return;
×
166
    }
167
    self.tags[key].keywords = this.tags[key].keywords || {};
25!
168

169
    if (typeof options === 'object' && options !== null && options.overrideKeyValue === true) {
25!
170
      utils._each(params, function(param, id) {
25✔
171
        self.tags[key].keywords[id] = param;
25✔
172
      });
173
    } else {
174
      utils._each(params, function (param, id) {
×
175
        if (!self.tags[key].keywords.hasOwnProperty(id)) {
×
176
          self.tags[key].keywords[id] = param;
×
177
        } else if (!utils.isArray(self.tags[key].keywords[id])) {
×
178
          self.tags[key].keywords[id] = [self.tags[key].keywords[id]].concat(param);
×
179
        } else {
180
          self.tags[key].keywords[id] = self.tags[key].keywords[id].concat(param);
×
181
        }
182
      })
183
    }
184
  },
185
  getTag: function(tagId) {
186
    return this.tags[tagId];
5✔
187
  },
188
  modifyTag: function(tagId, params) {
189
    var output = {};
4✔
190

191
    utils._each(this.tags[tagId], function(tag, id) {
4✔
192
      output[id] = tag;
4✔
193
    });
194

195
    utils._each(params, function(param, id) {
4✔
196
      output[id] = param;
4✔
197
    });
198

199
    this.tags[tagId] = output;
4✔
200
  }
201
}
202

203
describe('Unit: Prebid Module', function () {
1✔
204
  let bidExpiryStub, sandbox;
205
  before((done) => {
1✔
206
    hook.ready();
1✔
207
    pbjsModule.requestBids.getHooks().remove();
1✔
208
    resetDebugging();
1✔
209
    // preload creative renderer
210
    getCreativeRenderer({}).then(() => done());
1✔
211
  });
212

213
  beforeEach(function () {
1✔
214
    sandbox = sinon.createSandbox();
183✔
215
    mockFpdEnrichments(sandbox);
183✔
216
    bidExpiryStub = sinon.stub(filters, 'isBidNotExpired').callsFake(() => true);
267✔
217
    configObj.setConfig({ useBidCache: true });
183✔
218
    resetAuctionState();
183✔
219
  });
220

221
  afterEach(function() {
1✔
222
    sandbox.restore();
183✔
223
    pbjs.adUnits = [];
183✔
224
    bidExpiryStub.restore();
183✔
225
    configObj.setConfig({ useBidCache: false });
183✔
226
  });
227

228
  after(function() {
1✔
229
    auctionManager.clearAllAuctions();
1✔
230
  });
231

232
  describe('processQueue', () => {
1✔
233
    it('should insert a locator frame on the page', () => {
1✔
234
      pbjs.processQueue();
1✔
235
      expect(window.frames[PB_LOCATOR]).to.exist;
1✔
236
    });
237

238
    ['cmd', 'que'].forEach(prop => {
1✔
239
      describe(`using .${prop}`, () => {
2✔
240
        let queue, ran;
241
        beforeEach(() => {
2✔
242
          ran = false;
4✔
243
          queue = pbjs[prop] = [];
4✔
244
          resetQueSetup();
4✔
245
        });
246
        after(() => {
2✔
247
          pbjs.processQueue();
2✔
248
        })
249

250
        function pushToQueue(fn = () => { ran = true }) {
6✔
251
          return new Promise((resolve) => {
6✔
252
            queue.push(() => {
6✔
253
              fn();
6✔
254
              resolve();
6✔
255
            });
256
          })
257
        }
258

259
        it(`should patch .push`, async () => {
2✔
260
          pbjs.processQueue();
2✔
261
          await pushToQueue();
2✔
262
          expect(ran).to.be.true;
2✔
263
        });
264

265
        it('should respect insertion order', async () => {
2✔
266
          const log = [];
2✔
267
          pushToQueue(() => log.push(1));
2✔
268
          pbjs.processQueue();
2✔
269
          await pushToQueue(() => log.push(2));
2✔
270
          expect(log).to.eql([1, 2]);
2✔
271
        });
272
      })
273
    });
274
  })
275

276
  describe('and global adUnits', () => {
1✔
277
    const startingAdUnits = [
1✔
278
      {
279
        code: 'one',
280
      },
281
      {
282
        code: 'two',
283
      }
284
    ];
285
    let actualAdUnits, hookRan, done;
286

287
    function deferringHook(next, req) {
288
      setTimeout(() => {
2✔
289
        actualAdUnits = req.adUnits || pbjs.adUnits;
2!
290
        done();
2✔
291
      });
292
    }
293

294
    beforeEach(() => {
1✔
295
      pbjsModule.requestBids.before(deferringHook, 99);
2✔
296
      hookRan = new Promise((resolve) => {
2✔
297
        done = resolve;
2✔
298
      });
299
      pbjs.adUnits.splice(0, pbjs.adUnits.length, ...startingAdUnits);
2✔
300
    });
301

302
    afterEach(() => {
1✔
303
      pbjsModule.requestBids.getHooks({hook: deferringHook}).remove();
2✔
304
      pbjs.adUnits.splice(0, pbjs.adUnits.length);
2✔
305
    })
306

307
    Object.entries({
1✔
308
      'addAdUnits': (g) => g.addAdUnits({code: 'three'}),
1✔
309
      'removeAdUnit': (g) => g.removeAdUnit('one')
1✔
310
    }).forEach(([method, op]) => {
2✔
311
      it(`once called, should not be affected by ${method}`, () => {
2✔
312
        pbjs.requestBids({});
2✔
313
        op(pbjs);
2✔
314
        return hookRan.then(() => {
2✔
315
          expect(actualAdUnits).to.eql(startingAdUnits);
2✔
316
        })
317
      });
318
    });
319
  });
320

321
  describe('getAdserverTargetingForAdUnitCodeStr', function () {
1✔
322
    beforeEach(function () {
1✔
323
      resetAuction();
2✔
324
    });
325

326
    it('should return targeting info as a string', function () {
1✔
327
      const adUnitCode = config.adUnitCodes[0];
1✔
328
      pbjs.setConfig({ enableSendAllBids: true, targetingControls: { allBidsCustomTargeting: true } });
1✔
329
      var expectedResults = [`foobar=300x250%2C300x600%2C0x0`, `${TARGETING_KEYS.SIZE}=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}=10.00`, `${TARGETING_KEYS.AD_ID}=233bcbee889d46d`, `${TARGETING_KEYS.BIDDER}=appnexus`, `${TARGETING_KEYS.SIZE}_triplelift=0x0`, `${TARGETING_KEYS.PRICE_BUCKET}_triplelift=10.00`, `${TARGETING_KEYS.AD_ID}_triplelift=222bb26f9e8bd`, `${TARGETING_KEYS.BIDDER}_triplelift=triplelift`, `${TARGETING_KEYS.SIZE}_appnexus=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}_appnexus=10.00`, `${TARGETING_KEYS.AD_ID}_appnexus=233bcbee889d46d`, `${TARGETING_KEYS.BIDDER}_appnexus=appnexus`, `${TARGETING_KEYS.SIZE}_pagescience=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}_pagescience=10.00`, `${TARGETING_KEYS.AD_ID}_pagescience=25bedd4813632d7`, `${TARGETING_KEYS.BIDDER}_pagescienc=pagescience`, `${TARGETING_KEYS.SIZE}_brightcom=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}_brightcom=10.00`, `${TARGETING_KEYS.AD_ID}_brightcom=26e0795ab963896`, `${TARGETING_KEYS.BIDDER}_brightcom=brightcom`, `${TARGETING_KEYS.SIZE}_brealtime=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}_brealtime=10.00`, `${TARGETING_KEYS.AD_ID}_brealtime=275bd666f5a5a5d`, `${TARGETING_KEYS.BIDDER}_brealtime=brealtime`, `${TARGETING_KEYS.SIZE}_pubmatic=300x250`, `${TARGETING_KEYS.PRICE_BUCKET}_pubmatic=10.00`, `${TARGETING_KEYS.AD_ID}_pubmatic=28f4039c636b6a7`, `${TARGETING_KEYS.BIDDER}_pubmatic=pubmatic`, `${TARGETING_KEYS.SIZE}_rubicon=300x600`, `${TARGETING_KEYS.PRICE_BUCKET}_rubicon=10.00`, `${TARGETING_KEYS.AD_ID}_rubicon=29019e2ab586a5a`, `${TARGETING_KEYS.BIDDER}_rubicon=rubicon`];
1✔
330
      var result = pbjs.getAdserverTargetingForAdUnitCodeStr(adUnitCode);
1✔
331

332
      expectedResults.forEach(expected => {
1✔
333
        expect(result).to.include(expected);
33✔
334
      })
335
    });
336

337
    it('should log message if adunitCode param is falsey', function () {
1✔
338
      var spyLogMessage = sinon.spy(utils, 'logMessage');
1✔
339
      var result = pbjs.getAdserverTargetingForAdUnitCodeStr();
1✔
340
      assert.ok(spyLogMessage.calledWith('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'), 'expected message was logged');
1✔
341
      assert.equal(result, undefined, 'result is undefined');
1✔
342
      utils.logMessage.restore();
1✔
343
    });
344
  });
345

346
  describe('getAdserverTargetingForAdUnitCode', function () {
1✔
347
    it('should return targeting info as an object', function () {
1✔
348
      const adUnitCode = config.adUnitCodes[0];
1✔
349
      pbjs.setConfig({ enableSendAllBids: true });
1✔
350
      var result = pbjs.getAdserverTargetingForAdUnitCode(adUnitCode);
1✔
351
      const expected = getAdServerTargeting()[adUnitCode];
1✔
352
      sinon.assert.match(result, expected);
1✔
353
    });
354
  });
355

356
  describe('getAdServerTargeting', function () {
1✔
357
    beforeEach(function () {
1✔
358
      resetAuction();
6✔
359
    });
360

361
    afterEach(function () {
1✔
362
      resetAuction();
6✔
363
    });
364

365
    it('should return current targeting data for slots', function () {
1✔
366
      pbjs.setConfig({ enableSendAllBids: true });
1✔
367
      const targeting = pbjs.getAdserverTargeting(['/19968336/header-bid-tag-0', '/19968336/header-bid-tag1']);
1✔
368
      const expected = getAdServerTargeting(['/19968336/header-bid-tag-0, /19968336/header-bid-tag1']);
1✔
369
      sinon.assert.match(targeting, expected);
1✔
370
    });
371

372
    it('should return correct targeting with default settings', function () {
1✔
373
      var targeting = pbjs.getAdserverTargeting(['/19968336/header-bid-tag-0', '/19968336/header-bid-tag1']);
1✔
374
      var expected = {
1✔
375
        '/19968336/header-bid-tag-0': {
376
          foobar: '300x250,300x600,0x0',
377
          [TARGETING_KEYS.SIZE]: '300x250',
378
          [TARGETING_KEYS.PRICE_BUCKET]: '10.00',
379
          [TARGETING_KEYS.AD_ID]: '233bcbee889d46d',
380
          [TARGETING_KEYS.BIDDER]: 'appnexus'
381
        },
382
        '/19968336/header-bid-tag1': {
383
          foobar: '728x90',
384
          [TARGETING_KEYS.SIZE]: '728x90',
385
          [TARGETING_KEYS.PRICE_BUCKET]: '10.00',
386
          [TARGETING_KEYS.AD_ID]: '24bd938435ec3fc',
387
          [TARGETING_KEYS.BIDDER]: 'appnexus'
388
        }
389
      };
390
      sinon.assert.match(targeting, expected);
1✔
391
    });
392

393
    it('should return correct targeting with bid landscape targeting on', function () {
1✔
394
      pbjs.setConfig({ enableSendAllBids: true, targetingControls: { allBidsCustomTargeting: true } });
1✔
395
      var targeting = pbjs.getAdserverTargeting(['/19968336/header-bid-tag-0', '/19968336/header-bid-tag1']);
1✔
396
      var expected = getAdServerTargeting(['/19968336/header-bid-tag-0', '/19968336/header-bid-tag1']);
1✔
397
      sinon.assert.match(targeting, expected);
1✔
398
    });
399

400
    it("should include a losing bid's custom ad targeting key", function () {
1✔
401
      // Let's make sure we're getting the expected losing bid.
402
      assert.equal(auction.getBidsReceived()[0]['bidderCode'], 'triplelift');
1✔
403
      assert.equal(auction.getBidsReceived()[0]['cpm'], 0.112256);
1✔
404

405
      // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key.
406
      const _bidsReceived = getBidResponses();
1✔
407
      _bidsReceived[0]['adserverTargeting'] = {
1✔
408
        always_use_me: 'abc',
409
      };
410

411
      auction.getBidsReceived = function() { return _bidsReceived };
1✔
412

413
      var targeting = pbjs.getAdserverTargeting(['/19968336/header-bid-tag-0', '/19968336/header-bid-tag1']);
1✔
414

415
      // Ensure targeting for both ad placements includes the custom key.
416
      assert.equal(
1✔
417
        targeting['/19968336/header-bid-tag-0'].hasOwnProperty('always_use_me'),
418
        true
419
      );
420

421
      var expected = {
1✔
422
        '/19968336/header-bid-tag-0': {
423
          foobar: '300x250,300x600',
424
          always_use_me: 'abc',
425
          [TARGETING_KEYS.SIZE]: '300x250',
426
          [TARGETING_KEYS.PRICE_BUCKET]: '10.00',
427
          [TARGETING_KEYS.AD_ID]: '233bcbee889d46d',
428
          [TARGETING_KEYS.BIDDER]: 'appnexus'
429
        },
430
        '/19968336/header-bid-tag1': {
431
          foobar: '728x90',
432
          [TARGETING_KEYS.SIZE]: '728x90',
433
          [TARGETING_KEYS.PRICE_BUCKET]: '10.00',
434
          [TARGETING_KEYS.AD_ID]: '24bd938435ec3fc',
435
          [TARGETING_KEYS.BIDDER]: 'appnexus'
436
        }
437
      };
438
      sinon.assert.match(targeting, expected);
1✔
439
    });
440

441
    it('should not overwrite winning bids custom keys targeting key', function () {
1✔
442
      resetAuction();
1✔
443
      // mimic a bidderSetting.standard key here for each bid and alwaysUseBid true for every bid
444
      const _bidsReceived = getBidResponses();
1✔
445
      _bidsReceived.forEach(bid => {
1✔
446
        bid.adserverTargeting.custom_ad_id = bid.adId;
8✔
447
      });
448

449
      auction.getBidsReceived = function() { return _bidsReceived };
1✔
450

451
      pbjs.bidderSettings = {
1✔
452
        'standard': {
453
          adserverTargeting: [{
454
            key: TARGETING_KEYS.BIDDER,
455
            val: function(bidResponse) {
UNCOV
456
              return bidResponse.bidderCode;
×
457
            }
458
          }, {
459
            key: 'custom_ad_id',
460
            val: function(bidResponse) {
UNCOV
461
              return bidResponse.adId;
×
462
            }
463
          }, {
464
            key: TARGETING_KEYS.PRICE_BUCKET,
465
            val: function(bidResponse) {
UNCOV
466
              return bidResponse.pbMg;
×
467
            }
468
          }, {
469
            key: 'foobar',
470
            val: function(bidResponse) {
UNCOV
471
              return bidResponse.size;
×
472
            }
473
          }]
474
        }
475
      };
476

477
      var targeting = pbjs.getAdserverTargeting(['/19968336/header-bid-tag-0', '/19968336/header-bid-tag1']);
1✔
478

479
      var expected = {
1✔
480
        '/19968336/header-bid-tag-0': {
481
          foobar: '300x250',
482
          custom_ad_id: '233bcbee889d46d',
483
          [TARGETING_KEYS.SIZE]: '300x250',
484
          [TARGETING_KEYS.PRICE_BUCKET]: '10.00',
485
          [TARGETING_KEYS.AD_ID]: '233bcbee889d46d',
486
          [TARGETING_KEYS.BIDDER]: 'appnexus'
487
        },
488
        '/19968336/header-bid-tag1': {
489
          foobar: '728x90',
490
          [TARGETING_KEYS.SIZE]: '728x90',
491
          [TARGETING_KEYS.PRICE_BUCKET]: '10.00',
492
          [TARGETING_KEYS.AD_ID]: '24bd938435ec3fc',
493
          [TARGETING_KEYS.BIDDER]: 'appnexus',
494
          custom_ad_id: '24bd938435ec3fc'
495
        }
496
      };
497
      sinon.assert.match(targeting, expected);
1✔
498
      pbjs.bidderSettings = {};
1✔
499
    });
500

501
    it('should not send standard targeting keys when the bid has `sendStandardTargeting` set to `false`', function () {
1✔
502
      const _bidsReceived = getBidResponses();
1✔
503
      _bidsReceived.forEach(bid => {
1✔
504
        bid.adserverTargeting.custom_ad_id = bid.adId;
8✔
505
        bid.sendStandardTargeting = false;
8✔
506
      });
507

508
      auction.getBidsReceived = function() { return _bidsReceived };
1✔
509

510
      var targeting = pbjs.getAdserverTargeting(['/19968336/header-bid-tag-0', '/19968336/header-bid-tag1']);
1✔
511

512
      var expected = {
1✔
513
        '/19968336/header-bid-tag-0': {
514
          foobar: '300x250,300x600,0x0',
515
          custom_ad_id: '233bcbee889d46d,28f4039c636b6a7,29019e2ab586a5a,25bedd4813632d7,275bd666f5a5a5d,26e0795ab963896,222bb26f9e8bd'
516
        },
517
        '/19968336/header-bid-tag1': {
518
          foobar: '728x90',
519
          custom_ad_id: '24bd938435ec3fc'
520
        }
521
      };
522
      sinon.assert.match(targeting, expected);
1✔
523
      Object.values(targeting).forEach(targetingMap => {
1✔
524
        expect(targetingMap).to.have.keys(['foobar', 'custom_ad_id', 'hb_ver']);
2✔
525
      })
526
    });
527
  });
528

529
  describe('getAdserverTargeting', function() {
1✔
530
    const customConfigObject = {
1✔
531
      'buckets': [
532
        { 'precision': 2, 'max': 5, 'increment': 0.01 },
533
        { 'precision': 2, 'max': 8, 'increment': 0.05 },
534
        { 'precision': 2, 'max': 20, 'increment': 0.5 },
535
        { 'precision': 2, 'max': 25, 'increment': 1 }
536
      ]
537
    };
538
    let currentPriceBucket;
539
    let bid;
540
    let auction;
541
    let ajaxStub;
542
    let indexStub;
543
    const cbTimeout = 3000;
1✔
544
    let targeting;
545

546
    const RESPONSE = {
1✔
547
      'version': '0.0.1',
548
      'tags': [{
549
        'uuid': '4d0a6829338a07',
550
        'tag_id': 4799418,
551
        'auction_id': '2256922143947979797',
552
        'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad',
553
        'timeout_ms': 2500,
554
        'ads': [{
555
          'content_source': 'rtb',
556
          'ad_type': 'banner',
557
          'buyer_member_id': 958,
558
          'creative_id': 33989846,
559
          'media_type_id': 1,
560
          'media_subtype_id': 1,
561
          'cpm': 1.99,
562
          'cpm_publisher_currency': 0.500000,
563
          'publisher_currency_code': '$',
564
          'client_initiated_ad_counting': true,
565
          'rtb': {
566
            'banner': {
567
              'width': 300,
568
              'height': 250,
569
              'content': '<!-- Creative -->'
570
            },
571
            'trackers': [{
572
              'impression_urls': ['http://lax1-ib.adnxs.com/impression']
573
            }]
574
          },
575
          'viewability': {
576
            'config': '<script type=\'text/javascript\' async=\'true\' src=\'http://cdn.adnxs.com/v/s/152/trk.js#v;vk=appnexus.com-omid;tv=native1-18h;dom_id=%native_dom_id%;st=0;d=1x1;vc=iab;vid_ccr=1;tag_id=13232354;cb=http%3A%2F%2Fams1-ib.adnxs.com%2Fvevent%3Freferrer%3Dhttp%253A%252F%252Ftestpages-pmahe.tp.adnxs.net%252F01_basic_single%26e%3DwqT_3QLNB6DNAwAAAwDWAAUBCLfl_-MFEMStk8u3lPTjRxih88aF0fq_2QsqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlCDy74uWJzxW2AAaM26dXjzjwWAAQGKAQNVU0SSAQEG8FCYAQGgAQGoAQGwAQC4AQHAAQTIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NTE4ODkwNzkpO3VmKCdyJywgOTc0OTQ0MDM2HgDwjZIC8QEha0RXaXBnajgtTHdLRUlQTHZpNFlBQ0NjOFZzd0FEZ0FRQVJJN1VoUTR0R25CbGdBWU1rR2FBQndMSGlrTDRBQlVvZ0JwQy1RQVFHWUFRR2dBUUdvQVFPd0FRQzVBZk90YXFRQUFDUkF3UUh6cldxa0FBQWtRTWtCbWo4dDA1ZU84VF9aQVFBQUEBAyRQQV80QUVBOVFFAQ4sQW1BSUFvQUlBdFFJBRAAdg0IeHdBSUF5QUlBNEFJQTZBSUEtQUlBZ0FNQm1BTUJxQVAFzIh1Z01KUVUxVE1UbzBNekl3NEFPVENBLi6aAmEhUXcxdGNRagUoEfQkblBGYklBUW9BRAl8AEEBqAREbzJEABRRSk1JU1EBGwRBQQGsAFURDAxBQUFXHQzwWNgCAOACrZhI6gIzaHR0cDovL3Rlc3RwYWdlcy1wbWFoZS50cC5hZG54cy5uZXQvMDFfYmFzaWNfc2luZ2xl8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWMhYAPExFQUZfTkFNRRIA8gIeCho2HQAIQVNUAT7wnElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgD8ao-4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIECjEwLjIuMTIuMzioBIqpB7IEDggAEAEYACAAKAAwADgCuAQAwAQAyAQA0gQOOTMyNSNBTVMxOjQzMjDaBAIIAeAEAfAEg8u-LogFAZgFAKAF______8BAxgBwAUAyQUABQEU8D_SBQkJBQt8AAAA2AUB4AUB8AWZ9CH6BQQIABAAkAYBmAYAuAYAwQYBITAAAPA_yAYA2gYWChAAOgEAGBAAGADgBgw.%26s%3D971dce9d49b6bee447c8a58774fb30b40fe98171;ts=1551889079;cet=0;cecb=\'></script>'}
577
        }]
578
      }]
579
    };
580

581
    before(function () {
1✔
582
      pbjs.bidderSettings = {};
1✔
583
      currentPriceBucket = configObj.getConfig('priceGranularity');
1✔
584
      configObj.setConfig({ priceGranularity: customConfigObject });
1✔
585
      sinon.stub(adapterManager, 'makeBidRequests').callsFake(() => ([{
4✔
586
        'bidderCode': 'appnexus',
587
        'auctionId': '20882439e3238c',
588
        'bidderRequestId': '331f3cf3f1d9c8',
589
        'bids': [
590
          {
591
            'bidder': 'appnexus',
592
            'params': {
593
              'placementId': '10433394'
594
            },
595
            'adUnitCode': 'div-gpt-ad-1460505748561-0',
596
            'sizes': [
597
              [
598
                300,
599
                250
600
              ],
601
              [
602
                300,
603
                600
604
              ]
605
            ],
606
            'bidId': '4d0a6829338a07',
607
            'bidderRequestId': '331f3cf3f1d9c8',
608
            'auctionId': '20882439e3238c',
609
            'transactionId': 'trdiv-gpt-ad-1460505748561-0',
610
            'adUnitId': 'audiv-gpt-ad-1460505748561-0',
611
          }
612
        ],
613
        'auctionStart': 1505250713622,
614
        'timeout': 3000
615
      }]
616
      ));
617
    });
618

619
    after(function () {
1✔
620
      configObj.setConfig({ priceGranularity: currentPriceBucket });
1✔
621
      adapterManager.makeBidRequests.restore();
1✔
622
    })
623

624
    beforeEach(function () {
1✔
625
      const auctionManagerInstance = newAuctionManager();
4✔
626
      targeting = newTargeting(auctionManagerInstance);
4✔
627
      const adUnits = [{
4✔
628
        adUnitId: 'audiv-gpt-ad-1460505748561-0',
629
        transactionId: 'trdiv-gpt-ad-1460505748561-0',
630
        code: 'div-gpt-ad-1460505748561-0',
631
        sizes: [[300, 250], [300, 600]],
632
        bids: [{
633
          bidder: 'appnexus',
634
          params: {
635
            placementId: '10433394'
636
          }
637
        }]
638
      }];
639
      const adUnitCodes = ['div-gpt-ad-1460505748561-0'];
4✔
640
      auction = auctionManagerInstance.createAuction({adUnits, adUnitCodes});
4✔
641
      indexStub = sinon.stub(auctionManager, 'index');
4✔
642
      indexStub.get(() => auctionManagerInstance.index);
28✔
643
      ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(function() {
4✔
644
        return function(url, callback) {
4✔
645
          const fakeResponse = sinon.stub();
4✔
646
          fakeResponse.returns('headerContent');
4✔
647
          callback.success(JSON.stringify(RESPONSE), { getResponseHeader: fakeResponse });
4✔
648
        }
649
      });
650
    });
651

652
    afterEach(function () {
1✔
653
      ajaxStub.restore();
4✔
654
      indexStub.restore();
4✔
655
    });
656

657
    it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 0 to 5', async function () {
1✔
658
      RESPONSE.tags[0].ads[0].cpm = 2.1234;
1✔
659
      auction.callBids(cbTimeout);
1✔
660
      await auction.end;
1✔
661
      const bidTargeting = targeting.getAllTargeting();
1✔
662
      expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('2.12');
1✔
663
    });
664

665
    it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 5 to 8', async function () {
1✔
666
      RESPONSE.tags[0].ads[0].cpm = 6.78;
1✔
667
      auction.callBids(cbTimeout);
1✔
668
      await auction.end;
1✔
669
      const bidTargeting = targeting.getAllTargeting();
1✔
670
      expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('6.75');
1✔
671
    });
672

673
    it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 8 to 20', async function () {
1✔
674
      RESPONSE.tags[0].ads[0].cpm = 19.5234;
1✔
675
      auction.callBids(cbTimeout);
1✔
676
      await auction.end;
1✔
677
      const bidTargeting = targeting.getAllTargeting();
1✔
678
      expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('19.50');
1✔
679
    });
680

681
    it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 20 to 25', async function () {
1✔
682
      RESPONSE.tags[0].ads[0].cpm = 21.5234;
1✔
683
      auction.callBids(cbTimeout);
1✔
684
      await auction.end;
1✔
685
      const bidTargeting = targeting.getAllTargeting();
1✔
686
      expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('21.00');
1✔
687
    });
688
  });
689

690
  describe('getAdserverTargeting with `mediaTypePriceGranularity` set for media type', function() {
1✔
691
    let currentPriceBucket;
692
    let auction;
693
    let ajaxStub;
694
    let response;
695
    const cbTimeout = 3000;
1✔
696
    let auctionManagerInstance;
697
    let targeting;
698
    let indexStub;
699

700
    const bannerResponse = {
1✔
701
      'version': '0.0.1',
702
      'tags': [{
703
        'uuid': '4d0a6829338a07',
704
        'tag_id': 4799418,
705
        'auction_id': '2256922143947979797',
706
        'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad',
707
        'timeout_ms': 2500,
708
        'ads': [{
709
          'content_source': 'rtb',
710
          'ad_type': 'banner',
711
          'buyer_member_id': 958,
712
          'creative_id': 33989846,
713
          'media_type_id': 1,
714
          'media_subtype_id': 1,
715
          'cpm': 1.99,
716
          'cpm_publisher_currency': 0.500000,
717
          'publisher_currency_code': '$',
718
          'client_initiated_ad_counting': true,
719
          'rtb': {
720
            'banner': {
721
              'width': 300,
722
              'height': 250,
723
              'content': '<!-- Creative -->'
724
            },
725
            'trackers': [{
726
              'impression_urls': ['http://lax1-ib.adnxs.com/impression']
727
            }]
728
          },
729
          'viewability': {
730
            'config': '<script type=\'text/javascript\' async=\'true\' src=\'http://cdn.adnxs.com/v/s/152/trk.js#v;vk=appnexus.com-omid;tv=native1-18h;dom_id=%native_dom_id%;st=0;d=1x1;vc=iab;vid_ccr=1;tag_id=13232354;cb=http%3A%2F%2Fams1-ib.adnxs.com%2Fvevent%3Freferrer%3Dhttp%253A%252F%252Ftestpages-pmahe.tp.adnxs.net%252F01_basic_single%26e%3DwqT_3QLNB6DNAwAAAwDWAAUBCLfl_-MFEMStk8u3lPTjRxih88aF0fq_2QsqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlCDy74uWJzxW2AAaM26dXjzjwWAAQGKAQNVU0SSAQEG8FCYAQGgAQGoAQGwAQC4AQHAAQTIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NTE4ODkwNzkpO3VmKCdyJywgOTc0OTQ0MDM2HgDwjZIC8QEha0RXaXBnajgtTHdLRUlQTHZpNFlBQ0NjOFZzd0FEZ0FRQVJJN1VoUTR0R25CbGdBWU1rR2FBQndMSGlrTDRBQlVvZ0JwQy1RQVFHWUFRR2dBUUdvQVFPd0FRQzVBZk90YXFRQUFDUkF3UUh6cldxa0FBQWtRTWtCbWo4dDA1ZU84VF9aQVFBQUEBAyRQQV80QUVBOVFFAQ4sQW1BSUFvQUlBdFFJBRAAdg0IeHdBSUF5QUlBNEFJQTZBSUEtQUlBZ0FNQm1BTUJxQVAFzIh1Z01KUVUxVE1UbzBNekl3NEFPVENBLi6aAmEhUXcxdGNRagUoEfQkblBGYklBUW9BRAl8AEEBqAREbzJEABRRSk1JU1EBGwRBQQGsAFURDAxBQUFXHQzwWNgCAOACrZhI6gIzaHR0cDovL3Rlc3RwYWdlcy1wbWFoZS50cC5hZG54cy5uZXQvMDFfYmFzaWNfc2luZ2xl8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWMhYAPExFQUZfTkFNRRIA8gIeCho2HQAIQVNUAT7wnElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgD8ao-4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIECjEwLjIuMTIuMzioBIqpB7IEDggAEAEYACAAKAAwADgCuAQAwAQAyAQA0gQOOTMyNSNBTVMxOjQzMjDaBAIIAeAEAfAEg8u-LogFAZgFAKAF______8BAxgBwAUAyQUABQEU8D_SBQkJBQt8AAAA2AUB4AUB8AWZ9CH6BQQIABAAkAYBmAYAuAYAwQYBITAAAPA_yAYA2gYWChAAOgEAGBAAGADgBgw.%26s%3D971dce9d49b6bee447c8a58774fb30b40fe98171;ts=1551889079;cet=0;cecb=\'></script>'}
731
        }]
732
      }]
733
    };
734
    const videoResponse = {
1✔
735
      'version': '0.0.1',
736
      'tags': [{
737
        'uuid': '4d0a6829338a07',
738
        'tag_id': 4799418,
739
        'auction_id': '2256922143947979797',
740
        'no_ad_url': 'http://lax1-ib.adnxs.com/no-ad',
741
        'timeout_ms': 2500,
742
        'ads': [{
743
          'content_source': 'rtb',
744
          'ad_type': 'video',
745
          'buyer_member_id': 958,
746
          'creative_id': 33989846,
747
          'media_type_id': 1,
748
          'media_subtype_id': 1,
749
          'cpm': 1.99,
750
          'cpm_publisher_currency': 0.500000,
751
          'publisher_currency_code': '$',
752
          'client_initiated_ad_counting': true,
753
          'rtb': {
754
            'video': {
755
              'width': 300,
756
              'height': 250,
757
              'content': '<!-- Creative -->'
758
            },
759
            'trackers': [{
760
              'impression_urls': ['http://lax1-ib.adnxs.com/impression']
761
            }]
762
          },
763
          'viewability': {
764
            'config': '<script type=\'text/javascript\' async=\'true\' src=\'http://cdn.adnxs.com/v/s/152/trk.js#v;vk=appnexus.com-omid;tv=native1-18h;dom_id=%native_dom_id%;st=0;d=1x1;vc=iab;vid_ccr=1;tag_id=13232354;cb=http%3A%2F%2Fams1-ib.adnxs.com%2Fvevent%3Freferrer%3Dhttp%253A%252F%252Ftestpages-pmahe.tp.adnxs.net%252F01_basic_single%26e%3DwqT_3QLNB6DNAwAAAwDWAAUBCLfl_-MFEMStk8u3lPTjRxih88aF0fq_2QsqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlCDy74uWJzxW2AAaM26dXjzjwWAAQGKAQNVU0SSAQEG8FCYAQGgAQGoAQGwAQC4AQHAAQTIAQLQAQDYAQDgAQDwAQCKAjt1ZignYScsIDI1Mjk4ODUsIDE1NTE4ODkwNzkpO3VmKCdyJywgOTc0OTQ0MDM2HgDwjZIC8QEha0RXaXBnajgtTHdLRUlQTHZpNFlBQ0NjOFZzd0FEZ0FRQVJJN1VoUTR0R25CbGdBWU1rR2FBQndMSGlrTDRBQlVvZ0JwQy1RQVFHWUFRR2dBUUdvQVFPd0FRQzVBZk90YXFRQUFDUkF3UUh6cldxa0FBQWtRTWtCbWo4dDA1ZU84VF9aQVFBQUEBAyRQQV80QUVBOVFFAQ4sQW1BSUFvQUlBdFFJBRAAdg0IeHdBSUF5QUlBNEFJQTZBSUEtQUlBZ0FNQm1BTUJxQVAFzIh1Z01KUVUxVE1UbzBNekl3NEFPVENBLi6aAmEhUXcxdGNRagUoEfQkblBGYklBUW9BRAl8AEEBqAREbzJEABRRSk1JU1EBGwRBQQGsAFURDAxBQUFXHQzwWNgCAOACrZhI6gIzaHR0cDovL3Rlc3RwYWdlcy1wbWFoZS50cC5hZG54cy5uZXQvMDFfYmFzaWNfc2luZ2xl8gITCg9DVVNUT01fTU9ERUxfSUQSAPICGgoWMhYAPExFQUZfTkFNRRIA8gIeCho2HQAIQVNUAT7wnElGSUVEEgCAAwCIAwGQAwCYAxegAwGqAwDAA-CoAcgDANgD8ao-4AMA6AMA-AMBgAQAkgQNL3V0L3YzL3ByZWJpZJgEAKIECjEwLjIuMTIuMzioBIqpB7IEDggAEAEYACAAKAAwADgCuAQAwAQAyAQA0gQOOTMyNSNBTVMxOjQzMjDaBAIIAeAEAfAEg8u-LogFAZgFAKAF______8BAxgBwAUAyQUABQEU8D_SBQkJBQt8AAAA2AUB4AUB8AWZ9CH6BQQIABAAkAYBmAYAuAYAwQYBITAAAPA_yAYA2gYWChAAOgEAGBAAGADgBgw.%26s%3D971dce9d49b6bee447c8a58774fb30b40fe98171;ts=1551889079;cet=0;cecb=\'></script>'}
765
        }]
766
      }]
767
    };
768

769
    const createAdUnit = (code, mediaTypes) => {
1✔
770
      if (!mediaTypes) {
4✔
771
        mediaTypes = ['banner'];
3✔
772
      } else if (typeof mediaTypes === 'string') {
1✔
773
        mediaTypes = [mediaTypes];
1✔
774
      }
775

776
      const adUnit = {
4✔
777
        transactionId: `tr${code}`,
778
        adUnitId: `au${code}`,
779
        code: code,
780
        sizes: [[300, 250], [300, 600]],
781
        bids: [{
782
          bidder: 'appnexus',
783
          params: {
784
            placementId: '10433394'
785
          }
786
        }]
787
      };
788

789
      const _mediaTypes = {};
4✔
790
      if (mediaTypes.indexOf('banner') !== -1) {
4✔
791
        Object.assign(_mediaTypes, {
3✔
792
          'banner': {}
793
        });
794
      }
795
      if (mediaTypes.indexOf('video') !== -1) {
4✔
796
        Object.assign(_mediaTypes, {
1✔
797
          'video': {
798
            context: 'instream',
799
            playerSize: [300, 250]
800
          }
801
        });
802
      }
803
      if (mediaTypes.indexOf('native') !== -1) {
4!
UNCOV
804
        Object.assign(_mediaTypes, {
×
805
          'native': {}
806
        });
807
      }
808

809
      if (Object.keys(_mediaTypes).length > 0) {
4✔
810
        adUnit['mediaTypes'] = _mediaTypes;
4✔
811
        // if video type, add video to every bid.param object
812
        if (_mediaTypes.video) {
4✔
813
          adUnit.bids.forEach(bid => {
1✔
814
            bid.params['video'] = {
1✔
815
              width: 300,
816
              height: 250,
817
              vastUrl: '',
818
              ttl: 3600
819
            };
820
          });
821
        }
822
      }
823
      return adUnit;
4✔
824
    }
825
    const initTestConfig = (data) => {
1✔
826
      pbjs.bidderSettings = {};
4✔
827

828
      ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(function() {
4✔
829
        return function(url, callback) {
4✔
830
          const fakeResponse = sinon.stub();
4✔
831
          fakeResponse.returns('headerContent');
4✔
832
          callback.success(JSON.stringify(response), { getResponseHeader: fakeResponse });
4✔
833
        }
834
      });
835
      auctionManagerInstance = newAuctionManager();
4✔
836
      targeting = newTargeting(auctionManagerInstance)
4✔
837

838
      configObj.setConfig({
4✔
839
        'priceGranularity': {
840
          'buckets': [
841
            { 'precision': 2, 'max': 5, 'increment': 0.01 },
842
            { 'precision': 2, 'max': 8, 'increment': 0.05 },
843
            { 'precision': 2, 'max': 20, 'increment': 0.5 },
844
            { 'precision': 2, 'max': 25, 'increment': 1 }
845
          ]
846
        },
847
        'mediaTypePriceGranularity': {
848
          'banner': {
849
            'buckets': [
850
              { 'precision': 2, 'max': 5, 'increment': 0.25 },
851
              { 'precision': 2, 'max': 20, 'increment': 0.5 },
852
              { 'precision': 2, 'max': 100, 'increment': 1 }
853
            ]
854
          },
855
          'video': 'low',
856
          'native': 'high'
857
        }
858
      });
859

860
      auction = auctionManagerInstance.createAuction({
4✔
861
        adUnits: data.adUnits,
862
        adUnitCodes: data.adUnitCodes
863
      });
864
    };
865

866
    before(function () {
1✔
867
      currentPriceBucket = configObj.getConfig('priceGranularity');
1✔
868
      sinon.stub(adapterManager, 'makeBidRequests').callsFake(() => {
1✔
869
        const br = {
4✔
870
          'bidderCode': 'appnexus',
871
          'auctionId': '20882439e3238c',
872
          'bidderRequestId': '331f3cf3f1d9c8',
873
          'bids': [
874
            {
875
              'bidder': 'appnexus',
876
              'params': {
877
                'placementId': '10433394'
878
              },
879
              'adUnitCode': 'div-gpt-ad-1460505748561-0',
880
              'transactionId': 'trdiv-gpt-ad-1460505748561-0',
881
              'adUnitId': 'audiv-gpt-ad-1460505748561-0',
882
              'sizes': [
883
                [
884
                  300,
885
                  250
886
                ],
887
                [
888
                  300,
889
                  600
890
                ]
891
              ],
892
              'bidId': '4d0a6829338a07',
893
              'bidderRequestId': '331f3cf3f1d9c8',
894
              'auctionId': '20882439e3238c'
895
            }
896
          ],
897
          'auctionStart': 1505250713622,
898
          'timeout': 3000
899
        };
900
        const au = auction.getAdUnits().find((au) => au.transactionId === br.bids[0].transactionId);
4✔
901
        br.bids[0].mediaTypes = Object.assign({}, au.mediaTypes);
4✔
902
        return [br];
4✔
903
      });
904
    });
905

906
    after(function () {
1✔
907
      configObj.setConfig({ priceGranularity: currentPriceBucket });
1✔
908
      adapterManager.makeBidRequests.restore();
1✔
909
    })
910

911
    beforeEach(() => {
1✔
912
      indexStub = sinon.stub(auctionManager, 'index');
3✔
913
      indexStub.get(() => auctionManagerInstance.index);
29✔
914
    });
915

916
    afterEach(function () {
1✔
917
      ajaxStub.restore();
3✔
918
      indexStub.restore();
3✔
919
    });
920

921
    it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 0 - 5', async function () {
1✔
922
      initTestConfig({
1✔
923
        adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')],
924
        adUnitCodes: ['div-gpt-ad-1460505748561-0']
925
      });
926

927
      response = bannerResponse;
1✔
928
      response.tags[0].ads[0].cpm = 3.4288;
1✔
929

930
      auction.callBids(cbTimeout);
1✔
931
      await auction.end;
1✔
932
      const bidTargeting = targeting.getAllTargeting();
1✔
933
      expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25');
1✔
934
    });
935

936
    it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 21 - 100', async function () {
1✔
937
      initTestConfig({
1✔
938
        adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')],
939
        adUnitCodes: ['div-gpt-ad-1460505748561-0']
940
      });
941

942
      response = bannerResponse;
1✔
943
      response.tags[0].ads[0].cpm = 43.4288;
1✔
944

945
      auction.callBids(cbTimeout);
1✔
946
      await auction.end;
1✔
947
      const bidTargeting = targeting.getAllTargeting();
1✔
948
      expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('43.00');
1✔
949
    });
950

951
    it('should only apply price granularity if bid media type matches', async function () {
1✔
952
      initTestConfig({
1✔
953
        adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')],
954
        adUnitCodes: ['div-gpt-ad-1460505748561-0']
955
      });
956

957
      response = bannerResponse;
1✔
958
      response.tags[0].ads[0].cpm = 3.4288;
1✔
959

960
      auction.callBids(cbTimeout);
1✔
961
      await auction.end;
1✔
962
      const bidTargeting = targeting.getAllTargeting();
1✔
963
      expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25');
1✔
964

965
      if (FEATURES.VIDEO) {
1✔
966
        ajaxStub.restore();
1✔
967

968
        initTestConfig({
1✔
969
          adUnits: [createAdUnit('div-gpt-ad-1460505748561-0', 'video')],
970
          adUnitCodes: ['div-gpt-ad-1460505748561-0']
971
        });
972

973
        response = videoResponse;
1✔
974
        response.tags[0].ads[0].cpm = 3.4288;
1✔
975

976
        auction.callBids(cbTimeout);
1✔
977
        await auction.end;
1✔
978
        const bidTargeting = targeting.getAllTargeting();
1✔
979
        expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.00');
1✔
980
      }
981
    });
982
  });
983

984
  describe('getBidResponses', function () {
1✔
985
    it('should return empty obj when last auction Id had no responses', function () {
1✔
986
      auctionManager.getLastAuctionId = () => 999994;
1✔
987
      var result = pbjs.getBidResponses();
1✔
988
      assert.deepEqual(result, {}, 'expected bid responses are returned');
1✔
989
    });
990

991
    it('should return expected bid responses when not passed an adunitCode', function () {
1✔
992
      auctionManager.getLastAuctionId = () => 654321;
2✔
993
      var result = pbjs.getBidResponses();
1✔
994
      var compare = Object.fromEntries(Object.entries(getBidResponsesFromAPI()).map(([code, {bids}]) => {
1✔
995
        const arr = bids.slice();
1✔
996
        arr.bids = arr;
1✔
997
        return [code, arr];
1✔
998
      }));
999
      assert.deepEqual(result, compare, 'expected bid responses are returned');
1✔
1000
    });
1001

1002
    it('should return bid responses for most recent auctionId only', function () {
1✔
1003
      const responses = pbjs.getBidResponses();
1✔
1004
      assert.equal(responses[Object.keys(responses)[0]].bids.length, 4);
1✔
1005
    });
1006
  });
1007

1008
  describe('getBidResponsesForAdUnitCode', function () {
1✔
1009
    it('should return bid responses as expected', function () {
1✔
1010
      const adUnitCode = '/19968336/header-bid-tag-0';
1✔
1011
      const result = pbjs.getBidResponsesForAdUnitCode(adUnitCode);
1✔
1012
      const bids = getBidResponses().filter(bid => bid.adUnitCode === adUnitCode);
8✔
1013
      const compare = (() => { const arr = bids.slice(); arr.bids = arr; return arr; })();
1✔
1014
      assert.deepEqual(result, compare, 'expected id responses for ad unit code are returned');
1✔
1015
    });
1016
  });
1017

1018
  describe('setTargetingForGPTAsync', function () {
1✔
1019
    let logErrorSpy;
1020
    let targeting;
1021

1022
    beforeEach(function () {
1✔
1023
      logErrorSpy = sinon.spy(utils, 'logError');
10✔
1024
      resetAuction();
10✔
1025
    });
1026

1027
    afterEach(function () {
1✔
1028
      utils.logError.restore();
10✔
1029
    });
1030

1031
    it('should set pbjs targeting keys with values after calling setTargetingForGPTAsync function', function () {
1✔
1032
      var slots = createSlotArrayScenario2();
1✔
1033

1034
      window.googletag.pubads().setSlots(slots);
1✔
1035
      pbjs.setTargetingForGPTAsync([config.adUnitCodes[0]]);
1✔
1036
      pbjs.setConfig({ targetingControls: {allBidsCustomTargeting: true }});
1✔
1037

1038
      slots.forEach(function(slot) {
1✔
1039
        targeting = {};
2✔
1040
        slot.getTargetingKeys().forEach(function (key) {
2✔
1041
          const value = slot.getTargeting(key);
128✔
1042
          targeting[key] = value[0]
128✔
1043
        });
1044
        expect(targeting['pos1']).to.equal('750x350'); // non prebid targeting that was set should still be there
2✔
1045
        // Check that some of the keys with the hb_ prefix  have values with length > 1
1046
        const hasSomePrebidTargetingValues = Object.keys(targeting).some(target => target.startsWith('hb_') && targeting[target]?.length > 0);
106✔
1047
        expect(hasSomePrebidTargetingValues).to.equal(true);
2✔
1048
      });
1049
    });
1050
    it('should remove pbjs targeting when a new auction is run without any bids returned', function () {
1✔
1051
      auction.getBidsReceived = function() { return [] };
1✔
1052

1053
      var slots = createSlotArrayScenario2();
1✔
1054
      window.googletag.pubads().setSlots(slots);
1✔
1055

1056
      pbjs.setTargetingForGPTAsync([config.adUnitCodes[0]]);
1✔
1057

1058
      slots.forEach(function(slot) {
1✔
1059
        targeting = {};
2✔
1060
        slot.getTargetingKeys().forEach(function (key) {
2✔
1061
          const value = slot.getTargeting(key);
128✔
1062
          targeting[key] = value[0]
128✔
1063
        });
1064

1065
        expect(targeting['pos1']).to.equal('750x350'); // non prebid targeting that was set should still be there
2✔
1066
        // ensure that all of the keys with the hb_ prefix have values with length === 0, ignore other keys
1067
        const hasNoPrebidTargetingValues = Object.keys(targeting).every(targetKey => (targetKey.startsWith('hb_') && targeting[targetKey] === null) || !targetKey.startsWith('hb_'))
128✔
1068
        expect(hasNoPrebidTargetingValues).to.equal(true);
2✔
1069
      });
1070
    });
1071

1072
    it('should set googletag targeting keys to specific slot with customSlotMatching', function () {
1✔
1073
      // same ad unit code but two differnt divs
1074
      // we make sure we can set targeting for a specific one with customSlotMatching
1075

1076
      pbjs.setConfig({ enableSendAllBids: false });
1✔
1077

1078
      var slots = createSlotArrayScenario2();
1✔
1079

1080
      slots[0].spySetTargeting.resetHistory();
1✔
1081
      slots[1].spySetTargeting.resetHistory();
1✔
1082
      window.googletag.pubads().setSlots(slots);
1✔
1083
      pbjs.setConfig({ targetingControls: {allBidsCustomTargeting: true }});
1✔
1084
      pbjs.setTargetingForGPTAsync([config.adUnitCodes[0]], (slot) => {
1✔
1085
        return (adUnitCode) => {
2✔
1086
          return slots[0].getSlotElementId() === slot.getSlotElementId();
2✔
1087
        };
1088
      });
1089

1090
      var expected = getTargetingKeys();
1✔
1091
      expect(slots[0].spySetTargeting.args).to.deep.contain.members(expected);
1✔
1092
      expect(slots[1].spySetTargeting.args).to.not.deep.contain.members(expected);
1✔
1093
    });
1094

1095
    it('should set targeting when passed a string ad unit code with enableSendAllBids', function () {
1✔
1096
      var slots = createSlotArray();
1✔
1097
      window.googletag.pubads().setSlots(slots);
1✔
1098
      pbjs.setConfig({ enableSendAllBids: true });
1✔
1099

1100
      pbjs.setTargetingForGPTAsync('/19968336/header-bid-tag-0');
1✔
1101
      expect(slots[0].spySetTargeting.args).to.deep.contain.members([[TARGETING_KEYS.BIDDER, 'appnexus'], [TARGETING_KEYS.AD_ID + '_appnexus', '233bcbee889d46d'], [TARGETING_KEYS.PRICE_BUCKET + '_appnexus', '10.00']]);
1✔
1102
    });
1103

1104
    it('should set targeting when passed an array of ad unit codes with enableSendAllBids', function () {
1✔
1105
      var slots = createSlotArray();
1✔
1106
      window.googletag.pubads().setSlots(slots);
1✔
1107
      pbjs.setConfig({ enableSendAllBids: true });
1✔
1108

1109
      pbjs.setTargetingForGPTAsync(['/19968336/header-bid-tag-0']);
1✔
1110
      expect(slots[0].spySetTargeting.args).to.deep.contain.members([[TARGETING_KEYS.BIDDER, 'appnexus'], [TARGETING_KEYS.AD_ID + '_appnexus', '233bcbee889d46d'], [TARGETING_KEYS.PRICE_BUCKET + '_appnexus', '10.00']]);
1✔
1111
    });
1112

1113
    it('should set targeting from googletag data', function () {
1✔
1114
      var slots = createSlotArray();
1✔
1115
      slots[0].spySetTargeting.resetHistory();
1✔
1116
      window.googletag.pubads().setSlots(slots);
1✔
1117
      pbjs.setConfig({ enableSendAllBids: true, targetingControls: { allBidsCustomTargeting: true } });
1✔
1118
      pbjs.setTargetingForGPTAsync();
1✔
1119

1120
      var expected = getTargetingKeys();
1✔
1121
      expect(slots[0].spySetTargeting.args).to.deep.contain.members(expected);
1✔
1122
    });
1123

1124
    it('Calling enableSendAllBids should set targeting to include standard keys with bidder' +
1✔
1125
      ' append to key name', function () {
1126
      var slots = createSlotArray();
1✔
1127
      window.googletag.pubads().setSlots(slots);
1✔
1128

1129
      pbjs.setConfig({ enableSendAllBids: true });
1✔
1130
      pbjs.setTargetingForGPTAsync();
1✔
1131

1132
      var expected = getTargetingKeysBidLandscape();
1✔
1133
      expect(slots[0].spySetTargeting.args).to.deep.contain.members(expected);
1✔
1134
    });
1135

1136
    it('should set targeting for bids', function () {
1✔
1137
      // Make sure we're getting the expected losing bid.
1138
      assert.equal(auctionManager.getBidsReceived()[0]['bidderCode'], 'triplelift');
1✔
1139
      assert.equal(auctionManager.getBidsReceived()[0]['cpm'], 0.112256);
1✔
1140

1141
      resetAuction();
1✔
1142
      // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key.
1143
      const _bidsReceived = getBidResponses();
1✔
1144
      _bidsReceived[0]['adserverTargeting'] = {
1✔
1145
        always_use_me: 'abc',
1146
      };
1147

1148
      auction.getBidsReceived = function() { return _bidsReceived };
3✔
1149

1150
      var slots = createSlotArray();
1✔
1151
      window.googletag.pubads().setSlots(slots);
1✔
1152

1153
      pbjs.setTargetingForGPTAsync();
1✔
1154

1155
      var expected = [
1✔
1156
        [
1157
          TARGETING_KEYS.BIDDER,
1158
          'appnexus'
1159
        ],
1160
        [
1161
          TARGETING_KEYS.AD_ID,
1162
          '233bcbee889d46d'
1163
        ],
1164
        [
1165
          TARGETING_KEYS.PRICE_BUCKET,
1166
          '10.00'
1167
        ],
1168
        [
1169
          TARGETING_KEYS.SIZE,
1170
          '300x250'
1171
        ],
1172
        [
1173
          'foobar',
1174
          ['300x250', '300x600']
1175
        ],
1176
        [
1177
          'always_use_me',
1178
          'abc'
1179
        ]
1180
      ];
1181

1182
      expect(slots[0].spySetTargeting.args).to.deep.contain.members(expected);
1✔
1183
    });
1184

1185
    it('should log error when googletag is not defined on page', function () {
1✔
1186
      const error = 'window.googletag is not defined on the page';
1✔
1187
      const windowGoogletagBackup = window.googletag;
1✔
1188
      window.googletag = {};
1✔
1189

1190
      pbjs.setTargetingForGPTAsync();
1✔
1191
      assert.ok(logErrorSpy.calledWith(error), 'expected error was logged');
1✔
1192
      window.googletag = windowGoogletagBackup;
1✔
1193
    });
1194

1195
    it('should emit SET_TARGETING event when successfully invoked', function() {
1✔
1196
      var slots = createSlotArray();
1✔
1197
      window.googletag.pubads().setSlots(slots);
1✔
1198

1199
      var callback = sinon.spy();
1✔
1200

1201
      pbjs.onEvent('setTargeting', callback);
1✔
1202
      pbjs.setTargetingForGPTAsync(config.adUnitCodes);
1✔
1203

1204
      sinon.assert.calledOnce(callback);
1✔
1205
    });
1206
  });
1207

1208
  describe('renderAd', function () {
1✔
1209
    var bidId = 1;
1✔
1210
    var doc = {};
1✔
1211
    var elStub = {};
1✔
1212
    var adResponse = {};
1✔
1213
    var spyLogError = null;
1✔
1214
    var spyLogMessage = null;
1✔
1215
    var spyLogWarn = null;
1✔
1216
    var spyAddWinningBid;
1217
    var inIframe = true;
1✔
1218
    var triggerPixelStub;
1219

1220
    function pushBidResponseToAuction(obj) {
1221
      adResponse = Object.assign({
13✔
1222
        auctionId: 1,
1223
        adId: bidId,
1224
        width: 300,
1225
        height: 250,
1226
      }, obj);
1227
      auction.getBidsReceived = function() {
13✔
1228
        const bidsReceived = getBidResponses();
15✔
1229
        bidsReceived.push(adResponse);
15✔
1230
        return bidsReceived;
15✔
1231
      }
1232
      auction.getAuctionId = () => 1;
61✔
1233
    }
1234

1235
    beforeEach(function () {
1✔
1236
      bidId++;
15✔
1237
      doc = {
15✔
1238
        write: sinon.spy(),
1239
        close: sinon.spy(),
1240
        defaultView: {
1241
          frameElement: {
1242
            width: 0,
1243
            height: 0
1244
          }
1245
        },
1246
        body: {
1247
          appendChild: sinon.stub()
1248
        },
1249
        getElementsByTagName: sinon.stub(),
1250
        querySelector: sinon.stub(),
1251
        createElement: sinon.stub(),
1252
      };
1253
      doc.defaultView.document = doc;
15✔
1254

1255
      elStub = {
15✔
1256
        insertBefore: sinon.stub()
1257
      };
1258
      doc.getElementsByTagName.returns([elStub]);
15✔
1259
      doc.querySelector.returns(elStub);
15✔
1260

1261
      spyLogError = sinon.spy(utils, 'logError');
15✔
1262
      spyLogMessage = sinon.spy(utils, 'logMessage');
15✔
1263
      spyLogWarn = sinon.spy(utils, 'logWarn');
15✔
1264
      spyAddWinningBid = sinon.spy(auctionManager, 'addWinningBid');
15✔
1265

1266
      inIframe = true;
15✔
1267
      sinon.stub(utils, 'inIframe').callsFake(() => inIframe);
15✔
1268
      triggerPixelStub = sinon.stub(utils.internal, 'triggerPixel');
15✔
1269
    });
1270

1271
    afterEach(function () {
1✔
1272
      auction.getBidsReceived = getBidResponses;
15✔
1273
      utils.logError.restore();
15✔
1274
      utils.logMessage.restore();
15✔
1275
      utils.logWarn.restore();
15✔
1276
      utils.inIframe.restore();
15✔
1277
      triggerPixelStub.restore();
15✔
1278
      spyAddWinningBid.restore();
15✔
1279
    });
1280

1281
    function renderAd(...args) {
1282
      pbjs.renderAd(...args);
16✔
1283
      return new Promise((resolve) => {
16✔
1284
        setTimeout(resolve, 10);
16✔
1285
      });
1286
    }
1287

1288
    it('should require doc and id params', function () {
1✔
1289
      return renderAd().then(() => {
1✔
1290
        var error = 'Error rendering ad (id: undefined): missing adId';
1✔
1291
        assert.ok(spyLogError.calledWith(error), 'expected param error was logged');
1✔
1292
      })
1293
    });
1294

1295
    describe('when legacyRender is set', () => {
1✔
1296
      beforeEach(() => {
1✔
1297
        pbjs.setConfig({
1✔
1298
          auctionOptions: {
1299
            legacyRender: true
1300
          }
1301
        })
1302
      });
1303

1304
      afterEach(() => {
1✔
1305
        configObj.resetConfig()
1✔
1306
      });
1307

1308
      it('should immediately write the ad to the doc', function () {
1✔
1309
        pushBidResponseToAuction({
1✔
1310
          ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>"
1311
        });
1312
        pbjs.renderAd(doc, bidId);
1✔
1313
        sinon.assert.calledWith(doc.write, adResponse.ad);
1✔
1314
        sinon.assert.called(doc.close);
1✔
1315
      });
1316
    })
1317

1318
    describe('when legacyRender is NOT set', () => {
1✔
1319
      it('should use an iframe, not document.write', function () {
1✔
1320
        const ad = "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>";
1✔
1321
        pushBidResponseToAuction({
1✔
1322
          ad
1323
        });
1324
        const iframe = {};
1✔
1325
        doc.createElement.returns(iframe);
1✔
1326
        return renderAd(doc, bidId).then(() => {
1✔
1327
          expect(iframe.srcdoc).to.eql(ad);
1✔
1328
          sinon.assert.notCalled(doc.write);
1✔
1329
        })
1330
      });
1331
    })
1332

1333
    it('should place the url inside an iframe on the doc', function () {
1✔
1334
      pushBidResponseToAuction({
1✔
1335
        adUrl: 'http://server.example.com/ad/ad.js'
1336
      });
1337
      return renderAd(doc, bidId).then(() => {
1✔
1338
        sinon.assert.calledWith(doc.createElement, 'iframe');
1✔
1339
      });
1340
    });
1341

1342
    it('should log an error when no ad or url', function () {
1✔
1343
      pushBidResponseToAuction({});
1✔
1344
      return renderAd(doc, bidId).then(() => {
1✔
1345
        sinon.assert.called(spyLogError);
1✔
1346
      });
1347
    });
1348

1349
    it('should log an error when not in an iFrame', function () {
1✔
1350
      pushBidResponseToAuction({
1✔
1351
        ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>"
1352
      });
1353
      inIframe = false;
1✔
1354
      return renderAd(document, bidId).then(() => {
1✔
1355
        const error = `Error rendering ad (id: ${bidId}): renderAd was prevented from writing to the main document.`;
1✔
1356
        assert.ok(spyLogError.calledWith(error), 'expected error was logged');
1✔
1357
      });
1358
    });
1359

1360
    it('should emit AD_RENDER_SUCCEEDED', () => {
1✔
1361
      sandbox.stub(events, 'emit');
1✔
1362
      pushBidResponseToAuction({
1✔
1363
        ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>"
1364
      });
1365
      return renderAd(document, bidId).then(() => {
1✔
1366
        sinon.assert.calledWith(events.emit, EVENTS.AD_RENDER_SUCCEEDED, sinon.match({adId: bidId}));
1✔
1367
      });
1368
    });
1369

1370
    it('should not render videos', function () {
1✔
1371
      pushBidResponseToAuction({
1✔
1372
        mediatype: 'video'
1373
      });
1374
      return renderAd(doc, bidId).then(() => {
1✔
1375
        sinon.assert.notCalled(doc.createElement);
1✔
1376
      });
1377
    });
1378

1379
    it('should catch errors thrown when trying to write ads to the page', function () {
1✔
1380
      pushBidResponseToAuction({
1✔
1381
        ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>"
1382
      });
1383

1384
      var error = { message: 'doc write error' };
1✔
1385
      doc.createElement.throws(error);
1✔
1386

1387
      return renderAd(doc, bidId).then(() => {
1✔
1388
        var errorMessage = `Error rendering ad (id: ${bidId}): doc write error`
1✔
1389
        assert.ok(spyLogError.calledWith(errorMessage), 'expected error was logged');
1✔
1390
      });
1391
    });
1392

1393
    it('should log an error when ad not found', function () {
1✔
1394
      var fakeId = 99;
1✔
1395
      return renderAd(doc, fakeId).then(() => {
1✔
1396
        var error = `Error rendering ad (id: ${fakeId}): Cannot find ad '${fakeId}'`
1✔
1397
        assert.ok(spyLogError.calledWith(error), 'expected error was logged');
1✔
1398
      });
1399
    });
1400

1401
    it('should save bid displayed to winning bid', function () {
1✔
1402
      pushBidResponseToAuction({
1✔
1403
        ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>"
1404
      });
1405
      return renderAd(doc, bidId).then(() => {
1✔
1406
        const winningBid = pbjs.getAllWinningBids().find(el => el.adId === adResponse.adId);
7✔
1407
        expect(winningBid).to.eql(adResponse);
1✔
1408
      });
1409
    });
1410

1411
    it('fires impression trackers if present', function () {
1✔
1412
      const url = 'http://www.example.com/burl';
1✔
1413
      pushBidResponseToAuction({
1✔
1414
        ad: '<div>ad</div>',
1415
        source: 's2s',
1416
        eventtrackers: [
1417
          {event: 1, method: 1, url}
1418
        ]
1419
      });
1420

1421
      return renderAd(doc, bidId).then(() => {
1✔
1422
        sinon.assert.calledOnce(triggerPixelStub);
1✔
1423
        sinon.assert.calledWith(triggerPixelStub, url);
1✔
1424
      });
1425
    });
1426

1427
    it('should call addWinningBid', function () {
1✔
1428
      pushBidResponseToAuction({
1✔
1429
        ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>"
1430
      });
1431
      return renderAd(doc, bidId).then(() => {
1✔
1432
        sinon.assert.calledOnce(spyAddWinningBid);
1✔
1433
        sinon.assert.calledWith(spyAddWinningBid, adResponse);
1✔
1434
      });
1435
    });
1436

1437
    it('should warn stale rendering', function () {
1✔
1438
      var warning = `Ad id ${bidId} has been rendered before`;
1✔
1439
      var onWonEvent = sinon.stub();
1✔
1440
      var onStaleEvent = sinon.stub();
1✔
1441

1442
      pbjs.onEvent(EVENTS.BID_WON, onWonEvent);
1✔
1443
      pbjs.onEvent(EVENTS.STALE_RENDER, onStaleEvent);
1✔
1444

1445
      pushBidResponseToAuction({
1✔
1446
        ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>"
1447
      });
1448

1449
      // First render should pass with no warning and added to winning bids
1450
      return renderAd(doc, bidId).then(() => {
1✔
1451
        sinon.assert.neverCalledWith(spyLogWarn, warning);
1✔
1452

1453
        sinon.assert.calledOnce(spyAddWinningBid);
1✔
1454
        sinon.assert.calledWith(spyAddWinningBid, adResponse);
1✔
1455

1456
        sinon.assert.calledWith(onWonEvent, adResponse);
1✔
1457
        sinon.assert.notCalled(onStaleEvent);
1✔
1458
        expect(adResponse).to.have.property('status', BID_STATUS.RENDERED);
1✔
1459

1460
        // Reset call history for spies and stubs
1461
        spyLogMessage.resetHistory();
1✔
1462
        spyLogWarn.resetHistory();
1✔
1463
        spyAddWinningBid.resetHistory();
1✔
1464
        onWonEvent.resetHistory();
1✔
1465
        onStaleEvent.resetHistory();
1✔
1466
        doc.createElement.resetHistory();
1✔
1467
        return renderAd(doc, bidId);
1✔
1468
      }).then(() => {
1469
        // Second render should have a warning but still be rendered
1470
        sinon.assert.calledWith(spyLogWarn, warning);
1✔
1471
        sinon.assert.calledWith(onStaleEvent, adResponse);
1✔
1472
        sinon.assert.called(doc.createElement);
1✔
1473

1474
        // Clean up
1475
        pbjs.offEvent(EVENTS.BID_WON, onWonEvent);
1✔
1476
        pbjs.offEvent(EVENTS.STALE_RENDER, onStaleEvent);
1✔
1477
      });
1478
    });
1479

1480
    it('should stop stale rendering', function () {
1✔
1481
      var warning = `Ad id ${bidId} has been rendered before`;
1✔
1482
      var onWonEvent = sinon.stub();
1✔
1483
      var onStaleEvent = sinon.stub();
1✔
1484

1485
      // Setting suppressStaleRender to true explicitly
1486
      configObj.setConfig({'auctionOptions': {'suppressStaleRender': true}});
1✔
1487

1488
      pbjs.onEvent(EVENTS.BID_WON, onWonEvent);
1✔
1489
      pbjs.onEvent(EVENTS.STALE_RENDER, onStaleEvent);
1✔
1490

1491
      pushBidResponseToAuction({
1✔
1492
        ad: "<script type='text/javascript' src='http://server.example.com/ad/ad.js'></script>"
1493
      });
1494

1495
      // First render should pass with no warning and added to winning bids
1496
      return renderAd(doc, bidId).then(() => {
1✔
1497
        sinon.assert.neverCalledWith(spyLogWarn, warning);
1✔
1498

1499
        sinon.assert.calledOnce(spyAddWinningBid);
1✔
1500
        sinon.assert.calledWith(spyAddWinningBid, adResponse);
1✔
1501
        expect(adResponse).to.have.property('status', BID_STATUS.RENDERED);
1✔
1502

1503
        sinon.assert.calledWith(onWonEvent, adResponse);
1✔
1504
        sinon.assert.notCalled(onStaleEvent);
1✔
1505

1506
        // Reset call history for spies and stubs
1507
        spyLogMessage.resetHistory();
1✔
1508
        spyLogWarn.resetHistory();
1✔
1509
        spyAddWinningBid.resetHistory();
1✔
1510
        onWonEvent.resetHistory();
1✔
1511
        onStaleEvent.resetHistory();
1✔
1512

1513
        // Second render should have a warning and do not proceed further
1514
        return renderAd(doc, bidId);
1✔
1515
      }).then(() => {
1516
        sinon.assert.calledWith(spyLogWarn, warning);
1✔
1517

1518
        sinon.assert.notCalled(spyAddWinningBid);
1✔
1519

1520
        sinon.assert.notCalled(onWonEvent);
1✔
1521
        sinon.assert.calledWith(onStaleEvent, adResponse);
1✔
1522

1523
        // Clean up
1524
        pbjs.offEvent(EVENTS.BID_WON, onWonEvent);
1✔
1525
        pbjs.offEvent(EVENTS.STALE_RENDER, onStaleEvent);
1✔
1526
        configObj.setConfig({'auctionOptions': {}});
1✔
1527
      });
1528
    });
1529
  });
1530

1531
  describe('requestBids', function () {
1✔
1532
    let logMessageSpy;
1533
    let makeRequestsStub, createAuctionStub;
1534
    let adUnits;
1535
    let clock;
1536
    before(function () {
1✔
1537
      clock = sinon.useFakeTimers();
1✔
1538
    });
1539
    after(function () {
1✔
1540
      clock.restore();
1✔
1541
    });
1542

1543
    const BIDDER_CODE = 'sampleBidder';
1✔
1544
    const bids = [{
1✔
1545
      'ad': 'creative',
1546
      'cpm': '1.99',
1547
      'width': 300,
1548
      'height': 250,
1549
      'bidderCode': BIDDER_CODE,
1550
      'requestId': '4d0a6829338a07',
1551
      'creativeId': 'id',
1552
      'currency': 'USD',
1553
      'netRevenue': true,
1554
      'ttl': 360
1555
    }];
1556
    const bidRequests = [{
1✔
1557
      'bidderCode': BIDDER_CODE,
1558
      'auctionId': '20882439e3238c',
1559
      'bidderRequestId': '331f3cf3f1d9c8',
1560
      'bids': [
1561
        {
1562
          'bidder': BIDDER_CODE,
1563
          'params': {
1564
            'placementId': 'id'
1565
          },
1566
          'adUnitCode': 'adUnit-code',
1567
          'sizes': [[300, 250], [300, 600]],
1568
          'bidId': '4d0a6829338a07',
1569
          'bidderRequestId': '331f3cf3f1d9c8',
1570
          'auctionId': '20882439e3238c'
1571
        }
1572
      ],
1573
      'auctionStart': 1505250713622,
1574
      'timeout': 3000,
1575
      'start': 1000
1576
    }];
1577

1578
    let spec, indexStub, auction, completeAuction, auctionStarted;
1579

1580
    beforeEach(function () {
1✔
1581
      logMessageSpy = sinon.spy(utils, 'logMessage');
20✔
1582
      makeRequestsStub = sinon.stub(adapterManager, 'makeBidRequests');
20✔
1583
      makeRequestsStub.returns(bidRequests);
20✔
1584
      adUnits = [{
20✔
1585
        code: 'adUnit-code',
1586
        mediaTypes: {
1587
          banner: {
1588
            sizes: [[300, 250]]
1589
          }
1590
        },
1591
        transactionId: 'mock-tid',
1592
        adUnitId: 'mock-au',
1593
        bids: [
1594
          {bidder: BIDDER_CODE, params: {placementId: 'id'}},
1595
        ]
1596
      }];
1597
      indexStub = sinon.stub(auctionManager, 'index');
20✔
1598
      indexStub.get(() => stubAuctionIndex({adUnits, bidRequests}))
33✔
1599
      sinon.stub(adapterManager, 'callBids').callsFake((_, bidrequests, addBidResponse, adapterDone) => {
20✔
1600
        completeAuction = (bidsReceived) => {
16✔
1601
          bidsReceived.forEach((bid) => addBidResponse(bid.adUnitCode, Object.assign(createBid(), bid)));
4✔
1602
          bidRequests.forEach((req) => adapterDone.call(req));
4✔
1603
          return auction.end;
4✔
1604
        }
1605
      })
1606
      const origNewAuction = auctionModule.newAuction;
20✔
1607
      auctionStarted = new Promise((resolve) => {
20✔
1608
        sinon.stub(auctionModule, 'newAuction').callsFake(function (opts) {
20✔
1609
          auction = origNewAuction(opts);
16✔
1610
          resolve(auction);
16✔
1611
          return auction;
16✔
1612
        })
1613
      })
1614
      spec = {
20✔
1615
        code: BIDDER_CODE,
1616
        isBidRequestValid: sinon.stub(),
1617
        buildRequests: sinon.stub(),
1618
        interpretResponse: sinon.stub(),
1619
        getUserSyncs: sinon.stub(),
1620
        onTimeout: sinon.stub(),
1621
        onSetTargeting: sinon.stub(),
1622
      };
1623

1624
      registerBidder(spec);
20✔
1625
      spec.buildRequests.returns([{'id': 123, 'method': 'POST'}]);
20✔
1626
      spec.isBidRequestValid.returns(true);
20✔
1627
      spec.interpretResponse.returns(bids);
20✔
1628
    });
1629

1630
    afterEach(function () {
1✔
1631
      clock.restore();
20✔
1632
      adapterManager.makeBidRequests.restore();
20✔
1633
      adapterManager.callBids.restore();
20✔
1634
      indexStub.restore();
20✔
1635
      auction.getBidsReceived = () => [];
372✔
1636
      auctionModule.newAuction.restore();
20✔
1637
      utils.logMessage.restore();
20✔
1638
    });
1639

1640
    async function runAuction(request = {}) {
11✔
1641
      pbjs.requestBids(request);
11✔
1642
      await auctionStarted;
11✔
1643
    }
1644

1645
    it('should execute callback after timeout', async function () {
1✔
1646
      const requestObj = {
1✔
1647
        bidsBackHandler: sinon.stub(),
1648
        timeout: 2000,
1649
        adUnits: adUnits
1650
      };
1651
      await runAuction(requestObj);
1✔
1652

1653
      const re = new RegExp('^Auction [a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12} timedOut$');
1✔
1654
      await clock.tick(requestObj.timeout - 1);
1✔
1655
      assert.ok(logMessageSpy.neverCalledWith(sinon.match(re)), 'executeCallback not called');
1✔
1656

1657
      await clock.tick(1);
1✔
1658
      assert.ok(logMessageSpy.calledWith(sinon.match(re)), 'executeCallback called');
1✔
1659

1660
      expect(requestObj.bidsBackHandler.getCall(0).args[1]).to.equal(true,
1✔
1661
        'bidsBackHandler should be called with timedOut=true');
1662

1663
      sinon.assert.called(spec.onTimeout);
1✔
1664
    });
1665

1666
    describe('requestBids event', () => {
1✔
1667
      beforeEach(() => {
1✔
1668
        sandbox.stub(events, 'emit');
8✔
1669
      });
1670

1671
      it('should be emitted with request', async () => {
1✔
1672
        const request = {
1✔
1673
          adUnits
1674
        }
1675
        await runAuction(request);
1✔
1676
        sinon.assert.calledWith(events.emit, EVENTS.REQUEST_BIDS, request);
1✔
1677
      });
1678

1679
      it('should provide a request object when not supplied to requestBids()', async () => {
1✔
1680
        getGlobal().addAdUnits(adUnits);
1✔
1681
        try {
1✔
1682
          await runAuction();
1✔
1683
          sinon.assert.calledWith(events.emit, EVENTS.REQUEST_BIDS, sinon.match({
1✔
1684
            adUnits
1685
          }));
1686
        } finally {
1687
          adUnits.map(au => au.code).forEach(getGlobal().removeAdUnit)
1✔
1688
        }
1689
      });
1690

1691
      it('should not leak internal state', async () => {
1✔
1692
        const request = {
1✔
1693
          adUnits
1694
        };
1695
        await runAuction(Object.assign({}, request));
1✔
1696
        expect(events.emit.args[0][1].metrics).to.not.exist;
1✔
1697
      });
1698

1699
      describe('ad unit filter', () => {
1✔
1700
        let au, request;
1701

1702
        function requestBidsHook(next, req) {
1703
          request = req;
5✔
1704
          next(req);
5✔
1705
        }
1706
        before(() => {
1✔
1707
          pbjsModule.requestBids.before(requestBidsHook, 999);
1✔
1708
        })
1709
        after(() => {
1✔
1710
          pbjsModule.requestBids.getHooks({hook: requestBidsHook}).remove();
1✔
1711
        })
1712

1713
        beforeEach(() => {
1✔
1714
          request = null;
5✔
1715
          au = {
5✔
1716
            ...adUnits[0],
1717
            code: 'au'
1718
          }
1719
          adUnits.push(au);
5✔
1720
        });
1721
        it('should filter adUnits by code', async () => {
1✔
1722
          await runAuction({
1✔
1723
            adUnits,
1724
            adUnitCodes: ['au']
1725
          });
1726
          sinon.assert.calledWith(events.emit, EVENTS.REQUEST_BIDS, sinon.match({
1✔
1727
            adUnits: [au],
1728
          }));
1729
        });
1730
        it('should still pass unfiltered ad units to requestBids', () => {
1✔
1731
          runAuction({
1✔
1732
            adUnits: adUnits.slice(),
1733
            adUnitCodes: ['au']
1734
          });
1735
          expect(request.adUnits).to.have.deep.members(adUnits);
1✔
1736
        });
1737

1738
        it('should allow event handlers to add ad units', () => {
1✔
1739
          const extraAu = {
1✔
1740
            ...adUnits[0],
1741
            code: 'extra'
1742
          }
1743
          events.emit.callsFake((evt, request) => {
1✔
1744
            request.adUnits.push(extraAu)
1✔
1745
          });
1746
          runAuction({
1✔
1747
            adUnits: adUnits.slice(),
1748
            adUnitCodes: ['au']
1749
          });
1750
          expect(request.adUnits).to.have.deep.members([...adUnits, extraAu]);
1✔
1751
        });
1752

1753
        it('should allow event handlers to remove ad units', () => {
1✔
1754
          events.emit.callsFake((evt, request) => {
1✔
1755
            request.adUnits = [];
1✔
1756
          });
1757
          runAuction({
1✔
1758
            adUnits: adUnits.slice(),
1759
            adUnitCodes: ['au']
1760
          });
1761
          expect(request.adUnits).to.eql([adUnits[0]]);
1✔
1762
        });
1763

1764
        it('should NOT allow event handlers to modify adUnitCodes', () => {
1✔
1765
          events.emit.callsFake((evt, request) => {
1✔
1766
            request.adUnitCodes = ['other']
1✔
1767
          });
1768
          runAuction({
1✔
1769
            adUnits,
1770
            adUnitCodes: ['au']
1771
          });
1772
          expect(request.adUnitCodes).to.eql(['au']);
1✔
1773
        })
1774
      });
1775
    })
1776

1777
    it('should execute `onSetTargeting` after setTargetingForGPTAsync', async function () {
1✔
1778
      const bidId = 1;
1✔
1779
      const auctionId = 1;
1✔
1780
      const adResponse = Object.assign({
1✔
1781
        auctionId: auctionId,
1782
        adId: String(bidId),
1783
        width: 300,
1784
        height: 250,
1785
        adUnitCode: bidRequests[0].bids[0].adUnitCode,
1786
        transactionId: 'mock-tid',
1787
        adUnitId: 'mock-au',
1788
        adserverTargeting: {
1789
          'hb_bidder': BIDDER_CODE,
1790
          'hb_adid': bidId,
1791
          'hb_pb': bids[0].cpm,
1792
          'hb_size': '300x250',
1793
        },
1794
        bidder: bids[0].bidderCode,
1795
      }, bids[0]);
1796

1797
      const requestObj = {
1✔
1798
        bidsBackHandler: null,
1799
        timeout: 2000,
1800
        adUnits: adUnits
1801
      };
1802

1803
      await runAuction(requestObj);
1✔
1804
      await completeAuction([adResponse]);
1✔
1805
      pbjs.setTargetingForGPTAsync();
1✔
1806

1807
      sinon.assert.called(spec.onSetTargeting);
1✔
1808
    });
1809

1810
    describe('returns a promise that resolves', () => {
1✔
1811
      function delayHook(next, ...args) {
18!
1812
        setTimeout(() => next(...args))
9✔
1813
      }
1814

1815
      beforeEach(() => {
1✔
1816
        // make sure the return value works correctly when hooks give up priority
1817
        pbjsModule.requestBids.before(delayHook)
9✔
1818
      });
1819

1820
      afterEach(() => {
1✔
1821
        pbjsModule.requestBids.getHooks({hook: delayHook}).remove();
9✔
1822
      });
1823

1824
      Object.entries({
1✔
1825
        'immediately, without bidsBackHandler': (req) => pbjs.requestBids(req),
3✔
1826
        'after bidsBackHandler': (() => {
1827
          const bidsBackHandler = sinon.stub();
1✔
1828
          return function (req) {
1✔
1829
            return pbjs.requestBids({...req, bidsBackHandler}).then(({bids, timedOut, auctionId}) => {
3✔
1830
              sinon.assert.calledWith(bidsBackHandler, bids, timedOut, auctionId);
3✔
1831
              return {bids, timedOut, auctionId};
3✔
1832
            })
1833
          }
1834
        })(),
1835
        'after a bidsBackHandler that throws': (req) => pbjs.requestBids({...req, bidsBackHandler: () => { throw new Error() }})
3✔
1836
      }).forEach(([t, requestBids]) => {
3✔
1837
        describe(t, () => {
3✔
1838
          it('with no args, when no adUnits are defined', () => {
3✔
1839
            return requestBids({}).then((res) => {
3✔
1840
              expect(res).to.eql({
3✔
1841
                bids: undefined,
1842
                timedOut: undefined,
1843
                auctionId: undefined
1844
              });
1845
            });
1846
          });
1847

1848
          it('on timeout', (done) => {
3✔
1849
            requestBids({
3✔
1850
              auctionId: 'mock-auctionId',
1851
              adUnits,
1852
              timeout: 10
1853
            }).then(({timedOut, bids, auctionId}) => {
3✔
1854
              expect(timedOut).to.be.true;
3✔
1855
              expect(bids).to.eql({});
3✔
1856
              expect(auctionId).to.eql('mock-auctionId');
3✔
1857
              done();
3✔
1858
            });
1859
            clock.tick(12);
3✔
1860
          });
1861

1862
          it('with auction result', (done) => {
3✔
1863
            const bid = {
3✔
1864
              bidder: 'mock-bidder',
1865
              adUnitCode: adUnits[0].code,
1866
              transactionId: adUnits[0].transactionId,
1867
              adUnitId: adUnits[0].adUnitId,
1868
            }
1869
            requestBids({
3✔
1870
              adUnits,
1871
            }).then(({bids}) => {
3✔
1872
              sinon.assert.match(bids[bid.adUnitCode].bids[0], bid)
3✔
1873
              done();
3✔
1874
            });
1875
            // `completeAuction` won't work until we're out of `delayHook`
1876
            // and the mocked auction has been set up;
1877
            // setTimeout here takes us after the setTimeout in `delayHook`
1878
            setTimeout(() => completeAuction([bid]));
3✔
1879
          })
1880
        })
1881
      })
1882
    })
1883

1884
    it('should transfer ttlBuffer to adUnit.ttlBuffer', async () => {
1✔
1885
      await runAuction({
1✔
1886
        ttlBuffer: 123,
1887
        adUnits: [adUnits[0], {...adUnits[0], ttlBuffer: 0}]
1888
      });
1889
      sinon.assert.calledWithMatch(auctionModule.newAuction, {
1✔
1890
        adUnits: sinon.match((units) => units[0].ttlBuffer === 123 && units[1].ttlBuffer === 0)
1✔
1891
      })
1892
    });
1893
  })
1894

1895
  describe('requestBids', function () {
1✔
1896
    let sandbox;
1897
    beforeEach(function () {
1✔
1898
      sandbox = sinon.createSandbox();
10✔
1899
    });
1900
    afterEach(function () {
1✔
1901
      sandbox.restore();
10✔
1902
    });
1903
    describe('bidRequests is empty', function () {
1✔
1904
      it('should log warning message and execute callback if bidRequests is empty', async function () {
1✔
1905
        const bidsBackHandler = function bidsBackHandlerCallback() {
1✔
1906
        };
1907
        const spyExecuteCallback = sinon.spy(bidsBackHandler);
1✔
1908
        const logWarnSpy = sandbox.spy(utils, 'logWarn');
1✔
1909

1910
        await pbjs.requestBids({
1✔
1911
          adUnits: [
1912
            {
1913
              code: 'test1',
1914
              mediaTypes: {banner: {sizes: []}},
1915
              bids: [],
1916
            }, {
1917
              code: 'test2',
1918
              mediaTypes: {banner: {sizes: []}},
1919
              bids: [],
1920
            }
1921
          ],
1922
          bidsBackHandler: spyExecuteCallback
1923
        });
1924

1925
        assert.ok(logWarnSpy.calledWith('No valid bid requests returned for auction'), 'expected warning message was logged');
1✔
1926
        assert.ok(spyExecuteCallback.calledOnce, 'callback executed when bidRequests is empty');
1✔
1927
      });
1928
    });
1929

1930
    describe('starts auction', () => {
1✔
1931
      let startAuctionStub, auctionStarted, __started;
1932
      function saHook(fn, ...args) {
18!
1933
        __started();
9✔
1934
        return startAuctionStub(...args);
9✔
1935
      }
1936
      beforeEach(() => {
1✔
1937
        auctionStarted = new Promise(resolve => { __started = resolve });
9✔
1938
        startAuctionStub = sinon.stub();
9✔
1939
        pbjsModule.startAuction.before(saHook);
9✔
1940
        configObj.resetConfig();
9✔
1941
      });
1942
      afterEach(() => {
1✔
1943
        pbjsModule.startAuction.getHooks({hook: saHook}).remove();
9✔
1944
      })
1945
      after(() => {
1✔
1946
        configObj.resetConfig();
1✔
1947
      });
1948

1949
      async function runAuction(request = {}) {
7✔
1950
        pbjs.requestBids(request);
7✔
1951
        await auctionStarted;
7✔
1952
      }
1953

1954
      it('with normalized FPD', async () => {
1✔
1955
        configObj.setBidderConfig({
1✔
1956
          bidders: ['test'],
1957
          config: {
1958
            ortb2: {
1959
              source: {
1960
                schain: 'foo'
1961
              }
1962
            }
1963
          }
1964
        });
1965
        configObj.setConfig({
1✔
1966
          ortb2: {
1967
            source: {
1968
              schain: 'bar'
1969
            }
1970
          }
1971
        });
1972
        await runAuction();
1✔
1973
        sinon.assert.calledWith(startAuctionStub, sinon.match({
1✔
1974
          ortb2Fragments: {
1975
            global: {
1976
              source: {
1977
                ext: {
1978
                  schain: 'bar'
1979
                }
1980
              }
1981
            },
1982
            bidder: {
1983
              test: {
1984
                source: {
1985
                  ext: {
1986
                    schain: 'foo'
1987
                  }
1988
                }
1989
              }
1990
            }
1991
          }
1992
        }));
1993
      })
1994
      describe('with FPD', () => {
1✔
1995
        let globalFPD, auctionFPD, mergedFPD;
1996
        beforeEach(() => {
1✔
1997
          globalFPD = {
4✔
1998
            'k1': 'v1',
1999
            'k2': {
2000
              'k3': 'v3',
2001
              'k4': 'v4'
2002
            }
2003
          };
2004
          auctionFPD = {
4✔
2005
            'k5': 'v5',
2006
            'k2': {
2007
              'k3': 'override',
2008
              'k7': 'v7'
2009
            }
2010
          };
2011
          mergedFPD = {
4✔
2012
            'k1': 'v1',
2013
            'k5': 'v5',
2014
            'k2': {
2015
              'k3': 'override',
2016
              'k4': 'v4',
2017
              'k7': 'v7'
2018
            }
2019
          };
2020
        });
2021

2022
        it('merged from setConfig and requestBids', async () => {
1✔
2023
          configObj.setConfig({ortb2: globalFPD});
1✔
2024
          await runAuction({ortb2: auctionFPD});
1✔
2025
          sinon.assert.calledWith(startAuctionStub, sinon.match({
1✔
2026
            ortb2Fragments: {global: mergedFPD}
2027
          }));
2028
        });
2029

2030
        it('that cannot alter global config', () => {
1✔
2031
          configObj.setConfig({ortb2: {value: 'old'}});
1✔
2032
          startAuctionStub.callsFake(({ortb2Fragments}) => {
1✔
UNCOV
2033
            ortb2Fragments.global.value = 'new'
×
2034
          });
2035
          pbjs.requestBids({ortb2: auctionFPD});
1✔
2036
          expect(configObj.getAnyConfig('ortb2').value).to.eql('old');
1✔
2037
        });
2038

2039
        it('that cannot alter bidder config', () => {
1✔
2040
          configObj.setBidderConfig({
1✔
2041
            bidders: ['mockBidder'],
2042
            config: {
2043
              ortb2: {value: 'old'}
2044
            }
2045
          })
2046
          startAuctionStub.callsFake(({ortb2Fragments}) => {
1✔
UNCOV
2047
            ortb2Fragments.bidder.mockBidder.value = 'new';
×
2048
          })
2049
          pbjs.requestBids({ortb2: auctionFPD});
1✔
2050
          expect(configObj.getBidderConfig().mockBidder.ortb2.value).to.eql('old');
1✔
2051
        })
2052

2053
        it('enriched through enrichFPD', async () => {
1✔
2054
          function enrich(next, fpd) {
2055
            next.bail(fpd.then(ortb2 => {
1✔
2056
              ortb2.enrich = true;
1✔
2057
              return ortb2;
1✔
2058
            }))
2059
          }
2060

2061
          enrichFPD.before(enrich);
1✔
2062
          try {
1✔
2063
            configObj.setConfig({ortb2: globalFPD});
1✔
2064
            await runAuction({ortb2: auctionFPD});
1✔
2065
            sinon.assert.calledWith(startAuctionStub, sinon.match({
1✔
2066
              ortb2Fragments: {global: {...mergedFPD, enrich: true}}
2067
            }));
2068
          } finally {
2069
            enrichFPD.getHooks({hook: enrich}).remove();
1✔
2070
          }
2071
        })
2072
      });
2073

2074
      it('filtering adUnits by adUnitCodes', async () => {
1✔
2075
        await runAuction({
1✔
2076
          adUnits: [{code: 'one'}, {code: 'two'}],
2077
          adUnitCodes: 'two'
2078
        });
2079
        sinon.assert.calledWith(startAuctionStub, sinon.match({
1✔
2080
          adUnits: [{code: 'two'}],
2081
          adUnitCodes: ['two']
2082
        }));
2083
      });
2084

2085
      it('does not repeat ad unit codes on twin ad units', async () => {
1✔
2086
        await runAuction({
1✔
2087
          adUnits: [{code: 'au1'}, {code: 'au2'}, {code: 'au1'}, {code: 'au2'}],
2088
        });
2089
        sinon.assert.calledWith(startAuctionStub, sinon.match({
1✔
2090
          adUnitCodes: ['au1', 'au2']
2091
        }));
2092
      });
2093

2094
      it('filters out repeated ad unit codes from input', async () => {
1✔
2095
        await runAuction({adUnitCodes: ['au1', 'au1', 'au2']});
1✔
2096
        sinon.assert.calledWith(startAuctionStub, sinon.match({
1✔
2097
          adUnitCodes: ['au1', 'au2']
2098
        }));
2099
      });
2100

2101
      it('passing bidder-specific FPD as ortb2Fragments.bidder', async () => {
1✔
2102
        configObj.setBidderConfig({
1✔
2103
          bidders: ['bidderA', 'bidderC'],
2104
          config: {
2105
            ortb2: {
2106
              k1: 'v1'
2107
            }
2108
          }
2109
        });
2110
        configObj.setBidderConfig({
1✔
2111
          bidders: ['bidderB'],
2112
          config: {
2113
            ortb2: {
2114
              k2: 'v2'
2115
            }
2116
          }
2117
        });
2118
        await runAuction({})
1✔
2119
        sinon.assert.calledWith(startAuctionStub, sinon.match({
1✔
2120
          ortb2Fragments: {
2121
            bidder: {
2122
              bidderA: {
2123
                k1: 'v1'
2124
              },
2125
              bidderB: {
2126
                k2: 'v2'
2127
              },
2128
              bidderC: {
2129
                k1: 'v1'
2130
              }
2131
            }
2132
          }
2133
        }));
2134
      });
2135
    });
2136
  });
2137

2138
  describe('startAuction', () => {
1✔
2139
    let sandbox, newAuctionStub, auctionStarted;
2140

2141
    beforeEach(() => {
1✔
2142
      sandbox = sinon.createSandbox();
1✔
2143
      auctionStarted = new Promise(resolve => {
1✔
2144
        newAuctionStub = sandbox.stub(auctionManager, 'createAuction').callsFake(() => {
1✔
2145
          resolve();
1✔
2146
          return {
1✔
2147
            getAuctionId: () => 'mockAuctionId',
1✔
2148
            callBids: sinon.stub()
2149
          }
2150
        });
2151
      });
2152
    });
2153

2154
    afterEach(() => {
1✔
2155
      sandbox.restore();
1✔
2156
    });
2157

2158
    it('passes ortb2 fragments to createAuction', async () => {
1✔
2159
      const ortb2Fragments = {global: {}, bidder: {}};
1✔
2160
      pbjsModule.startAuction({
1✔
2161
        adUnits: [{
2162
          code: 'au',
2163
          mediaTypes: {banner: {sizes: [[300, 250]]}},
2164
          bids: [{bidder: 'bd'}]
2165
        }],
2166
        adUnitCodes: ['au'],
2167
        ortb2Fragments
2168
      });
2169
      await auctionStarted;
1✔
2170
      sinon.assert.calledWith(newAuctionStub, sinon.match({
1✔
2171
        ortb2Fragments: sinon.match.same(ortb2Fragments)
2172
      }));
2173
    });
2174
  })
2175

2176
  describe('requestBids', function () {
1✔
2177
    var adUnitsBackup;
2178
    var auctionManagerStub;
2179
    let logMessageSpy;
2180
    let logInfoSpy;
2181
    let logErrorSpy;
2182

2183
    const spec = {
1✔
2184
      code: 'sampleBidder',
2185
      isBidRequestValid: () => {},
2186
      buildRequests: () => {},
2187
      interpretResponse: () => {},
2188
      getUserSyncs: () => {}
2189
    };
2190
    registerBidder(spec);
1✔
2191

2192
    describe('part 1', function () {
1✔
2193
      let auctionArgs, auctionStarted;
2194

2195
      beforeEach(function () {
1✔
2196
        adUnitsBackup = auction.getAdUnits
44✔
2197
        auctionStarted = new Promise(resolve => {
44✔
2198
          auctionManagerStub = sinon.stub(auctionManager, 'createAuction').callsFake(function() {
44✔
2199
            auctionArgs = arguments[0];
41✔
2200
            resolve();
41✔
2201
            return auction;
41✔
2202
          });
2203
        })
2204
        logMessageSpy = sinon.spy(utils, 'logMessage');
44✔
2205
        logInfoSpy = sinon.spy(utils, 'logInfo');
44✔
2206
        logErrorSpy = sinon.spy(utils, 'logError');
44✔
2207
      });
2208

2209
      afterEach(function () {
1✔
2210
        auction.getAdUnits = adUnitsBackup;
44✔
2211
        auctionManager.createAuction.restore();
44✔
2212
        utils.logMessage.restore();
44✔
2213
        utils.logInfo.restore();
44✔
2214
        utils.logError.restore();
44✔
2215
        resetAuction();
44✔
2216
      });
2217

2218
      function runAuction(request = {}) {
41!
2219
        pbjs.requestBids(request);
41✔
2220
        return auctionStarted;
41✔
2221
      }
2222

2223
      it('should log message when adUnits not configured', async function () {
1✔
2224
        pbjs.adUnits = [];
1✔
2225
        try {
1✔
2226
          await pbjs.requestBids({});
1✔
2227
        } catch (e) {
2228
        }
2229
        assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged');
1✔
2230
      });
2231

2232
      it('should always attach new transactionIds to adUnits passed to requestBids', async function () {
1✔
2233
        await runAuction({
1✔
2234
          adUnits: [
2235
            {
2236
              code: 'test1',
2237
              transactionId: 'd0676a3c-ff32-45a5-af65-8175a8e7ddca',
2238
              mediaTypes: {banner: {sizes: []}},
2239
              bids: []
2240
            }, {
2241
              code: 'test2',
2242
              mediaTypes: {banner: {sizes: []}},
2243
              bids: []
2244
            }
2245
          ]
2246
        });
2247

2248
        expect(auctionArgs.adUnits[0]).to.have.property('transactionId')
1✔
2249
          .and.to.match(/[a-f0-9\-]{36}/i)
2250
          .and.not.to.equal('d0676a3c-ff32-45a5-af65-8175a8e7ddca');
2251
        expect(auctionArgs.adUnits[1]).to.have.property('transactionId')
1✔
2252
          .and.to.match(/[a-f0-9\-]{36}/i);
2253
      });
2254

2255
      it('should use the same transactionID for ad units with the same code', async () => {
1✔
2256
        await runAuction({
1✔
2257
          adUnits: [
2258
            {
2259
              code: 'twin',
2260
              mediaTypes: {banner: {sizes: []}},
2261
              bids: []
2262
            }, {
2263
              code: 'twin',
2264
              mediaTypes: {banner: {sizes: []}},
2265
              bids: []
2266
            }
2267
          ]
2268
        });
2269
        const tid = auctionArgs.adUnits[0].transactionId;
1✔
2270
        expect(tid).to.exist;
1✔
2271
        expect(auctionArgs.adUnits[1].transactionId).to.eql(tid);
1✔
2272
      });
2273

2274
      it('should re-use pub-provided transaction ID for ad units with the same code', async () => {
1✔
2275
        await runAuction({
1✔
2276
          adUnits: [
2277
            {
2278
              code: 'twin',
2279
              mediaTypes: {banner: {sizes: []}},
2280
              bids: [],
2281
            }, {
2282
              code: 'twin',
2283
              mediaTypes: {banner: {sizes: []}},
2284
              bids: [],
2285
              ortb2Imp: {
2286
                ext: {
2287
                  tid: 'pub-tid'
2288
                }
2289
              }
2290
            }
2291
          ]
2292
        });
2293
        expect(auctionArgs.adUnits.map(au => au.transactionId)).to.eql(['pub-tid', 'pub-tid']);
2✔
2294
      });
2295

2296
      it('should use pub-provided TIDs when they conflict for ad units with the same code', async () => {
1✔
2297
        await runAuction({
1✔
2298
          adUnits: [
2299
            {
2300
              code: 'twin',
2301
              mediaTypes: {banner: {sizes: []}},
2302
              bids: [],
2303
              ortb2Imp: {
2304
                ext: {
2305
                  tid: 't1'
2306
                }
2307
              }
2308
            }, {
2309
              code: 'twin',
2310
              mediaTypes: {banner: {sizes: []}},
2311
              bids: [],
2312
              ortb2Imp: {
2313
                ext: {
2314
                  tid: 't2'
2315
                }
2316
              }
2317
            }
2318
          ]
2319
        });
2320
        expect(auctionArgs.adUnits.map(au => au.transactionId)).to.eql(['t1', 't2']);
2✔
2321
      });
2322

2323
      it('should generate unique adUnitId', async () => {
1✔
2324
        await runAuction({
1✔
2325
          adUnits: [
2326
            {
2327
              code: 'single',
2328
              mediaTypes: {banner: {sizes: []}},
2329
              bids: []
2330
            }, {
2331
              code: 'twin',
2332
              mediaTypes: {banner: {sizes: []}},
2333
              bids: []
2334
            },
2335
            {
2336
              code: 'twin',
2337
              mediaTypes: {banner: {sizes: []}},
2338
              bids: []
2339
            }
2340
          ]
2341
        });
2342

2343
        const ids = new Set();
1✔
2344
        auctionArgs.adUnits.forEach(au => {
1✔
2345
          expect(au.adUnitId).to.exist;
3✔
2346
          ids.add(au.adUnitId);
3✔
2347
        });
2348
        expect(ids.size).to.eql(3);
1✔
2349
      });
2350

2351
      describe('transactionId', () => {
1✔
2352
        let adUnit;
2353
        beforeEach(() => {
1✔
2354
          adUnit = {
2✔
2355
            code: 'adUnit',
2356
            mediaTypes: {
2357
              banner: {
2358
                sizes: [300, 250]
2359
              }
2360
            },
2361
            bids: [
2362
              {
2363
                bidder: 'mock-bidder',
2364
              }
2365
            ]
2366
          };
2367
        });
2368
        it('should be set to ortb2Imp.ext.tid, if specified', async () => {
1✔
2369
          await runAuction({
1✔
2370
            adUnits: [
2371
              {...adUnit, ortb2Imp: {ext: {tid: 'custom-tid'}}}
2372
            ]
2373
          });
2374
          sinon.assert.match(auctionArgs.adUnits[0], {
1✔
2375
            transactionId: 'custom-tid',
2376
            ortb2Imp: {
2377
              ext: {
2378
                tid: 'custom-tid'
2379
              }
2380
            }
2381
          })
2382
        });
2383
        it('should NOT be copied to ortb2Imp.ext.tid, if not specified', async () => {
1✔
2384
          await runAuction({
1✔
2385
            adUnits: [
2386
              adUnit
2387
            ]
2388
          });
2389
          const tid = auctionArgs.adUnits[0].transactionId;
1✔
2390
          expect(tid).to.exist;
1✔
2391
          expect(auctionArgs.adUnits[0].ortb2Imp?.ext?.tid).to.not.exist;
1✔
2392
        });
2393
      });
2394

2395
      it('should NOT set ortb2.ext.tid same as transactionId in adUnits', async function () {
1✔
2396
        await runAuction({
1✔
2397
          adUnits: [
2398
            {
2399
              code: 'test1',
2400
              mediaTypes: {banner: {sizes: []}},
2401
              bids: []
2402
            }, {
2403
              code: 'test2',
2404
              mediaTypes: {banner: {sizes: []}},
2405
              bids: []
2406
            }
2407
          ]
2408
        });
2409

2410
        expect(auctionArgs.adUnits[0]).to.have.property('transactionId');
1✔
2411
        expect(auctionArgs.adUnits[0].ortb2Imp?.ext?.tid).to.not.exist;
1✔
2412
        expect(auctionArgs.adUnits[1]).to.have.property('transactionId');
1✔
2413
        expect(auctionArgs.adUnits[0].ortb2Imp?.ext?.tid).to.not.exist;
1✔
2414
      });
2415

2416
      it('should notify targeting of the latest auction for each adUnit', async function () {
1✔
2417
        const latestStub = sinon.stub(targeting, 'setLatestAuctionForAdUnit');
1✔
2418
        const getAuctionStub = sinon.stub(auction, 'getAuctionId').returns(2);
1✔
2419

2420
        await runAuction({
1✔
2421
          adUnits: [
2422
            {
2423
              code: 'test1',
2424
              mediaTypes: {banner: {sizes: []}},
2425
              bids: []
2426
            }, {
2427
              code: 'test2',
2428
              mediaTypes: {banner: {sizes: []}},
2429
              bids: []
2430
            }
2431
          ]
2432
        });
2433

2434
        expect(latestStub.firstCall.calledWith('test1', 2)).to.equal(true);
1✔
2435
        expect(latestStub.secondCall.calledWith('test2', 2)).to.equal(true);
1✔
2436

2437
        latestStub.restore();
1✔
2438
        getAuctionStub.restore();
1✔
2439
      });
2440

2441
      it('should execute callback immediately if adUnits is empty', async function () {
1✔
2442
        var bidsBackHandler = function bidsBackHandlerCallback() {
1✔
2443
        };
2444
        var spyExecuteCallback = sinon.spy(bidsBackHandler);
1✔
2445

2446
        pbjs.adUnits = [];
1✔
2447
        await pbjs.requestBids({
1✔
2448
          bidsBackHandler: spyExecuteCallback
2449
        });
2450

2451
        assert.ok(spyExecuteCallback.calledOnce, 'callback executed immediately when adUnits is' +
1✔
2452
          ' empty');
2453
      });
2454

2455
      it('should not propagate exceptions from bidsBackHandler', function () {
1✔
2456
        pbjs.adUnits = [];
1✔
2457

2458
        var requestObj = {
1✔
2459
          bidsBackHandler: function bidsBackHandlerCallback() {
2460
            var test;
2461
            return test.test;
1✔
2462
          }
2463
        };
2464

2465
        expect(() => {
1✔
2466
          pbjs.requestBids(requestObj);
1✔
2467
        }).not.to.throw();
2468
      });
2469

2470
      describe('checkAdUnitSetup', function() {
1✔
2471
        describe('positive tests for validating adUnits', function() {
1✔
2472
          describe('should maintain adUnit structure and adUnit.sizes is replaced', () => {
1✔
2473
            it('full ad unit', async () => {
1✔
2474
              const fullAdUnit = [{
1✔
2475
                code: 'test1',
2476
                sizes: [[300, 250], [300, 600]],
2477
                mediaTypes: {
2478
                  banner: {
2479
                    sizes: [[300, 250]]
2480
                  },
2481
                  video: {
2482
                    playerSize: [[640, 480]]
2483
                  },
2484
                  native: {
2485
                    image: {
2486
                      sizes: [150, 150],
2487
                      aspect_ratios: [140, 140]
2488
                    },
2489
                    icon: {
2490
                      sizes: [75, 75]
2491
                    }
2492
                  }
2493
                },
2494
                bids: []
2495
              }];
2496
              await runAuction({
1✔
2497
                adUnits: fullAdUnit
2498
              });
2499
              expect(auctionArgs.adUnits[0].sizes).to.deep.equal(
1✔
2500
                FEATURES.VIDEO ? [[640, 480]] : [[300, 250]]
1!
2501
              );
2502
              expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]);
1✔
2503
              expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.deep.equal([150, 150]);
1✔
2504
              expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.deep.equal([75, 75]);
1✔
2505
              expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.deep.equal([140, 140]);
1✔
2506
            })
2507
            it('no optional field', async () => {
1✔
2508
              const noOptnlFieldAdUnit = [{
1✔
2509
                code: 'test2',
2510
                bids: [],
2511
                sizes: [[300, 250], [300, 600]],
2512
                mediaTypes: {
2513
                  banner: {
2514
                    sizes: [[300, 250]]
2515
                  },
2516
                  video: {
2517
                    context: 'outstream'
2518
                  },
2519
                  native: {
2520
                    image: {
2521
                      required: true
2522
                    },
2523
                    icon: {
2524
                      required: true
2525
                    }
2526
                  }
2527
                }
2528
              }];
2529
              await runAuction({
1✔
2530
                adUnits: noOptnlFieldAdUnit
2531
              });
2532
              expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250]]);
1✔
2533
              expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist;
1✔
2534
            })
2535
            if (FEATURES.VIDEO) {
1✔
2536
              it('mixed ad unit', async () => {
1✔
2537
                const mixedAdUnit = [{
1✔
2538
                  code: 'test3',
2539
                  bids: [],
2540
                  sizes: [[300, 250], [300, 600]],
2541
                  mediaTypes: {
2542
                    video: {
2543
                      context: 'outstream',
2544
                      playerSize: [[400, 350]]
2545
                    },
2546
                    native: {
2547
                      image: {
2548
                        aspect_ratios: [200, 150],
2549
                        required: true
2550
                      }
2551
                    }
2552
                  }
2553
                }];
2554
                await runAuction({
1✔
2555
                  adUnits: mixedAdUnit
2556
                });
2557
                expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[400, 350]]);
1✔
2558
                expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist;
1✔
2559
              });
2560
              it('alternative video size', async () => {
1✔
2561
                const altVideoPlayerSize = [{
1✔
2562
                  code: 'test4',
2563
                  bids: [],
2564
                  sizes: [[600, 600]],
2565
                  mediaTypes: {
2566
                    video: {
2567
                      playerSize: [640, 480]
2568
                    }
2569
                  }
2570
                }];
2571
                await runAuction({
1✔
2572
                  adUnits: altVideoPlayerSize
2573
                });
2574
                expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[640, 480]]);
1✔
2575
                expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.deep.equal([[640, 480]]);
1✔
2576
                expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist;
1✔
2577
              })
2578
            }
2579
          })
2580

2581
          it('should normalize adUnit.sizes and adUnit.mediaTypes.banner.sizes', async function () {
1✔
2582
            const normalizeAdUnit = [{
1✔
2583
              code: 'test5',
2584
              bids: [],
2585
              sizes: [300, 250],
2586
              mediaTypes: {
2587
                banner: {
2588
                  sizes: [300, 250]
2589
                }
2590
              }
2591
            }];
2592
            await runAuction({
1✔
2593
              adUnits: normalizeAdUnit
2594
            });
2595
            expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250]]);
1✔
2596
            expect(auctionArgs.adUnits[0].mediaTypes.banner.sizes).to.deep.equal([[300, 250]]);
1✔
2597
          });
2598

2599
          it('should filter mediaType pos value if not integer', async function () {
1✔
2600
            const adUnit = [{
1✔
2601
              code: 'test5',
2602
              bids: [],
2603
              sizes: [300, 250],
2604
              mediaTypes: {
2605
                banner: {
2606
                  sizes: [300, 250],
2607
                  pos: 'foo'
2608
                }
2609
              }
2610
            }];
2611
            await runAuction({
1✔
2612
              adUnits: adUnit
2613
            });
2614
            expect(auctionArgs.adUnits[0].mediaTypes.banner.pos).to.be.undefined;
1✔
2615
          });
2616

2617
          it('should pass mediaType pos value if integer', async function () {
1✔
2618
            const adUnit = [{
1✔
2619
              code: 'test5',
2620
              bids: [],
2621
              sizes: [300, 250],
2622
              mediaTypes: {
2623
                banner: {
2624
                  sizes: [300, 250],
2625
                  pos: 2
2626
                }
2627
              }
2628
            }, {
2629
              code: 'test6',
2630
              bids: [],
2631
              sizes: [300, 250],
2632
              mediaTypes: {
2633
                banner: {
2634
                  sizes: [300, 250],
2635
                  pos: 0
2636
                }
2637
              }
2638
            }];
2639
            await runAuction({
1✔
2640
              adUnits: adUnit
2641
            });
2642
            expect(auctionArgs.adUnits[0].mediaTypes.banner.pos).to.equal(2);
1✔
2643
            expect(auctionArgs.adUnits[1].mediaTypes.banner.pos).to.equal(0);
1✔
2644
          });
2645

2646
          it(`should allow no bids if 'ortb2Imp' is specified`, async () => {
1✔
2647
            const adUnit = {
1✔
2648
              code: 'test',
2649
              mediaTypes: {
2650
                banner: {
2651
                  sizes: [[300, 250]]
2652
                }
2653
              },
2654
              ortb2Imp: {}
2655
            };
2656
            await runAuction({
1✔
2657
              adUnits: [adUnit]
2658
            });
2659
            sinon.assert.match(auctionArgs.adUnits[0], adUnit);
1✔
2660
          });
2661

2662
          describe('banner.format', () => {
1✔
2663
            let au;
2664
            beforeEach(() => {
1✔
2665
              au = {
12✔
2666
                code: 'test',
2667
                bids: [],
2668
                mediaTypes: {
2669
                  banner: {}
2670
                }
2671
              };
2672
            });
2673
            const EXPDIR = ['ortb2Imp.banner.expdir', 'mediaTypes.banner.expdir'];
1✔
2674
            EXPDIR.forEach(prop => {
1✔
2675
              it(`should make ${prop} avaliable under both ${EXPDIR.join(' and ')}`, async () => {
2✔
2676
                au.mediaTypes.banner.sizes = [1, 2];
2✔
2677
                deepSetValue(au, prop, [1, 2]);
2✔
2678
                await runAuction({
2✔
2679
                  adUnits: [au]
2680
                })
2681
                EXPDIR.forEach(dest => {
2✔
2682
                  expect(deepAccess(auctionArgs.adUnits[0], dest)).to.eql([1, 2]);
4✔
2683
                });
2684
              })
2685
            });
2686
            ['ortb2Imp.banner.format', 'mediaTypes.banner.format'].forEach(prop => {
1✔
2687
              it(`should accept ${prop} instead of sizes`, async () => {
2✔
2688
                deepSetValue(au, prop, [{w: 123, h: 321}, {w: 444, h: 555}]);
2✔
2689
                await runAuction({
2✔
2690
                  adUnits: [au]
2691
                })
2692
                expect(auctionArgs.adUnits[0].mediaTypes.banner.sizes).to.deep.equal([[123, 321], [444, 555]]);
2✔
2693
              });
2694

2695
              it(`should make ${prop} available under both mediaTypes.banner and ortb2Imp.format`, async () => {
2✔
2696
                const format = [{w: 123, h: 321}];
2✔
2697
                deepSetValue(au, prop, format);
2✔
2698
                await runAuction({
2✔
2699
                  adUnits: [au]
2700
                })
2701
                expect(auctionArgs.adUnits[0].mediaTypes.banner.format).to.deep.equal(format);
2✔
2702
                expect(auctionArgs.adUnits[0].ortb2Imp.banner.format).to.deep.equal(format);
2✔
2703
              })
2704

2705
              it(`should transform wratio/hratio from ${prop} into placeholder sizes`, async () => {
2✔
2706
                deepSetValue(au, prop, [{w: 123, h: 321}, {wratio: 2, hratio: 1}]);
2✔
2707
                await runAuction({
2✔
2708
                  adUnits: [au]
2709
                })
2710
                expect(auctionArgs.adUnits[0].mediaTypes.banner.sizes).to.deep.equal([[123, 321], [2, 1]]);
2✔
2711
              });
2712
              it(`should ignore ${prop} elements that specify both w/h and wratio/hratio`, async () => {
2✔
2713
                deepSetValue(au, prop, [{w: 333, hratio: 2}, {w: 123, h: 321}]);
2✔
2714
                await runAuction({
2✔
2715
                  adUnits: [au]
2716
                })
2717
                expect(auctionArgs.adUnits[0].mediaTypes.banner.sizes).to.deep.equal([[123, 321]]);
2✔
2718
              });
2719

2720
              it('should ignore incomplete formats', async () => {
2✔
2721
                deepSetValue(au, prop, [{w: 123, h: 321}, {w: 123}, {wratio: 2}]);
2✔
2722
                await runAuction({
2✔
2723
                  adUnits: [au]
2724
                })
2725
                expect(auctionArgs.adUnits[0].mediaTypes.banner.sizes).to.deep.equal([[123, 321]]);
2✔
2726
              })
2727
            });
2728
          })
2729
        });
2730

2731
        describe('negative tests for validating adUnits', function() {
1✔
2732
          describe('should throw error message and delete an object/property', () => {
1✔
2733
            it('bad banner', async () => {
1✔
2734
              const badBanner = [{
1✔
2735
                code: 'testb1',
2736
                bids: [],
2737
                sizes: [[300, 250], [300, 600]],
2738
                mediaTypes: {
2739
                  banner: {
2740
                    name: 'test'
2741
                  }
2742
                }
2743
              }];
2744
              await runAuction({
1✔
2745
                adUnits: badBanner
2746
              });
2747
              expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[300, 250], [300, 600]]);
1✔
2748
              expect(auctionArgs.adUnits[0].mediaTypes.banner).to.be.undefined;
1✔
2749
              assert.ok(logErrorSpy.calledWith('Detected a mediaTypes.banner object without a proper sizes field.  Please ensure the sizes are listed like: [[300, 250], ...].  Removing invalid mediaTypes.banner object from request.'));
1✔
2750
            });
2751
            if (FEATURES.VIDEO) {
1✔
2752
              it('bad video 1', async () => {
1✔
2753
                const badVideo1 = [{
1✔
2754
                  code: 'testb2',
2755
                  bids: [],
2756
                  sizes: [[600, 600]],
2757
                  mediaTypes: {
2758
                    video: {
2759
                      playerSize: ['600x400']
2760
                    }
2761
                  }
2762
                }];
2763
                await runAuction({
1✔
2764
                  adUnits: badVideo1
2765
                });
2766
                expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]);
1✔
2767
                expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined;
1✔
2768
                expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist;
1✔
2769
                assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize.  Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.'));
1✔
2770
              });
2771
              it('bad video 2', async () => {
1✔
2772
                const badVideo2 = [{
1✔
2773
                  code: 'testb3',
2774
                  bids: [],
2775
                  sizes: [[600, 600]],
2776
                  mediaTypes: {
2777
                    video: {
2778
                      playerSize: [['300', '200']]
2779
                    }
2780
                  }
2781
                }];
2782
                await runAuction({
1✔
2783
                  adUnits: badVideo2
2784
                });
2785
                expect(auctionArgs.adUnits[0].sizes).to.deep.equal([[600, 600]]);
1✔
2786
                expect(auctionArgs.adUnits[0].mediaTypes.video.playerSize).to.be.undefined;
1✔
2787
                expect(auctionArgs.adUnits[0].mediaTypes.video).to.exist;
1✔
2788
                assert.ok(logErrorSpy.calledWith('Detected incorrect configuration of mediaTypes.video.playerSize.  Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.'));
1✔
2789
              })
2790
            }
2791
            if (FEATURES.NATIVE) {
1✔
2792
              it('bad native img size', async () => {
1✔
2793
                const badNativeImgSize = [{
1✔
2794
                  code: 'testb4',
2795
                  bids: [],
2796
                  mediaTypes: {
2797
                    native: {
2798
                      image: {
2799
                        sizes: '300x250'
2800
                      }
2801
                    }
2802
                  }
2803
                }];
2804
                await runAuction({
1✔
2805
                  adUnits: badNativeImgSize
2806
                });
2807
                expect(auctionArgs.adUnits[0].mediaTypes.native.image.sizes).to.be.undefined;
1✔
2808
                expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist;
1✔
2809
                assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.sizes field.  Removing invalid mediaTypes.native.image.sizes property from request.'));
1✔
2810
              });
2811
              it('bad native aspect ratio', async () => {
1✔
2812
                const badNativeImgAspRat = [{
1✔
2813
                  code: 'testb5',
2814
                  bids: [],
2815
                  mediaTypes: {
2816
                    native: {
2817
                      image: {
2818
                        aspect_ratios: '300x250'
2819
                      }
2820
                    }
2821
                  }
2822
                }];
2823
                await runAuction({
1✔
2824
                  adUnits: badNativeImgAspRat
2825
                });
2826
                expect(auctionArgs.adUnits[0].mediaTypes.native.image.aspect_ratios).to.be.undefined;
1✔
2827
                expect(auctionArgs.adUnits[0].mediaTypes.native.image).to.exist;
1✔
2828
                assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.image.aspect_ratios field.  Removing invalid mediaTypes.native.image.aspect_ratios property from request.'));
1✔
2829
              });
2830
              it('bad native icon', async () => {
1✔
2831
                const badNativeIcon = [{
1✔
2832
                  code: 'testb6',
2833
                  bids: [],
2834
                  mediaTypes: {
2835
                    native: {
2836
                      icon: {
2837
                        sizes: '300x250'
2838
                      }
2839
                    }
2840
                  }
2841
                }];
2842
                await runAuction({
1✔
2843
                  adUnits: badNativeIcon
2844
                });
2845
                expect(auctionArgs.adUnits[0].mediaTypes.native.icon.sizes).to.be.undefined;
1✔
2846
                expect(auctionArgs.adUnits[0].mediaTypes.native.icon).to.exist;
1✔
2847
                assert.ok(logErrorSpy.calledWith('Please use an array of sizes for native.icon.sizes field.  Removing invalid mediaTypes.native.icon.sizes property from request.'));
1✔
2848
              })
2849
            }
2850
          })
2851

2852
          if (FEATURES.NATIVE) {
1✔
2853
            Object.entries({
1✔
2854
              missing: {},
2855
              negative: {id: -1},
2856
              'not an integer': {id: 1.23},
2857
              NaN: {id: 'garbage'}
2858
            }).forEach(([t, props]) => {
4✔
2859
              it(`should reject native ortb when asset ID is ${t}`, async () => {
4✔
2860
                const adUnit = {
4✔
2861
                  code: 'au',
2862
                  mediaTypes: {
2863
                    native: {
2864
                      ortb: {
2865
                        assets: [props]
2866
                      }
2867
                    }
2868
                  },
2869
                  bids: [{bidder: 'appnexus'}]
2870
                };
2871
                await runAuction({
4✔
2872
                  adUnits: [adUnit]
2873
                });
2874
                expect(auctionArgs.adUnits[0].bids.length).to.equal(0);
4✔
2875
              });
2876
            });
2877

2878
            ['types'].forEach(key => {
1✔
2879
              it(`should reject native that includes both ortb and ${key}`, async () => {
1✔
2880
                const adUnit = {
1✔
2881
                  code: 'au',
2882
                  mediaTypes: {
2883
                    native: {
2884
                      ortb: {},
2885
                      [key]: {}
2886
                    }
2887
                  },
2888
                  bids: [{bidder: 'appnexus'}]
2889
                };
2890
                await runAuction({
1✔
2891
                  adUnits: [adUnit]
2892
                });
2893
                expect(auctionArgs.adUnits[0].bids.length).to.equal(0);
1✔
2894
              })
2895
            });
2896
          }
2897

2898
          it('should throw error message and remove adUnit if adUnit.bids is not defined correctly', async function () {
1✔
2899
            const adUnits = [{
1✔
2900
              code: 'ad-unit-1',
2901
              mediaTypes: {
2902
                banner: {
2903
                  sizes: [300, 400]
2904
                }
2905
              },
2906
              bids: [{code: 'appnexus', params: 1234}]
2907
            }, {
2908
              code: 'bad-ad-unit-2',
2909
              mediaTypes: {
2910
                banner: {
2911
                  sizes: [300, 400]
2912
                }
2913
              }
2914
            }];
2915

2916
            await runAuction({
1✔
2917
              adUnits: adUnits
2918
            });
2919
            expect(auctionArgs.adUnits.length).to.equal(1);
1✔
2920
            expect(auctionArgs.adUnits[1]).to.not.exist;
1✔
2921
            assert.ok(logErrorSpy.calledWith("adUnit.code 'bad-ad-unit-2' has no 'adUnit.bids' and no 'adUnit.ortb2Imp'. Removing adUnit from auction"));
1✔
2922
          });
2923
        });
2924
      });
2925
    });
2926

2927
    describe('multiformat requests', function () {
1✔
2928
      if (!FEATURES.NATIVE) {
1!
UNCOV
2929
        return;
×
2930
      }
2931
      let adUnits, auctionStarted;
2932

2933
      beforeEach(function () {
1✔
2934
        adUnits = [{
3✔
2935
          code: 'adUnit-code',
2936
          mediaTypes: {
2937
            banner: {
2938
              sizes: [[300, 250]]
2939
            },
2940
            native: {},
2941
          },
2942
          sizes: [[300, 250], [300, 600]],
2943
          bids: [
2944
            {bidder: 'appnexus', params: {placementId: 'id'}},
2945
            {bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}
2946
          ]
2947
        }];
2948
        adUnitCodes = ['adUnit-code'];
3✔
2949
        configObj.setConfig({maxRequestsPerOrigin: Number.MAX_SAFE_INTEGER || 99999999});
3!
2950
        auctionStarted = new Promise(resolve => {
3✔
2951
          sinon.stub(adapterManager, 'callBids').callsFake(function() {
3✔
2952
            resolve();
3✔
2953
            return adapterManager.callBids.wrappedMethod.apply(this, arguments);
3✔
2954
          });
2955
        })
2956
      })
2957

2958
      afterEach(function () {
1✔
2959
        adapterManager.callBids.restore();
3✔
2960
      });
2961

2962
      it('bidders that support one of the declared formats are allowed to participate', async function () {
1✔
2963
        pbjs.requestBids({adUnits});
1✔
2964
        await auctionStarted;
1✔
2965
        sinon.assert.calledOnce(adapterManager.callBids);
1✔
2966

2967
        const spyArgs = adapterManager.callBids.getCall(0);
1✔
2968
        const biddersCalled = spyArgs.args[0][0].bids;
1✔
2969

2970
        // appnexus and sampleBidder both support banner
2971
        expect(biddersCalled.length).to.equal(2);
1✔
2972
      });
2973

2974
      it('bidders that do not support one of the declared formats are dropped', async function () {
1✔
2975
        delete adUnits[0].mediaTypes.banner;
1✔
2976

2977
        pbjs.requestBids({adUnits});
1✔
2978
        await auctionStarted;
1✔
2979
        sinon.assert.calledOnce(adapterManager.callBids);
1✔
2980

2981
        const spyArgs = adapterManager.callBids.getCall(0);
1✔
2982
        const biddersCalled = spyArgs.args[0][0].bids;
1✔
2983
        // only appnexus supports native
2984
        expect(biddersCalled.length).to.equal(1);
1✔
2985
      });
2986

2987
      it('module bids should not be filtered out', async () => {
1✔
2988
        delete adUnits[0].mediaTypes.banner;
1✔
2989
        adUnits[0].bids.push({
1✔
2990
          module: 'pbsBidAdapter',
2991
          ortb2Imp: {}
2992
        });
2993

2994
        pbjs.requestBids({adUnits});
1✔
2995
        await auctionStarted;
1✔
2996
        expect(adapterManager.callBids.getCall(0).args[0][0].bids.length).to.eql(2);
1✔
2997
      })
2998
    });
2999

3000
    describe('part 2', function () {
1✔
3001
      if (!FEATURES.NATIVE) {
1!
UNCOV
3002
        return;
×
3003
      }
3004
      let spyCallBids;
3005
      let adUnits, adUnitCodes;
3006

3007
      beforeEach(function () {
1✔
3008
        adUnitCodes = ['adUnit-code'];
3✔
3009
        spyCallBids = sinon.spy(adapterManager, 'callBids');
3✔
3010
      })
3011

3012
      afterEach(function () {
1✔
3013
        adapterManager.callBids.restore();
3✔
3014
      })
3015

3016
      function runAuction(request = {}) {
3!
3017
        const auctionStarted = new Promise(resolve => {
3✔
3018
          sandbox.stub(auctionModule, 'newAuction').callsFake((...args) => {
3✔
3019
            resolve();
3✔
3020
            return auctionModule.newAuction.wrappedMethod(...args);
3✔
3021
          });
3022
        })
3023
        pbjs.requestBids(request);
3✔
3024
        return auctionStarted;
3✔
3025
      }
3026

3027
      it('should callBids if a native adUnit has all native bidders', async function () {
1✔
3028
        adUnits = [{
1✔
3029
          code: 'adUnit-code',
3030
          mediaTypes: { native: {} },
3031
          bids: [
3032
            {bidder: 'appnexus', params: {placementId: '10433394'}}
3033
          ]
3034
        }];
3035
        await runAuction({adUnits});
1✔
3036
        sinon.assert.calledOnce(adapterManager.callBids);
1✔
3037
      });
3038

3039
      it('should call callBids function on adapterManager', async function () {
1✔
3040
        adUnits = [{
1✔
3041
          code: 'adUnit-code',
3042
          mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}},
3043
          bids: [
3044
            {bidder: 'appnexus', params: {placementId: '10433394'}}
3045
          ]
3046
        }];
3047
        await runAuction({adUnits});
1✔
3048
        assert.ok(spyCallBids.called, 'called adapterManager.callBids');
1✔
3049
      });
3050

3051
      it('splits native type to individual native assets', async function () {
1✔
3052
        adUnits = [{
1✔
3053
          code: 'adUnit-code',
3054
          mediaTypes: {native: {type: 'image'}},
3055
          bids: [
3056
            {bidder: 'appnexus', params: {placementId: 'id'}}
3057
          ]
3058
        }];
3059
        await runAuction({adUnits});
1✔
3060
        const spyArgs = adapterManager.callBids.getCall(0);
1✔
3061
        const nativeRequest = spyArgs.args[1][0].bids[0].nativeParams;
1✔
3062
        expect(nativeRequest.ortb.assets).to.deep.equal([
1✔
3063
          {
3064
            required: 1,
3065
            id: 1,
3066
            img: {
3067
              type: 3,
3068
              wmin: 100,
3069
              hmin: 100,
3070
            }
3071
          },
3072
          {
3073
            required: 1,
3074
            id: 2,
3075
            title: {
3076
              len: 140,
3077
            }
3078
          },
3079
          {
3080
            required: 1,
3081
            id: 3,
3082
            data: {
3083
              type: 1,
3084
            }
3085
          },
3086
          {
3087
            required: 0,
3088
            id: 4,
3089
            data: {
3090
              type: 2,
3091
            }
3092
          },
3093
          {
3094
            required: 0,
3095
            id: 5,
3096
            img: {
3097
              type: 1,
3098
              wmin: 20,
3099
              hmin: 20,
3100
            }
3101
          },
3102
        ]);
3103
        resetAuction();
1✔
3104
      });
3105
    });
3106

3107
    describe('part-3', function () {
1✔
3108
      const auctionManagerInstance = newAuctionManager();
1✔
3109
      let auctionManagerStub;
3110
      const adUnits1 = getAdUnits().filter((adUnit) => {
1✔
3111
        return adUnit.code === '/19968336/header-bid-tag1';
2✔
3112
      });
3113
      const adUnitCodes1 = getAdUnits().map(unit => unit.code);
2✔
3114
      const auction1 = auctionManagerInstance.createAuction({adUnits: adUnits1, adUnitCodes: adUnitCodes1});
1✔
3115

3116
      const adUnits2 = getAdUnits().filter((adUnit) => {
1✔
3117
        return adUnit.code === '/19968336/header-bid-tag-0';
2✔
3118
      });
3119
      const adUnitCodes2 = getAdUnits().map(unit => unit.code);
2✔
3120
      const auction2 = auctionManagerInstance.createAuction({adUnits: adUnits2, adUnitCodes: adUnitCodes2});
1✔
3121
      let spyCallBids;
3122

3123
      auction1.getBidRequests = function() {
1✔
3124
        return getBidRequests().map((req) => {
×
UNCOV
3125
          req.bids = req.bids.filter((bid) => {
×
UNCOV
3126
            return bid.adUnitCode === '/19968336/header-bid-tag1';
×
3127
          });
3128
          return (req.bids.length > 0) ? req : undefined;
×
3129
        }).filter((item) => {
UNCOV
3130
          return item !== undefined;
×
3131
        });
3132
      };
3133
      auction1.getBidsReceived = function() {
1✔
UNCOV
3134
        return getBidResponses().filter((bid) => {
×
UNCOV
3135
          return bid.adUnitCode === '/19968336/header-bid-tag1';
×
3136
        });
3137
      };
3138

3139
      auction2.getBidRequests = function() {
1✔
UNCOV
3140
        return getBidRequests().map((req) => {
×
UNCOV
3141
          req.bids = req.bids.filter((bid) => {
×
UNCOV
3142
            return bid.adUnitCode === '/19968336/header-bid-tag-0';
×
3143
          });
UNCOV
3144
          return (req.bids.length > 0) ? req : undefined;
×
3145
        }).filter((item) => {
UNCOV
3146
          return item !== undefined;
×
3147
        });
3148
      };
3149
      auction2.getBidsReceived = function() {
1✔
UNCOV
3150
        return getBidResponses().filter((bid) => {
×
UNCOV
3151
          return bid.adUnitCode === '/19968336/header-bid-tag-0';
×
3152
        });
3153
      };
3154

3155
      let auctionsStarted;
3156

3157
      beforeEach(function() {
1✔
3158
        spyCallBids = sinon.spy(adapterManager, 'callBids');
1✔
3159
        auctionManagerStub = sinon.stub(auctionManager, 'createAuction');
1✔
3160
        auctionsStarted = Promise.all(
1✔
3161
          [auction1, auction2].map((au, i) => new Promise((resolve) => {
2✔
3162
            auctionManagerStub.onCall(i).callsFake(() => {
2✔
3163
              resolve();
2✔
3164
              return au;
2✔
3165
            });
3166
          }))
3167
        );
3168
      });
3169

3170
      afterEach(function() {
1✔
3171
        auctionManager.createAuction.restore();
1✔
3172
        adapterManager.callBids.restore();
1✔
3173
      });
3174

3175
      it('should not queue bid requests when a previous bid request is in process', async function () {
1✔
3176
        var requestObj1 = {
1✔
3177
          bidsBackHandler: function bidsBackHandlerCallback() {
3178
          },
3179
          timeout: 2000,
3180
          adUnits: auction1.getAdUnits()
3181
        };
3182

3183
        var requestObj2 = {
1✔
3184
          bidsBackHandler: function bidsBackHandlerCallback() {
3185
          },
3186
          timeout: 2000,
3187
          adUnits: auction2.getAdUnits()
3188
        };
3189

3190
        assert.equal(auctionManager.getBidsReceived().length, 8, '_bidsReceived contains 8 bids');
1✔
3191
        pbjs.setConfig({ targetingControls: {allBidsCustomTargeting: true }});
1✔
3192
        pbjs.requestBids(requestObj1);
1✔
3193
        pbjs.requestBids(requestObj2);
1✔
3194
        await auctionsStarted;
1✔
3195

3196
        assert.ok(spyCallBids.calledTwice, 'When two requests for bids are made both should be' +
1✔
3197
          ' callBids immediately');
3198

3199
        const result = targeting.getAllTargeting(['/19968336/header-bid-tag-0', '/19968336/header-bid-tag1']); // pbjs.getAdserverTargeting();
1✔
3200
        const expected = {
1✔
3201
          '/19968336/header-bid-tag-0': {
3202
            'foobar': '300x250,300x600,0x0',
3203
            [TARGETING_KEYS.SIZE]: '300x250',
3204
            [TARGETING_KEYS.PRICE_BUCKET]: '10.00',
3205
            [TARGETING_KEYS.AD_ID]: '233bcbee889d46d',
3206
            [TARGETING_KEYS.BIDDER]: 'appnexus'
3207
          },
3208
          '/19968336/header-bid-tag1': {
3209
            [TARGETING_KEYS.BIDDER]: 'appnexus',
3210
            [TARGETING_KEYS.AD_ID]: '24bd938435ec3fc',
3211
            [TARGETING_KEYS.PRICE_BUCKET]: '10.00',
3212
            [TARGETING_KEYS.SIZE]: '728x90',
3213
            'foobar': '728x90'
3214
          }
3215
        }
3216
        sinon.assert.match(result, expected)
1✔
3217
      });
3218
    });
3219
  });
3220

3221
  describe('onEvent', function () {
1✔
3222
    it('should log an error when handler is not a function', function () {
1✔
3223
      var spyLogError = sinon.spy(utils, 'logError');
1✔
3224
      var event = 'testEvent';
1✔
3225
      pbjs.onEvent(event);
1✔
3226
      assert.ok(spyLogError.calledWith('The event handler provided is not a function and was not set on event "' + event + '".'),
1✔
3227
        'expected error was logged');
3228
      utils.logError.restore();
1✔
3229
    });
3230

3231
    it('should log an error when id provided is not valid for event', function () {
1✔
3232
      var spyLogError = sinon.spy(utils, 'logError');
1✔
3233
      var event = 'bidWon';
1✔
3234
      pbjs.onEvent(event, Function, 'testId');
1✔
3235
      assert.ok(spyLogError.calledWith('The id provided is not valid for event "' + event + '" and no handler was set.'),
1✔
3236
        'expected error was logged');
3237
      utils.logError.restore();
1✔
3238
    });
3239

3240
    it('should call events.on with valid parameters', function () {
1✔
3241
      var spyEventsOn = sinon.spy(events, 'on');
1✔
3242
      pbjs.onEvent('bidWon', Function);
1✔
3243
      assert.ok(spyEventsOn.calledWith('bidWon', Function));
1✔
3244
      events.on.restore();
1✔
3245
    });
3246

3247
    it('should emit event BID_ACCEPTED when invoked', function () {
1✔
3248
      var callback = sinon.spy();
1✔
3249
      pbjs.onEvent('bidAccepted', callback);
1✔
3250
      events.emit(EVENTS.BID_ACCEPTED);
1✔
3251
      sinon.assert.calledOnce(callback);
1✔
3252
    });
3253

3254
    describe('beforeRequestBids', function () {
1✔
3255
      let bidRequestedHandler;
3256
      let beforeRequestBidsHandler;
3257
      const bidsBackHandler = function bidsBackHandler() {};
1✔
3258
      let auctionStarted;
3259

3260
      let bidsBackSpy;
3261
      let bidRequestedSpy;
3262
      let beforeRequestBidsSpy;
3263

3264
      beforeEach(function () {
1✔
3265
        resetAuction();
3✔
3266
        bidsBackSpy = sinon.spy(bidsBackHandler);
3✔
3267
        googletag.pubads().setSlots(createSlotArrayScenario2());
3✔
3268
        auctionStarted = new Promise((resolve) => sandbox.stub(adapterManager, 'callBids').callsFake(function() {
3✔
3269
          resolve()
3✔
3270
          return adapterManager.callBids.wrappedMethod.apply(this, arguments);
3✔
3271
        }));
3272
      });
3273

3274
      afterEach(function () {
1✔
3275
        bidsBackSpy.resetHistory();
3✔
3276

3277
        if (bidRequestedSpy) {
3✔
3278
          pbjs.offEvent('bidRequested', bidRequestedSpy);
3✔
3279
          bidRequestedSpy.resetHistory();
3✔
3280
        }
3281

3282
        if (beforeRequestBidsSpy) {
3✔
3283
          pbjs.offEvent('beforeRequestBids', beforeRequestBidsSpy);
3✔
3284
          beforeRequestBidsSpy.resetHistory();
3✔
3285
        }
3286
      });
3287

3288
      it('should allow creation of a fpd.context.pbAdSlot property on adUnits from inside the event handler', async function () {
1✔
3289
        // verify adUnits passed to handler then alter the adUnits
3290
        beforeRequestBidsHandler = function beforeRequestBidsHandler(beforeRequestBidsAdUnits) {
1✔
3291
          expect(beforeRequestBidsAdUnits).to.be.a('array');
1✔
3292
          expect(beforeRequestBidsAdUnits).to.have.lengthOf(1);
1✔
3293
          expect(beforeRequestBidsAdUnits[0]).to.be.a('object');
1✔
3294
          // adUnit should not contain a context property yet
3295
          expect(beforeRequestBidsAdUnits[0]).to.not.have.property('fpd')
1✔
3296
          // alter the adUnit by adding the property for context.pbAdSlot
3297
          beforeRequestBidsAdUnits[0].fpd = {
1✔
3298
            context: {
3299
              pbAdSlot: '/19968336/header-bid-tag-pbadslot-0'
3300
            }
3301
          };
3302
        };
3303
        beforeRequestBidsSpy = sinon.spy(beforeRequestBidsHandler);
1✔
3304

3305
        // use this handler to verify if the adUnits alterations were applied successfully by the beforeRequestBids handler
3306
        bidRequestedHandler = function bidRequestedHandler(bidRequest) {
1✔
3307
          expect(bidRequest).to.be.a('object');
1✔
3308
          expect(bidRequest).to.have.property('bids');
1✔
3309
          expect(bidRequest.bids).to.be.a('array');
1✔
3310
          expect(bidRequest.bids).to.have.lengthOf(1);
1✔
3311
          const bid = bidRequest['bids'][0];
1✔
3312
          expect(bid).to.be.a('object');
1✔
3313
          expect(bid).to.have.property('fpd');
1✔
UNCOV
3314
          expect(bid.fpd).to.be.a('object');
×
UNCOV
3315
          expect(bid.fpd).to.have.property('context');
×
UNCOV
3316
          expect(bid.fpd.context).to.be.a('object');
×
UNCOV
3317
          expect(bid.fpd.context).to.have.property('pbAdSlot');
×
UNCOV
3318
          expect(bid.fpd.context.pbAdSlot).to.equal('/19968336/header-bid-tag-pbadslot-0');
×
3319
        };
3320
        bidRequestedSpy = sinon.spy(bidRequestedHandler);
1✔
3321

3322
        pbjs.onEvent('beforeRequestBids', beforeRequestBidsSpy);
1✔
3323
        pbjs.onEvent('bidRequested', bidRequestedSpy);
1✔
3324
        pbjs.requestBids({
1✔
3325
          adUnits: [{
3326
            code: '/19968336/header-bid-tag-0',
3327
            mediaTypes: {
3328
              banner: {
3329
                sizes: [[750, 350]]
3330
              }
3331
            },
3332
            bids: [{
3333
              bidder: 'appnexus',
3334
              params: {
3335
                placementId: 13122370
3336
              }
3337
            }]
3338
          }],
3339
          bidsBackHandler: bidsBackSpy
3340
        });
3341

3342
        await auctionStarted;
1✔
3343

3344
        sinon.assert.calledOnce(beforeRequestBidsSpy);
1✔
3345
        sinon.assert.calledOnce(bidRequestedSpy);
1✔
3346
      });
3347

3348
      it('should allow creation of a fpd.context.pbAdSlot property on adUnits from inside the event handler', async function () {
1✔
3349
        // verify adUnits passed to handler then alter the adUnits
3350
        beforeRequestBidsHandler = function beforeRequestBidsHandler(beforeRequestBidsAdUnits) {
1✔
3351
          expect(beforeRequestBidsAdUnits).to.be.a('array');
1✔
3352
          expect(beforeRequestBidsAdUnits).to.have.lengthOf(2);
1✔
3353
          expect(beforeRequestBidsAdUnits[0]).to.be.a('object');
1✔
3354
          expect(beforeRequestBidsAdUnits[1]).to.be.a('object');
1✔
3355
          // adUnit should not contain a context property yet
3356
          expect(beforeRequestBidsAdUnits[0]).to.not.have.property('fpd');
1✔
3357
          expect(beforeRequestBidsAdUnits[1]).to.not.have.property('fpd');
1✔
3358
          // alter the adUnit by adding the property for context.pbAdSlot
3359
          beforeRequestBidsAdUnits[0].fpd = {
1✔
3360
            context: {
3361
              pbAdSlot: '/19968336/header-bid-tag-pbadslot-0'
3362
            }
3363
          };
3364
          beforeRequestBidsAdUnits[1].fpd = {
1✔
3365
            context: {
3366
              pbAdSlot: '/19968336/header-bid-tag-pbadslot-1'
3367
            }
3368
          };
3369
        };
3370
        beforeRequestBidsSpy = sinon.spy(beforeRequestBidsHandler);
1✔
3371

3372
        // use this handler to verify if the adUnits alterations were applied successfully by the beforeRequestBids handler
3373
        bidRequestedHandler = function bidRequestedHandler(bidRequest) {
1✔
3374
          expect(bidRequest).to.be.a('object');
1✔
3375
          expect(bidRequest).to.have.property('bids');
1✔
3376
          expect(bidRequest.bids).to.be.a('array');
1✔
3377
          expect(bidRequest.bids).to.have.lengthOf(2);
1✔
3378
          const bid0 = bidRequest['bids'][0];
1✔
3379
          expect(bid0).to.be.a('object');
1✔
3380
          expect(bid0).to.have.property('fpd');
1✔
UNCOV
3381
          expect(bid0.fpd).to.be.a('object');
×
UNCOV
3382
          expect(bid0.fpd).to.have.property('context');
×
UNCOV
3383
          expect(bid0.fpd.context).to.be.a('object');
×
UNCOV
3384
          expect(bid0.fpd.context).to.have.property('pbAdSlot');
×
UNCOV
3385
          expect(bid0.fpd.context.pbAdSlot).to.equal('/19968336/header-bid-tag-pbadslot-0');
×
3386

UNCOV
3387
          const bid1 = bidRequest['bids'][1];
×
UNCOV
3388
          expect(bid1).to.be.a('object');
×
UNCOV
3389
          expect(bid1).to.have.property('fpd');
×
UNCOV
3390
          expect(bid1.fpd).to.be.a('object');
×
UNCOV
3391
          expect(bid1.fpd).to.have.property('context');
×
UNCOV
3392
          expect(bid1.fpd.context).to.be.a('object');
×
UNCOV
3393
          expect(bid1.fpd.context).to.have.property('pbAdSlot');
×
UNCOV
3394
          expect(bid1.fpd.context.pbAdSlot).to.equal('/19968336/header-bid-tag-pbadslot-1');
×
3395
        };
3396
        bidRequestedSpy = sinon.spy(bidRequestedHandler);
1✔
3397

3398
        pbjs.onEvent('beforeRequestBids', beforeRequestBidsSpy);
1✔
3399
        pbjs.onEvent('bidRequested', bidRequestedSpy);
1✔
3400
        pbjs.requestBids({
1✔
3401
          adUnits: [{
3402
            code: '/19968336/header-bid-tag-0',
3403
            mediaTypes: {
3404
              banner: {
3405
                sizes: [[750, 350]]
3406
              }
3407
            },
3408
            bids: [{
3409
              bidder: 'appnexus',
3410
              params: {
3411
                placementId: 13122370
3412
              }
3413
            }]
3414
          }, {
3415
            code: '/19968336/header-bid-tag-1',
3416
            mediaTypes: {
3417
              banner: {
3418
                sizes: [[750, 350]]
3419
              }
3420
            },
3421
            bids: [{
3422
              bidder: 'appnexus',
3423
              params: {
3424
                placementId: 14122380
3425
              }
3426
            }]
3427
          }],
3428
          bidsBackHandler: bidsBackSpy
3429
        });
3430
        await auctionStarted;
1✔
3431

3432
        sinon.assert.calledOnce(beforeRequestBidsSpy);
1✔
3433
        sinon.assert.calledOnce(bidRequestedSpy);
1✔
3434
      });
3435

3436
      it('should not create a context property on adUnits if not added by handler', async function () {
1✔
3437
        // verify adUnits passed to handler then alter the adUnits
3438
        beforeRequestBidsHandler = function beforeRequestBidsHandler(beforeRequestBidsAdUnits) {
1✔
3439
          expect(beforeRequestBidsAdUnits).to.be.a('array');
1✔
3440
          expect(beforeRequestBidsAdUnits).to.have.lengthOf(1);
1✔
3441
          expect(beforeRequestBidsAdUnits[0]).to.be.a('object');
1✔
3442
          // adUnit should not contain a context property yet
3443
          expect(beforeRequestBidsAdUnits[0]).to.not.have.property('context')
1✔
3444
        };
3445
        beforeRequestBidsSpy = sinon.spy(beforeRequestBidsHandler);
1✔
3446

3447
        // use this handler to verify if the adUnits alterations were applied successfully by the beforeRequestBids handler
3448
        bidRequestedHandler = function bidRequestedHandler(bidRequest) {
1✔
3449
          expect(bidRequest).to.be.a('object');
1✔
3450
          expect(bidRequest).to.have.property('bids');
1✔
3451
          expect(bidRequest.bids).to.be.a('array');
1✔
3452
          expect(bidRequest.bids).to.have.lengthOf(1);
1✔
3453
          const bid = bidRequest['bids'][0];
1✔
3454
          expect(bid).to.be.a('object');
1✔
3455
          expect(bid).to.not.have.property('context');
1✔
3456
        };
3457
        bidRequestedSpy = sinon.spy(bidRequestedHandler);
1✔
3458

3459
        pbjs.onEvent('beforeRequestBids', beforeRequestBidsSpy);
1✔
3460
        pbjs.onEvent('bidRequested', bidRequestedSpy);
1✔
3461
        pbjs.requestBids({
1✔
3462
          adUnits: [{
3463
            code: '/19968336/header-bid-tag-0',
3464
            mediaTypes: {
3465
              banner: {
3466
                sizes: [[750, 350]]
3467
              }
3468
            },
3469
            bids: [{
3470
              bidder: 'appnexus',
3471
              params: {
3472
                placementId: 13122370
3473
              }
3474
            }]
3475
          }],
3476
          bidsBackHandler: bidsBackSpy
3477
        });
3478
        await auctionStarted;
1✔
3479

3480
        sinon.assert.calledOnce(beforeRequestBidsSpy);
1✔
3481
        sinon.assert.calledOnce(bidRequestedSpy);
1✔
3482
      });
3483
    });
3484
  });
3485

3486
  describe('offEvent', function () {
1✔
3487
    it('should return when id provided is not valid for event', function () {
1✔
3488
      var spyEventsOff = sinon.spy(events, 'off');
1✔
3489
      pbjs.offEvent('bidWon', Function, 'testId');
1✔
3490
      assert.ok(spyEventsOff.notCalled);
1✔
3491
      events.off.restore();
1✔
3492
    });
3493

3494
    it('should call events.off with valid parameters', function () {
1✔
3495
      var spyEventsOff = sinon.spy(events, 'off');
1✔
3496
      pbjs.offEvent('bidWon', Function);
1✔
3497
      assert.ok(spyEventsOff.calledWith('bidWon', Function));
1✔
3498
      events.off.restore();
1✔
3499
    });
3500
  });
3501

3502
  describe('emit', function () {
1✔
3503
    it('should be able to emit event without arguments', function () {
1✔
3504
      var spyEventsEmit = sinon.spy(events, 'emit');
1✔
3505
      events.emit(EVENTS.REQUEST_BIDS);
1✔
3506
      assert.ok(spyEventsEmit.calledWith('requestBids'));
1✔
3507
      events.emit.restore();
1✔
3508
    });
3509
  });
3510

3511
  describe('registerBidAdapter', function () {
1✔
3512
    it('should register bidAdaptor with adapterManager', function () {
1✔
3513
      var registerBidAdapterSpy = sinon.spy(adapterManager, 'registerBidAdapter');
1✔
3514
      pbjs.registerBidAdapter(Function, 'biddercode');
1✔
3515
      assert.ok(registerBidAdapterSpy.called, 'called adapterManager.registerBidAdapter');
1✔
3516
      adapterManager.registerBidAdapter.restore();
1✔
3517
    });
3518

3519
    it('should catch thrown errors', function () {
1✔
3520
      var spyLogError = sinon.spy(utils, 'logError');
1✔
3521
      var errorObject = { message: 'bidderAdaptor error' };
1✔
3522
      var bidderAdaptor = sinon.stub().throws(errorObject);
1✔
3523

3524
      pbjs.registerBidAdapter(bidderAdaptor, 'biddercode');
1✔
3525

3526
      var errorMessage = 'Error registering bidder adapter : ' + errorObject.message;
1✔
3527
      assert.ok(spyLogError.calledWith(errorMessage), 'expected error was caught');
1✔
3528
      utils.logError.restore();
1✔
3529
    });
3530
  });
3531

3532
  describe('aliasBidder', function () {
1✔
3533
    it('should call adapterManager.aliasBidder', function () {
1✔
3534
      const aliasBidAdapterSpy = sinon.spy(adapterManager, 'aliasBidAdapter');
1✔
3535
      const bidderCode = 'testcode';
1✔
3536
      const alias = 'testalias';
1✔
3537

3538
      pbjs.aliasBidder(bidderCode, alias);
1✔
3539
      assert.ok(aliasBidAdapterSpy.calledWith(bidderCode, alias), 'called adapterManager.aliasBidAdapterSpy');
1✔
3540
      adapterManager.aliasBidAdapter();
1✔
3541
    });
3542

3543
    it('should log error when not passed correct arguments', function () {
1✔
3544
      const logErrorSpy = sinon.spy(utils, 'logError');
1✔
3545
      const error = 'bidderCode and alias must be passed as arguments';
1✔
3546

3547
      pbjs.aliasBidder();
1✔
3548
      assert.ok(logErrorSpy.calledWith(error), 'expected error was logged');
1✔
3549
      utils.logError.restore();
1✔
3550
    });
3551
  });
3552

3553
  describe('aliasRegistry', function () {
1✔
3554
    it('should return the same value as adapterManager.aliasRegistry by default', function () {
1✔
3555
      const adapterManagerAliasRegistry = adapterManager.aliasRegistry;
1✔
3556
      const pbjsAliasRegistry = pbjs.aliasRegistry;
1✔
3557
      assert.equal(adapterManagerAliasRegistry, pbjsAliasRegistry);
1✔
3558
    });
3559

3560
    it('should return undefined if the aliasRegistry config option is set to private', function () {
1✔
3561
      configObj.setConfig({ aliasRegistry: 'private' });
1✔
3562
      const pbjsAliasRegistry = pbjs.aliasRegistry;
1✔
3563
      assert.equal(pbjsAliasRegistry, undefined);
1✔
3564
    });
3565
  });
3566

3567
  describe('setPriceGranularity', function () {
1✔
3568
    it('should log error when not passed granularity', function () {
1✔
3569
      const logErrorSpy = sinon.spy(utils, 'logError');
1✔
3570
      const error = 'Prebid Error: no value passed to `setPriceGranularity()`';
1✔
3571

3572
      pbjs.setConfig({ priceGranularity: null });
1✔
3573
      assert.ok(logErrorSpy.calledWith(error), 'expected error was logged');
1✔
3574
      utils.logError.restore();
1✔
3575
    });
3576

3577
    it('should log error when not passed a valid config object', function () {
1✔
3578
      const logErrorSpy = sinon.spy(utils, 'logError');
1✔
3579
      const error = 'Invalid custom price value passed to `setPriceGranularity()`';
1✔
3580
      const badConfig = {
1✔
3581
        'buckets': [{
3582
          'max': 3,
3583
          'increment': 0.01,
3584
        },
3585
        {
3586
          'max': 18,
3587
          // missing increment prop
3588
          'cap': true
3589
        }
3590
        ]
3591
      };
3592

3593
      pbjs.setConfig({ priceGranularity: badConfig });
1✔
3594
      assert.ok(logErrorSpy.calledWith(error), 'expected error was logged');
1✔
3595
      utils.logError.restore();
1✔
3596
    });
3597

3598
    it('should set customPriceBucket with custom config buckets', function () {
1✔
3599
      const customPriceBucket = configObj.getConfig('customPriceBucket');
1✔
3600
      const goodConfig = {
1✔
3601
        'buckets': [{
3602
          'max': 3,
3603
          'increment': 0.01,
3604
          'cap': true
3605
        }
3606
        ]
3607
      };
3608
      configObj.setConfig({ priceGranularity: goodConfig });
1✔
3609
      const priceGranularity = configObj.getConfig('priceGranularity');
1✔
3610
      const newCustomPriceBucket = configObj.getConfig('customPriceBucket');
1✔
3611
      expect(goodConfig).to.deep.equal(newCustomPriceBucket);
1✔
3612
      expect(priceGranularity).to.equal(GRANULARITY_OPTIONS.CUSTOM);
1✔
3613
    });
3614
  });
3615

3616
  describe('emit event', function () {
1✔
3617
    let auctionManagerStub;
3618
    beforeEach(function () {
1✔
UNCOV
3619
      auctionManagerStub = sinon.stub(auctionManager, 'createAuction').callsFake(function() {
×
UNCOV
3620
        return auction;
×
3621
      });
3622
    });
3623

3624
    afterEach(function () {
1✔
UNCOV
3625
      auctionManager.createAuction.restore();
×
3626
    });
3627
  });
3628

3629
  describe('removeAdUnit', function () {
1✔
3630
    it('should remove given adUnit in adUnits array', function () {
1✔
3631
      const adUnit1 = {
1✔
3632
        code: 'adUnit1',
3633
        bids: [{
3634
          bidder: 'appnexus',
3635
          params: { placementId: '123' }
3636
        }]
3637
      };
3638
      const adUnit2 = {
1✔
3639
        code: 'adUnit2',
3640
        bids: [{
3641
          bidder: 'rubicon',
3642
          params: {
3643
            accountId: '1234',
3644
            siteId: '1234',
3645
            zoneId: '1234'
3646
          }
3647
        }]
3648
      };
3649
      const adUnits = [adUnit1, adUnit2];
1✔
3650
      pbjs.adUnits = adUnits;
1✔
3651
      pbjs.removeAdUnit('foobar');
1✔
3652
      assert.deepEqual(pbjs.adUnits, adUnits);
1✔
3653
      pbjs.removeAdUnit('adUnit1');
1✔
3654
      assert.deepEqual(pbjs.adUnits, [adUnit2]);
1✔
3655
    });
3656
    it('should remove all adUnits in adUnits array if no adUnits are given', function () {
1✔
3657
      const adUnit1 = {
1✔
3658
        code: 'adUnit1',
3659
        bids: [{
3660
          bidder: 'appnexus',
3661
          params: { placementId: '123' }
3662
        }]
3663
      };
3664
      const adUnit2 = {
1✔
3665
        code: 'adUnit2',
3666
        bids: [{
3667
          bidder: 'rubicon',
3668
          params: {
3669
            accountId: '1234',
3670
            siteId: '1234',
3671
            zoneId: '1234'
3672
          }
3673
        }]
3674
      };
3675
      const adUnits = [adUnit1, adUnit2];
1✔
3676
      pbjs.adUnits = adUnits;
1✔
3677
      pbjs.removeAdUnit();
1✔
3678
      assert.deepEqual(pbjs.adUnits, []);
1✔
3679
    });
3680
    it('should remove adUnits which match addUnitCodes in adUnit array argument', function () {
1✔
3681
      const adUnit1 = {
1✔
3682
        code: 'adUnit1',
3683
        bids: [{
3684
          bidder: 'appnexus',
3685
          params: { placementId: '123' }
3686
        }]
3687
      };
3688
      const adUnit2 = {
1✔
3689
        code: 'adUnit2',
3690
        bids: [{
3691
          bidder: 'rubicon',
3692
          params: {
3693
            accountId: '1234',
3694
            siteId: '1234',
3695
            zoneId: '1234'
3696
          }
3697
        }]
3698
      };
3699
      const adUnit3 = {
1✔
3700
        code: 'adUnit3',
3701
        bids: [{
3702
          bidder: 'rubicon3',
3703
          params: {
3704
            accountId: '12345',
3705
            siteId: '12345',
3706
            zoneId: '12345'
3707
          }
3708
        }]
3709
      };
3710
      const adUnits = [adUnit1, adUnit2, adUnit3];
1✔
3711
      pbjs.adUnits = adUnits;
1✔
3712
      pbjs.removeAdUnit([adUnit1.code, adUnit2.code]);
1✔
3713
      assert.deepEqual(pbjs.adUnits, [adUnit3]);
1✔
3714
    });
3715
  });
3716

3717
  describe('getDealTargeting', function () {
1✔
3718
    beforeEach(function () {
1✔
3719
      resetAuction();
1✔
3720
    });
3721

3722
    afterEach(function () {
1✔
3723
      resetAuction();
1✔
3724
    });
3725

3726
    it('should truncate deal keys', function () {
1✔
3727
      pbjs._bidsReceived = [
1✔
3728
        {
3729
          'bidderCode': 'appnexusDummyName',
3730
          'dealId': '1234',
3731
          'width': 300,
3732
          'height': 250,
3733
          'statusMessage': 'Bid available',
3734
          'adId': '233bcbee889d46d',
3735
          'creative_id': 29681110,
3736
          'cpm': 10,
3737
          'adUrl': 'http://lax1-ib.adnxs.com/ab?e=wqT_3QL8BKh8AgAAAwDWAAUBCMjAybkFEMLLiJWTu9PsVxjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbLkw5YweAnYABokUB4190DgAEBigEDVVNEkgUG8FKYAawCoAH6AagBAbABALgBAcABA8gBANABANgBAOABAPABAIoCOnVmKCdhJywgNDk0NDcyLCAxNDYyOTE5MjQwKTt1ZigncicsIDI5NjgxMTEwLDIeAPBskgLZASFmU21rZ0FpNjBJY0VFTmJMa3c0WUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCd0EzZ0RnQUVEaUFFRGtBRUJtQUVCb0FFQnFBRURzQUVBdVFFQUFBQUFBQURnUDhFQgkMTEFBNERfSkFRMkxMcEVUMU93XzJRFSggd1AtQUJBUFVCBSxASmdDaW9EVTJnV2dBZ0MxQWcBFgRDOQkIqERBQWdQSUFnUFFBZ1BZQWdQZ0FnRG9BZ0Q0QWdDQUF3RS6aAiUhV1FrbmI63AAcd2VBbklBUW8JXPCVVS7YAugH4ALH0wHqAh9odHRwOi8vcHJlYmlkLm9yZzo5OTk5L2dwdC5odG1sgAMAiAMBkAMAmAMFoAMBqgMAsAMAuAMAwAOsAsgDANgDAOADAOgDAPgDA4AEAJIEBC9qcHSYBACiBAoxMC4xLjEzLjM3qAQAsgQICAAQABgAIAC4BADABADIBADSBAoxMC4wLjg1Ljkx&s=1bf15e8cdc7c0c8c119614c6386ab1496560da39&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html',
3738
          'responseTimestamp': 1462919239340,
3739
          'requestTimestamp': 1462919238919,
3740
          'bidder': 'appnexus',
3741
          'adUnitCode': '/19968336/header-bid-tag-0',
3742
          'timeToRespond': 421,
3743
          'pbLg': '5.00',
3744
          'pbMg': '10.00',
3745
          'pbHg': '10.00',
3746
          'pbAg': '10.00',
3747
          'size': '300x250',
3748
          'alwaysUseBid': true,
3749
          'auctionId': 123456,
3750
          'adserverTargeting': {
3751
            'foobar': '300x250',
3752
            [TARGETING_KEYS.BIDDER]: 'appnexus',
3753
            [TARGETING_KEYS.AD_ID]: '233bcbee889d46d',
3754
            [TARGETING_KEYS.PRICE_BUCKET]: '10.00',
3755
            [TARGETING_KEYS.SIZE]: '300x250',
3756
            [TARGETING_KEYS.DEAL + '_appnexusDummyName']: '1234'
3757
          }
3758
        }
3759
      ];
3760

3761
      var result = pbjs.getAdserverTargeting();
1✔
3762
      Object.keys(result['/19968336/header-bid-tag-0']).forEach(value => {
1✔
3763
        expect(value).to.have.length.of.at.most(20);
6✔
3764
      });
3765
    });
3766
  });
3767

3768
  describe('getHighestUnusedBidResponseForAdUnitCode', () => {
1✔
3769
    afterEach(() => {
1✔
3770
      resetAuction();
4✔
3771
    })
3772

3773
    it('returns null if there is no bid for the given adUnitCode', () => {
1✔
3774
      const highestBid = pbjs.getHighestUnusedBidResponseForAdUnitCode('stallone');
1✔
3775
      expect(highestBid).to.equal(null);
1✔
3776
    })
3777

3778
    it('returns undefined if adUnitCode is provided', () => {
1✔
3779
      const highestBid = pbjs.getHighestUnusedBidResponseForAdUnitCode();
1✔
3780
      expect(highestBid).to.be.undefined;
1✔
3781
    })
3782

3783
    it('should ignore bids that have already been used (\'rendered\')', () => {
1✔
3784
      const _bidsReceived = getBidResponses().slice(0, 3);
1✔
3785
      _bidsReceived[0].cpm = 11
1✔
3786
      _bidsReceived[1].cpm = 13
1✔
3787
      _bidsReceived[2].cpm = 12
1✔
3788

3789
      _bidsReceived.forEach((bid) => {
1✔
3790
        bid.adUnitCode = '/19968336/header-bid-tag-0';
3✔
3791
      });
3792

3793
      auction.getBidsReceived = function() { return _bidsReceived };
2✔
3794
      const highestBid1 = pbjs.getHighestUnusedBidResponseForAdUnitCode('/19968336/header-bid-tag-0');
1✔
3795
      expect(highestBid1).to.deep.equal(_bidsReceived[1])
1✔
3796
      _bidsReceived[1].status = BID_STATUS.RENDERED
1✔
3797
      const highestBid2 = pbjs.getHighestUnusedBidResponseForAdUnitCode('/19968336/header-bid-tag-0');
1✔
3798
      expect(highestBid2).to.deep.equal(_bidsReceived[2])
1✔
3799
    })
3800

3801
    it('should ignore expired bids', () => {
1✔
3802
      const _bidsReceived = getBidResponses().slice(0, 3);
1✔
3803
      _bidsReceived[0].cpm = 11
1✔
3804
      _bidsReceived[1].cpm = 13
1✔
3805
      _bidsReceived[2].cpm = 12
1✔
3806

3807
      _bidsReceived.forEach((bid) => {
1✔
3808
        bid.adUnitCode = '/19968336/header-bid-tag-0';
3✔
3809
      });
3810

3811
      auction.getBidsReceived = function() { return _bidsReceived };
1✔
3812

3813
      bidExpiryStub.restore();
1✔
3814
      bidExpiryStub = sinon.stub(filters, 'isBidNotExpired').callsFake((bid) => bid.cpm !== 13);
3✔
3815
      const highestBid = pbjs.getHighestUnusedBidResponseForAdUnitCode('/19968336/header-bid-tag-0');
1✔
3816
      expect(highestBid).to.deep.equal(_bidsReceived[2])
1✔
3817
    })
3818
  });
3819

3820
  describe('getHighestCpmBids', () => {
1✔
3821
    after(() => {
1✔
3822
      resetAuction();
1✔
3823
    });
3824
    it('returns an array containing the highest bid object for the given adUnitCode', function () {
1✔
3825
      const adUnitcode = '/19968336/header-bid-tag-0';
1✔
3826
      targeting.setLatestAuctionForAdUnit(adUnitcode, auctionId)
1✔
3827
      const highestCpmBids = pbjs.getHighestCpmBids(adUnitcode);
1✔
3828
      expect(highestCpmBids.length).to.equal(1);
1✔
3829
      const expectedBid = auctionManager.getBidsReceived()[1];
1✔
3830
      expectedBid.latestTargetedAuctionId = auctionId;
1✔
3831
      expect(highestCpmBids[0]).to.deep.equal(expectedBid);
1✔
3832
    });
3833

3834
    it('returns an empty array when the given adUnit is not found', function () {
1✔
3835
      const highestCpmBids = pbjs.getHighestCpmBids('/stallone');
1✔
3836
      expect(highestCpmBids.length).to.equal(0);
1✔
3837
    });
3838

3839
    it('returns an empty array when the given adUnit has no bids', function () {
1✔
3840
      const _bidsReceived = getBidResponses()[0];
1✔
3841
      _bidsReceived.cpm = 0;
1✔
3842
      auction.getBidsReceived = function() { return _bidsReceived };
1✔
3843

3844
      const highestCpmBids = pbjs.getHighestCpmBids('/19968336/header-bid-tag-0');
1✔
3845
      expect(highestCpmBids.length).to.equal(0);
1✔
3846
    });
3847

3848
    it('should not return rendered bid', function() {
1✔
3849
      const _bidsReceived = getBidResponses().slice(0, 3);
1✔
3850
      _bidsReceived[0].cpm = 12;
1✔
3851
      _bidsReceived[0].status = 'rendered';
1✔
3852
      _bidsReceived[1].cpm = 9;
1✔
3853
      _bidsReceived[2].cpm = 11;
1✔
3854

3855
      _bidsReceived.forEach((bid) => {
1✔
3856
        bid.adUnitCode = '/19968336/header-bid-tag-0';
3✔
3857
      });
3858

3859
      auction.getBidsReceived = function() { return _bidsReceived };
2✔
3860

3861
      const highestCpmBids = pbjs.getHighestCpmBids('/19968336/header-bid-tag-0');
1✔
3862
      expect(highestCpmBids[0]).to.deep.equal(auctionManager.getBidsReceived()[2]);
1✔
3863
    });
3864
  });
3865

3866
  if (FEATURES.VIDEO) {
1✔
3867
    describe('markWinningBidAsUsed', function () {
1✔
3868
      const adUnitCode = '/19968336/header-bid-tag-0';
1✔
3869
      let winningBid, markedBid;
3870

3871
      beforeEach(() => {
1✔
3872
        const bidsReceived = pbjs.getBidResponsesForAdUnitCode(adUnitCode);
7✔
3873
        auction.getBidsReceived = function() { return bidsReceived.bids };
22✔
3874

3875
        // mark the bid and verify the state has changed to RENDERED
3876
        winningBid = targeting.getWinningBids(adUnitCode)[0];
7✔
3877
        auction.getAuctionId = function() { return winningBid.auctionId };
7✔
3878
        sandbox.stub(events, 'emit');
7✔
3879
        markedBid = pbjs.getBidResponsesForAdUnitCode(adUnitCode).bids.find(
7✔
3880
          bid => bid.adId === winningBid.adId);
14✔
3881
      })
3882

3883
      afterEach(() => {
1✔
3884
        resetAuction();
7✔
3885
      })
3886

3887
      function checkBidRendered() {
3888
        expect(markedBid.status).to.equal(BID_STATUS.RENDERED);
6✔
3889
      }
3890

3891
      Object.entries({
1✔
3892
        'events=true': {
3893
          mark(options = {}) {
3!
3894
            pbjs.markWinningBidAsUsed(Object.assign({events: true}, options))
3✔
3895
          },
3896
          checkBidWon() {
3897
            sinon.assert.calledWith(events.emit, EVENTS.BID_WON, markedBid);
3✔
3898
          }
3899
        },
3900
        'events=false': {
3901
          mark(options = {}) {
3!
3902
            pbjs.markWinningBidAsUsed(options)
3✔
3903
          },
3904
          checkBidWon() {
3905
            sinon.assert.notCalled(events.emit)
3✔
3906
          }
3907
        }
3908
      }).forEach(([t, {mark, checkBidWon}]) => {
2✔
3909
        describe(`when ${t}`, () => {
2✔
3910
          it('marks the bid object as used for the given adUnitCode/adId combination', function () {
2✔
3911
            mark({ adUnitCode, adId: winningBid.adId });
2✔
3912
            checkBidRendered();
2✔
3913
            checkBidWon();
2✔
3914
          });
3915
          it('marks the winning bid object as used for the given adUnitCode', function () {
2✔
3916
            mark({ adUnitCode });
2✔
3917
            checkBidRendered();
2✔
3918
            checkBidWon();
2✔
3919
          });
3920

3921
          it('marks a bid object as used for the given adId', function () {
2✔
3922
            mark({ adId: winningBid.adId });
2✔
3923
            checkBidRendered();
2✔
3924
            checkBidWon();
2✔
3925
          });
3926
        })
3927
      })
3928

3929
      it('try and mark the bid object, but fail because we supplied the wrong adId', function () {
1✔
3930
        pbjs.markWinningBidAsUsed({ adUnitCode, adId: 'miss' });
1✔
3931
        const markedBid = pbjs.getBidResponsesForAdUnitCode(adUnitCode).bids.find(
1✔
3932
          bid => bid.adId === winningBid.adId);
2✔
3933

3934
        expect(markedBid.status).to.not.equal(BID_STATUS.RENDERED);
1✔
3935
      });
3936
    });
3937
  }
3938

3939
  describe('setTargetingForAst', function () {
1✔
3940
    let targeting;
3941
    let auctionManagerInstance;
3942

3943
    beforeEach(function () {
1✔
3944
      resetAuction();
3✔
3945
      auctionManagerInstance = newAuctionManager();
3✔
3946
      sinon.stub(auctionManagerInstance, 'getBidsReceived').callsFake(function() {
3✔
3947
        const bidResponse = getBidResponses()[1];
5✔
3948
        // add a pt0 value for special case.
3949
        bidResponse.adserverTargeting.pt0 = 'someVal';
5✔
3950
        return [bidResponse];
5✔
3951
      });
3952
      sinon.stub(auctionManagerInstance, 'getAdUnitCodes').callsFake(function() {
3✔
3953
        return ['/19968336/header-bid-tag-0'];
7✔
3954
      });
3955
      targeting = newTargeting(auctionManagerInstance);
3✔
3956
    });
3957

3958
    afterEach(function () {
1✔
3959
      auctionManagerInstance.getBidsReceived.restore();
3✔
3960
      auctionManagerInstance.getAdUnitCodes.restore();
3✔
3961
      resetAuction();
3✔
3962
    });
3963

3964
    it('should set targeting for appnexus apntag object', function () {
1✔
3965
      const bids = auctionManagerInstance.getBidsReceived();
1✔
3966
      const adUnitCode = '/19968336/header-bid-tag-0';
1✔
3967

3968
      var expectedAdserverTargeting = bids[0].adserverTargeting;
1✔
3969
      var newAdserverTargeting = {};
1✔
3970
      const regex = /pt[0-9]/;
1✔
3971

3972
      for (var key in expectedAdserverTargeting) {
1✔
3973
        if (key.search(regex) < 0) {
6✔
3974
          newAdserverTargeting[key.toUpperCase()] = expectedAdserverTargeting[key];
5✔
3975
        } else {
3976
          newAdserverTargeting[key] = expectedAdserverTargeting[key];
1✔
3977
        }
3978
      }
3979
      targeting.setTargetingForAst();
1✔
3980
      sinon.assert.match(window.apntag.tags[adUnitCode].keywords, newAdserverTargeting);
1✔
3981
    });
3982

3983
    it('should reset targeting for appnexus apntag object', function () {
1✔
3984
      const bids = auctionManagerInstance.getBidsReceived();
1✔
3985
      const adUnitCode = '/19968336/header-bid-tag-0';
1✔
3986

3987
      var expectedAdserverTargeting = bids[0].adserverTargeting;
1✔
3988
      var newAdserverTargeting = {};
1✔
3989
      const regex = /pt[0-9]/;
1✔
3990

3991
      for (var key in expectedAdserverTargeting) {
1✔
3992
        if (key.search(regex) < 0) {
6✔
3993
          newAdserverTargeting[key.toUpperCase()] = expectedAdserverTargeting[key];
5✔
3994
        } else {
3995
          newAdserverTargeting[key] = expectedAdserverTargeting[key];
1✔
3996
        }
3997
      }
3998
      targeting.setTargetingForAst();
1✔
3999
      sinon.assert.match(window.apntag.tags[adUnitCode].keywords, newAdserverTargeting)
1✔
4000
      targeting.resetPresetTargetingAST();
1✔
4001
      expect(window.apntag.tags[adUnitCode].keywords).to.deep.equal({});
1✔
4002
    });
4003

4004
    it('should not find ' + TARGETING_KEYS.AD_ID + ' key in lowercase for all bidders', function () {
1✔
4005
      const adUnitCode = '/19968336/header-bid-tag-0';
1✔
4006
      pbjs.setConfig({ enableSendAllBids: true });
1✔
4007
      targeting.setTargetingForAst();
1✔
4008
      const keywords = Object.keys(window.apntag.tags[adUnitCode].keywords).filter(keyword => (keyword.substring(0, TARGETING_KEYS.AD_ID.length) === TARGETING_KEYS.AD_ID));
11✔
4009
      expect(keywords.length).to.equal(0);
1✔
4010
    });
4011
  });
4012

4013
  describe('The monkey-patched queue.push function', function() {
1✔
4014
    beforeEach(function initializeSpies() {
1✔
4015
      sinon.spy(utils, 'logError');
3✔
4016
    });
4017

4018
    afterEach(function resetSpies() {
1✔
4019
      utils.logError.restore();
3✔
4020
    });
4021

4022
    function push(cmd) {
4023
      return new Promise((resolve) => {
3✔
4024
        pbjs.cmd.push(() => {
3✔
4025
          try {
3✔
4026
            cmd();
3✔
4027
          } finally {
4028
            resolve();
3✔
4029
          }
4030
        })
4031
      })
4032
    }
4033

4034
    it('should run commands which are pushed into it', async function () {
1✔
4035
      const cmd = sinon.spy();
1✔
4036
      await push(cmd);
1✔
4037
      assert.isTrue(cmd.called);
1✔
4038
    });
4039

4040
    it('should log an error when given non-functions', async function () {
1✔
4041
      pbjs.cmd.push(5);
1✔
4042
      await push(() => null);
1✔
4043
      assert.isTrue(utils.logError.calledOnce);
1✔
4044
    });
4045

4046
    it('should log an error if the command passed into it fails', async function () {
1✔
4047
      await push(function () {
1✔
4048
        throw new Error('Failed function.');
1✔
4049
      });
4050
      assert.isTrue(utils.logError.calledOnce);
1✔
4051
    });
4052
  });
4053

4054
  describe('The monkey-patched que.push function', function() {
1✔
4055
    it('should be the same as the cmd.push function', function() {
1✔
4056
      assert.equal(pbjs.que.push, pbjs.cmd.push);
1✔
4057
    });
4058
  });
4059

4060
  describe('getAllPrebidWinningBids', function () {
1✔
4061
    let auctionManagerStub;
4062
    let logWarnSpy;
4063
    beforeEach(function () {
1✔
4064
      auctionManagerStub = sinon.stub(auctionManager, 'getBidsReceived');
1✔
4065
      logWarnSpy = sandbox.spy(utils, 'logWarn');
1✔
4066
    });
4067

4068
    afterEach(function () {
1✔
4069
      auctionManagerStub.restore();
1✔
4070
      logWarnSpy.restore();
1✔
4071
    });
4072

4073
    it('should warn and return prebid auction winning bids', function () {
1✔
4074
      const bidsReceived = [
1✔
4075
        createBidReceived({bidder: 'appnexus', cpm: 7, auctionId: 1, responseTimestamp: 100, adUnitCode: 'code-0', adId: 'adid-1', status: 'targetingSet', requestId: 'reqid-1'}),
4076
        createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 1, responseTimestamp: 101, adUnitCode: 'code-1', adId: 'adid-2', requestId: 'reqid-2'}),
4077
        createBidReceived({bidder: 'appnexus', cpm: 6, auctionId: 2, responseTimestamp: 102, adUnitCode: 'code-0', adId: 'adid-3', requestId: 'reqid-3'}),
4078
        createBidReceived({bidder: 'rubicon', cpm: 6, auctionId: 2, responseTimestamp: 103, adUnitCode: 'code-1', adId: 'adid-4', requestId: 'reqid-4'}),
4079
      ];
4080
      auctionManagerStub.returns(bidsReceived)
1✔
4081
      const bids = pbjs.getAllPrebidWinningBids();
1✔
4082

4083
      expect(bids.length).to.equal(1);
1✔
4084
      expect(bids[0].adId).to.equal('adid-1');
1✔
4085
      sinon.assert.calledOnce(logWarnSpy);
1✔
4086
    });
4087
  });
4088

4089
  describe('deferred billing', function () {
1✔
4090
    let bid;
4091

4092
    beforeEach(function () {
1✔
4093
      bid = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1', adUnitId: '1234567890', adId: 'abcdefg' };
2✔
4094
      sandbox.spy(adapterManager, 'triggerBilling');
2✔
4095
      sandbox.stub(auctionManager, 'getAllWinningBids').returns([bid]);
2✔
4096
    });
4097

4098
    Object.entries({
1✔
4099
      'bid': () => bid,
1✔
4100
      'adUnitCode': () => ({adUnitCode: bid.adUnitCode})
1✔
4101
    }).forEach(([t, val]) => {
2✔
4102
      it(`should trigger billing when invoked with ${t}`, () => {
2✔
4103
        pbjs.triggerBilling(val());
2✔
4104
        sinon.assert.calledWith(adapterManager.triggerBilling, bid);
2✔
4105
      })
4106
    })
4107
  });
4108

4109
  describe('clearAllAuctions', () => {
1✔
4110
    after(() => {
1✔
4111
      resetAuction();
1✔
4112
    });
4113
    it('clears auction data', function () {
1✔
4114
      expect(auctionManager.getBidsReceived().length).to.not.equal(0);
1✔
4115
      pbjs.clearAllAuctions();
1✔
4116
      expect(auctionManager.getBidsReceived().length).to.equal(0);
1✔
4117
    });
4118
  });
4119
});
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc