prebid.js 9.53.2 → 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 (229) 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/adagioRtdProvider.js +1 -1
  8. package/dist/adagioUtils.js +1 -1
  9. package/dist/addefendBidAdapter.js +1 -1
  10. package/dist/adgenerationBidAdapter.js +1 -1
  11. package/dist/adlooxRtdProvider.js +1 -1
  12. package/dist/adqueryBidAdapter.js +1 -1
  13. package/dist/adrelevantisBidAdapter.js +1 -1
  14. package/dist/adstirBidAdapter.js +1 -1
  15. package/dist/adtrgtmeBidAdapter.js +1 -1
  16. package/dist/adxcgAnalyticsAdapter.js +1 -1
  17. package/dist/adxcgBidAdapter.js +1 -1
  18. package/dist/adyoulikeBidAdapter.js +1 -1
  19. package/dist/agmaAnalyticsAdapter.js +1 -1
  20. package/dist/ajaBidAdapter.js +1 -1
  21. package/dist/amxBidAdapter.js +1 -1
  22. package/dist/amxIdSystem.js +1 -1
  23. package/dist/aniviewBidAdapter.js +1 -1
  24. package/dist/appierAnalyticsAdapter.js +1 -1
  25. package/dist/appnexusBidAdapter.js +1 -1
  26. package/dist/asoBidAdapter.js +1 -1
  27. package/dist/axonixBidAdapter.js +1 -1
  28. package/dist/beopBidAdapter.js +1 -1
  29. package/dist/bidderTimeoutUtils.js +1 -0
  30. package/dist/bidglassBidAdapter.js +1 -1
  31. package/dist/big-richmediaBidAdapter.js +1 -1
  32. package/dist/bitmediaBidAdapter.js +1 -1
  33. package/dist/bridBidAdapter.js +1 -1
  34. package/dist/bridgeuppBidAdapter.js +1 -1
  35. package/dist/bridgewellBidAdapter.js +1 -1
  36. package/dist/brightMountainMediaBidAdapter.js +1 -1
  37. package/dist/carodaBidAdapter.js +1 -1
  38. package/dist/chtnwBidAdapter.js +1 -1
  39. package/dist/chunk-core.js +1 -1
  40. package/dist/concertBidAdapter.js +1 -1
  41. package/dist/connectadBidAdapter.js +1 -1
  42. package/dist/consumableBidAdapter.js +1 -1
  43. package/dist/contxtfulBidAdapter.js +1 -1
  44. package/dist/conversantAnalyticsAdapter.js +1 -1
  45. package/dist/conversantBidAdapter.js +1 -1
  46. package/dist/craftBidAdapter.js +1 -1
  47. package/dist/criteoBidAdapter.js +1 -1
  48. package/dist/cwireBidAdapter.js +1 -1
  49. package/dist/dailymotionBidAdapter.js +1 -1
  50. package/dist/debugging-standalone.js +1 -1
  51. package/dist/dependencies.json +10 -1
  52. package/dist/dspxBidAdapter.js +1 -1
  53. package/dist/dxkultureBidAdapter.js +1 -1
  54. package/dist/eplanningBidAdapter.js +1 -1
  55. package/dist/equativBidAdapter.js +1 -1
  56. package/dist/eskimiBidAdapter.js +1 -1
  57. package/dist/euidIdSystem.js +1 -1
  58. package/dist/exadsBidAdapter.js +1 -1
  59. package/dist/excoBidAdapter.js +1 -1
  60. package/dist/feedadBidAdapter.js +1 -1
  61. package/dist/finativeBidAdapter.js +1 -1
  62. package/dist/freewheel-sspBidAdapter.js +1 -1
  63. package/dist/fwsspBidAdapter.js +1 -1
  64. package/dist/gmosspBidAdapter.js +1 -1
  65. package/dist/greenbidsAnalyticsAdapter.js +1 -1
  66. package/dist/greenbidsBidAdapter.js +1 -1
  67. package/dist/greenbidsRtdProvider.js +1 -1
  68. package/dist/gridBidAdapter.js +1 -1
  69. package/dist/gumgumBidAdapter.js +1 -1
  70. package/dist/h12mediaBidAdapter.js +1 -1
  71. package/dist/hypelabBidAdapter.js +1 -1
  72. package/dist/id5AnalyticsAdapter.js +1 -1
  73. package/dist/id5IdSystem.js +1 -1
  74. package/dist/imdsBidAdapter.js +1 -1
  75. package/dist/improvedigitalBidAdapter.js +1 -1
  76. package/dist/inmobiBidAdapter.js +1 -1
  77. package/dist/insticatorBidAdapter.js +1 -1
  78. package/dist/intentIqAnalyticsAdapter.js +1 -1
  79. package/dist/ixBidAdapter.js +1 -1
  80. package/dist/jixieBidAdapter.js +1 -1
  81. package/dist/justpremiumBidAdapter.js +1 -1
  82. package/dist/kargoBidAdapter.js +1 -1
  83. package/dist/kimberliteBidAdapter.js +1 -1
  84. package/dist/konduitAnalyticsAdapter.js +1 -1
  85. package/dist/kueezBidAdapter.js +1 -1
  86. package/dist/lassoBidAdapter.js +1 -1
  87. package/dist/lifestreetBidAdapter.js +1 -1
  88. package/dist/liveIntentId.js +1 -1
  89. package/dist/logicadBidAdapter.js +1 -1
  90. package/dist/loglyliftBidAdapter.js +1 -1
  91. package/dist/luceadBidAdapter.js +1 -1
  92. package/dist/mabidderBidAdapter.js +1 -1
  93. package/dist/madsenseBidAdapter.js +1 -1
  94. package/dist/magniteAnalyticsAdapter.js +1 -1
  95. package/dist/malltvAnalyticsAdapter.js +1 -1
  96. package/dist/marsmediaBidAdapter.js +1 -1
  97. package/dist/mediafuseBidAdapter.js +1 -1
  98. package/dist/medianetBidAdapter.js +1 -1
  99. package/dist/medianetUtils.js +1 -1
  100. package/dist/mediasquareBidAdapter.js +1 -1
  101. package/dist/mgidBidAdapter.js +1 -1
  102. package/dist/missenaBidAdapter.js +1 -1
  103. package/dist/mobilefuseBidAdapter.js +1 -1
  104. package/dist/nextMillenniumBidAdapter.js +1 -1
  105. package/dist/nexx360Utils.js +1 -1
  106. package/dist/nobidAnalyticsAdapter.js +1 -1
  107. package/dist/nobidBidAdapter.js +1 -1
  108. package/dist/nodalsAiRtdProvider.js +1 -1
  109. package/dist/not-for-prod/prebid.js +178 -175
  110. package/dist/objectGuard.js +1 -1
  111. package/dist/oguryBidAdapter.js +1 -1
  112. package/dist/onetagBidAdapter.js +1 -1
  113. package/dist/ooloAnalyticsAdapter.js +1 -1
  114. package/dist/openxBidAdapter.js +1 -1
  115. package/dist/optableRtdProvider.js +1 -1
  116. package/dist/optidigitalBidAdapter.js +1 -1
  117. package/dist/orbidderBidAdapter.js +1 -1
  118. package/dist/outbrainBidAdapter.js +1 -1
  119. package/dist/pixfutureBidAdapter.js +1 -1
  120. package/dist/publinkIdSystem.js +1 -1
  121. package/dist/pubmaticAnalyticsAdapter.js +1 -1
  122. package/dist/pubmaticBidAdapter.js +1 -1
  123. package/dist/pubmaticIdSystem.js +1 -1
  124. package/dist/pubmaticRtdProvider.js +1 -1
  125. package/dist/pubmaticUtils.js +1 -0
  126. package/dist/pubwiseAnalyticsAdapter.js +1 -1
  127. package/dist/pubxaiAnalyticsAdapter.js +1 -1
  128. package/dist/pxyzBidAdapter.js +1 -1
  129. package/dist/quantcastBidAdapter.js +1 -1
  130. package/dist/readpeakBidAdapter.js +1 -1
  131. package/dist/relaidoBidAdapter.js +1 -1
  132. package/dist/retailspotBidAdapter.js +1 -1
  133. package/dist/rhythmoneBidAdapter.js +1 -1
  134. package/dist/riseUtils.js +1 -1
  135. package/dist/rtdModule.js +1 -1
  136. package/dist/rubiconBidAdapter.js +1 -1
  137. package/dist/seedingAllianceBidAdapter.js +1 -1
  138. package/dist/seedtagBidAdapter.js +1 -1
  139. package/dist/sevioBidAdapter.js +1 -0
  140. package/dist/sharethroughAnalyticsAdapter.js +1 -1
  141. package/dist/sharethroughBidAdapter.js +1 -1
  142. package/dist/showheroes-bsBidAdapter.js +1 -1
  143. package/dist/smaatoBidAdapter.js +1 -1
  144. package/dist/smartadserverBidAdapter.js +1 -1
  145. package/dist/smartxBidAdapter.js +1 -1
  146. package/dist/smilewantedBidAdapter.js +1 -1
  147. package/dist/snigelBidAdapter.js +1 -1
  148. package/dist/sonobiBidAdapter.js +1 -1
  149. package/dist/sovrnBidAdapter.js +1 -1
  150. package/dist/sparteoBidAdapter.js +1 -1
  151. package/dist/sspBCBidAdapter.js +1 -1
  152. package/dist/stvBidAdapter.js +1 -1
  153. package/dist/sublimeBidAdapter.js +1 -1
  154. package/dist/taboolaBidAdapter.js +1 -1
  155. package/dist/tappxBidAdapter.js +1 -1
  156. package/dist/targetVideoBidAdapter.js +1 -1
  157. package/dist/teadsBidAdapter.js +1 -1
  158. package/dist/terceptAnalyticsAdapter.js +1 -1
  159. package/dist/themoneytizerBidAdapter.js +1 -1
  160. package/dist/timeoutRtdProvider.js +1 -1
  161. package/dist/trionBidAdapter.js +1 -1
  162. package/dist/tripleliftBidAdapter.js +1 -1
  163. package/dist/ttdBidAdapter.js +1 -1
  164. package/dist/ucfunnelAnalyticsAdapter.js +1 -1
  165. package/dist/uid2IdSystem.js +1 -1
  166. package/dist/underdogmediaBidAdapter.js +1 -1
  167. package/dist/undertoneBidAdapter.js +1 -1
  168. package/dist/unrulyBidAdapter.js +1 -1
  169. package/dist/userId.js +1 -1
  170. package/dist/vidazooUtils.js +1 -1
  171. package/dist/videobyteBidAdapter.js +1 -1
  172. package/dist/visxBidAdapter.js +1 -1
  173. package/dist/vuukleBidAdapter.js +1 -1
  174. package/dist/widespaceBidAdapter.js +1 -1
  175. package/dist/winrBidAdapter.js +1 -1
  176. package/dist/yahooAdsBidAdapter.js +1 -1
  177. package/dist/yandexBidAdapter.js +1 -1
  178. package/dist/yieldmoBidAdapter.js +1 -1
  179. package/dist/yieldoneAnalyticsAdapter.js +1 -1
  180. package/integrationExamples/gpt/pubmaticRtdProvider_Example.html +161 -0
  181. package/libraries/bidderTimeoutUtils/bidderTimeoutUtils.js +119 -0
  182. package/libraries/objectGuard/objectGuard.js +170 -48
  183. package/libraries/objectGuard/ortbGuard.js +33 -43
  184. package/libraries/pubmaticUtils/plugins/dynamicTimeout.js +209 -0
  185. package/libraries/pubmaticUtils/plugins/floorProvider.js +168 -0
  186. package/libraries/pubmaticUtils/plugins/pluginManager.js +106 -0
  187. package/libraries/pubmaticUtils/plugins/unifiedPricingRule.js +375 -0
  188. package/libraries/pubmaticUtils/pubmaticUtils.js +76 -0
  189. package/modules/adagioAnalyticsAdapter.js +6 -1
  190. package/modules/adagioBidAdapter.js +12 -5
  191. package/modules/adagioRtdProvider.js +41 -35
  192. package/modules/fwsspBidAdapter.js +134 -69
  193. package/modules/fwsspBidAdapter.md +121 -26
  194. package/modules/optableRtdProvider.js +33 -12
  195. package/modules/pubmaticAnalyticsAdapter.js +315 -587
  196. package/modules/pubmaticBidAdapter.js +71 -8
  197. package/modules/pubmaticIdSystem.js +4 -4
  198. package/modules/pubmaticRtdProvider.js +105 -613
  199. package/modules/rtdModule/index.js +23 -6
  200. package/modules/sevioBidAdapter.js +413 -0
  201. package/modules/sevioBidAdapter.md +29 -0
  202. package/modules/sparteoBidAdapter.js +122 -10
  203. package/modules/timeoutRtdProvider.js +2 -105
  204. package/modules/ttdBidAdapter.js +0 -5
  205. package/modules/userId/eids.js +1 -1
  206. package/modules/userId/index.js +32 -1
  207. package/package.json +1 -1
  208. package/src/auction.js +3 -0
  209. package/test/spec/activities/objectGuard_spec.js +189 -32
  210. package/test/spec/activities/ortbGuard_spec.js +10 -15
  211. package/test/spec/libraries/bidderTimeoutUtils/bidderTimeoutUtils_spec.js +213 -0
  212. package/test/spec/libraries/pubmaticUtils/plugins/dynamicTimeout_spec.js +746 -0
  213. package/test/spec/libraries/pubmaticUtils/plugins/floorProvider_spec.js +184 -0
  214. package/test/spec/libraries/pubmaticUtils/plugins/pluginManager_spec.js +489 -0
  215. package/test/spec/libraries/pubmaticUtils/plugins/unifiedPricingRule_spec.js +359 -0
  216. package/test/spec/libraries/pubmaticUtils/pubmaticUtils_spec.js +236 -0
  217. package/test/spec/modules/adagioAnalyticsAdapter_spec.js +94 -24
  218. package/test/spec/modules/adagioRtdProvider_spec.js +17 -17
  219. package/test/spec/modules/fwsspBidAdapter_spec.js +513 -78
  220. package/test/spec/modules/optableRtdProvider_spec.js +55 -5
  221. package/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +634 -916
  222. package/test/spec/modules/pubmaticBidAdapter_spec.js +260 -1
  223. package/test/spec/modules/pubmaticRtdProvider_spec.js +252 -1505
  224. package/test/spec/modules/realTimeDataModule_spec.js +58 -8
  225. package/test/spec/modules/sevioBidAdapter_spec.js +513 -0
  226. package/test/spec/modules/sparteoBidAdapter_spec.js +528 -43
  227. package/test/spec/modules/timeoutRtdProvider_spec.js +1 -201
  228. package/test/spec/modules/ttdBidAdapter_spec.js +0 -33
  229. package/test/spec/modules/userId_spec.js +115 -1
@@ -0,0 +1,746 @@
1
+ import sinon from 'sinon';
2
+ import { expect } from 'chai';
3
+ import * as utils from '../../../../../src/utils.js';
4
+ import * as prebidGlobal from '../../../../../src/prebidGlobal.js';
5
+ import * as dynamicTimeout from '../../../../../libraries/pubmaticUtils/plugins/dynamicTimeout.js';
6
+ import { bidderTimeoutFunctions } from '../../../../../libraries/bidderTimeoutUtils/bidderTimeoutUtils.js';
7
+ import * as pubmaticUtils from '../../../../../libraries/pubmaticUtils/pubmaticUtils.js';
8
+
9
+ describe('DynamicTimeout Plugin', () => {
10
+ let sandbox;
11
+
12
+ // Sample configuration objects
13
+ const enabledConfig = {
14
+ enabled: true,
15
+ config: {
16
+ skipRate: 20,
17
+ bidderTimeout: 1000,
18
+ timeoutRules: {
19
+ includesVideo: {
20
+ 'true': 200,
21
+ 'false': 50
22
+ },
23
+ numAdUnits: {
24
+ '1-5': 100,
25
+ '6-10': 200
26
+ }
27
+ }
28
+ }
29
+ };
30
+
31
+ const configWithData = {
32
+ enabled: true,
33
+ config: {
34
+ skipRate: 20,
35
+ bidderTimeout: 1000
36
+ },
37
+ data: {
38
+ includesVideo: {
39
+ 'true': 300,
40
+ 'false': 100
41
+ },
42
+ deviceType: {
43
+ '2': 50,
44
+ '4': 150
45
+ }
46
+ }
47
+ };
48
+
49
+ const disabledConfig = {
50
+ enabled: false,
51
+ config: {
52
+ skipRate: 0
53
+ }
54
+ };
55
+
56
+ beforeEach(() => {
57
+ sandbox = sinon.createSandbox();
58
+
59
+ // Reset dynamic timeout config before each test
60
+ dynamicTimeout.setDynamicTimeoutConfig(null);
61
+ });
62
+
63
+ afterEach(() => {
64
+ sandbox.restore();
65
+ });
66
+
67
+ describe('init', () => {
68
+ it('should initialize successfully with valid config', async () => {
69
+ const configJsonManager = {
70
+ getConfigByName: sandbox.stub().returns(enabledConfig)
71
+ };
72
+
73
+ const logInfoStub = sandbox.stub(utils, 'logInfo');
74
+
75
+ const result = await dynamicTimeout.init('dynamicTimeout', configJsonManager);
76
+
77
+ expect(result).to.be.true;
78
+ expect(dynamicTimeout.getDynamicTimeoutConfig()).to.deep.equal(enabledConfig);
79
+ expect(logInfoStub.called).to.be.false;
80
+ });
81
+
82
+ it('should return false if config is not found', async () => {
83
+ const configJsonManager = {
84
+ getConfigByName: sandbox.stub().returns(null)
85
+ };
86
+
87
+ const logInfoStub = sandbox.stub(utils, 'logInfo');
88
+
89
+ const result = await dynamicTimeout.init('dynamicTimeout', configJsonManager);
90
+
91
+ expect(result).to.be.false;
92
+ expect(logInfoStub.calledOnce).to.be.true;
93
+ expect(logInfoStub.firstCall.args[0]).to.include('Dynamic Timeout configuration not found');
94
+ });
95
+
96
+ it('should return false if config is disabled', async () => {
97
+ const configJsonManager = {
98
+ getConfigByName: sandbox.stub().returns(disabledConfig)
99
+ };
100
+
101
+ const logInfoStub = sandbox.stub(utils, 'logInfo');
102
+
103
+ const result = await dynamicTimeout.init('dynamicTimeout', configJsonManager);
104
+
105
+ expect(result).to.be.false;
106
+ expect(logInfoStub.calledOnce).to.be.true;
107
+ expect(logInfoStub.firstCall.args[0]).to.include('Dynamic Timeout configuration is disabled');
108
+ });
109
+ });
110
+
111
+ describe('processBidRequest', () => {
112
+ let logInfoStub;
113
+ let getGlobalStub;
114
+ let calculateTimeoutModifierStub;
115
+ let shouldThrottleStub;
116
+
117
+ beforeEach(() => {
118
+ logInfoStub = sandbox.stub(utils, 'logInfo');
119
+ getGlobalStub = sandbox.stub(prebidGlobal, 'getGlobal').returns({
120
+ getConfig: sandbox.stub().returns(800),
121
+ adUnits: [{ code: 'test-div' }]
122
+ });
123
+ calculateTimeoutModifierStub = sandbox.stub(bidderTimeoutFunctions, 'calculateTimeoutModifier').returns(150);
124
+ shouldThrottleStub = sandbox.stub(pubmaticUtils, 'shouldThrottle');
125
+
126
+ // Set up default config for most tests
127
+ dynamicTimeout.setDynamicTimeoutConfig(enabledConfig);
128
+ });
129
+
130
+ it('should skip processing if throttled by skipRate', async () => {
131
+ shouldThrottleStub.returns(true);
132
+
133
+ const reqBidsConfigObj = { timeout: 800 };
134
+ const result = await dynamicTimeout.processBidRequest(reqBidsConfigObj);
135
+
136
+ expect(result).to.equal(reqBidsConfigObj);
137
+ expect(logInfoStub.calledOnce).to.be.true;
138
+ expect(logInfoStub.firstCall.args[0]).to.include('Dynamic timeout is skipped');
139
+ expect(calculateTimeoutModifierStub.called).to.be.false;
140
+ });
141
+
142
+ it('should apply timeout adjustment when not throttled', async () => {
143
+ shouldThrottleStub.returns(false);
144
+
145
+ const reqBidsConfigObj = { timeout: 800 };
146
+ const result = await dynamicTimeout.processBidRequest(reqBidsConfigObj);
147
+
148
+ // The actual code uses the bidderTimeout from config (1000) + additionalTimeout (150)
149
+ expect(result.timeout).to.equal(1150);
150
+ expect(logInfoStub.called).to.be.true;
151
+ expect(calculateTimeoutModifierStub.calledOnce).to.be.true;
152
+ });
153
+
154
+ it('should use adUnits from reqBidsConfigObj if available', async () => {
155
+ shouldThrottleStub.returns(false);
156
+
157
+ const reqBidsConfigObj = {
158
+ timeout: 800,
159
+ adUnits: [
160
+ { code: 'div1' },
161
+ { code: 'div2' }
162
+ ]
163
+ };
164
+
165
+ await dynamicTimeout.processBidRequest(reqBidsConfigObj);
166
+
167
+ expect(calculateTimeoutModifierStub.calledOnce).to.be.true;
168
+ expect(calculateTimeoutModifierStub.firstCall.args[0]).to.deep.equal(reqBidsConfigObj.adUnits);
169
+ });
170
+
171
+ it('should use adUnits from global if not in reqBidsConfigObj', async () => {
172
+ shouldThrottleStub.returns(false);
173
+
174
+ const globalAdUnits = [{ code: 'global-div' }];
175
+ getGlobalStub.returns({
176
+ getConfig: sandbox.stub().returns(800),
177
+ adUnits: globalAdUnits
178
+ });
179
+
180
+ const reqBidsConfigObj = { timeout: 800 };
181
+ await dynamicTimeout.processBidRequest(reqBidsConfigObj);
182
+
183
+ expect(calculateTimeoutModifierStub.calledOnce).to.be.true;
184
+ expect(calculateTimeoutModifierStub.firstCall.args[0]).to.deep.equal(globalAdUnits);
185
+ });
186
+
187
+ it('should use bidderTimeout from config if available', async () => {
188
+ shouldThrottleStub.returns(false);
189
+
190
+ const reqBidsConfigObj = {};
191
+ await dynamicTimeout.processBidRequest(reqBidsConfigObj);
192
+
193
+ // Should use the bidderTimeout from config (1000) not the global one (800)
194
+ expect(reqBidsConfigObj.timeout).to.equal(1150); // 1000 + 150
195
+ });
196
+
197
+ it('should use timeout from reqBidsConfigObj if available and no config bidderTimeout', async () => {
198
+ shouldThrottleStub.returns(false);
199
+
200
+ // Remove bidderTimeout from config
201
+ const configWithoutBidderTimeout = {
202
+ ...enabledConfig,
203
+ config: { ...enabledConfig.config }
204
+ };
205
+ delete configWithoutBidderTimeout.config.bidderTimeout;
206
+ dynamicTimeout.setDynamicTimeoutConfig(configWithoutBidderTimeout);
207
+
208
+ const reqBidsConfigObj = { timeout: 900 };
209
+ await dynamicTimeout.processBidRequest(reqBidsConfigObj);
210
+
211
+ expect(reqBidsConfigObj.timeout).to.equal(1050); // 900 + 150
212
+ });
213
+
214
+ it('should fall back to global bidderTimeout if needed', async () => {
215
+ shouldThrottleStub.returns(false);
216
+
217
+ // Remove bidderTimeout from config
218
+ const configWithoutBidderTimeout = {
219
+ ...enabledConfig,
220
+ config: { ...enabledConfig.config }
221
+ };
222
+ delete configWithoutBidderTimeout.config.bidderTimeout;
223
+ dynamicTimeout.setDynamicTimeoutConfig(configWithoutBidderTimeout);
224
+
225
+ const reqBidsConfigObj = {};
226
+ await dynamicTimeout.processBidRequest(reqBidsConfigObj);
227
+
228
+ expect(reqBidsConfigObj.timeout).to.equal(950); // 800 + 150
229
+ });
230
+
231
+ it('should use skipRate 0 when explicitly set to 0', async () => {
232
+ // Set a config with skipRate explicitly set to 0
233
+ const configWithZeroSkipRate = {
234
+ enabled: true,
235
+ config: {
236
+ skipRate: 0,
237
+ bidderTimeout: 1000
238
+ }
239
+ };
240
+ dynamicTimeout.setDynamicTimeoutConfig(configWithZeroSkipRate);
241
+
242
+ const reqBidsConfigObj = { timeout: 800 };
243
+ await dynamicTimeout.processBidRequest(reqBidsConfigObj);
244
+
245
+ // Verify shouldThrottle was called with skipRate=0, not the default value
246
+ expect(shouldThrottleStub.calledOnce).to.be.true;
247
+ expect(shouldThrottleStub.firstCall.args[0]).to.equal(0);
248
+ });
249
+
250
+ it('should use default skipRate when skipRate is not present in config', async () => {
251
+ // Set a config without skipRate
252
+ const configWithoutSkipRate = {
253
+ enabled: true,
254
+ config: {
255
+ bidderTimeout: 1000
256
+ }
257
+ };
258
+ dynamicTimeout.setDynamicTimeoutConfig(configWithoutSkipRate);
259
+
260
+ const reqBidsConfigObj = { timeout: 800 };
261
+ await dynamicTimeout.processBidRequest(reqBidsConfigObj);
262
+
263
+ // Verify shouldThrottle was called with the default skipRate
264
+ expect(shouldThrottleStub.calledOnce).to.be.true;
265
+ expect(shouldThrottleStub.firstCall.args[0]).to.equal(dynamicTimeout.CONSTANTS.DEFAULT_SKIP_RATE);
266
+ });
267
+ });
268
+
269
+ describe('getBidderTimeout', () => {
270
+ let getGlobalStub;
271
+
272
+ beforeEach(() => {
273
+ getGlobalStub = sandbox.stub(prebidGlobal, 'getGlobal').returns({
274
+ getConfig: sandbox.stub().returns(800)
275
+ });
276
+ });
277
+
278
+ it('should return bidderTimeout from config if available', () => {
279
+ dynamicTimeout.setDynamicTimeoutConfig(enabledConfig);
280
+
281
+ const result = dynamicTimeout.getBidderTimeout({});
282
+
283
+ expect(result).to.equal(1000);
284
+ });
285
+
286
+ it('should return timeout from reqBidsConfigObj if no config bidderTimeout', () => {
287
+ const configWithoutBidderTimeout = {
288
+ ...enabledConfig,
289
+ config: { ...enabledConfig.config }
290
+ };
291
+ delete configWithoutBidderTimeout.config.bidderTimeout;
292
+ dynamicTimeout.setDynamicTimeoutConfig(configWithoutBidderTimeout);
293
+
294
+ const result = dynamicTimeout.getBidderTimeout({ timeout: 900 });
295
+
296
+ expect(result).to.equal(900);
297
+ });
298
+
299
+ it('should fall back to global bidderTimeout if needed', () => {
300
+ const configWithoutBidderTimeout = {
301
+ ...enabledConfig,
302
+ config: { ...enabledConfig.config }
303
+ };
304
+ delete configWithoutBidderTimeout.config.bidderTimeout;
305
+ dynamicTimeout.setDynamicTimeoutConfig(configWithoutBidderTimeout);
306
+
307
+ const result = dynamicTimeout.getBidderTimeout({});
308
+
309
+ expect(result).to.equal(800);
310
+ });
311
+ });
312
+
313
+ describe('getFinalTimeout', () => {
314
+ let logInfoStub;
315
+
316
+ beforeEach(() => {
317
+ logInfoStub = sandbox.stub(utils, 'logInfo');
318
+ // Set up a default config for getFinalTimeout tests
319
+ dynamicTimeout.setDynamicTimeoutConfig({
320
+ enabled: true,
321
+ config: {
322
+ thresholdTimeout: 500
323
+ }
324
+ });
325
+ });
326
+
327
+ it('should return calculated timeout when above threshold', () => {
328
+ const bidderTimeout = 1000;
329
+ const additionalTimeout = 200;
330
+
331
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
332
+
333
+ expect(result).to.equal(1200); // 1000 + 200
334
+ });
335
+
336
+ it('should return threshold timeout when calculated timeout is below threshold', () => {
337
+ const bidderTimeout = 300;
338
+ const additionalTimeout = 100;
339
+
340
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
341
+
342
+ expect(result).to.equal(500); // threshold timeout
343
+ });
344
+
345
+ it('should handle negative additional timeout gracefully', () => {
346
+ const bidderTimeout = 1000;
347
+ const additionalTimeout = -600; // Results in 400ms, below threshold
348
+
349
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
350
+
351
+ expect(result).to.equal(500); // threshold timeout
352
+ expect(logInfoStub.calledOnce).to.be.true;
353
+ expect(logInfoStub.firstCall.args[0]).to.include('Calculated timeout (400ms) below threshold');
354
+ });
355
+
356
+ it('should handle negative bidder timeout gracefully', () => {
357
+ const bidderTimeout = -200;
358
+ const additionalTimeout = 100;
359
+
360
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
361
+
362
+ expect(result).to.equal(500); // threshold timeout (since -200 + 100 = -100 < 500)
363
+ expect(logInfoStub.calledOnce).to.be.true;
364
+ expect(logInfoStub.firstCall.args[0]).to.include('Warning: Negative timeout calculated (-100ms)');
365
+ });
366
+
367
+ it('should handle both negative values gracefully', () => {
368
+ const bidderTimeout = -300;
369
+ const additionalTimeout = -200;
370
+
371
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
372
+
373
+ expect(result).to.equal(500); // threshold timeout (since -300 + -200 = -500 < 500)
374
+ expect(logInfoStub.calledOnce).to.be.true;
375
+ expect(logInfoStub.firstCall.args[0]).to.include('Warning: Negative timeout calculated (-500ms)');
376
+ });
377
+
378
+ it('should use default threshold when not configured', () => {
379
+ // Remove threshold from config
380
+ dynamicTimeout.setDynamicTimeoutConfig({
381
+ enabled: true,
382
+ config: {}
383
+ });
384
+
385
+ const bidderTimeout = 200;
386
+ const additionalTimeout = 100;
387
+
388
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
389
+
390
+ expect(result).to.equal(500); // default threshold (500ms)
391
+ });
392
+
393
+ it('should handle zero values', () => {
394
+ const bidderTimeout = 0;
395
+ const additionalTimeout = 0;
396
+
397
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
398
+
399
+ expect(result).to.equal(500); // threshold timeout
400
+ });
401
+
402
+ it('should handle large timeout values', () => {
403
+ const bidderTimeout = 5000;
404
+ const additionalTimeout = 2000;
405
+
406
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
407
+
408
+ expect(result).to.equal(7000); // 5000 + 2000
409
+ });
410
+
411
+ it('should handle custom threshold timeout', () => {
412
+ // Set custom threshold
413
+ dynamicTimeout.setDynamicTimeoutConfig({
414
+ enabled: true,
415
+ config: {
416
+ thresholdTimeout: 1000
417
+ }
418
+ });
419
+
420
+ const bidderTimeout = 800;
421
+ const additionalTimeout = 100;
422
+
423
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
424
+
425
+ expect(result).to.equal(1000); // custom threshold (since 800 + 100 = 900 < 1000)
426
+ });
427
+
428
+ it('should handle exactly threshold value', () => {
429
+ const bidderTimeout = 400;
430
+ const additionalTimeout = 100;
431
+
432
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
433
+
434
+ expect(result).to.equal(500); // exactly at threshold, should return calculated value
435
+ });
436
+
437
+ it('should handle no config gracefully', () => {
438
+ dynamicTimeout.setDynamicTimeoutConfig(null);
439
+
440
+ const bidderTimeout = 300;
441
+ const additionalTimeout = 100;
442
+
443
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
444
+
445
+ expect(result).to.equal(500); // default threshold (500ms)
446
+ });
447
+
448
+ it('should handle very large negative additional timeout', () => {
449
+ const bidderTimeout = 1000;
450
+ const additionalTimeout = -2000; // Results in -1000ms, well below threshold
451
+
452
+ const result = dynamicTimeout.getFinalTimeout(bidderTimeout, additionalTimeout);
453
+
454
+ expect(result).to.equal(500); // threshold timeout
455
+ });
456
+ });
457
+
458
+ describe('getRules', () => {
459
+ it('should return data rules if available', () => {
460
+ dynamicTimeout.setDynamicTimeoutConfig(configWithData);
461
+
462
+ const result = dynamicTimeout.getRules(1000);
463
+
464
+ expect(result).to.deep.equal(configWithData.data);
465
+ });
466
+
467
+ it('should return config timeoutRules if no data rules', () => {
468
+ dynamicTimeout.setDynamicTimeoutConfig(enabledConfig);
469
+
470
+ const result = dynamicTimeout.getRules(1000);
471
+
472
+ expect(result).to.deep.equal(enabledConfig.config.timeoutRules);
473
+ });
474
+
475
+ it('should create dynamic rules if no data or config rules', () => {
476
+ const configWithoutRules = {
477
+ enabled: true,
478
+ config: {
479
+ skipRate: 20,
480
+ bidderTimeout: 1000
481
+ }
482
+ };
483
+ dynamicTimeout.setDynamicTimeoutConfig(configWithoutRules);
484
+
485
+ const result = dynamicTimeout.getRules(1000);
486
+
487
+ expect(result).to.deep.equal(dynamicTimeout.createDynamicRules(dynamicTimeout.RULES_PERCENTAGE, 1000));
488
+ });
489
+ });
490
+
491
+ describe('createDynamicRules', () => {
492
+ let logInfoStub;
493
+
494
+ beforeEach(() => {
495
+ logInfoStub = sandbox.stub(utils, 'logInfo');
496
+ });
497
+ it('should convert percentage rules to millisecond values', () => {
498
+ const percentageRules = {
499
+ includesVideo: {
500
+ 'true': 20, // 20% of bidderTimeout
501
+ 'false': 5 // 5% of bidderTimeout
502
+ },
503
+ numAdUnits: {
504
+ '1-5': 10, // 10% of bidderTimeout
505
+ '6-10': 20 // 20% of bidderTimeout
506
+ }
507
+ };
508
+
509
+ const bidderTimeout = 1000;
510
+ const result = dynamicTimeout.createDynamicRules(percentageRules, bidderTimeout);
511
+
512
+ expect(result).to.deep.equal({
513
+ includesVideo: {
514
+ 'true': 200, // 20% of 1000
515
+ 'false': 50 // 5% of 1000
516
+ },
517
+ numAdUnits: {
518
+ '1-5': 100, // 10% of 1000
519
+ '6-10': 200 // 20% of 1000
520
+ }
521
+ });
522
+ });
523
+
524
+ it('should handle invalid percentage values', () => {
525
+ const percentageRules = {
526
+ includesVideo: {
527
+ 'true': 'invalid',
528
+ 'false': 5
529
+ }
530
+ };
531
+
532
+ const bidderTimeout = 1000;
533
+ const result = dynamicTimeout.createDynamicRules(percentageRules, bidderTimeout);
534
+
535
+ expect(result).to.deep.equal({
536
+ includesVideo: {
537
+ 'false': 50
538
+ }
539
+ });
540
+ });
541
+
542
+ it('should return empty object for invalid inputs', () => {
543
+ expect(dynamicTimeout.createDynamicRules(null, 1000)).to.deep.equal({});
544
+ expect(dynamicTimeout.createDynamicRules({}, 0)).to.deep.equal({});
545
+ expect(dynamicTimeout.createDynamicRules('invalid', 1000)).to.deep.equal({});
546
+ expect(dynamicTimeout.createDynamicRules({}, -10)).to.deep.equal({});
547
+
548
+ // Verify logging for invalid inputs
549
+ expect(logInfoStub.callCount).to.equal(4);
550
+ expect(logInfoStub.getCall(0).args[0]).to.include('Invalid percentage rules provided');
551
+ expect(logInfoStub.getCall(1).args[0]).to.include('Invalid bidderTimeout (0ms)');
552
+ expect(logInfoStub.getCall(2).args[0]).to.include('Invalid percentage rules provided');
553
+ expect(logInfoStub.getCall(3).args[0]).to.include('Invalid bidderTimeout (-10ms)');
554
+ });
555
+
556
+ it('should skip non-object rule categories', () => {
557
+ const percentageRules = {
558
+ includesVideo: {
559
+ 'true': 20
560
+ },
561
+ numAdUnits: 'invalid'
562
+ };
563
+
564
+ const bidderTimeout = 1000;
565
+ const result = dynamicTimeout.createDynamicRules(percentageRules, bidderTimeout);
566
+
567
+ expect(result).to.deep.equal({
568
+ includesVideo: {
569
+ 'true': 200
570
+ }
571
+ });
572
+ expect(logInfoStub.calledOnce).to.be.true;
573
+ expect(logInfoStub.firstCall.args[0]).to.include('Skipping invalid rule category: numAdUnits');
574
+ });
575
+
576
+ it('should handle negative bidderTimeout gracefully', () => {
577
+ const percentageRules = {
578
+ includesVideo: {
579
+ 'true': 20,
580
+ 'false': 5
581
+ }
582
+ };
583
+
584
+ const result = dynamicTimeout.createDynamicRules(percentageRules, -500);
585
+ expect(result).to.deep.equal({});
586
+ expect(logInfoStub.calledOnce).to.be.true;
587
+ expect(logInfoStub.firstCall.args[0]).to.include('Invalid bidderTimeout (-500ms)');
588
+ });
589
+
590
+ it('should handle zero percentage values', () => {
591
+ const percentageRules = {
592
+ includesVideo: {
593
+ 'true': 0,
594
+ 'false': 5
595
+ }
596
+ };
597
+
598
+ const bidderTimeout = 1000;
599
+ const result = dynamicTimeout.createDynamicRules(percentageRules, bidderTimeout);
600
+
601
+ expect(result).to.deep.equal({
602
+ includesVideo: {
603
+ 'false': 50
604
+ }
605
+ });
606
+ });
607
+
608
+ it('should handle negative percentage values', () => {
609
+ const percentageRules = {
610
+ includesVideo: {
611
+ 'true': -20,
612
+ 'false': 5
613
+ }
614
+ };
615
+
616
+ const bidderTimeout = 1000;
617
+ const result = dynamicTimeout.createDynamicRules(percentageRules, bidderTimeout);
618
+
619
+ expect(result).to.deep.equal({
620
+ includesVideo: {
621
+ 'true': -200,
622
+ 'false': 50
623
+ }
624
+ });
625
+ expect(logInfoStub.calledOnce).to.be.true;
626
+ expect(logInfoStub.firstCall.args[0]).to.include('Warning: Negative timeout calculated for includesVideo.true: -200ms');
627
+ });
628
+
629
+ it('should handle very large percentage values', () => {
630
+ const percentageRules = {
631
+ includesVideo: {
632
+ 'true': 500, // 500% of bidderTimeout
633
+ 'false': 5
634
+ }
635
+ };
636
+
637
+ const bidderTimeout = 1000;
638
+ const result = dynamicTimeout.createDynamicRules(percentageRules, bidderTimeout);
639
+
640
+ expect(result).to.deep.equal({
641
+ includesVideo: {
642
+ 'true': 5000, // 500% of 1000
643
+ 'false': 50
644
+ }
645
+ });
646
+ });
647
+
648
+ it('should handle decimal percentage values', () => {
649
+ const percentageRules = {
650
+ includesVideo: {
651
+ 'true': 2.5,
652
+ 'false': 7.8
653
+ }
654
+ };
655
+
656
+ const bidderTimeout = 1000;
657
+ const result = dynamicTimeout.createDynamicRules(percentageRules, bidderTimeout);
658
+
659
+ expect(result).to.deep.equal({
660
+ includesVideo: {
661
+ 'true': 25, // Math.floor(1000 * 2.5 / 100)
662
+ 'false': 78 // Math.floor(1000 * 7.8 / 100)
663
+ }
664
+ });
665
+ });
666
+
667
+ it('should handle empty percentage rules object', () => {
668
+ const percentageRules = {};
669
+ const bidderTimeout = 1000;
670
+ const result = dynamicTimeout.createDynamicRules(percentageRules, bidderTimeout);
671
+
672
+ expect(result).to.deep.equal({});
673
+ });
674
+
675
+ it('should handle categories with empty rule objects', () => {
676
+ const percentageRules = {
677
+ includesVideo: {},
678
+ numAdUnits: {
679
+ '1-5': 10
680
+ }
681
+ };
682
+
683
+ const bidderTimeout = 1000;
684
+ const result = dynamicTimeout.createDynamicRules(percentageRules, bidderTimeout);
685
+
686
+ expect(result).to.deep.equal({
687
+ includesVideo: {},
688
+ numAdUnits: {
689
+ '1-5': 100
690
+ }
691
+ });
692
+ });
693
+ });
694
+
695
+ describe('getTargeting', () => {
696
+ it('should return undefined', () => {
697
+ expect(dynamicTimeout.getTargeting()).to.be.undefined;
698
+ });
699
+ });
700
+
701
+ describe('CONSTANTS', () => {
702
+ it('should have the correct LOG_PRE_FIX value', () => {
703
+ expect(dynamicTimeout.CONSTANTS.LOG_PRE_FIX).to.equal('PubMatic-Dynamic-Timeout: ');
704
+ });
705
+
706
+ it('should have the correct rule type constants', () => {
707
+ expect(dynamicTimeout.CONSTANTS.INCLUDES_VIDEOS).to.equal('includesVideo');
708
+ expect(dynamicTimeout.CONSTANTS.NUM_AD_UNITS).to.equal('numAdUnits');
709
+ expect(dynamicTimeout.CONSTANTS.DEVICE_TYPE).to.equal('deviceType');
710
+ expect(dynamicTimeout.CONSTANTS.CONNECTION_SPEED).to.equal('connectionSpeed');
711
+ });
712
+
713
+ it('should be frozen', () => {
714
+ expect(Object.isFrozen(dynamicTimeout.CONSTANTS)).to.be.true;
715
+ });
716
+ });
717
+
718
+ describe('RULES_PERCENTAGE', () => {
719
+ it('should have the correct percentage values', () => {
720
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.INCLUDES_VIDEOS]['true']).to.equal(20);
721
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.INCLUDES_VIDEOS]['false']).to.equal(5);
722
+
723
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.NUM_AD_UNITS]['1-5']).to.equal(10);
724
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.NUM_AD_UNITS]['6-10']).to.equal(20);
725
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.NUM_AD_UNITS]['11-15']).to.equal(30);
726
+
727
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.DEVICE_TYPE]['2']).to.equal(5);
728
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.DEVICE_TYPE]['4']).to.equal(10);
729
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.DEVICE_TYPE]['5']).to.equal(20);
730
+
731
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.CONNECTION_SPEED]['slow']).to.equal(20);
732
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.CONNECTION_SPEED]['medium']).to.equal(10);
733
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.CONNECTION_SPEED]['fast']).to.equal(5);
734
+ expect(dynamicTimeout.RULES_PERCENTAGE[dynamicTimeout.CONSTANTS.CONNECTION_SPEED]['unknown']).to.equal(1);
735
+ });
736
+ });
737
+
738
+ describe('DynamicTimeout export', () => {
739
+ it('should export the correct interface', () => {
740
+ expect(dynamicTimeout.DynamicTimeout).to.be.an('object');
741
+ expect(dynamicTimeout.DynamicTimeout.init).to.be.a('function');
742
+ expect(dynamicTimeout.DynamicTimeout.processBidRequest).to.be.a('function');
743
+ expect(dynamicTimeout.DynamicTimeout.getTargeting).to.be.a('function');
744
+ });
745
+ });
746
+ });