prebid.js 9.53.3 → 9.53.4

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 (208) hide show
  1. package/dist/33acrossAnalyticsAdapter.js +1 -1
  2. package/dist/33acrossBidAdapter.js +1 -1
  3. package/dist/33acrossIdSystem.js +1 -1
  4. package/dist/BTBidAdapter.js +1 -1
  5. package/dist/adagioAnalyticsAdapter.js +1 -1
  6. package/dist/adagioBidAdapter.js +1 -1
  7. package/dist/adagioUtils.js +1 -1
  8. package/dist/addefendBidAdapter.js +1 -1
  9. package/dist/adgenerationBidAdapter.js +1 -1
  10. package/dist/adlooxRtdProvider.js +1 -1
  11. package/dist/adqueryBidAdapter.js +1 -1
  12. package/dist/adrelevantisBidAdapter.js +1 -1
  13. package/dist/adstirBidAdapter.js +1 -1
  14. package/dist/adtrgtmeBidAdapter.js +1 -1
  15. package/dist/adxcgAnalyticsAdapter.js +1 -1
  16. package/dist/adxcgBidAdapter.js +1 -1
  17. package/dist/adyoulikeBidAdapter.js +1 -1
  18. package/dist/agmaAnalyticsAdapter.js +1 -1
  19. package/dist/ajaBidAdapter.js +1 -1
  20. package/dist/amxBidAdapter.js +1 -1
  21. package/dist/amxIdSystem.js +1 -1
  22. package/dist/aniviewBidAdapter.js +1 -1
  23. package/dist/appierAnalyticsAdapter.js +1 -1
  24. package/dist/appnexusBidAdapter.js +1 -1
  25. package/dist/asoBidAdapter.js +1 -1
  26. package/dist/axonixBidAdapter.js +1 -1
  27. package/dist/beopBidAdapter.js +1 -1
  28. package/dist/bidderTimeoutUtils.js +1 -0
  29. package/dist/bidglassBidAdapter.js +1 -1
  30. package/dist/big-richmediaBidAdapter.js +1 -1
  31. package/dist/bitmediaBidAdapter.js +1 -1
  32. package/dist/bridBidAdapter.js +1 -1
  33. package/dist/bridgeuppBidAdapter.js +1 -1
  34. package/dist/bridgewellBidAdapter.js +1 -1
  35. package/dist/brightMountainMediaBidAdapter.js +1 -1
  36. package/dist/carodaBidAdapter.js +1 -1
  37. package/dist/chtnwBidAdapter.js +1 -1
  38. package/dist/chunk-core.js +1 -1
  39. package/dist/concertBidAdapter.js +1 -1
  40. package/dist/connectadBidAdapter.js +1 -1
  41. package/dist/consumableBidAdapter.js +1 -1
  42. package/dist/contxtfulBidAdapter.js +1 -1
  43. package/dist/conversantAnalyticsAdapter.js +1 -1
  44. package/dist/conversantBidAdapter.js +1 -1
  45. package/dist/craftBidAdapter.js +1 -1
  46. package/dist/criteoBidAdapter.js +1 -1
  47. package/dist/cwireBidAdapter.js +1 -1
  48. package/dist/dailymotionBidAdapter.js +1 -1
  49. package/dist/dependencies.json +10 -1
  50. package/dist/dspxBidAdapter.js +1 -1
  51. package/dist/dxkultureBidAdapter.js +1 -1
  52. package/dist/eplanningBidAdapter.js +1 -1
  53. package/dist/equativBidAdapter.js +1 -1
  54. package/dist/eskimiBidAdapter.js +1 -1
  55. package/dist/euidIdSystem.js +1 -1
  56. package/dist/exadsBidAdapter.js +1 -1
  57. package/dist/excoBidAdapter.js +1 -1
  58. package/dist/feedadBidAdapter.js +1 -1
  59. package/dist/finativeBidAdapter.js +1 -1
  60. package/dist/freewheel-sspBidAdapter.js +1 -1
  61. package/dist/fwsspBidAdapter.js +1 -1
  62. package/dist/gmosspBidAdapter.js +1 -1
  63. package/dist/greenbidsAnalyticsAdapter.js +1 -1
  64. package/dist/greenbidsBidAdapter.js +1 -1
  65. package/dist/greenbidsRtdProvider.js +1 -1
  66. package/dist/gridBidAdapter.js +1 -1
  67. package/dist/gumgumBidAdapter.js +1 -1
  68. package/dist/h12mediaBidAdapter.js +1 -1
  69. package/dist/hypelabBidAdapter.js +1 -1
  70. package/dist/id5AnalyticsAdapter.js +1 -1
  71. package/dist/id5IdSystem.js +1 -1
  72. package/dist/imdsBidAdapter.js +1 -1
  73. package/dist/improvedigitalBidAdapter.js +1 -1
  74. package/dist/inmobiBidAdapter.js +1 -1
  75. package/dist/insticatorBidAdapter.js +1 -1
  76. package/dist/intentIqAnalyticsAdapter.js +1 -1
  77. package/dist/ixBidAdapter.js +1 -1
  78. package/dist/jixieBidAdapter.js +1 -1
  79. package/dist/justpremiumBidAdapter.js +1 -1
  80. package/dist/kargoBidAdapter.js +1 -1
  81. package/dist/kimberliteBidAdapter.js +1 -1
  82. package/dist/konduitAnalyticsAdapter.js +1 -1
  83. package/dist/kueezBidAdapter.js +1 -1
  84. package/dist/lassoBidAdapter.js +1 -1
  85. package/dist/lifestreetBidAdapter.js +1 -1
  86. package/dist/liveIntentId.js +1 -1
  87. package/dist/logicadBidAdapter.js +1 -1
  88. package/dist/loglyliftBidAdapter.js +1 -1
  89. package/dist/luceadBidAdapter.js +1 -1
  90. package/dist/mabidderBidAdapter.js +1 -1
  91. package/dist/madsenseBidAdapter.js +1 -1
  92. package/dist/magniteAnalyticsAdapter.js +1 -1
  93. package/dist/malltvAnalyticsAdapter.js +1 -1
  94. package/dist/marsmediaBidAdapter.js +1 -1
  95. package/dist/mediafuseBidAdapter.js +1 -1
  96. package/dist/medianetBidAdapter.js +1 -1
  97. package/dist/medianetUtils.js +1 -1
  98. package/dist/mediasquareBidAdapter.js +1 -1
  99. package/dist/mgidBidAdapter.js +1 -1
  100. package/dist/missenaBidAdapter.js +1 -1
  101. package/dist/mobilefuseBidAdapter.js +1 -1
  102. package/dist/nextMillenniumBidAdapter.js +1 -1
  103. package/dist/nexx360Utils.js +1 -1
  104. package/dist/nobidAnalyticsAdapter.js +1 -1
  105. package/dist/nobidBidAdapter.js +1 -1
  106. package/dist/nodalsAiRtdProvider.js +1 -1
  107. package/dist/not-for-prod/prebid.js +175 -172
  108. package/dist/objectGuard.js +1 -1
  109. package/dist/oguryBidAdapter.js +1 -1
  110. package/dist/onetagBidAdapter.js +1 -1
  111. package/dist/ooloAnalyticsAdapter.js +1 -1
  112. package/dist/openxBidAdapter.js +1 -1
  113. package/dist/optableRtdProvider.js +1 -1
  114. package/dist/optidigitalBidAdapter.js +1 -1
  115. package/dist/orbidderBidAdapter.js +1 -1
  116. package/dist/outbrainBidAdapter.js +1 -1
  117. package/dist/pixfutureBidAdapter.js +1 -1
  118. package/dist/publinkIdSystem.js +1 -1
  119. package/dist/pubmaticAnalyticsAdapter.js +1 -1
  120. package/dist/pubmaticBidAdapter.js +1 -1
  121. package/dist/pubmaticRtdProvider.js +1 -1
  122. package/dist/pubmaticUtils.js +1 -0
  123. package/dist/pubwiseAnalyticsAdapter.js +1 -1
  124. package/dist/pubxaiAnalyticsAdapter.js +1 -1
  125. package/dist/pxyzBidAdapter.js +1 -1
  126. package/dist/quantcastBidAdapter.js +1 -1
  127. package/dist/readpeakBidAdapter.js +1 -1
  128. package/dist/relaidoBidAdapter.js +1 -1
  129. package/dist/retailspotBidAdapter.js +1 -1
  130. package/dist/rhythmoneBidAdapter.js +1 -1
  131. package/dist/riseUtils.js +1 -1
  132. package/dist/rtdModule.js +1 -1
  133. package/dist/rubiconBidAdapter.js +1 -1
  134. package/dist/seedingAllianceBidAdapter.js +1 -1
  135. package/dist/seedtagBidAdapter.js +1 -1
  136. package/dist/sevioBidAdapter.js +1 -0
  137. package/dist/sharethroughAnalyticsAdapter.js +1 -1
  138. package/dist/sharethroughBidAdapter.js +1 -1
  139. package/dist/showheroes-bsBidAdapter.js +1 -1
  140. package/dist/smaatoBidAdapter.js +1 -1
  141. package/dist/smartadserverBidAdapter.js +1 -1
  142. package/dist/smartxBidAdapter.js +1 -1
  143. package/dist/smilewantedBidAdapter.js +1 -1
  144. package/dist/snigelBidAdapter.js +1 -1
  145. package/dist/sonobiBidAdapter.js +1 -1
  146. package/dist/sovrnBidAdapter.js +1 -1
  147. package/dist/sparteoBidAdapter.js +1 -1
  148. package/dist/sspBCBidAdapter.js +1 -1
  149. package/dist/stvBidAdapter.js +1 -1
  150. package/dist/sublimeBidAdapter.js +1 -1
  151. package/dist/taboolaBidAdapter.js +1 -1
  152. package/dist/tappxBidAdapter.js +1 -1
  153. package/dist/targetVideoBidAdapter.js +1 -1
  154. package/dist/teadsBidAdapter.js +1 -1
  155. package/dist/terceptAnalyticsAdapter.js +1 -1
  156. package/dist/themoneytizerBidAdapter.js +1 -1
  157. package/dist/timeoutRtdProvider.js +1 -1
  158. package/dist/trionBidAdapter.js +1 -1
  159. package/dist/tripleliftBidAdapter.js +1 -1
  160. package/dist/ttdBidAdapter.js +1 -1
  161. package/dist/ucfunnelAnalyticsAdapter.js +1 -1
  162. package/dist/uid2IdSystem.js +1 -1
  163. package/dist/underdogmediaBidAdapter.js +1 -1
  164. package/dist/undertoneBidAdapter.js +1 -1
  165. package/dist/unrulyBidAdapter.js +1 -1
  166. package/dist/vidazooUtils.js +1 -1
  167. package/dist/videobyteBidAdapter.js +1 -1
  168. package/dist/visxBidAdapter.js +1 -1
  169. package/dist/vuukleBidAdapter.js +1 -1
  170. package/dist/widespaceBidAdapter.js +1 -1
  171. package/dist/winrBidAdapter.js +1 -1
  172. package/dist/yahooAdsBidAdapter.js +1 -1
  173. package/dist/yandexBidAdapter.js +1 -1
  174. package/dist/yieldmoBidAdapter.js +1 -1
  175. package/dist/yieldoneAnalyticsAdapter.js +1 -1
  176. package/integrationExamples/gpt/pubmaticRtdProvider_Example.html +161 -0
  177. package/libraries/bidderTimeoutUtils/bidderTimeoutUtils.js +119 -0
  178. package/libraries/objectGuard/objectGuard.js +36 -15
  179. package/libraries/pubmaticUtils/plugins/dynamicTimeout.js +209 -0
  180. package/libraries/pubmaticUtils/plugins/floorProvider.js +168 -0
  181. package/libraries/pubmaticUtils/plugins/pluginManager.js +106 -0
  182. package/libraries/pubmaticUtils/plugins/unifiedPricingRule.js +375 -0
  183. package/libraries/pubmaticUtils/pubmaticUtils.js +76 -0
  184. package/modules/fwsspBidAdapter.js +134 -69
  185. package/modules/fwsspBidAdapter.md +121 -26
  186. package/modules/optableRtdProvider.js +33 -12
  187. package/modules/pubmaticAnalyticsAdapter.js +5 -1
  188. package/modules/pubmaticRtdProvider.js +105 -565
  189. package/modules/rtdModule/index.js +23 -2
  190. package/modules/sevioBidAdapter.js +413 -0
  191. package/modules/sevioBidAdapter.md +29 -0
  192. package/modules/sparteoBidAdapter.js +122 -10
  193. package/modules/timeoutRtdProvider.js +2 -105
  194. package/package.json +1 -1
  195. package/test/spec/activities/objectGuard_spec.js +49 -16
  196. package/test/spec/libraries/bidderTimeoutUtils/bidderTimeoutUtils_spec.js +213 -0
  197. package/test/spec/libraries/pubmaticUtils/plugins/dynamicTimeout_spec.js +746 -0
  198. package/test/spec/libraries/pubmaticUtils/plugins/floorProvider_spec.js +184 -0
  199. package/test/spec/libraries/pubmaticUtils/plugins/pluginManager_spec.js +489 -0
  200. package/test/spec/libraries/pubmaticUtils/plugins/unifiedPricingRule_spec.js +359 -0
  201. package/test/spec/libraries/pubmaticUtils/pubmaticUtils_spec.js +236 -0
  202. package/test/spec/modules/fwsspBidAdapter_spec.js +513 -78
  203. package/test/spec/modules/optableRtdProvider_spec.js +55 -5
  204. package/test/spec/modules/pubmaticRtdProvider_spec.js +252 -1183
  205. package/test/spec/modules/realTimeDataModule_spec.js +58 -8
  206. package/test/spec/modules/sevioBidAdapter_spec.js +513 -0
  207. package/test/spec/modules/sparteoBidAdapter_spec.js +528 -43
  208. package/test/spec/modules/timeoutRtdProvider_spec.js +1 -201
@@ -1,1241 +1,310 @@
1
1
  import { expect } from 'chai';
2
- import * as priceFloors from '../../../modules/priceFloors';
3
- import * as utils from '../../../src/utils.js';
4
- import * as suaModule from '../../../src/fpd/sua.js';
5
- import { config as conf } from '../../../src/config';
6
- import * as hook from '../../../src/hook.js';
7
- import * as prebidGlobal from '../../../src/prebidGlobal.js';
8
- import {
9
- registerSubModule, pubmaticSubmodule, getFloorsConfig, fetchData,
10
- getCurrentTimeOfDay, getBrowserType, getOs, getDeviceType, getCountry, getUtm, getBidder, _country,
11
- _profileConfigs, _floorsData, defaultValueTemplate, withTimeout, configMerged,
12
- getProfileConfigs, setProfileConfigs, getTargetingData
13
- } from '../../../modules/pubmaticRtdProvider.js';
14
2
  import sinon from 'sinon';
3
+ import * as utils from '../../../src/utils.js';
4
+ import * as pubmaticRtdProvider from '../../../modules/pubmaticRtdProvider.js';
5
+ import { FloorProvider } from '../../../libraries/pubmaticUtils/plugins/floorProvider.js';
6
+ import { UnifiedPricingRule } from '../../../libraries/pubmaticUtils/plugins/unifiedPricingRule.js';
15
7
 
16
8
  describe('Pubmatic RTD Provider', () => {
17
- let sandbox;
18
-
19
- beforeEach(() => {
20
- sandbox = sinon.createSandbox();
21
- sandbox.stub(conf, 'getConfig').callsFake(() => {
22
- return {
23
- floors: {
24
- 'enforcement': {
25
- 'floorDeals': true,
26
- 'enforceJS': true
27
- }
28
- },
29
- realTimeData: {
30
- auctionDelay: 100
31
- }
32
- };
33
- });
34
- });
35
-
36
- afterEach(() => {
37
- sandbox.restore();
9
+ let sandbox;
10
+ let fetchStub;
11
+ let logErrorStub;
12
+ let originalPluginManager;
13
+ let originalConfigJsonManager;
14
+ let pluginManagerStub;
15
+ let configJsonManagerStub;
16
+
17
+ beforeEach(() => {
18
+ sandbox = sinon.createSandbox();
19
+ fetchStub = sandbox.stub(window, 'fetch');
20
+ logErrorStub = sinon.stub(utils, 'logError');
21
+
22
+ // Store original implementations
23
+ originalPluginManager = Object.assign({}, pubmaticRtdProvider.pluginManager);
24
+ originalConfigJsonManager = Object.assign({}, pubmaticRtdProvider.configJsonManager);
25
+
26
+ // Create stubs
27
+ pluginManagerStub = {
28
+ initialize: sinon.stub().resolves(),
29
+ executeHook: sinon.stub().resolves(),
30
+ register: sinon.stub()
31
+ };
32
+
33
+ configJsonManagerStub = {
34
+ fetchConfig: sinon.stub().resolves(true),
35
+ getYMConfig: sinon.stub(),
36
+ getConfigByName: sinon.stub(),
37
+ country: 'IN'
38
+ };
39
+
40
+ // Replace exported objects with stubs
41
+ Object.keys(pluginManagerStub).forEach(key => {
42
+ pubmaticRtdProvider.pluginManager[key] = pluginManagerStub[key];
38
43
  });
39
44
 
40
- describe('registerSubModule', () => {
41
- it('should register RTD submodule provider', () => {
42
- let submoduleStub = sinon.stub(hook, 'submodule');
43
- registerSubModule();
44
- assert(submoduleStub.calledOnceWith('realTimeData', pubmaticSubmodule));
45
- submoduleStub.restore();
45
+ Object.keys(configJsonManagerStub).forEach(key => {
46
+ if (key === 'country') {
47
+ Object.defineProperty(pubmaticRtdProvider.configJsonManager, key, {
48
+ get: () => configJsonManagerStub[key]
46
49
  });
50
+ } else {
51
+ pubmaticRtdProvider.configJsonManager[key] = configJsonManagerStub[key];
52
+ }
47
53
  });
48
54
 
49
- describe('submodule', () => {
50
- describe('name', () => {
51
- it('should be pubmatic', () => {
52
- expect(pubmaticSubmodule.name).to.equal('pubmatic');
53
- });
54
- });
55
- });
56
-
57
- describe('init', () => {
58
- let logErrorStub;
59
- let continueAuctionStub;
60
-
61
- const getConfig = () => ({
62
- params: {
63
- publisherId: 'test-publisher-id',
64
- profileId: 'test-profile-id'
65
- },
66
- });
55
+ // Reset _ymConfigPromise for each test
56
+ pubmaticRtdProvider.setYmConfigPromise(Promise.resolve());
57
+ });
67
58
 
68
- beforeEach(() => {
69
- logErrorStub = sandbox.stub(utils, 'logError');
70
- continueAuctionStub = sandbox.stub(priceFloors, 'continueAuction');
71
- });
72
-
73
- it('should return false if publisherId is missing', () => {
74
- const config = {
75
- params: {
76
- profileId: 'test-profile-id'
77
- }
78
- };
79
- expect(pubmaticSubmodule.init(config)).to.be.false;
80
- });
59
+ afterEach(() => {
60
+ sandbox.restore();
61
+ logErrorStub.restore();
81
62
 
82
- it('should return false if profileId is missing', () => {
83
- const config = {
84
- params: {
85
- publisherId: 'test-publisher-id'
86
- }
87
- };
88
- expect(pubmaticSubmodule.init(config)).to.be.false;
89
- });
90
-
91
- it('should accept numeric publisherId and convert to string', () => {
92
- const config = {
93
- params: {
94
- publisherId: 123,
95
- profileId: 'test-profile-id'
96
- }
97
- };
98
- expect(pubmaticSubmodule.init(config)).to.be.true;
99
- });
100
-
101
- it('should accept numeric profileId and convert to string', () => {
102
- const config = {
103
- params: {
104
- publisherId: 'test-publisher-id',
105
- profileId: 345
106
- }
107
- };
108
- expect(pubmaticSubmodule.init(config)).to.be.true;
109
- });
110
-
111
- it('should initialize successfully with valid config', () => {
112
- expect(pubmaticSubmodule.init(getConfig())).to.be.true;
113
- });
114
-
115
- it('should handle empty config object', () => {
116
- expect(pubmaticSubmodule.init({})).to.be.false;
117
- expect(logErrorStub.calledWith(sinon.match(/Missing publisher Id/))).to.be.true;
118
- });
119
-
120
- it('should return false if continueAuction is not a function', () => {
121
- continueAuctionStub.value(undefined);
122
- expect(pubmaticSubmodule.init(getConfig())).to.be.false;
123
- expect(logErrorStub.calledWith(sinon.match(/continueAuction is not a function/))).to.be.true;
124
- });
63
+ // Restore original implementations
64
+ Object.keys(originalPluginManager).forEach(key => {
65
+ pubmaticRtdProvider.pluginManager[key] = originalPluginManager[key];
125
66
  });
126
67
 
127
- describe('getCurrentTimeOfDay', () => {
128
- let clock;
129
-
130
- beforeEach(() => {
131
- clock = sandbox.useFakeTimers(new Date('2024-01-01T12:00:00')); // Set fixed time for testing
132
- });
133
-
134
- afterEach(() => {
135
- clock.restore();
136
- });
137
-
138
- const testTimes = [
139
- { hour: 6, expected: 'morning' },
140
- { hour: 13, expected: 'afternoon' },
141
- { hour: 18, expected: 'evening' },
142
- { hour: 22, expected: 'night' },
143
- { hour: 4, expected: 'night' }
144
- ];
145
-
146
- testTimes.forEach(({ hour, expected }) => {
147
- it(`should return ${expected} at ${hour}:00`, () => {
148
- clock.setSystemTime(new Date().setHours(hour));
149
- const result = getCurrentTimeOfDay();
150
- expect(result).to.equal(expected);
151
- });
68
+ Object.keys(originalConfigJsonManager).forEach(key => {
69
+ if (key === 'country') {
70
+ Object.defineProperty(pubmaticRtdProvider.configJsonManager, 'country', {
71
+ get: () => originalConfigJsonManager[key]
152
72
  });
73
+ } else {
74
+ pubmaticRtdProvider.configJsonManager[key] = originalConfigJsonManager[key];
75
+ }
153
76
  });
154
-
155
- describe('getBrowserType', () => {
156
- let userAgentStub, getLowEntropySUAStub;
157
-
158
- const USER_AGENTS = {
159
- chrome: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
160
- firefox: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0',
161
- edge: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/91.0.864.67 Safari/537.36',
162
- safari: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.6 Mobile/15E148 Safari/604.1',
163
- ie: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)',
164
- opera: 'Opera/9.80 (Windows NT 6.1; WOW64) Presto/2.12.388 Version/12.16',
165
- unknown: 'UnknownBrowser/1.0'
166
- };
167
-
168
- beforeEach(() => {
169
- userAgentStub = sandbox.stub(navigator, 'userAgent');
170
- getLowEntropySUAStub = sandbox.stub(suaModule, 'getLowEntropySUA').returns(undefined);
171
- });
172
-
173
- afterEach(() => {
174
- userAgentStub.restore();
175
- getLowEntropySUAStub.restore();
176
- });
177
-
178
- it('should detect Chrome', () => {
179
- userAgentStub.value(USER_AGENTS.chrome);
180
- expect(getBrowserType()).to.equal('9');
181
- });
182
-
183
- it('should detect Firefox', () => {
184
- userAgentStub.value(USER_AGENTS.firefox);
185
- expect(getBrowserType()).to.equal('12');
186
- });
187
-
188
- it('should detect Edge', () => {
189
- userAgentStub.value(USER_AGENTS.edge);
190
- expect(getBrowserType()).to.equal('2');
191
- });
192
-
193
- it('should detect Internet Explorer', () => {
194
- userAgentStub.value(USER_AGENTS.ie);
195
- expect(getBrowserType()).to.equal('4');
196
- });
197
-
198
- it('should detect Opera', () => {
199
- userAgentStub.value(USER_AGENTS.opera);
200
- expect(getBrowserType()).to.equal('3');
201
- });
202
-
203
- it('should return 0 for unknown browser', () => {
204
- userAgentStub.value(USER_AGENTS.unknown);
205
- expect(getBrowserType()).to.equal('0');
206
- });
207
-
208
- it('should return -1 when userAgent is null', () => {
209
- userAgentStub.value(null);
210
- expect(getBrowserType()).to.equal('-1');
211
- });
77
+ });
78
+
79
+ describe('init', () => {
80
+ const validConfig = {
81
+ params: {
82
+ publisherId: 'test-publisher-id',
83
+ profileId: 'test-profile-id'
84
+ }
85
+ };
86
+
87
+ it('should return false if publisherId is missing', () => {
88
+ const config = {
89
+ params: {
90
+ profileId: 'test-profile-id'
91
+ }
92
+ };
93
+ const result = pubmaticRtdProvider.pubmaticSubmodule.init(config);
94
+ expect(result).to.be.false;
95
+ expect(logErrorStub.calledOnce).to.be.true;
96
+ expect(logErrorStub.firstCall.args[0]).to.equal(`${pubmaticRtdProvider.CONSTANTS.LOG_PRE_FIX} Missing publisher Id.`);
212
97
  });
213
98
 
214
- describe('Utility functions', () => {
215
- it('should set browser correctly', () => {
216
- expect(getBrowserType()).to.be.a('string');
217
- });
218
-
219
- it('should set OS correctly', () => {
220
- expect(getOs()).to.be.a('string');
221
- });
222
-
223
- it('should set device type correctly', () => {
224
- expect(getDeviceType()).to.be.a('string');
225
- });
226
-
227
- it('should set time of day correctly', () => {
228
- expect(getCurrentTimeOfDay()).to.be.a('string');
229
- });
230
-
231
- it('should set country correctly', () => {
232
- expect(getCountry()).to.satisfy(value => typeof value === 'string' || value === undefined);
233
- });
234
-
235
- it('should set UTM correctly', () => {
236
- expect(getUtm()).to.be.a('string');
237
- expect(getUtm()).to.be.oneOf(['0', '1']);
238
- });
239
-
240
- it('should extract bidder correctly', () => {
241
- expect(getBidder({ bidder: 'pubmatic' })).to.equal('pubmatic');
242
- expect(getBidder({})).to.be.undefined;
243
- expect(getBidder(null)).to.be.undefined;
244
- expect(getBidder(undefined)).to.be.undefined;
245
- });
99
+ it('should return false if publisherId is not a string', () => {
100
+ const config = {
101
+ params: {
102
+ publisherId: 123,
103
+ profileId: 'test-profile-id'
104
+ }
105
+ };
106
+ const result = pubmaticRtdProvider.pubmaticSubmodule.init(config);
107
+ expect(result).to.be.false;
108
+ expect(logErrorStub.calledOnce).to.be.true;
109
+ expect(logErrorStub.firstCall.args[0]).to.equal(`${pubmaticRtdProvider.CONSTANTS.LOG_PRE_FIX} Publisher Id should be a string.`);
246
110
  });
247
111
 
248
- describe('getFloorsConfig', () => {
249
- let floorsData, profileConfigs;
250
- let sandbox;
251
- let logErrorStub;
252
-
253
- beforeEach(() => {
254
- sandbox = sinon.createSandbox();
255
- logErrorStub = sandbox.stub(utils, 'logError');
256
- floorsData = {
257
- "currency": "USD",
258
- "floorProvider": "PM",
259
- "floorsSchemaVersion": 2,
260
- "modelGroups": [
261
- {
262
- "modelVersion": "M_1",
263
- "modelWeight": 100,
264
- "schema": {
265
- "fields": [
266
- "domain"
267
- ]
268
- },
269
- "values": {
270
- "*": 2.00
271
- }
272
- }
273
- ],
274
- "skipRate": 0
275
- };
276
- profileConfigs = {
277
- 'plugins': {
278
- 'dynamicFloors': {
279
- 'enabled': true,
280
- 'config': {
281
- 'enforcement': {
282
- 'floorDeals': false,
283
- 'enforceJS': false
284
- },
285
- 'floorMin': 0.1111,
286
- 'skipRate': 11,
287
- 'defaultValues': {
288
- "*|*": 0.2
289
- }
290
- }
291
- }
292
- }
293
- }
294
- });
295
-
296
- afterEach(() => {
297
- sandbox.restore();
298
- });
299
-
300
- it('should return correct config structure', () => {
301
- const result = getFloorsConfig(floorsData, profileConfigs);
302
-
303
- expect(result.floors).to.be.an('object');
304
- expect(result.floors).to.be.an('object');
305
- expect(result.floors).to.have.property('enforcement');
306
- expect(result.floors.enforcement).to.have.property('floorDeals', false);
307
- expect(result.floors.enforcement).to.have.property('enforceJS', false);
308
- expect(result.floors).to.have.property('floorMin', 0.1111);
309
-
310
- // Verify the additionalSchemaFields structure
311
- expect(result.floors.additionalSchemaFields).to.have.all.keys([
312
- 'deviceType',
313
- 'timeOfDay',
314
- 'browser',
315
- 'os',
316
- 'country',
317
- 'utm',
318
- 'bidder'
319
- ]);
320
-
321
- Object.values(result.floors.additionalSchemaFields).forEach(field => {
322
- expect(field).to.be.a('function');
323
- });
324
- });
325
-
326
- it('should return undefined when plugin is disabled', () => {
327
- profileConfigs.plugins.dynamicFloors.enabled = false;
328
- const result = getFloorsConfig(floorsData, profileConfigs);
329
-
330
- expect(result).to.equal(undefined);
331
- });
332
-
333
- it('should initialise default values to empty object when not available', () => {
334
- profileConfigs.plugins.dynamicFloors.config.defaultValues = undefined;
335
- floorsData = undefined;
336
- const result = getFloorsConfig(floorsData, profileConfigs);
337
-
338
- expect(result.floors.data).to.have.property('currency', 'USD');
339
- expect(result.floors.data).to.have.property('skipRate', 11);
340
- expect(result.floors.data.schema).to.deep.equal(defaultValueTemplate.schema);
341
- expect(result.floors.data.value).to.deep.equal(defaultValueTemplate.value);
342
- });
343
-
344
- it('should replace skipRate from config to data when avaialble', () => {
345
- const result = getFloorsConfig(floorsData, profileConfigs);
346
-
347
- expect(result.floors.data).to.have.property('skipRate', 11);
348
- });
349
-
350
- it('should not replace skipRate from config to data when not avaialble', () => {
351
- delete profileConfigs.plugins.dynamicFloors.config.skipRate;
352
- const result = getFloorsConfig(floorsData, profileConfigs);
353
-
354
- expect(result.floors.data).to.have.property('skipRate', 0);
355
- });
356
-
357
- it('should maintain correct function references', () => {
358
- const result = getFloorsConfig(floorsData, profileConfigs);
359
-
360
- expect(result.floors.additionalSchemaFields.deviceType).to.equal(getDeviceType);
361
- expect(result.floors.additionalSchemaFields.timeOfDay).to.equal(getCurrentTimeOfDay);
362
- expect(result.floors.additionalSchemaFields.browser).to.equal(getBrowserType);
363
- expect(result.floors.additionalSchemaFields.os).to.equal(getOs);
364
- expect(result.floors.additionalSchemaFields.country).to.equal(getCountry);
365
- expect(result.floors.additionalSchemaFields.utm).to.equal(getUtm);
366
- expect(result.floors.additionalSchemaFields.bidder).to.equal(getBidder);
367
- });
368
-
369
- it('should log error when profileConfigs is not an object', () => {
370
- profileConfigs = 'invalid';
371
- const result = getFloorsConfig(floorsData, profileConfigs);
372
- expect(result).to.be.undefined;
373
- expect(logErrorStub.calledWith(sinon.match(/profileConfigs is not an object or is empty/))).to.be.true;
374
- });
112
+ it('should return false if profileId is missing', () => {
113
+ const config = {
114
+ params: {
115
+ publisherId: 'test-publisher-id'
116
+ }
117
+ };
118
+ const result = pubmaticRtdProvider.pubmaticSubmodule.init(config);
119
+ expect(result).to.be.false;
120
+ expect(logErrorStub.calledOnce).to.be.true;
121
+ expect(logErrorStub.firstCall.args[0]).to.equal(`${pubmaticRtdProvider.CONSTANTS.LOG_PRE_FIX} Missing profile Id.`);
375
122
  });
376
123
 
377
- describe('fetchData for configs', () => {
378
- let logErrorStub;
379
- let fetchStub;
380
- let confStub;
381
-
382
- beforeEach(() => {
383
- logErrorStub = sandbox.stub(utils, 'logError');
384
- fetchStub = sandbox.stub(window, 'fetch');
385
- confStub = sandbox.stub(conf, 'setConfig');
386
- });
387
-
388
- afterEach(() => {
389
- sandbox.restore();
390
- });
391
-
392
- it('should successfully fetch profile configs', async () => {
393
- const mockApiResponse = {
394
- "profileName": "profie name",
395
- "desc": "description",
396
- "plugins": {
397
- "dynamicFloors": {
398
- "enabled": false
399
- }
400
- }
401
- };
402
-
403
- fetchStub.resolves(new Response(JSON.stringify(mockApiResponse), { status: 200 }));
404
-
405
- const result = await fetchData('1234', '123', 'CONFIGS');
406
- expect(result).to.deep.equal(mockApiResponse);
407
- });
408
-
409
- it('should log error when JSON parsing fails', async () => {
410
- fetchStub.resolves(new Response('Invalid JSON', { status: 200 }));
411
-
412
- await fetchData('1234', '123', 'CONFIGS');
413
- expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*CONFIGS/))).to.be.true;
414
- });
415
-
416
- it('should log error when response is not ok', async () => {
417
- fetchStub.resolves(new Response(null, { status: 500 }));
418
-
419
- await fetchData('1234', '123', 'CONFIGS');
420
- expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*CONFIGS/))).to.be.true;
421
- });
422
-
423
- it('should log error on network failure', async () => {
424
- fetchStub.rejects(new Error('Network Error'));
425
-
426
- await fetchData('1234', '123', 'CONFIGS');
427
- expect(logErrorStub.called).to.be.true;
428
- expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*CONFIGS/))).to.be.true;
429
- });
124
+ it('should return false if profileId is not a string', () => {
125
+ const config = {
126
+ params: {
127
+ publisherId: 'test-publisher-id',
128
+ profileId: 345
129
+ }
130
+ };
131
+ const result = pubmaticRtdProvider.pubmaticSubmodule.init(config);
132
+ expect(result).to.be.false;
133
+ expect(logErrorStub.calledOnce).to.be.true;
134
+ expect(logErrorStub.firstCall.args[0]).to.equal(`${pubmaticRtdProvider.CONSTANTS.LOG_PRE_FIX} Profile Id should be a string.`);
430
135
  });
431
136
 
432
- describe('fetchData for floors', () => {
433
- let logErrorStub;
434
- let fetchStub;
435
- let confStub;
436
-
437
- beforeEach(() => {
438
- logErrorStub = sandbox.stub(utils, 'logError');
439
- fetchStub = sandbox.stub(window, 'fetch');
440
- confStub = sandbox.stub(conf, 'setConfig');
441
- global._country = undefined;
442
- });
443
-
444
- afterEach(() => {
445
- sandbox.restore();
446
- });
447
-
448
- it('should successfully fetch and parse floor rules', async () => {
449
- const mockApiResponse = {
450
- data: {
451
- currency: 'USD',
452
- modelGroups: [],
453
- values: {}
454
- }
455
- };
137
+ it('should initialize successfully with valid config', async () => {
138
+ configJsonManagerStub.fetchConfig.resolves(true);
139
+ pluginManagerStub.initialize.resolves();
456
140
 
457
- fetchStub.resolves(new Response(JSON.stringify(mockApiResponse), { status: 200, headers: { 'country_code': 'US' } }));
141
+ const result = pubmaticRtdProvider.pubmaticSubmodule.init(validConfig);
142
+ expect(result).to.be.true;
143
+ expect(configJsonManagerStub.fetchConfig.calledOnce).to.be.true;
144
+ expect(configJsonManagerStub.fetchConfig.firstCall.args[0]).to.equal('test-publisher-id');
145
+ expect(configJsonManagerStub.fetchConfig.firstCall.args[1]).to.equal('test-profile-id');
458
146
 
459
- const result = await fetchData('1234', '123', 'FLOORS');
460
- expect(result).to.deep.equal(mockApiResponse);
461
- expect(_country).to.equal('US');
462
- });
463
-
464
- it('should correctly extract the first unique country code from response headers', async () => {
465
- fetchStub.resolves(new Response(JSON.stringify({}), {
466
- status: 200,
467
- headers: { 'country_code': 'US,IN,US' }
468
- }));
469
-
470
- await fetchData('1234', '123', 'FLOORS');
471
- expect(_country).to.equal('US');
472
- });
473
-
474
- it('should set _country to undefined if country_code header is missing', async () => {
475
- fetchStub.resolves(new Response(JSON.stringify({}), {
476
- status: 200
477
- }));
478
-
479
- await fetchData('1234', '123', 'FLOORS');
480
- expect(_country).to.be.undefined;
481
- });
482
-
483
- it('should log error when JSON parsing fails', async () => {
484
- fetchStub.resolves(new Response('Invalid JSON', { status: 200 }));
147
+ // Wait for promise to resolve
148
+ await pubmaticRtdProvider.getYmConfigPromise();
149
+ expect(pluginManagerStub.initialize.calledOnce).to.be.true;
150
+ expect(pluginManagerStub.initialize.firstCall.args[0]).to.equal(pubmaticRtdProvider.configJsonManager);
151
+ });
485
152
 
486
- await fetchData('1234', '123', 'FLOORS');
487
- expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*FLOORS/))).to.be.true;
488
- });
153
+ it('should handle config fetch error gracefully', async () => {
154
+ configJsonManagerStub.fetchConfig.resolves(false);
489
155
 
490
- it('should log error when response is not ok', async () => {
491
- fetchStub.resolves(new Response(null, { status: 500 }));
156
+ const result = pubmaticRtdProvider.pubmaticSubmodule.init(validConfig);
157
+ expect(result).to.be.true;
492
158
 
493
- await fetchData('1234', '123', 'FLOORS');
494
- expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*FLOORS/))).to.be.true;
495
- });
159
+ try {
160
+ await pubmaticRtdProvider.getYmConfigPromise();
161
+ } catch (e) {
162
+ expect(e.message).to.equal('Failed to fetch configuration');
163
+ }
496
164
 
497
- it('should log error on network failure', async () => {
498
- fetchStub.rejects(new Error('Network Error'));
499
-
500
- await fetchData('1234', '123', 'FLOORS');
501
- expect(logErrorStub.called).to.be.true;
502
- expect(logErrorStub.calledWith(sinon.match(/Error while fetching\s*FLOORS/))).to.be.true;
503
- });
165
+ expect(pluginManagerStub.initialize.called).to.be.false;
504
166
  });
505
-
506
- describe('getBidRequestData', function () {
507
- let callback, continueAuctionStub, mergeDeepStub, logErrorStub;
508
-
509
- const reqBidsConfigObj = {
510
- adUnits: [{ code: 'ad-slot-code-0' }],
511
- auctionId: 'auction-id-0',
512
- ortb2Fragments: {
513
- bidder: {
514
- user: {
515
- ext: {
516
- ctr: 'US',
517
- }
518
- }
519
- }
520
- }
521
- };
522
-
523
- const ortb2 = {
524
- user: {
525
- ext: {
526
- ctr: 'US',
527
- }
528
- }
167
+ });
168
+
169
+ describe('getBidRequestData', () => {
170
+ const reqBidsConfigObj = {
171
+ ortb2Fragments: {
172
+ bidder: {}
173
+ },
174
+ adUnits: [
175
+ {
176
+ code: 'div-1',
177
+ bids: [{ bidder: 'pubmatic', params: {} }]
178
+ },
179
+ {
180
+ code: 'div-2',
181
+ bids: [{ bidder: 'pubmatic', params: {} }]
529
182
  }
183
+ ]
184
+ };
185
+ let callback;
530
186
 
531
- const hookConfig = {
532
- reqBidsConfigObj,
533
- context: this,
534
- nextFn: () => true,
535
- haveExited: false,
536
- timer: null
537
- };
538
-
539
- beforeEach(() => {
540
- callback = sinon.spy();
541
- continueAuctionStub = sandbox.stub(priceFloors, 'continueAuction');
542
- logErrorStub = sandbox.stub(utils, 'logError');
543
-
544
- global.configMergedPromise = Promise.resolve();
545
- });
546
-
547
- afterEach(() => {
548
- sandbox.restore(); // Restore all stubs/spies
549
- });
550
-
551
- it('should call continueAuction with correct hookConfig', async function () {
552
- configMerged();
553
- await pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback);
554
-
555
- expect(continueAuctionStub.called).to.be.true;
556
- expect(continueAuctionStub.firstCall.args[0]).to.have.property('reqBidsConfigObj', reqBidsConfigObj);
557
- expect(continueAuctionStub.firstCall.args[0]).to.have.property('haveExited', false);
558
- });
559
-
560
- // it('should merge country data into ortb2Fragments.bidder', async function () {
561
- // configMerged();
562
- // global._country = 'US';
563
- // pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback);
187
+ beforeEach(() => {
188
+ callback = sinon.stub();
189
+ pubmaticRtdProvider.setYmConfigPromise(Promise.resolve());
190
+ });
564
191
 
565
- // expect(reqBidsConfigObj.ortb2Fragments.bidder).to.have.property('pubmatic');
566
- // // expect(reqBidsConfigObj.ortb2Fragments.bidder.pubmatic.user.ext.ctr).to.equal('US');
567
- // });
192
+ it('should call pluginManager executeHook with correct parameters', (done) => {
193
+ pluginManagerStub.executeHook.resolves();
568
194
 
569
- it('should call callback once after execution', async function () {
570
- configMerged();
571
- await pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback);
195
+ pubmaticRtdProvider.pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback);
572
196
 
573
- expect(callback.called).to.be.true;
574
- });
197
+ setTimeout(() => {
198
+ expect(pluginManagerStub.executeHook.calledOnce).to.be.true;
199
+ expect(pluginManagerStub.executeHook.firstCall.args[0]).to.equal('processBidRequest');
200
+ expect(pluginManagerStub.executeHook.firstCall.args[1]).to.deep.equal(reqBidsConfigObj);
201
+ expect(callback.calledOnce).to.be.true;
202
+ done();
203
+ }, 0);
575
204
  });
576
205
 
577
- describe('withTimeout', function () {
578
- it('should resolve with the original promise value if it resolves before the timeout', async function () {
579
- const promise = new Promise((resolve) => setTimeout(() => resolve('success'), 50));
580
- const result = await withTimeout(promise, 100);
581
- expect(result).to.equal('success');
582
- });
206
+ it('should add country information to ORTB2', (done) => {
207
+ pluginManagerStub.executeHook.resolves();
583
208
 
584
- it('should resolve with undefined if the promise takes longer than the timeout', async function () {
585
- const promise = new Promise((resolve) => setTimeout(() => resolve('success'), 200));
586
- const result = await withTimeout(promise, 100);
587
- expect(result).to.be.undefined;
588
- });
209
+ pubmaticRtdProvider.pubmaticSubmodule.getBidRequestData(reqBidsConfigObj, callback);
589
210
 
590
- it('should properly handle rejected promises', async function () {
591
- const promise = new Promise((resolve, reject) => setTimeout(() => reject(new Error('Failure')), 50));
592
- try {
593
- await withTimeout(promise, 100);
594
- } catch (error) {
595
- expect(error.message).to.equal('Failure');
211
+ setTimeout(() => {
212
+ expect(reqBidsConfigObj.ortb2Fragments.bidder[pubmaticRtdProvider.CONSTANTS.SUBMODULE_NAME]).to.deep.equal({
213
+ user: {
214
+ ext: {
215
+ ctr: 'IN'
596
216
  }
217
+ }
597
218
  });
598
-
599
- it('should resolve with undefined if the original promise is rejected but times out first', async function () {
600
- const promise = new Promise((resolve, reject) => setTimeout(() => reject(new Error('Failure')), 200));
601
- const result = await withTimeout(promise, 100);
602
- expect(result).to.be.undefined;
603
- });
604
-
605
- it('should clear the timeout when the promise resolves before the timeout', async function () {
606
- const clock = sinon.useFakeTimers();
607
- const clearTimeoutSpy = sinon.spy(global, 'clearTimeout');
608
-
609
- const promise = new Promise((resolve) => setTimeout(() => resolve('success'), 50));
610
- const resultPromise = withTimeout(promise, 100);
611
-
612
- clock.tick(50);
613
- await resultPromise;
614
-
615
- expect(clearTimeoutSpy.called).to.be.true;
616
-
617
- clearTimeoutSpy.restore();
618
- clock.restore();
619
- });
219
+ done();
220
+ }, 0);
221
+ });
222
+ });
223
+
224
+ describe('getTargetingData', () => {
225
+ const adUnitCodes = ['div-1', 'div-2'];
226
+ const config = {
227
+ params: {
228
+ publisherId: 'test-publisher-id',
229
+ profileId: 'test-profile-id'
230
+ }
231
+ };
232
+ const userConsent = {};
233
+ const auction = {};
234
+ const unifiedPricingRule = {
235
+ 'div-1': { key1: 'value1' },
236
+ 'div-2': { key2: 'value2' }
237
+ };
238
+
239
+ it('should call pluginManager executeHook with correct parameters', () => {
240
+ pluginManagerStub.executeHook.returns(unifiedPricingRule);
241
+
242
+ const result = pubmaticRtdProvider.getTargetingData(adUnitCodes, config, userConsent, auction);
243
+
244
+ expect(pluginManagerStub.executeHook.calledOnce).to.be.true;
245
+ expect(pluginManagerStub.executeHook.firstCall.args[0]).to.equal('getTargeting');
246
+ expect(pluginManagerStub.executeHook.firstCall.args[1]).to.equal(adUnitCodes);
247
+ expect(pluginManagerStub.executeHook.firstCall.args[2]).to.equal(config);
248
+ expect(pluginManagerStub.executeHook.firstCall.args[3]).to.equal(userConsent);
249
+ expect(pluginManagerStub.executeHook.firstCall.args[4]).to.equal(auction);
250
+ expect(result).to.equal(unifiedPricingRule);
620
251
  });
621
252
 
622
- describe('getTargetingData', function () {
623
- let sandbox;
624
- let logInfoStub;
625
-
626
- beforeEach(() => {
627
- sandbox = sinon.createSandbox();
628
- logInfoStub = sandbox.stub(utils, 'logInfo');
629
- });
630
-
631
- afterEach(() => {
632
- sandbox.restore();
633
- });
634
-
635
- it('should return empty object when profileConfigs is undefined', function () {
636
- // Store the original value to restore it later
637
- const originalProfileConfigs = getProfileConfigs();
638
- // Set profileConfigs to undefined
639
- setProfileConfigs(undefined);
640
-
641
- const adUnitCodes = ['test-ad-unit'];
642
- const config = {};
643
- const userConsent = {};
644
- const auction = {};
645
-
646
- const result = getTargetingData(adUnitCodes, config, userConsent, auction);
647
-
648
- // Restore the original value
649
- setProfileConfigs(originalProfileConfigs);
650
-
651
- expect(result).to.deep.equal({});
652
- expect(logInfoStub.calledWith(sinon.match(/pmTargetingKeys is disabled or profileConfigs is undefined/))).to.be.true;
653
- });
654
-
655
- it('should return empty object when pmTargetingKeys.enabled is false', function () {
656
- // Create profileConfigs with pmTargetingKeys.enabled set to false
657
- const profileConfigsMock = {
658
- plugins: {
659
- dynamicFloors: {
660
- pmTargetingKeys: {
661
- enabled: false
662
- }
663
- }
664
- }
665
- };
666
-
667
- // Store the original value to restore it later
668
- const originalProfileConfigs = getProfileConfigs();
669
- // Set profileConfigs to our mock
670
- setProfileConfigs(profileConfigsMock);
671
-
672
- const adUnitCodes = ['test-ad-unit'];
673
- const config = {};
674
- const userConsent = {};
675
- const auction = {};
676
-
677
- const result = getTargetingData(adUnitCodes, config, userConsent, auction);
678
-
679
- // Restore the original value
680
- setProfileConfigs(originalProfileConfigs);
681
-
682
- expect(result).to.deep.equal({});
683
- expect(logInfoStub.calledWith(sinon.match(/pmTargetingKeys is disabled or profileConfigs is undefined/))).to.be.true;
684
- });
685
-
686
- it('should set pm_ym_flrs to 0 when no RTD floor is applied to any bid', function () {
687
- // Create profileConfigs with pmTargetingKeys.enabled set to true
688
- const profileConfigsMock = {
689
- plugins: {
690
- dynamicFloors: {
691
- pmTargetingKeys: {
692
- enabled: true
693
- }
694
- }
695
- }
696
- };
697
-
698
- // Store the original value to restore it later
699
- const originalProfileConfigs = getProfileConfigs();
700
- // Set profileConfigs to our mock
701
- setProfileConfigs(profileConfigsMock);
702
-
703
- // Create multiple ad unit codes to test
704
- const adUnitCodes = ['ad-unit-1', 'ad-unit-2'];
705
- const config = {};
706
- const userConsent = {};
707
-
708
- // Create a mock auction object with bids that don't have RTD floors applied
709
- // This tests several scenarios where RTD floor is not applied:
710
- // 1. No floorData
711
- // 2. floorData but floorProvider is not 'PM'
712
- // 3. floorData with floorProvider 'PM' but skipped is true
713
- const auction = {
714
- adUnits: [
715
- {
716
- code: 'ad-unit-1',
717
- bids: [
718
- { bidder: 'bidderA' }, // No floorData
719
- { bidder: 'bidderB', floorData: { floorProvider: 'OTHER' } } // Not PM provider
720
- ]
721
- },
722
- {
723
- code: 'ad-unit-2',
724
- bids: [
725
- { bidder: 'bidderC', floorData: { floorProvider: 'PM', skipped: true } } // PM but skipped
726
- ]
727
- }
728
- ],
729
- bidsReceived: [
730
- { adUnitCode: 'ad-unit-1', bidder: 'bidderA' },
731
- { adUnitCode: 'ad-unit-1', bidder: 'bidderB', floorData: { floorProvider: 'OTHER' } },
732
- { adUnitCode: 'ad-unit-2', bidder: 'bidderC', floorData: { floorProvider: 'PM', skipped: true } }
733
- ]
734
- };
735
-
736
- const result = getTargetingData(adUnitCodes, config, userConsent, auction);
737
-
738
- // Restore the original value
739
- setProfileConfigs(originalProfileConfigs);
740
-
741
- // Verify that for each ad unit code, only pm_ym_flrs is set to 0
742
- expect(result['ad-unit-1']).to.have.property('pm_ym_flrs', 0);
743
- expect(result['ad-unit-2']).to.have.property('pm_ym_flrs', 0);
744
- });
745
-
746
- it('should set pm_ym_flrs to 1 when RTD floor is applied to a bid', function () {
747
- // Create profileConfigs with pmTargetingKeys.enabled set to true
748
- const profileConfigsMock = {
749
- plugins: {
750
- dynamicFloors: {
751
- pmTargetingKeys: {
752
- enabled: true
753
- }
754
- }
755
- }
756
- };
757
-
758
- // Store the original value to restore it later
759
- const originalProfileConfigs = getProfileConfigs();
760
- // Set profileConfigs to our mock
761
- setProfileConfigs(profileConfigsMock);
762
-
763
- // Create multiple ad unit codes to test
764
- const adUnitCodes = ['ad-unit-1', 'ad-unit-2'];
765
- const config = {};
766
- const userConsent = {};
767
-
768
- // Create a mock auction object with bids that have RTD floors applied
769
- const auction = {
770
- adUnits: [
771
- {
772
- code: 'ad-unit-1',
773
- bids: [
774
- { bidder: 'bidderA', floorData: { floorProvider: 'PM', skipped: false } },
775
- { bidder: 'bidderB', floorData: { floorProvider: 'PM', skipped: false } }
776
- ]
777
- },
778
- {
779
- code: 'ad-unit-2',
780
- bids: [
781
- { bidder: 'bidderC', floorData: { floorProvider: 'PM', skipped: false } },
782
- { bidder: 'bidderD', floorData: { floorProvider: 'PM', skipped: false } }
783
- ]
784
- }
785
- ],
786
- bidsReceived: [
787
- { adUnitCode: 'ad-unit-1', bidder: 'bidderA', floorData: { floorProvider: 'PM', skipped: false } },
788
- { adUnitCode: 'ad-unit-1', bidder: 'bidderB', floorData: { floorProvider: 'PM', skipped: false } },
789
- { adUnitCode: 'ad-unit-2', bidder: 'bidderC', floorData: { floorProvider: 'PM', skipped: false } },
790
- { adUnitCode: 'ad-unit-2', bidder: 'bidderD', floorData: { floorProvider: 'PM', skipped: false } }
791
- ]
792
- };
793
-
794
- const result = getTargetingData(adUnitCodes, config, userConsent, auction);
795
-
796
- // Restore the original value
797
- setProfileConfigs(originalProfileConfigs);
798
-
799
- // Verify that for each ad unit code, pm_ym_flrs is set to 1
800
- expect(result['ad-unit-1']).to.have.property('pm_ym_flrs', 1);
801
- expect(result['ad-unit-2']).to.have.property('pm_ym_flrs', 1);
802
- });
803
-
804
- it('should set different targeting keys for winning bids (status 1) and floored bids (status 2)', function () {
805
- // Create profileConfigs with pmTargetingKeys.enabled set to true
806
- const profileConfigsMock = {
807
- plugins: {
808
- dynamicFloors: {
809
- pmTargetingKeys: {
810
- enabled: true
811
- }
812
- }
813
- }
814
- };
815
-
816
- const mockPbjs = {
817
- getHighestCpmBids: (adUnitCode) => {
818
- // For div2, return a winning bid
819
- if (adUnitCode === 'div2') {
820
- return [{
821
- adUnitCode: 'div2',
822
- cpm: 5.5,
823
- floorData: {
824
- floorValue: 5.0,
825
- floorProvider: 'PM'
826
- }
827
- }];
828
- }
829
- // For all other ad units, return empty array (no winning bids)
830
- return [];
831
- }
832
- };
833
-
834
- // Stub getGlobal to return our mock object
835
- const getGlobalStub = sandbox.stub(prebidGlobal, 'getGlobal').returns(mockPbjs);
836
-
837
- // Store the original value to restore it later
838
- const originalProfileConfigs = getProfileConfigs();
839
- // Set profileConfigs to our mock
840
- setProfileConfigs(profileConfigsMock);
841
-
842
- // Create ad unit codes to test
843
- const adUnitCodes = ['div2', 'div3'];
844
- const config = {};
845
- const userConsent = {};
846
-
847
- // Create a mock auction object with bids that have RTD floors applied
848
- const auction = {
849
- adUnits: [
850
- { code: "div2", bids: [{ floorData: { floorProvider: "PM", skipped: false } }] },
851
- { code: "div3", bids: [{ floorData: { floorProvider: "PM", skipped: false } }] }
852
- ],
853
- adUnitCodes: ["div2", "div3"],
854
- bidsReceived: [[
855
- {
856
- "bidderCode": "appnexus",
857
- "auctionId": "a262767c-5499-4e98-b694-af36dbcb50f6",
858
- "mediaType": "banner",
859
- "source": "client",
860
- "cpm": 5.5,
861
- "adUnitCode": "div2",
862
- "adapterCode": "appnexus",
863
- "originalCpm": 5.5,
864
- "floorData": {
865
- "floorValue": 5,
866
- "floorRule": "banner|*|*|div2|*|*|*|*|*",
867
- "floorRuleValue": 5,
868
- "floorCurrency": "USD"
869
- },
870
- "bidder": "appnexus",
871
- }
872
- ]],
873
- bidsRejected: [
874
- {adUnitCode: "div3", bidder: "pubmatic", cpm: 20, floorData: { floorValue: 40 }, rejectionReason: "Bid does not meet price floor" }]
875
- };
876
-
877
- const result = getTargetingData(adUnitCodes, config, userConsent, auction);
878
-
879
- // Restore the original value
880
- setProfileConfigs(originalProfileConfigs);
881
- // Check the test results
882
- expect(result['div2']).to.have.property('pm_ym_flrs', 1);
883
- expect(result['div2']).to.have.property('pm_ym_flrv', '5.50');
884
- expect(result['div2']).to.have.property('pm_ym_bid_s', 1);
885
-
886
- expect(result['div3']).to.have.property('pm_ym_flrs', 1);
887
- expect(result['div3']).to.have.property('pm_ym_flrv', '32.00');
888
- expect(result['div3']).to.have.property('pm_ym_bid_s', 2);
889
-
890
- getGlobalStub.restore();
891
- });
892
-
893
- describe('should handle the no bid scenario correctly', function () {
894
- it('should handle no bid scenario correctly', function () {
895
- // Create profileConfigs with pmTargetingKeys enabled
896
- const profileConfigsMock = {
897
- plugins: {
898
- dynamicFloors: {
899
- pmTargetingKeys: {
900
- enabled: true,
901
- multiplier: {
902
- nobid: 1.2 // Explicit nobid multiplier
903
- }
904
- }
905
- }
906
- }
907
- };
908
-
909
- // Store the original value to restore it later
910
- const originalProfileConfigs = getProfileConfigs();
911
- // Set profileConfigs to our mock
912
- setProfileConfigs(profileConfigsMock);
913
-
914
- // Create ad unit codes to test
915
- const adUnitCodes = ['Div2'];
916
- const config = {};
917
- const userConsent = {};
918
-
919
- // Create a mock auction with no bids but with RTD floor applied
920
- // For this test, we'll observe what the function actually does rather than
921
- // try to match specific multiplier values
922
- const auction = {
923
- "auctionId": "faf0b7d0-3a12-4774-826a-3d56033d9a74",
924
- "auctionStatus": "completed",
925
- "adUnits": [
926
- {
927
- "code": "Div2",
928
- "sizes": [[300, 250]],
929
- "mediaTypes": {
930
- "banner": { "sizes": [[300, 250]] }
931
- },
932
- "bids": [
933
- {
934
- "bidder": "pubmatic",
935
- "params": {
936
- "publisherId": "164392",
937
- "adSlot": "/4374asd3431/DMDemo1@160x600"
938
- },
939
- "floorData": {
940
- "floorProvider": "PM"
941
- }
942
- }
943
- ]
944
- }
945
- ],
946
- "adUnitCodes": ["Div2"],
947
- "bidderRequests": [
948
- {
949
- "bidderCode": "pubmatic",
950
- "auctionId": "faf0b7d0-3a12-4774-826a-3d56033d9a74",
951
- "bids": [
952
- {
953
- "bidder": "pubmatic",
954
- "adUnitCode": "Div2",
955
- "floorData": {
956
- "floorProvider": "PM"
957
- },
958
- "mediaTypes": {
959
- "banner": { "sizes": [[300, 250]] }
960
- },
961
- "getFloor": () => { return { floor: 0.05, currency: 'USD' }; }
962
- }
963
- ]
964
- }
965
- ],
966
- "noBids": [
967
- {
968
- "bidder": "pubmatic",
969
- "adUnitCode": "Div2",
970
- "floorData": {
971
- "floorProvider": "PM",
972
- "floorMin": 0.05
973
- }
974
- }
975
- ],
976
- "bidsReceived": [],
977
- "bidsRejected": [],
978
- "winningBids": []
979
- };
980
-
981
- const result = getTargetingData(adUnitCodes, config, userConsent, auction);
982
-
983
- // Restore the original value
984
- setProfileConfigs(originalProfileConfigs);
985
-
986
- // Verify correct values for no bid scenario
987
- expect(result['Div2']['pm_ym_flrs']).to.equal(1); // RTD floor was applied
988
- expect(result['Div2']['pm_ym_bid_s']).to.equal(0); // NOBID status
989
-
990
- // Since finding floor values from bidder requests depends on implementation details
991
- // we'll just verify the type rather than specific value
992
- expect(result['Div2']['pm_ym_flrv']).to.be.a('string');
993
- });
994
-
995
- it('should handle no bid scenario correctly for single ad unit multiple size scenarios', function () {
996
- // Create profileConfigs with pmTargetingKeys enabled
997
- const profileConfigsMock = {
998
- plugins: {
999
- dynamicFloors: {
1000
- pmTargetingKeys: {
1001
- enabled: true,
1002
- multiplier: {
1003
- nobid: 1.2 // Explicit nobid multiplier
1004
- }
1005
- }
1006
- }
1007
- }
1008
- };
1009
-
1010
- // Store the original value to restore it later
1011
- const originalProfileConfigs = getProfileConfigs();
1012
- // Set profileConfigs to our mock
1013
- setProfileConfigs(profileConfigsMock);
1014
-
1015
- // Create ad unit codes to test
1016
- const adUnitCodes = ['Div2'];
1017
- const config = {};
1018
- const userConsent = {};
1019
-
1020
- // Create a mock auction with no bids but with RTD floor applied
1021
- // For this test, we'll observe what the function actually does rather than
1022
- // try to match specific multiplier values
1023
- const auction = {
1024
- "auctionId": "faf0b7d0-3a12-4774-826a-3d56033d9a74",
1025
- "auctionStatus": "completed",
1026
- "adUnits": [
1027
- {
1028
- "code": "Div2",
1029
- "sizes": [[300, 250]],
1030
- "mediaTypes": {"banner": { "sizes": [[300, 250]] }},
1031
- "bids": [
1032
- {
1033
- "bidder": "pubmatic",
1034
- "params": {
1035
- "publisherId": "164392",
1036
- "adSlot": "/4374asd3431/DMDemo1@160x600"
1037
- },
1038
- "floorData": {
1039
- "floorProvider": "PM"
1040
- }
1041
- }
1042
- ]
1043
- }
1044
- ],
1045
- "adUnitCodes": [ "Div2"],
1046
- "bidderRequests": [
1047
- {
1048
- "bidderCode": "pubmatic",
1049
- "auctionId": "faf0b7d0-3a12-4774-826a-3d56033d9a74",
1050
- "bids": [
1051
- {
1052
- "bidder": "pubmatic",
1053
- "adUnitCode": "Div2",
1054
- "floorData": {
1055
- "floorProvider": "PM"
1056
- },
1057
- "mediaTypes": {
1058
- "banner": { "sizes": [[300, 250]] }
1059
- },
1060
- "getFloor": () => { return { floor: 5, currency: 'USD' }; }
1061
- }
1062
- ]
1063
- }
1064
- ],
1065
- "noBids": [
1066
- {
1067
- "bidder": "pubmatic",
1068
- "adUnitCode": "Div2",
1069
- "floorData": {
1070
- "floorProvider": "PM",
1071
- "floorMin": 0.05
1072
- }
1073
- }
1074
- ],
1075
- "bidsReceived": [],
1076
- "bidsRejected": [],
1077
- "winningBids": []
1078
- };
1079
-
1080
- const result = getTargetingData(adUnitCodes, config, userConsent, auction);
1081
-
1082
- // Restore the original value
1083
- setProfileConfigs(originalProfileConfigs);
1084
-
1085
- // Verify correct values for no bid scenario
1086
- expect(result['Div2']['pm_ym_flrs']).to.equal(1); // RTD floor was applied
1087
- expect(result['Div2']['pm_ym_bid_s']).to.equal(0); // NOBID status
1088
-
1089
- // Since finding floor values from bidder requests depends on implementation details
1090
- // we'll just verify the type rather than specific value
1091
- expect(result['Div2']['pm_ym_flrv']).to.be.a('string');
1092
- expect(result['Div2']['pm_ym_flrv']).to.equal("6.00");
1093
- });
1094
-
1095
- it('should handle no bid scenario correctly for multi-format ad unit with different floors', function () {
1096
- // Create profileConfigs with pmTargetingKeys enabled
1097
- const profileConfigsMock = {
1098
- plugins: {
1099
- dynamicFloors: {
1100
- pmTargetingKeys: {
1101
- enabled: true,
1102
- multiplier: {
1103
- nobid: 1.2 // Explicit nobid multiplier
1104
- }
1105
- }
1106
- }
1107
- }
1108
- };
1109
-
1110
- // Store the original value to restore it later
1111
- const originalProfileConfigs = getProfileConfigs();
1112
- // Set profileConfigs to our mock
1113
- setProfileConfigs(profileConfigsMock);
253
+ it('should return empty object if no targeting data', () => {
254
+ pluginManagerStub.executeHook.returns({});
1114
255
 
1115
- // Create ad unit codes to test
1116
- const adUnitCodes = ['multiFormatDiv'];
1117
- const config = {};
1118
- const userConsent = {};
256
+ const result = pubmaticRtdProvider.getTargetingData(adUnitCodes, config, userConsent, auction);
257
+ expect(result).to.deep.equal({});
258
+ });
259
+ });
1119
260
 
1120
- // Mock getFloor implementation that returns different floors for different media types
1121
- const mockGetFloor = (params) => {
1122
- const floors = {
1123
- 'banner': 0.50, // Higher floor for banner
1124
- 'video': 0.25 // Lower floor for video
1125
- };
261
+ describe('ConfigJsonManager', () => {
262
+ let configManager;
1126
263
 
1127
- return {
1128
- floor: floors[params.mediaType] || 0.10,
1129
- currency: 'USD'
1130
- };
1131
- };
264
+ beforeEach(() => {
265
+ configManager = pubmaticRtdProvider.ConfigJsonManager();
266
+ });
1132
267
 
1133
- // Create a mock auction with a multi-format ad unit (banner + video)
1134
- const auction = {
1135
- "auctionId": "multi-format-test-auction",
1136
- "auctionStatus": "completed",
1137
- "adUnits": [
1138
- {
1139
- "code": "multiFormatDiv",
1140
- "mediaTypes": {
1141
- "banner": {
1142
- "sizes": [[300, 250], [300, 600]]
1143
- },
1144
- "video": {
1145
- "playerSize": [[640, 480]],
1146
- "context": "instream"
1147
- }
1148
- },
1149
- "bids": [
1150
- {
1151
- "bidder": "pubmatic",
1152
- "params": {
1153
- "publisherId": "test-publisher",
1154
- "adSlot": "/test/slot"
1155
- },
1156
- "floorData": {
1157
- "floorProvider": "PM"
1158
- }
1159
- }
1160
- ]
1161
- }
1162
- ],
1163
- "adUnitCodes": ["multiFormatDiv"],
1164
- "bidderRequests": [
1165
- {
1166
- "bidderCode": "pubmatic",
1167
- "auctionId": "multi-format-test-auction",
1168
- "bids": [
1169
- {
1170
- "bidder": "pubmatic",
1171
- "adUnitCode": "multiFormatDiv",
1172
- "mediaTypes": {
1173
- "banner": {
1174
- "sizes": [[300, 250], [300, 600]]
1175
- },
1176
- "video": {
1177
- "playerSize": [[640, 480]],
1178
- "context": "instream"
1179
- }
1180
- },
1181
- "floorData": {
1182
- "floorProvider": "PM"
1183
- },
1184
- "getFloor": mockGetFloor
1185
- }
1186
- ]
1187
- }
1188
- ],
1189
- "noBids": [
1190
- {
1191
- "bidder": "pubmatic",
1192
- "adUnitCode": "multiFormatDiv",
1193
- "floorData": {
1194
- "floorProvider": "PM"
1195
- }
1196
- }
1197
- ],
1198
- "bidsReceived": [],
1199
- "bidsRejected": [],
1200
- "winningBids": []
1201
- };
268
+ it('should fetch config successfully', async () => {
269
+ const mockResponse = {
270
+ ok: true,
271
+ headers: {
272
+ get: sinon.stub().withArgs('country_code').returns('US')
273
+ },
274
+ json: sinon.stub().resolves({ plugins: { test: { enabled: true } } })
275
+ };
1202
276
 
1203
- // Create a spy to monitor the getFloor calls
1204
- const getFloorSpy = sinon.spy(auction.bidderRequests[0].bids[0], "getFloor");
277
+ fetchStub.resolves(mockResponse);
1205
278
 
1206
- // Run the targeting function
1207
- const result = getTargetingData(adUnitCodes, config, userConsent, auction);
279
+ const result = await configManager.fetchConfig('pub-123', 'profile-456');
1208
280
 
1209
- // Restore the original value
1210
- setProfileConfigs(originalProfileConfigs);
281
+ expect(result).to.be.true;
282
+ expect(fetchStub.calledOnce).to.be.true;
283
+ expect(fetchStub.firstCall.args[0]).to.equal(`${pubmaticRtdProvider.CONSTANTS.ENDPOINTS.BASEURL}/pub-123/profile-456/${pubmaticRtdProvider.CONSTANTS.ENDPOINTS.CONFIGS}`);
284
+ expect(configManager.country).to.equal('US');
285
+ });
1211
286
 
1212
- // Verify correct values for no bid scenario
1213
- expect(result['multiFormatDiv']['pm_ym_flrs']).to.equal(1); // RTD floor was applied
1214
- expect(result['multiFormatDiv']['pm_ym_bid_s']).to.equal(0); // NOBID status
287
+ it('should handle fetch errors', async () => {
288
+ fetchStub.rejects(new Error('Network error'));
1215
289
 
1216
- // Verify that getFloor was called with both media types
1217
- expect(getFloorSpy.called).to.be.true;
1218
- let bannerCallFound = false;
1219
- let videoCallFound = false;
290
+ const result = await configManager.fetchConfig('pub-123', 'profile-456');
1220
291
 
1221
- getFloorSpy.getCalls().forEach(call => {
1222
- const args = call.args[0];
1223
- if (args.mediaType === 'banner') bannerCallFound = true;
1224
- if (args.mediaType === 'video') videoCallFound = true;
1225
- });
292
+ expect(result).to.be.null;
293
+ expect(logErrorStub.calledOnce).to.be.true;
294
+ expect(logErrorStub.firstCall.args[0]).to.include('Error while fetching config');
295
+ });
1226
296
 
1227
- expect(bannerCallFound).to.be.true; // Verify banner format was checked
1228
- expect(videoCallFound).to.be.true; // Verify video format was checked
297
+ it('should get config by name', () => {
298
+ const mockConfig = {
299
+ plugins: {
300
+ testPlugin: { enabled: true }
301
+ }
302
+ };
1229
303
 
1230
- // Since we created the mockGetFloor to return 0.25 for video (lower than 0.50 for banner),
1231
- // we expect the RTD provider to use the minimum floor value (0.25)
1232
- // We can't test the exact value due to multiplier application, but we can make sure
1233
- // it's derived from the lower value
1234
- expect(parseFloat(result['multiFormatDiv']['pm_ym_flrv'])).to.be.closeTo(0.25 * 1.2, 0.001); // 0.25 * nobid multiplier (1.2)
304
+ configManager.setYMConfig(mockConfig);
1235
305
 
1236
- // Clean up
1237
- getFloorSpy.restore();
1238
- });
1239
- });
306
+ const result = configManager.getConfigByName('testPlugin');
307
+ expect(result).to.deep.equal({ enabled: true });
1240
308
  });
309
+ });
1241
310
  });