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
@@ -0,0 +1,184 @@
1
+ import sinon from 'sinon';
2
+ import * as floorProvider from '../../../../../libraries/pubmaticUtils/plugins/floorProvider.js';
3
+ import * as priceFloors from '../../../../../modules/priceFloors.js';
4
+ import * as pubmaticUtils from '../../../../../libraries/pubmaticUtils/pubmaticUtils.js';
5
+ import {expect} from 'chai';
6
+
7
+ describe('FloorProvider', () => {
8
+ const floorsobj = {
9
+ enabled: true,
10
+ pmTargetingKeys: {
11
+ enabled: true,
12
+ multiplier: { win: 4, floored: 10, nobid: 100 }
13
+ },
14
+ config: {
15
+ endpoint: 'https://pubmatic.com/floor',
16
+ enforcement: { floorDeals: false, enforceJS: false },
17
+ floorMin: 0.22,
18
+ skipRate: 0,
19
+ defaultValues: { '*|*': 0.22 }
20
+ },
21
+ data: {
22
+ currency: 'USD',
23
+ skipRate: 0,
24
+ modelVersion: 'Mock API model version',
25
+ multiplier: { win: 1.1, floored: 0.9, nobid: 1.3 },
26
+ schema: {
27
+ fields: ['mediaType', 'size', 'domain', 'adUnitCode', 'deviceType', 'timeOfDay', 'browser', 'os', 'utm', 'country', 'bidder']
28
+ },
29
+ values: {
30
+ "banner|728x90|localhost|div1|0|afternoon|9|1|0|IN|pubmatic": 9.234,
31
+ },
32
+ default: 0.23,
33
+ userIds: ['id5id']
34
+ }
35
+ };
36
+
37
+ beforeEach(() => {
38
+ floorProvider.init('dynamicFloors', {
39
+ getConfigByName: () => floorsobj
40
+ });
41
+ });
42
+ it('should initialize floor provider and set config correctly', async () => {
43
+ const pluginName = 'dynamicFloors';
44
+ const configJsonManager = {
45
+ getConfigByName: (name) => name === pluginName ? floorsobj : undefined,
46
+ country: "IN"
47
+ };
48
+
49
+ let continueAuctionStub;
50
+ before(() => {
51
+ continueAuctionStub = sinon.stub(priceFloors, 'continueAuction').callsFake(() => true);
52
+ });
53
+
54
+ const result = await floorProvider.init(pluginName, configJsonManager);
55
+
56
+ expect(result).to.be.true;
57
+ expect(floorProvider.getFloorConfig()).to.deep.equal(floorsobj);
58
+
59
+ after(() => {
60
+ sinon.restore();
61
+ });
62
+ });
63
+
64
+ it('should return input unchanged if floor config is missing or disabled', async () => {
65
+ const input = {
66
+ adUnits: [
67
+ {
68
+ adUnitCode: 'div1',
69
+ sizes: [728, 90],
70
+ bids: [{ bidder: 'pubmatic' }]
71
+ }
72
+ ],
73
+ adUnitCodes: ['div1']
74
+ };
75
+ const result = await floorProvider.processBidRequest(input);
76
+ // Check that adUnitCodes are unchanged
77
+ expect(result.adUnitCodes).to.deep.equal(input.adUnitCodes);
78
+ // Check that adUnits core fields are unchanged
79
+ expect(result.adUnits[0].adUnitCode).to.equal('div1');
80
+ expect(result.adUnits[0].sizes).to.deep.equal([728, 90]);
81
+ expect(result.adUnits[0].bids[0]).to.include({ bidder: 'pubmatic' });
82
+ });
83
+
84
+ it('should handle errors in continueAuction gracefully', async () => {
85
+ let continueAuctionStub;
86
+ before(() => {
87
+ continueAuctionStub = sinon.stub(priceFloors, 'continueAuction').callsFake(() => { throw new Error('fail!'); });
88
+ });
89
+
90
+ floorProvider.init('dynamicFloors', {
91
+ getConfigByName: () => floorsobj
92
+ });
93
+
94
+ const req = {err: 4};
95
+ const result = await floorProvider.processBidRequest(req);
96
+
97
+ expect(result).to.equal(req);
98
+
99
+ after(() => {
100
+ sinon.restore();
101
+ });
102
+ });
103
+
104
+ it('getTargeting should return undefined or do nothing', () => {
105
+ expect(floorProvider.getTargeting([], {}, {}, {})).to.be.undefined;
106
+ });
107
+ it('should return correct floor config using getFloorConfig', () => {
108
+ floorProvider.init('dynamicFloors', {
109
+ getConfigByName: () => floorsobj
110
+ });
111
+ expect(floorProvider.getFloorConfig()).to.deep.equal(floorsobj);
112
+ });
113
+
114
+ it('should return false if getConfigByName returns undefined', async () => {
115
+ const result = await floorProvider.init('', { getConfigByName: () => undefined });
116
+ expect(result).to.equal(false);
117
+ });
118
+
119
+ it('should return false when floor configuration is disabled', async () => {
120
+ const disabledConfig = { ...floorsobj, enabled: false };
121
+ const result = await floorProvider.init('dynamicFloors', {
122
+ getConfigByName: () => disabledConfig
123
+ });
124
+ expect(result).to.equal(false);
125
+ expect(floorProvider.getFloorConfig()).to.deep.equal(disabledConfig);
126
+ });
127
+
128
+ it('should cover getConfigJsonManager export and log its value', async () => {
129
+ const configJsonManager = { getConfigByName: () => floorsobj };
130
+ const result = await floorProvider.init('testPlugin', configJsonManager);
131
+ const mgr = floorProvider.getConfigJsonManager();
132
+ expect(mgr.getConfigByName('testPlugin')).to.deep.equal(floorsobj);
133
+ expect(result).to.be.true;
134
+ });
135
+ describe('Utility Exports', () => {
136
+ afterEach(() => {
137
+ sinon.restore();
138
+ });
139
+
140
+ it('getCountry should return country from configJsonManager', async () => {
141
+ const enabledConfig = { ...floorsobj, enabled: true };
142
+ floorProvider.init('any', { country: 'IN', getConfigByName: () => enabledConfig });
143
+ expect(floorProvider.getCountry()).to.equal('IN');
144
+ });
145
+
146
+ it('getOs should return string from getOS', async () => {
147
+ // Import userAgentUtils and stub getOS there
148
+ const userAgentUtils = require('libraries/userAgentUtils/index.js');
149
+ const fakeOS = { toString: () => 'MacOS' };
150
+ const stub = sinon.stub(userAgentUtils, 'getOS').returns(fakeOS);
151
+ expect(floorProvider.getOs()).to.equal('MacOS');
152
+ stub.restore();
153
+ });
154
+ afterEach(() => {
155
+ sinon.restore();
156
+ });
157
+
158
+ it('getTimeOfDay should return result from getCurrentTimeOfDay', async () => {
159
+ const stub = sinon.stub(pubmaticUtils, 'getCurrentTimeOfDay').returns('evening');
160
+ expect(floorProvider.getTimeOfDay()).to.equal('evening');
161
+ });
162
+
163
+ it('should return a string device type using getDeviceType', async () => {
164
+ expect(floorProvider.getDeviceType()).to.be.a('string');
165
+ });
166
+
167
+ it('getBrowser should return result from getBrowser', async () => {
168
+ const stub = sinon.stub(pubmaticUtils, 'getBrowserType').returns('Chrome');
169
+ expect(floorProvider.getBrowser()).to.equal('Chrome');
170
+ });
171
+
172
+ it('getUtm should return result from getUtmValue', async () => {
173
+ const stub = sinon.stub(pubmaticUtils, 'getUtmValue').returns('evening');
174
+ expect(floorProvider.getUtm()).to.equal('evening');
175
+ });
176
+
177
+ it('getBidder should return bidder from request', async () => {
178
+ floorProvider.init('dynamicFloors', { getConfigByName: () => floorsobj });
179
+ expect(floorProvider.getBidder({ bidder: 'pubmatic' })).to.equal('pubmatic');
180
+ expect(floorProvider.getBidder({})).to.equal(undefined);
181
+ expect(floorProvider.getBidder(undefined)).to.equal(undefined);
182
+ });
183
+ });
184
+ });
@@ -0,0 +1,489 @@
1
+ import { expect } from 'chai';
2
+ import sinon from 'sinon';
3
+ import * as utils from '../../../../../src/utils.js';
4
+ import { PluginManager, plugins, CONSTANTS } from '../../../../../libraries/pubmaticUtils/plugins/pluginManager.js';
5
+
6
+ describe('Plugin Manager', () => {
7
+ let sandbox;
8
+ let logInfoStub;
9
+ let logWarnStub;
10
+ let logErrorStub;
11
+ let pluginManager;
12
+ let mockPlugin;
13
+ let mockConfigJsonManager;
14
+
15
+ beforeEach(() => {
16
+ sandbox = sinon.createSandbox();
17
+ logInfoStub = sandbox.stub(utils, 'logInfo');
18
+ logWarnStub = sandbox.stub(utils, 'logWarn');
19
+ logErrorStub = sandbox.stub(utils, 'logError');
20
+
21
+ // Clear plugins map before each test
22
+ plugins.clear();
23
+
24
+ pluginManager = PluginManager();
25
+
26
+ // Create mock plugin with synchronous methods
27
+ mockPlugin = {
28
+ init: sandbox.stub().resolves(true),
29
+ testHook: sandbox.stub().returns({ result: 'success' }),
30
+ errorHook: sandbox.stub().throws(new Error('Test error')),
31
+ nullHook: sandbox.stub().returns(null),
32
+ objectHook: sandbox.stub().returns({ key1: 'value1', key2: 'value2' })
33
+ };
34
+
35
+ // Create mock config manager
36
+ mockConfigJsonManager = {
37
+ getConfigByName: sandbox.stub().returns({ enabled: true })
38
+ };
39
+ });
40
+
41
+ afterEach(() => {
42
+ sandbox.restore();
43
+ });
44
+
45
+ describe('register', () => {
46
+ it('should register a plugin successfully', () => {
47
+ pluginManager.register('testPlugin', mockPlugin);
48
+
49
+ expect(plugins.has('testPlugin')).to.be.true;
50
+ expect(plugins.get('testPlugin')).to.equal(mockPlugin);
51
+ });
52
+
53
+ it('should log warning when registering a plugin with existing name', () => {
54
+ pluginManager.register('testPlugin', mockPlugin);
55
+ pluginManager.register('testPlugin', { init: () => {} });
56
+
57
+ expect(logWarnStub.calledOnce).to.be.true;
58
+ expect(logWarnStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Plugin testPlugin already registered`);
59
+ expect(plugins.get('testPlugin')).to.equal(mockPlugin); // Should keep the original plugin
60
+ });
61
+
62
+ it('should handle registering plugins with null or undefined values', () => {
63
+ pluginManager.register('nullPlugin', null);
64
+ pluginManager.register('undefinedPlugin', undefined);
65
+
66
+ expect(plugins.has('nullPlugin')).to.be.true;
67
+ expect(plugins.get('nullPlugin')).to.be.null;
68
+ expect(plugins.has('undefinedPlugin')).to.be.true;
69
+ expect(plugins.get('undefinedPlugin')).to.be.undefined;
70
+ });
71
+ });
72
+
73
+ // Test the unregister functionality through the initialize method
74
+ describe('unregister functionality', () => {
75
+ it('should unregister plugins when initialization fails', async () => {
76
+ const failingPlugin = {
77
+ init: sandbox.stub().resolves(false)
78
+ };
79
+
80
+ pluginManager.register('failingPlugin', failingPlugin);
81
+
82
+ await pluginManager.initialize(mockConfigJsonManager);
83
+
84
+ // Verify plugin was removed
85
+ expect(plugins.has('failingPlugin')).to.be.false;
86
+ expect(logInfoStub.calledOnce).to.be.true;
87
+ expect(logInfoStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Unregistering plugin failingPlugin`);
88
+ });
89
+
90
+ it('should not unregister plugins when initialization succeeds', async () => {
91
+ pluginManager.register('testPlugin', mockPlugin);
92
+
93
+ await pluginManager.initialize(mockConfigJsonManager);
94
+
95
+ // Verify plugin was not removed
96
+ expect(plugins.has('testPlugin')).to.be.true;
97
+ expect(logInfoStub.called).to.be.false;
98
+ });
99
+
100
+ it('should handle multiple plugins with some failing initialization', async () => {
101
+ const failingPlugin = {
102
+ init: sandbox.stub().resolves(false)
103
+ };
104
+
105
+ pluginManager.register('failingPlugin', failingPlugin);
106
+ pluginManager.register('testPlugin', mockPlugin);
107
+
108
+ await pluginManager.initialize(mockConfigJsonManager);
109
+
110
+ // Verify only failing plugin was removed
111
+ expect(plugins.has('failingPlugin')).to.be.false;
112
+ expect(plugins.has('testPlugin')).to.be.true;
113
+ expect(logInfoStub.calledOnce).to.be.true;
114
+ expect(logInfoStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Unregistering plugin failingPlugin`);
115
+ });
116
+ });
117
+
118
+ describe('initialize', () => {
119
+ it('should initialize all registered plugins', async () => {
120
+ pluginManager.register('testPlugin1', mockPlugin);
121
+
122
+ const anotherPlugin = {
123
+ init: sandbox.stub().resolves(true)
124
+ };
125
+ pluginManager.register('testPlugin2', anotherPlugin);
126
+
127
+ await pluginManager.initialize(mockConfigJsonManager);
128
+
129
+ expect(mockPlugin.init.calledOnce).to.be.true;
130
+ expect(mockPlugin.init.firstCall.args[0]).to.equal('testPlugin1');
131
+ expect(mockPlugin.init.firstCall.args[1]).to.equal(mockConfigJsonManager);
132
+
133
+ expect(anotherPlugin.init.calledOnce).to.be.true;
134
+ expect(anotherPlugin.init.firstCall.args[0]).to.equal('testPlugin2');
135
+ expect(anotherPlugin.init.firstCall.args[1]).to.equal(mockConfigJsonManager);
136
+ });
137
+
138
+ it('should unregister plugin if initialization fails', async () => {
139
+ const failingPlugin = {
140
+ init: sandbox.stub().resolves(false)
141
+ };
142
+
143
+ pluginManager.register('failingPlugin', failingPlugin);
144
+ pluginManager.register('testPlugin', mockPlugin);
145
+
146
+ await pluginManager.initialize(mockConfigJsonManager);
147
+
148
+ expect(plugins.has('failingPlugin')).to.be.false;
149
+ expect(plugins.has('testPlugin')).to.be.true;
150
+ expect(logInfoStub.calledOnce).to.be.true;
151
+ expect(logInfoStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Unregistering plugin failingPlugin`);
152
+ });
153
+
154
+ it('should handle plugins without init method', async () => {
155
+ const pluginWithoutInit = {
156
+ testHook: sandbox.stub().returns({ result: 'success' })
157
+ };
158
+
159
+ pluginManager.register('pluginWithoutInit', pluginWithoutInit);
160
+ pluginManager.register('testPlugin', mockPlugin);
161
+
162
+ await pluginManager.initialize(mockConfigJsonManager);
163
+
164
+ expect(plugins.has('pluginWithoutInit')).to.be.true;
165
+ expect(plugins.has('testPlugin')).to.be.true;
166
+ expect(mockPlugin.init.calledOnce).to.be.true;
167
+ });
168
+
169
+ it('should handle rejected promises during initialization', async () => {
170
+ const rejectingPlugin = {
171
+ init: sandbox.stub().rejects(new Error('Initialization error'))
172
+ };
173
+
174
+ pluginManager.register('rejectingPlugin', rejectingPlugin);
175
+ pluginManager.register('testPlugin', mockPlugin);
176
+
177
+ try {
178
+ await pluginManager.initialize(mockConfigJsonManager);
179
+ // If we get here without an error being thrown, the test should fail
180
+ expect.fail('Expected initialize to throw an error');
181
+ } catch (e) {
182
+ // Expected to catch the error
183
+ expect(e.message).to.equal('Initialization error');
184
+ }
185
+
186
+ // The plugin should still be registered since the unregister happens only on false return
187
+ expect(plugins.has('rejectingPlugin')).to.be.true;
188
+ expect(plugins.has('testPlugin')).to.be.true;
189
+ });
190
+
191
+ it('should handle null or undefined configJsonManager', async () => {
192
+ pluginManager.register('testPlugin', mockPlugin);
193
+
194
+ await pluginManager.initialize(null);
195
+
196
+ expect(mockPlugin.init.calledOnce).to.be.true;
197
+ expect(mockPlugin.init.firstCall.args[0]).to.equal('testPlugin');
198
+ expect(mockPlugin.init.firstCall.args[1]).to.be.null;
199
+
200
+ // Reset for next test
201
+ mockPlugin.init.reset();
202
+
203
+ await pluginManager.initialize(undefined);
204
+
205
+ expect(mockPlugin.init.calledOnce).to.be.true;
206
+ expect(mockPlugin.init.firstCall.args[0]).to.equal('testPlugin');
207
+ expect(mockPlugin.init.firstCall.args[1]).to.be.undefined;
208
+ });
209
+ });
210
+
211
+ describe('executeHook', () => {
212
+ beforeEach(() => {
213
+ pluginManager.register('testPlugin', mockPlugin);
214
+ });
215
+
216
+ it('should execute hook on all registered plugins', () => {
217
+ const results = pluginManager.executeHook('testHook', 'arg1', 'arg2');
218
+
219
+ expect(mockPlugin.testHook.calledOnce).to.be.true;
220
+ expect(mockPlugin.testHook.firstCall.args[0]).to.equal('arg1');
221
+ expect(mockPlugin.testHook.firstCall.args[1]).to.equal('arg2');
222
+ expect(results).to.deep.equal({ result: 'success' });
223
+ });
224
+
225
+ it('should handle errors during hook execution', () => {
226
+ const results = pluginManager.executeHook('errorHook');
227
+
228
+ expect(mockPlugin.errorHook.calledOnce).to.be.true;
229
+ expect(logErrorStub.calledOnce).to.be.true;
230
+ expect(logErrorStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Error executing hook errorHook in plugin testPlugin: Test error`);
231
+ expect(results).to.deep.equal({});
232
+ });
233
+
234
+ it('should skip null or undefined results', () => {
235
+ const results = pluginManager.executeHook('nullHook');
236
+
237
+ expect(mockPlugin.nullHook.calledOnce).to.be.true;
238
+ expect(results).to.deep.equal({});
239
+ });
240
+
241
+ it('should merge results from multiple plugins', () => {
242
+ const anotherPlugin = {
243
+ testHook: sandbox.stub().returns({ key3: 'value3', key4: 'value4' })
244
+ };
245
+
246
+ pluginManager.register('anotherPlugin', anotherPlugin);
247
+
248
+ const results = pluginManager.executeHook('testHook');
249
+
250
+ expect(mockPlugin.testHook.calledOnce).to.be.true;
251
+ expect(anotherPlugin.testHook.calledOnce).to.be.true;
252
+ expect(results).to.deep.equal({
253
+ result: 'success',
254
+ key3: 'value3',
255
+ key4: 'value4'
256
+ });
257
+ });
258
+
259
+ it('should handle non-object results', () => {
260
+ mockPlugin.testHook = sandbox.stub().returns('string result');
261
+
262
+ const results = pluginManager.executeHook('testHook');
263
+
264
+ expect(mockPlugin.testHook.calledOnce).to.be.true;
265
+ expect(results).to.deep.equal({});
266
+ });
267
+
268
+ it('should handle plugins without the requested hook', () => {
269
+ const results = pluginManager.executeHook('nonExistentHook');
270
+
271
+ expect(results).to.deep.equal({});
272
+ });
273
+
274
+ it('should merge results from multiple object hooks', () => {
275
+ const results = pluginManager.executeHook('objectHook');
276
+
277
+ expect(mockPlugin.objectHook.calledOnce).to.be.true;
278
+ expect(results).to.deep.equal({
279
+ key1: 'value1',
280
+ key2: 'value2'
281
+ });
282
+ });
283
+
284
+ it('should handle errors during plugin filtering', () => {
285
+ // Create a scenario where Array.from throws an error
286
+ const originalArrayFrom = Array.from;
287
+ Array.from = sandbox.stub().throws(new Error('Array.from error'));
288
+
289
+ const results = pluginManager.executeHook('testHook');
290
+
291
+ expect(logErrorStub.calledOnce).to.be.true;
292
+ expect(logErrorStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Error in executeHookSync: Array.from error`);
293
+ expect(results).to.deep.equal({});
294
+
295
+ // Restore original Array.from
296
+ Array.from = originalArrayFrom;
297
+ });
298
+
299
+ it('should handle synchronous hook functions', () => {
300
+ const syncPlugin = {
301
+ syncHook: sandbox.stub().returns({ syncKey: 'syncValue' })
302
+ };
303
+
304
+ pluginManager.register('syncPlugin', syncPlugin);
305
+
306
+ const results = pluginManager.executeHook('syncHook');
307
+
308
+ expect(syncPlugin.syncHook.calledOnce).to.be.true;
309
+ expect(results).to.deep.equal({ syncKey: 'syncValue' });
310
+ });
311
+
312
+ it('should handle overwriting properties when merging results', () => {
313
+ const plugin1 = {
314
+ duplicateHook: sandbox.stub().returns({ key: 'value1' })
315
+ };
316
+
317
+ const plugin2 = {
318
+ duplicateHook: sandbox.stub().returns({ key: 'value2' })
319
+ };
320
+
321
+ pluginManager.register('plugin1', plugin1);
322
+ pluginManager.register('plugin2', plugin2);
323
+
324
+ const results = pluginManager.executeHook('duplicateHook');
325
+
326
+ expect(plugin1.duplicateHook.calledOnce).to.be.true;
327
+ expect(plugin2.duplicateHook.calledOnce).to.be.true;
328
+
329
+ // The last plugin's value should win in case of duplicate keys
330
+ expect(results).to.deep.equal({ key: 'value2' });
331
+ });
332
+
333
+ it('should handle empty plugins map', () => {
334
+ // Clear all plugins
335
+ plugins.clear();
336
+
337
+ const results = pluginManager.executeHook('testHook');
338
+
339
+ expect(results).to.deep.equal({});
340
+ });
341
+
342
+ it('should handle complex nested object results', () => {
343
+ const complexPlugin = {
344
+ complexHook: sandbox.stub().returns({
345
+ level1: {
346
+ level2: {
347
+ level3: 'deep value'
348
+ },
349
+ array: [1, 2, 3]
350
+ },
351
+ topLevel: 'top value'
352
+ })
353
+ };
354
+
355
+ pluginManager.register('complexPlugin', complexPlugin);
356
+
357
+ const results = pluginManager.executeHook('complexHook');
358
+
359
+ expect(complexPlugin.complexHook.calledOnce).to.be.true;
360
+ expect(results).to.deep.equal({
361
+ level1: {
362
+ level2: {
363
+ level3: 'deep value'
364
+ },
365
+ array: [1, 2, 3]
366
+ },
367
+ topLevel: 'top value'
368
+ });
369
+ });
370
+
371
+ it('should handle plugins that return promises', () => {
372
+ const promisePlugin = {
373
+ promiseHook: sandbox.stub().returns(Promise.resolve({ promiseKey: 'promiseValue' }))
374
+ };
375
+
376
+ pluginManager.register('promisePlugin', promisePlugin);
377
+
378
+ const results = pluginManager.executeHook('promiseHook');
379
+
380
+ // Since executeHook is synchronous, it should treat the promise as an object
381
+ expect(promisePlugin.promiseHook.calledOnce).to.be.true;
382
+ expect(results).to.deep.equal({});
383
+ });
384
+ });
385
+
386
+ describe('CONSTANTS', () => {
387
+ it('should have the correct LOG_PRE_FIX value', () => {
388
+ expect(CONSTANTS.LOG_PRE_FIX).to.equal('PubMatic-Plugin-Manager: ');
389
+ });
390
+
391
+ it('should be frozen', () => {
392
+ expect(Object.isFrozen(CONSTANTS)).to.be.true;
393
+ });
394
+
395
+ it('should not allow modification of constants', () => {
396
+ try {
397
+ CONSTANTS.LOG_PRE_FIX = 'Modified prefix';
398
+ // If we get here, the test should fail because the constant was modified
399
+ expect.fail('Expected an error when modifying frozen CONSTANTS');
400
+ } catch (e) {
401
+ // This is expected behavior
402
+ expect(e).to.be.an.instanceof(TypeError);
403
+ expect(CONSTANTS.LOG_PRE_FIX).to.equal('PubMatic-Plugin-Manager: ');
404
+ }
405
+ });
406
+ });
407
+
408
+ // Test browser compatibility
409
+ describe('browser compatibility', () => {
410
+ let originalMap;
411
+ let originalObjectEntries;
412
+ let originalObjectAssign;
413
+
414
+ beforeEach(() => {
415
+ // Store original implementations
416
+ originalMap = global.Map;
417
+ originalObjectEntries = Object.entries;
418
+ originalObjectAssign = Object.assign;
419
+ });
420
+
421
+ afterEach(() => {
422
+ // Restore original implementations
423
+ global.Map = originalMap;
424
+ Object.entries = originalObjectEntries;
425
+ Object.assign = originalObjectAssign;
426
+ });
427
+
428
+ it('should handle browser environments where Map is not available', function() {
429
+ // Skip this test if running in a real browser environment
430
+ if (typeof window !== 'undefined' && window.Map) {
431
+ this.skip();
432
+ return;
433
+ }
434
+
435
+ // Mock a browser environment where Map is not available
436
+ const MapBackup = global.Map;
437
+ global.Map = undefined;
438
+
439
+ try {
440
+ // This should not throw an error
441
+ expect(() => {
442
+ const pm = PluginManager();
443
+ pm.register('testPlugin', {});
444
+ }).to.not.throw();
445
+ } finally {
446
+ // Restore Map
447
+ global.Map = MapBackup;
448
+ }
449
+ });
450
+
451
+ it('should handle browser environments where Object.entries is not available', function() {
452
+ // Skip this test if running in a real browser environment
453
+ if (typeof window !== 'undefined') {
454
+ this.skip();
455
+ return;
456
+ }
457
+
458
+ // Mock a browser environment where Object.entries is not available
459
+ Object.entries = undefined;
460
+
461
+ // Register a plugin
462
+ pluginManager.register('testPlugin', mockPlugin);
463
+
464
+ // This should not throw an error
465
+ expect(() => {
466
+ pluginManager.executeHook('testHook');
467
+ }).to.not.throw();
468
+ });
469
+
470
+ it('should handle browser environments where Object.assign is not available', function() {
471
+ // Skip this test if running in a real browser environment
472
+ if (typeof window !== 'undefined') {
473
+ this.skip();
474
+ return;
475
+ }
476
+
477
+ // Mock a browser environment where Object.assign is not available
478
+ Object.assign = undefined;
479
+
480
+ // Register a plugin
481
+ pluginManager.register('testPlugin', mockPlugin);
482
+
483
+ // This should not throw an error
484
+ expect(() => {
485
+ pluginManager.executeHook('testHook');
486
+ }).to.not.throw();
487
+ });
488
+ });
489
+ });