prebid.js 5.19.0 → 5.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/modules/airgridRtdProvider.js +1 -1
  2. package/modules/appnexusBidAdapter.js +5 -3
  3. package/modules/atsAnalyticsAdapter.js +67 -46
  4. package/modules/atsAnalyticsAdapter.md +1 -0
  5. package/modules/betweenBidAdapter.js +2 -1
  6. package/modules/browsiRtdProvider.js +106 -18
  7. package/modules/cleanioRtdProvider.js +192 -0
  8. package/modules/cleanioRtdProvider.md +59 -0
  9. package/modules/deltaprojectsBidAdapter.js +252 -0
  10. package/modules/deltaprojectsBidAdapter.md +32 -0
  11. package/modules/gridBidAdapter.js +1 -0
  12. package/modules/ixBidAdapter.js +7 -1
  13. package/modules/jixieBidAdapter.js +8 -2
  14. package/modules/justpremiumBidAdapter.js +6 -1
  15. package/modules/livewrappedAnalyticsAdapter.js +5 -0
  16. package/modules/multibid/index.js +3 -3
  17. package/modules/nativoBidAdapter.js +5 -1
  18. package/modules/openxBidAdapter.js +1 -1
  19. package/modules/operaadsBidAdapter.js +21 -1
  20. package/modules/otmBidAdapter.js +146 -0
  21. package/modules/otmBidAdapter.md +27 -26
  22. package/modules/outbrainBidAdapter.js +5 -0
  23. package/modules/playwireBidAdapter.md +61 -0
  24. package/modules/rtdModule/index.js +2 -2
  25. package/modules/sonobiBidAdapter.js +7 -0
  26. package/modules/sortableBidAdapter.js +1 -0
  27. package/modules/teadsBidAdapter.js +3 -0
  28. package/modules/trustxBidAdapter.js +8 -6
  29. package/modules/ventesBidAdapter.js +370 -0
  30. package/modules/ventesBidAdapter.md +94 -0
  31. package/modules/yahoosspBidAdapter.js +6 -6
  32. package/package.json +1 -1
  33. package/src/auction.js +11 -11
  34. package/test/spec/modules/appnexusBidAdapter_spec.js +2 -1
  35. package/test/spec/modules/atsAnalyticsAdapter_spec.js +42 -9
  36. package/test/spec/modules/browsiRtdProvider_spec.js +62 -7
  37. package/test/spec/modules/cleanioRtdProvider_spec.js +188 -0
  38. package/test/spec/modules/deltaprojectsBidAdapter_spec.js +399 -0
  39. package/test/spec/modules/ixBidAdapter_spec.js +3 -3
  40. package/test/spec/modules/jixieBidAdapter_spec.js +13 -11
  41. package/test/spec/modules/justpremiumBidAdapter_spec.js +9 -2
  42. package/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +23 -4
  43. package/test/spec/modules/multibid_spec.js +31 -31
  44. package/test/spec/modules/openxBidAdapter_spec.js +0 -26
  45. package/test/spec/modules/operaadsBidAdapter_spec.js +38 -6
  46. package/test/spec/modules/otmBidAdapter_spec.js +67 -0
  47. package/test/spec/modules/outbrainBidAdapter_spec.js +18 -0
  48. package/test/spec/modules/sonobiBidAdapter_spec.js +34 -1
  49. package/test/spec/modules/sortableBidAdapter_spec.js +11 -0
  50. package/test/spec/modules/teadsBidAdapter_spec.js +132 -0
  51. package/test/spec/modules/trustxBidAdapter_spec.js +3 -3
  52. package/test/spec/modules/ventesBidAdapter_spec.js +845 -0
  53. package/test/spec/unit/core/adapterManager_spec.js +2 -1
@@ -144,9 +144,9 @@ function getAdapterMode() {
144
144
 
145
145
  function getResponseFormat(bid) {
146
146
  const adm = bid.adm;
147
- if (adm.includes('o2playerSettings') || adm.includes('YAHOO.VideoPlatform.VideoPlayer') || adm.includes('AdPlacement')) {
147
+ if (adm.indexOf('o2playerSettings') !== -1 || adm.indexOf('YAHOO.VideoPlatform.VideoPlayer') !== -1 || adm.indexOf('AdPlacement') !== -1) {
148
148
  return BANNER;
149
- } else if (adm.includes('VAST')) {
149
+ } else if (adm.indexOf('VAST') !== -1) {
150
150
  return VIDEO;
151
151
  }
152
152
  };
@@ -188,23 +188,23 @@ function validateAppendObject(validationType, allowedKeys, inputObject, appendTo
188
188
  for (const objectKey in inputObject) {
189
189
  switch (validationType) {
190
190
  case 'string':
191
- if (allowedKeys.includes(objectKey) && isStr(inputObject[objectKey])) {
191
+ if (allowedKeys.indexOf(objectKey) !== -1 && isStr(inputObject[objectKey])) {
192
192
  outputObject[objectKey] = inputObject[objectKey];
193
193
  };
194
194
  break;
195
195
  case 'number':
196
- if (allowedKeys.includes(objectKey) && isNumber(inputObject[objectKey])) {
196
+ if (allowedKeys.indexOf(objectKey) !== -1 && isNumber(inputObject[objectKey])) {
197
197
  outputObject[objectKey] = inputObject[objectKey];
198
198
  };
199
199
  break;
200
200
 
201
201
  case 'array':
202
- if (allowedKeys.includes(objectKey) && isArray(inputObject[objectKey])) {
202
+ if (allowedKeys.indexOf(objectKey) !== -1 && isArray(inputObject[objectKey])) {
203
203
  outputObject[objectKey] = inputObject[objectKey];
204
204
  };
205
205
  break;
206
206
  case 'object':
207
- if (allowedKeys.includes(objectKey) && isPlainObject(inputObject[objectKey])) {
207
+ if (allowedKeys.indexOf(objectKey) !== -1 && isPlainObject(inputObject[objectKey])) {
208
208
  outputObject[objectKey] = inputObject[objectKey];
209
209
  };
210
210
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prebid.js",
3
- "version": "5.19.0",
3
+ "version": "5.20.0",
4
4
  "description": "Header Bidding Management Library",
5
5
  "main": "src/prebid.js",
6
6
  "scripts": {
package/src/auction.js CHANGED
@@ -613,7 +613,8 @@ export const getPriceGranularity = (mediaType, bidReq) => {
613
613
  * @returns {function}
614
614
  */
615
615
  export const getPriceByGranularity = (granularity) => {
616
- return (bid) => {
616
+ return (bid, bidReq) => {
617
+ granularity = granularity || getPriceGranularity(bid.mediaType, bidReq);
617
618
  if (granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) {
618
619
  return bid.pbAg;
619
620
  } else if (granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) {
@@ -646,14 +647,14 @@ export const getAdvertiserDomain = () => {
646
647
  * @param {BidRequest} bidReq
647
648
  * @returns {*}
648
649
  */
649
- export function getStandardBidderSettings(mediaType, bidderCode, bidReq) {
650
+ export function getStandardBidderSettings(mediaType, bidderCode) {
650
651
  // factory for key value objs
651
652
  function createKeyVal(key, value) {
652
653
  return {
653
654
  key,
654
655
  val: (typeof value === 'function')
655
- ? function (bidResponse) {
656
- return value(bidResponse);
656
+ ? function (bidResponse, bidReq) {
657
+ return value(bidResponse, bidReq);
657
658
  }
658
659
  : function (bidResponse) {
659
660
  return getValue(bidResponse, value);
@@ -661,7 +662,6 @@ export function getStandardBidderSettings(mediaType, bidderCode, bidReq) {
661
662
  };
662
663
  }
663
664
  const TARGETING_KEYS = CONSTANTS.TARGETING_KEYS;
664
- const granularity = getPriceGranularity(mediaType, bidReq);
665
665
 
666
666
  let bidderSettings = $$PREBID_GLOBAL$$.bidderSettings;
667
667
  if (!bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]) {
@@ -671,7 +671,7 @@ export function getStandardBidderSettings(mediaType, bidderCode, bidReq) {
671
671
  bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = [
672
672
  createKeyVal(TARGETING_KEYS.BIDDER, 'bidderCode'),
673
673
  createKeyVal(TARGETING_KEYS.AD_ID, 'adId'),
674
- createKeyVal(TARGETING_KEYS.PRICE_BUCKET, getPriceByGranularity(granularity)),
674
+ createKeyVal(TARGETING_KEYS.PRICE_BUCKET, getPriceByGranularity()),
675
675
  createKeyVal(TARGETING_KEYS.SIZE, 'size'),
676
676
  createKeyVal(TARGETING_KEYS.DEAL, 'dealId'),
677
677
  createKeyVal(TARGETING_KEYS.SOURCE, 'source'),
@@ -716,12 +716,12 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj, bidReq) {
716
716
  // 1) set the keys from "standard" setting or from prebid defaults
717
717
  if (bidderSettings) {
718
718
  // initialize default if not set
719
- const standardSettings = getStandardBidderSettings(custBidObj.mediaType, bidderCode, bidReq);
720
- setKeys(keyValues, standardSettings, custBidObj);
719
+ const standardSettings = getStandardBidderSettings(custBidObj.mediaType, bidderCode);
720
+ setKeys(keyValues, standardSettings, custBidObj, bidReq);
721
721
 
722
722
  // 2) set keys from specific bidder setting override if they exist
723
723
  if (bidderCode && bidderSettings[bidderCode] && bidderSettings[bidderCode][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) {
724
- setKeys(keyValues, bidderSettings[bidderCode], custBidObj);
724
+ setKeys(keyValues, bidderSettings[bidderCode], custBidObj, bidReq);
725
725
  custBidObj.sendStandardTargeting = bidderSettings[bidderCode].sendStandardTargeting;
726
726
  }
727
727
  }
@@ -734,7 +734,7 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj, bidReq) {
734
734
  return keyValues;
735
735
  }
736
736
 
737
- function setKeys(keyValues, bidderSettings, custBidObj) {
737
+ function setKeys(keyValues, bidderSettings, custBidObj, bidReq) {
738
738
  var targeting = bidderSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING];
739
739
  custBidObj.size = custBidObj.getSize();
740
740
 
@@ -748,7 +748,7 @@ function setKeys(keyValues, bidderSettings, custBidObj) {
748
748
 
749
749
  if (isFn(value)) {
750
750
  try {
751
- value = value(custBidObj);
751
+ value = value(custBidObj, bidReq);
752
752
  } catch (e) {
753
753
  logError('bidmanager', 'ERROR', e);
754
754
  }
@@ -1047,7 +1047,8 @@ describe('AppNexusAdapter', function () {
1047
1047
  'trackers': [
1048
1048
  {
1049
1049
  'impression_urls': [
1050
- 'https://lax1-ib.adnxs.com/impression'
1050
+ 'https://lax1-ib.adnxs.com/impression',
1051
+ 'https://www.test.com/tracker'
1051
1052
  ],
1052
1053
  'video_events': {}
1053
1054
  }
@@ -12,11 +12,15 @@ let constants = require('src/constants.json');
12
12
 
13
13
  export const storage = getStorageManager();
14
14
  let sandbox;
15
+ let clock;
16
+ let now = new Date();
17
+
15
18
  describe('ats analytics adapter', function () {
16
19
  beforeEach(function () {
17
20
  sinon.stub(events, 'getEvents').returns([]);
18
21
  storage.setCookie('_lr_env_src_ats', 'true', 'Thu, 01 Jan 1970 00:00:01 GMT');
19
22
  sandbox = sinon.sandbox.create();
23
+ clock = sandbox.useFakeTimers(now.getTime());
20
24
  });
21
25
 
22
26
  afterEach(function () {
@@ -25,18 +29,20 @@ describe('ats analytics adapter', function () {
25
29
  atsAnalyticsAdapter.disableAnalytics();
26
30
  Math.random.restore();
27
31
  sandbox.restore();
32
+ clock.restore();
28
33
  });
29
34
 
30
35
  describe('track', function () {
31
36
  it('builds and sends request and response data', function () {
32
37
  sinon.stub(Math, 'random').returns(0.99);
33
- sinon.stub(atsAnalyticsAdapter, 'shouldFireRequest').returns(true);
34
38
  sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25');
35
- let now = new Date();
39
+
36
40
  now.setTime(now.getTime() + 3600000);
37
41
  storage.setCookie('_lr_env_src_ats', 'true', now.toUTCString());
38
42
  storage.setCookie('_lr_sampling_rate', '10', now.toUTCString());
39
43
 
44
+ this.timeout(2100);
45
+
40
46
  let initOptions = {
41
47
  pid: '10433394'
42
48
  };
@@ -62,7 +68,7 @@ describe('ats analytics adapter', function () {
62
68
  'refererInfo': {
63
69
  'referer': 'https://example.com/dev'
64
70
  },
65
- 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7',
71
+ 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7'
66
72
  };
67
73
  // prepare general auction - response
68
74
  let bidResponse = {
@@ -90,7 +96,7 @@ describe('ats analytics adapter', function () {
90
96
  let expectedAfterBid = {
91
97
  'Data': [{
92
98
  'has_envelope': true,
93
- 'adapter_version': 2,
99
+ 'adapter_version': 3,
94
100
  'bidder': 'appnexus',
95
101
  'bid_id': '30c77d079cdf17',
96
102
  'auction_id': 'a5b849e5-87d7-4205-8300-d063084fcfb7',
@@ -103,10 +109,30 @@ describe('ats analytics adapter', function () {
103
109
  'response_time_stamp': '2020-02-03T14:23:11.978Z',
104
110
  'currency': 'USD',
105
111
  'cpm': 0.5,
106
- 'net_revenue': true
112
+ 'net_revenue': true,
113
+ 'bid_won': true
107
114
  }]
108
115
  };
109
116
 
117
+ let wonRequest = {
118
+ 'adId': '2eddfdc0c791dc',
119
+ 'mediaType': 'banner',
120
+ 'requestId': '30c77d079cdf17',
121
+ 'cpm': 0.5,
122
+ 'creativeId': 29681110,
123
+ 'currency': 'USD',
124
+ 'netRevenue': true,
125
+ 'ttl': 300,
126
+ 'auctionId': 'a5b849e5-87d7-4205-8300-d063084fcfb7',
127
+ 'statusMessage': 'Bid available',
128
+ 'responseTimestamp': 1633525319061,
129
+ 'requestTimestamp': 1633525319258,
130
+ 'bidder': 'appnexus',
131
+ 'adUnitCode': 'div-gpt-ad-1438287399331-0',
132
+ 'size': '300x250',
133
+ 'status': 'rendered'
134
+ };
135
+
110
136
  // lets simulate that some bidders timeout
111
137
  let bidTimeoutArgsV1 = [
112
138
  {
@@ -148,6 +174,14 @@ describe('ats analytics adapter', function () {
148
174
 
149
175
  // Step 5: Send auction end event
150
176
  events.emit(constants.EVENTS.AUCTION_END, {});
177
+ // Step 6: Send bid won event
178
+ events.emit(constants.EVENTS.BID_WON, wonRequest);
179
+
180
+ sandbox.stub($$PREBID_GLOBAL$$, 'getAllWinningBids').callsFake((key) => {
181
+ return [wonRequest]
182
+ });
183
+
184
+ clock.tick(2000);
151
185
 
152
186
  let requests = server.requests.filter(req => {
153
187
  return req.url.indexOf(analyticsUrl) > -1;
@@ -156,13 +190,12 @@ describe('ats analytics adapter', function () {
156
190
  expect(requests.length).to.equal(1);
157
191
 
158
192
  let realAfterBid = JSON.parse(requests[0].requestBody);
159
- // Step 6: assert real data after bid and expected data
193
+
194
+ // Step 7: assert real data after bid and expected data
160
195
  expect(realAfterBid['Data']).to.deep.equal(expectedAfterBid['Data']);
161
196
 
162
197
  // check that the publisher ID is configured via options
163
198
  expect(atsAnalyticsAdapter.context.pid).to.equal(initOptions.pid);
164
-
165
- atsAnalyticsAdapter.shouldFireRequest.restore();
166
199
  })
167
200
  it('check browser is safari', function () {
168
201
  sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25');
@@ -204,7 +237,7 @@ describe('ats analytics adapter', function () {
204
237
  sinon.stub(atsAnalyticsAdapter, 'getUserAgent').returns('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/536.25 (KHTML, like Gecko) Version/6.0 Safari/536.25');
205
238
  sinon.stub(Math, 'random').returns(0.99);
206
239
  // publisher can try to pass anything they want but we will set sampling rate to 100, which means we will have 1% of requests
207
- let result = atsAnalyticsAdapter.shouldFireRequest(10);
240
+ let result = atsAnalyticsAdapter.shouldFireRequest(8);
208
241
  expect(result).to.equal(true);
209
242
  })
210
243
  it('should not fire analytics request if math random is something other then 0.99', function () {
@@ -1,5 +1,6 @@
1
1
  import * as browsiRTD from '../../../modules/browsiRtdProvider.js';
2
2
  import {makeSlot} from '../integration/faker/googletag.js';
3
+ import * as utils from '../../../src/utils'
3
4
 
4
5
  describe('browsi Real time data sub module', function () {
5
6
  const conf = {
@@ -29,11 +30,11 @@ describe('browsi Real time data sub module', function () {
29
30
  });
30
31
 
31
32
  it('should match placement with ad unit', function () {
32
- const slot = makeSlot({code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1'});
33
+ const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'});
33
34
 
34
- const test1 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_300x250']); // true
35
- const test2 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_300x250', '/57778053/Browsi']); // true
36
- const test3 = browsiRTD.isIdMatchingAdUnit(slot, ['/57778053/Browsi_Demo_Low']); // false
35
+ const test1 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc']); // true
36
+ const test2 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc', '/456/def']); // true
37
+ const test3 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/def']); // false
37
38
  const test4 = browsiRTD.isIdMatchingAdUnit(slot, []); // true
38
39
 
39
40
  expect(test1).to.equal(true);
@@ -43,12 +44,12 @@ describe('browsi Real time data sub module', function () {
43
44
  });
44
45
 
45
46
  it('should return correct macro values', function () {
46
- const slot = makeSlot({code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1'});
47
+ const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'});
47
48
 
48
49
  slot.setTargeting('test', ['test', 'value']);
49
50
  // slot getTargeting doesn't act like GPT so we can't expect real value
50
51
  const macroResult = browsiRTD.getMacroId({p: '<AD_UNIT>/<KEY_test>'}, slot);
51
- expect(macroResult).to.equal('/57778053/Browsi_Demo_300x250/NA');
52
+ expect(macroResult).to.equal('/123/abc/NA');
52
53
 
53
54
  const macroResultB = browsiRTD.getMacroId({}, slot);
54
55
  expect(macroResultB).to.equal('browsiAd_1');
@@ -72,7 +73,7 @@ describe('browsi Real time data sub module', function () {
72
73
  it('should return prediction from server', function () {
73
74
  makeSlot({code: 'hasPrediction', divId: 'hasPrediction'});
74
75
  const data = {
75
- p: {'hasPrediction': {p: 0.234}},
76
+ p: {'hasPrediction': {ps: {0: 0.234}}},
76
77
  kn: 'bv',
77
78
  pmd: undefined
78
79
  };
@@ -80,4 +81,58 @@ describe('browsi Real time data sub module', function () {
80
81
  expect(browsiRTD.browsiSubmodule.getTargetingData(['hasPrediction'])).to.eql({hasPrediction: {bv: '0.20'}});
81
82
  })
82
83
  })
84
+
85
+ describe('should return matching prediction', function () {
86
+ const predictions = {
87
+ 0: 0.123,
88
+ 1: 0.254,
89
+ 3: 0,
90
+ 4: 0.8
91
+ }
92
+ const singlePrediction = {
93
+ 0: 0.123
94
+ }
95
+ it('should return raw value if valid', function () {
96
+ expect(browsiRTD.getCurrentData(predictions, 0)).to.equal(0.123);
97
+ expect(browsiRTD.getCurrentData(predictions, 1)).to.equal(0.254);
98
+ })
99
+ it('should return 0 for prediction = 0', function () {
100
+ expect(browsiRTD.getCurrentData(predictions, 3)).to.equal(0);
101
+ })
102
+ it('should return -1 for invalid params', function () {
103
+ expect(browsiRTD.getCurrentData(null, 3)).to.equal(-1);
104
+ expect(browsiRTD.getCurrentData(predictions, null)).to.equal(-1);
105
+ })
106
+ it('should return prediction according to object keys length ', function () {
107
+ expect(browsiRTD.getCurrentData(singlePrediction, 0)).to.equal(0.123);
108
+ expect(browsiRTD.getCurrentData(singlePrediction, 1)).to.equal(-1);
109
+ expect(browsiRTD.getCurrentData(singlePrediction, 2)).to.equal(-1);
110
+ expect(browsiRTD.getCurrentData(predictions, 4)).to.equal(0.8);
111
+ expect(browsiRTD.getCurrentData(predictions, 5)).to.equal(0.8);
112
+ expect(browsiRTD.getCurrentData(predictions, 8)).to.equal(0.8);
113
+ })
114
+ })
115
+ describe('should set bid request data', function () {
116
+ const data = {
117
+ p: {
118
+ 'adUnit1': {ps: {0: 0.234}},
119
+ 'adUnit2': {ps: {0: 0.134}}},
120
+ kn: 'bv',
121
+ pmd: undefined
122
+ };
123
+ browsiRTD.setData(data);
124
+ const fakeAdUnits = [
125
+ {
126
+ code: 'adUnit1'
127
+ },
128
+ {
129
+ code: 'adUnit2'
130
+ }
131
+ ]
132
+ browsiRTD.browsiSubmodule.getBidRequestData({adUnits: fakeAdUnits}, () => {}, {}, null);
133
+ it('should set ad unit params with prediction values', function () {
134
+ expect(utils.deepAccess(fakeAdUnits[0], 'ortb2Imp.ext.data.browsi')).to.eql({bv: '0.20'});
135
+ expect(utils.deepAccess(fakeAdUnits[1], 'ortb2Imp.ext.data.browsi')).to.eql({bv: '0.10'});
136
+ })
137
+ })
83
138
  });
@@ -0,0 +1,188 @@
1
+ import * as utils from '../../../src/utils.js';
2
+ import * as hook from '../../../src/hook.js'
3
+
4
+ import { __TEST__ } from '../../../modules/cleanioRtdProvider.js';
5
+
6
+ const {
7
+ readConfig,
8
+ ConfigError,
9
+ pageInitStepPreloadScript,
10
+ pageInitStepProtectPage,
11
+ bidWrapStepAugmentHtml,
12
+ bidWrapStepProtectByWrapping,
13
+ beforeInit,
14
+ } = __TEST__;
15
+
16
+ sinon.assert.expose(chai.assert, { prefix: 'sinon' });
17
+
18
+ const fakeScriptURL = 'https://example.com/script.js';
19
+
20
+ function makeFakeBidResponse() {
21
+ return {
22
+ ad: '<body>hello ad</body>',
23
+ bidderCode: 'BIDDER',
24
+ creativeId: 'CREATIVE',
25
+ cpm: 1.23,
26
+ };
27
+ }
28
+
29
+ describe('clean.io RTD module', function () {
30
+ describe('readConfig()', function() {
31
+ it('should throw ConfigError on invalid configurations', function() {
32
+ expect(() => readConfig({})).to.throw(ConfigError);
33
+ expect(() => readConfig({ params: {} })).to.throw(ConfigError);
34
+ expect(() => readConfig({ params: { protectionMode: 'bids' } })).to.throw(ConfigError);
35
+ expect(() => readConfig({ params: { cdnUrl: 'abc' } })).to.throw(ConfigError);
36
+ expect(() => readConfig({ params: { cdnUrl: 'abc', protectionMode: 'bids' } })).to.throw(ConfigError);
37
+ expect(() => readConfig({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: '123' } })).to.throw(ConfigError);
38
+ });
39
+
40
+ it('should accept valid configurations', function() {
41
+ expect(() => readConfig({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'full' } })).to.not.throw();
42
+ expect(() => readConfig({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'bids' } })).to.not.throw();
43
+ expect(() => readConfig({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'bids-nowait' } })).to.not.throw();
44
+ });
45
+ });
46
+
47
+ describe('Module initialization step', function() {
48
+ let insertElementStub;
49
+ beforeEach(function() {
50
+ insertElementStub = sinon.stub(utils, 'insertElement');
51
+ });
52
+ afterEach(function() {
53
+ utils.insertElement.restore();
54
+ });
55
+
56
+ it('pageInitStepPreloadScript() should insert link/preload element', function() {
57
+ pageInitStepPreloadScript(fakeScriptURL);
58
+
59
+ sinon.assert.calledOnce(insertElementStub);
60
+ sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'LINK'));
61
+ sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.rel === 'preload'));
62
+ sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.as === 'script'));
63
+ sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.href === fakeScriptURL));
64
+ });
65
+
66
+ it('pageInitStepProtectPage() should insert script element', function() {
67
+ pageInitStepProtectPage(fakeScriptURL);
68
+
69
+ sinon.assert.calledOnce(insertElementStub);
70
+ sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'SCRIPT'));
71
+ sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.type === 'text/javascript'));
72
+ sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.src === fakeScriptURL));
73
+ });
74
+ });
75
+
76
+ function ensurePrependToBidResponse(fakeBidResponse) {
77
+ expect(fakeBidResponse).to.have.own.property('ad').which.is.a('string');
78
+ expect(fakeBidResponse.ad).to.contain('<!-- pbad://creativeId=CREATIVE&bidderCode=BIDDER&cpm=1.23 -->');
79
+ }
80
+
81
+ function ensureWrapBidResponse(fakeBidResponse, scriptUrl) {
82
+ expect(fakeBidResponse).to.have.own.property('ad').which.is.a('string');
83
+ expect(fakeBidResponse.ad).to.contain(`src="${scriptUrl}"`);
84
+ expect(fakeBidResponse.ad).to.contain('agent.put(ad)');
85
+ }
86
+
87
+ describe('Bid processing step', function() {
88
+ it('bidWrapStepAugmentHtml() should prepend bid-specific information in a comment', function() {
89
+ const fakeBidResponse = makeFakeBidResponse();
90
+ bidWrapStepAugmentHtml(fakeBidResponse);
91
+ ensurePrependToBidResponse(fakeBidResponse);
92
+ });
93
+
94
+ it('bidWrapStepProtectByWrapping() should wrap payload into a script tag', function() {
95
+ const fakeBidResponse = makeFakeBidResponse();
96
+ bidWrapStepProtectByWrapping(fakeScriptURL, 0, fakeBidResponse);
97
+ ensureWrapBidResponse(fakeBidResponse, fakeScriptURL);
98
+ });
99
+ });
100
+
101
+ describe('Sumbodule execution', function() {
102
+ let submoduleStub;
103
+ let insertElementStub;
104
+ beforeEach(function () {
105
+ submoduleStub = sinon.stub(hook, 'submodule');
106
+ insertElementStub = sinon.stub(utils, 'insertElement');
107
+ });
108
+ afterEach(function () {
109
+ utils.insertElement.restore();
110
+ submoduleStub.restore();
111
+ });
112
+
113
+ function getModule() {
114
+ beforeInit();
115
+
116
+ expect(submoduleStub.calledOnceWith('realTimeData')).to.equal(true);
117
+
118
+ const registeredSubmoduleDefinition = submoduleStub.getCall(0).args[1];
119
+ expect(registeredSubmoduleDefinition).to.be.an('object');
120
+ expect(registeredSubmoduleDefinition).to.have.own.property('name', 'clean.io');
121
+ expect(registeredSubmoduleDefinition).to.have.own.property('init').that.is.a('function');
122
+ expect(registeredSubmoduleDefinition).to.have.own.property('onBidResponseEvent').that.is.a('function');
123
+
124
+ return registeredSubmoduleDefinition;
125
+ }
126
+
127
+ it('should register clean.io RTD submodule provider', function () {
128
+ getModule();
129
+ });
130
+
131
+ it('should refuse initialization with incorrect parameters', function () {
132
+ const { init } = getModule();
133
+ expect(init({ params: { cdnUrl: 'abc', protectionMode: 'full' } }, {})).to.equal(false); // too short distribution name
134
+ sinon.assert.notCalled(insertElementStub);
135
+ });
136
+
137
+ it('should iniitalize in full (page) protection mode', function () {
138
+ const { init, onBidResponseEvent } = getModule();
139
+ expect(init({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'full' } }, {})).to.equal(true);
140
+ sinon.assert.calledOnce(insertElementStub);
141
+ sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'SCRIPT'));
142
+
143
+ const fakeBidResponse = makeFakeBidResponse();
144
+ onBidResponseEvent(fakeBidResponse, {}, {});
145
+ ensurePrependToBidResponse(fakeBidResponse);
146
+ });
147
+
148
+ it('should iniitalize in bids (frame) protection mode', function () {
149
+ const { init, onBidResponseEvent } = getModule();
150
+ expect(init({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'bids' } }, {})).to.equal(true);
151
+ sinon.assert.calledOnce(insertElementStub);
152
+ sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'LINK'));
153
+
154
+ const fakeBidResponse = makeFakeBidResponse();
155
+ onBidResponseEvent(fakeBidResponse, {}, {});
156
+ ensureWrapBidResponse(fakeBidResponse, 'https://abc1234567890.cloudfront.net/script.js');
157
+ });
158
+
159
+ it('should respect preload status in bids-nowait protection mode', function () {
160
+ const { init, onBidResponseEvent } = getModule();
161
+ expect(init({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'bids-nowait' } }, {})).to.equal(true);
162
+ sinon.assert.calledOnce(insertElementStub);
163
+ sinon.assert.calledWith(insertElementStub, sinon.match(elem => elem.tagName === 'LINK'));
164
+ const preloadLink = insertElementStub.getCall(0).args[0];
165
+ expect(preloadLink).to.have.property('onload').which.is.a('function');
166
+ expect(preloadLink).to.have.property('onerror').which.is.a('function');
167
+
168
+ const fakeBidResponse1 = makeFakeBidResponse();
169
+ onBidResponseEvent(fakeBidResponse1, {}, {});
170
+ ensurePrependToBidResponse(fakeBidResponse1);
171
+
172
+ // Simulate successful preloading
173
+ preloadLink.onload();
174
+
175
+ const fakeBidResponse2 = makeFakeBidResponse();
176
+ onBidResponseEvent(fakeBidResponse2, {}, {});
177
+ ensureWrapBidResponse(fakeBidResponse2, 'https://abc1234567890.cloudfront.net/script.js');
178
+
179
+ // Simulate error
180
+ preloadLink.onerror();
181
+
182
+ // Now we should fallback to just prepending
183
+ const fakeBidResponse3 = makeFakeBidResponse();
184
+ onBidResponseEvent(fakeBidResponse3, {}, {});
185
+ ensurePrependToBidResponse(fakeBidResponse3);
186
+ });
187
+ });
188
+ });