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

prebid / Prebid.js / 19950878469

05 Dec 2025 02:41AM UTC coverage: 96.212% (+0.003%) from 96.209%
19950878469

push

github

web-flow
PubMatic RTD Provider: Dynamic timeout plugin & code refactoring (#14104)

* PubMatic RTD Provider: Plugin based architectural changes

* PubMatic RTD Provider: Minor changes to the Plugins

* PubMatic RTD Provider: Moved configJsonManager to the RTD provider

* PubMatic RTD Provider: Move bidderOptimisation to one file

* Adding testcases for floorProvider.js for floorPlugin

* PubMatic RTD provider: Update endpoint to actual after testing

* Adding test cases for floorProvider

* Adding test cases for config

* PubMatic RTD provider: Handle the getTargetting effectively

* Pubmatic RTD Provider: Handle null case

* Adding test cases for floorProviders

* fixing linting issues

* Fixing linting and other errors

* RTD provider: Update pubmaticRtdProvider.js

* RTD Provider: Update pubmaticRtdProvider_spec.js

* RTD Provider: Dynamic Timeout Plugin

* RTD Provider: Dynamic Timeout Plugin Updated with new schema

* RTD Provider: Plugin changes

* RTD Provider: Dynamic Timeout fixes

* RTD Provider: Plugin changes

* RTD Provider: Plugin changes

* RTD Provider: Plugin changes

(cherry picked from commit d440ca6ae)

* RTD Provider: Plugin changes for Dynamic Timeout

* RTD PRovider: Test cases : PubmaticUTils test cases and

* RTD PRovider: Plugin Manager test cases

* RTD Provider: Lint issues and Spec removed

* RTD Provider: Dynamic TImeout Test cases

* Add unit test cases for unifiedPricingRule.js

* RTD Provider: Fixes for Floor and UPR working

* RTD Provider: test cases updated

* Dynamic timeout comments added

* Remove bidder optimization

* PubMatic RTD Provider: Handle Empty object case for floor data

* RTD Provider: Update the priority for dynamic timeout rules

* Dynamic timeout: handle default skipRate

* Dynamic Timeout - Handle Negative values gracefully with 500 Default threshold

* Review comments resolved

* Comments added

* Update pubmaticRtdProvider_Example.html

* Fix lint & test cases

* Update de... (continued)

53903 of 66028 branches covered (81.64%)

1396 of 1453 new or added lines in 15 files covered. (96.08%)

24 existing lines in 7 files now uncovered.

205952 of 214061 relevant lines covered (96.21%)

71.49 hits per line

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

91.15
/test/spec/libraries/pubmaticUtils/plugins/pluginManager_spec.js
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', () => {
1✔
7
  let sandbox;
8
  let logInfoStub;
9
  let logWarnStub;
10
  let logErrorStub;
11
  let pluginManager;
12
  let mockPlugin;
13
  let mockConfigJsonManager;
14

15
  beforeEach(() => {
1✔
16
    sandbox = sinon.createSandbox();
30✔
17
    logInfoStub = sandbox.stub(utils, 'logInfo');
30✔
18
    logWarnStub = sandbox.stub(utils, 'logWarn');
30✔
19
    logErrorStub = sandbox.stub(utils, 'logError');
30✔
20

21
    // Clear plugins map before each test
22
    plugins.clear();
30✔
23

24
    pluginManager = PluginManager();
30✔
25

26
    // Create mock plugin with synchronous methods
27
    mockPlugin = {
30✔
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 = {
30✔
37
      getConfigByName: sandbox.stub().returns({ enabled: true })
38
    };
39
  });
40

41
  afterEach(() => {
1✔
42
    sandbox.restore();
30✔
43
  });
44

45
  describe('register', () => {
1✔
46
    it('should register a plugin successfully', () => {
1✔
47
      pluginManager.register('testPlugin', mockPlugin);
1✔
48

49
      expect(plugins.has('testPlugin')).to.be.true;
1✔
50
      expect(plugins.get('testPlugin')).to.equal(mockPlugin);
1✔
51
    });
52

53
    it('should log warning when registering a plugin with existing name', () => {
1✔
54
      pluginManager.register('testPlugin', mockPlugin);
1✔
55
      pluginManager.register('testPlugin', { init: () => {} });
1✔
56

57
      expect(logWarnStub.calledOnce).to.be.true;
1✔
58
      expect(logWarnStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Plugin testPlugin already registered`);
1✔
59
      expect(plugins.get('testPlugin')).to.equal(mockPlugin); // Should keep the original plugin
1✔
60
    });
61

62
    it('should handle registering plugins with null or undefined values', () => {
1✔
63
      pluginManager.register('nullPlugin', null);
1✔
64
      pluginManager.register('undefinedPlugin', undefined);
1✔
65

66
      expect(plugins.has('nullPlugin')).to.be.true;
1✔
67
      expect(plugins.get('nullPlugin')).to.be.null;
1✔
68
      expect(plugins.has('undefinedPlugin')).to.be.true;
1✔
69
      expect(plugins.get('undefinedPlugin')).to.be.undefined;
1✔
70
    });
71
  });
72

73
  // Test the unregister functionality through the initialize method
74
  describe('unregister functionality', () => {
1✔
75
    it('should unregister plugins when initialization fails', async () => {
1✔
76
      const failingPlugin = {
1✔
77
        init: sandbox.stub().resolves(false)
78
      };
79

80
      pluginManager.register('failingPlugin', failingPlugin);
1✔
81

82
      await pluginManager.initialize(mockConfigJsonManager);
1✔
83

84
      // Verify plugin was removed
85
      expect(plugins.has('failingPlugin')).to.be.false;
1✔
86
      expect(logInfoStub.calledOnce).to.be.true;
1✔
87
      expect(logInfoStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Unregistering plugin failingPlugin`);
1✔
88
    });
89

90
    it('should not unregister plugins when initialization succeeds', async () => {
1✔
91
      pluginManager.register('testPlugin', mockPlugin);
1✔
92

93
      await pluginManager.initialize(mockConfigJsonManager);
1✔
94

95
      // Verify plugin was not removed
96
      expect(plugins.has('testPlugin')).to.be.true;
1✔
97
      expect(logInfoStub.called).to.be.false;
1✔
98
    });
99

100
    it('should handle multiple plugins with some failing initialization', async () => {
1✔
101
      const failingPlugin = {
1✔
102
        init: sandbox.stub().resolves(false)
103
      };
104

105
      pluginManager.register('failingPlugin', failingPlugin);
1✔
106
      pluginManager.register('testPlugin', mockPlugin);
1✔
107

108
      await pluginManager.initialize(mockConfigJsonManager);
1✔
109

110
      // Verify only failing plugin was removed
111
      expect(plugins.has('failingPlugin')).to.be.false;
1✔
112
      expect(plugins.has('testPlugin')).to.be.true;
1✔
113
      expect(logInfoStub.calledOnce).to.be.true;
1✔
114
      expect(logInfoStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Unregistering plugin failingPlugin`);
1✔
115
    });
116
  });
117

118
  describe('initialize', () => {
1✔
119
    it('should initialize all registered plugins', async () => {
1✔
120
      pluginManager.register('testPlugin1', mockPlugin);
1✔
121

122
      const anotherPlugin = {
1✔
123
        init: sandbox.stub().resolves(true)
124
      };
125
      pluginManager.register('testPlugin2', anotherPlugin);
1✔
126

127
      await pluginManager.initialize(mockConfigJsonManager);
1✔
128

129
      expect(mockPlugin.init.calledOnce).to.be.true;
1✔
130
      expect(mockPlugin.init.firstCall.args[0]).to.equal('testPlugin1');
1✔
131
      expect(mockPlugin.init.firstCall.args[1]).to.equal(mockConfigJsonManager);
1✔
132

133
      expect(anotherPlugin.init.calledOnce).to.be.true;
1✔
134
      expect(anotherPlugin.init.firstCall.args[0]).to.equal('testPlugin2');
1✔
135
      expect(anotherPlugin.init.firstCall.args[1]).to.equal(mockConfigJsonManager);
1✔
136
    });
137

138
    it('should unregister plugin if initialization fails', async () => {
1✔
139
      const failingPlugin = {
1✔
140
        init: sandbox.stub().resolves(false)
141
      };
142

143
      pluginManager.register('failingPlugin', failingPlugin);
1✔
144
      pluginManager.register('testPlugin', mockPlugin);
1✔
145

146
      await pluginManager.initialize(mockConfigJsonManager);
1✔
147

148
      expect(plugins.has('failingPlugin')).to.be.false;
1✔
149
      expect(plugins.has('testPlugin')).to.be.true;
1✔
150
      expect(logInfoStub.calledOnce).to.be.true;
1✔
151
      expect(logInfoStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Unregistering plugin failingPlugin`);
1✔
152
    });
153

154
    it('should handle plugins without init method', async () => {
1✔
155
      const pluginWithoutInit = {
1✔
156
        testHook: sandbox.stub().returns({ result: 'success' })
157
      };
158

159
      pluginManager.register('pluginWithoutInit', pluginWithoutInit);
1✔
160
      pluginManager.register('testPlugin', mockPlugin);
1✔
161

162
      await pluginManager.initialize(mockConfigJsonManager);
1✔
163

164
      expect(plugins.has('pluginWithoutInit')).to.be.true;
1✔
165
      expect(plugins.has('testPlugin')).to.be.true;
1✔
166
      expect(mockPlugin.init.calledOnce).to.be.true;
1✔
167
    });
168

169
    it('should handle rejected promises during initialization', async () => {
1✔
170
      const rejectingPlugin = {
1✔
171
        init: sandbox.stub().rejects(new Error('Initialization error'))
172
      };
173

174
      pluginManager.register('rejectingPlugin', rejectingPlugin);
1✔
175
      pluginManager.register('testPlugin', mockPlugin);
1✔
176

177
      try {
1✔
178
        await pluginManager.initialize(mockConfigJsonManager);
1✔
179
        // If we get here without an error being thrown, the test should fail
NEW
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');
1✔
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;
1✔
188
      expect(plugins.has('testPlugin')).to.be.true;
1✔
189
    });
190

191
    it('should handle null or undefined configJsonManager', async () => {
1✔
192
      pluginManager.register('testPlugin', mockPlugin);
1✔
193

194
      await pluginManager.initialize(null);
1✔
195

196
      expect(mockPlugin.init.calledOnce).to.be.true;
1✔
197
      expect(mockPlugin.init.firstCall.args[0]).to.equal('testPlugin');
1✔
198
      expect(mockPlugin.init.firstCall.args[1]).to.be.null;
1✔
199

200
      // Reset for next test
201
      mockPlugin.init.reset();
1✔
202

203
      await pluginManager.initialize(undefined);
1✔
204

205
      expect(mockPlugin.init.calledOnce).to.be.true;
1✔
206
      expect(mockPlugin.init.firstCall.args[0]).to.equal('testPlugin');
1✔
207
      expect(mockPlugin.init.firstCall.args[1]).to.be.undefined;
1✔
208
    });
209
  });
210

211
  describe('executeHook', () => {
1✔
212
    beforeEach(() => {
1✔
213
      pluginManager.register('testPlugin', mockPlugin);
13✔
214
    });
215

216
    it('should execute hook on all registered plugins', () => {
1✔
217
      const results = pluginManager.executeHook('testHook', 'arg1', 'arg2');
1✔
218

219
      expect(mockPlugin.testHook.calledOnce).to.be.true;
1✔
220
      expect(mockPlugin.testHook.firstCall.args[0]).to.equal('arg1');
1✔
221
      expect(mockPlugin.testHook.firstCall.args[1]).to.equal('arg2');
1✔
222
      expect(results).to.deep.equal({ result: 'success' });
1✔
223
    });
224

225
    it('should handle errors during hook execution', () => {
1✔
226
      const results = pluginManager.executeHook('errorHook');
1✔
227

228
      expect(mockPlugin.errorHook.calledOnce).to.be.true;
1✔
229
      expect(logErrorStub.calledOnce).to.be.true;
1✔
230
      expect(logErrorStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Error executing hook errorHook in plugin testPlugin: Test error`);
1✔
231
      expect(results).to.deep.equal({});
1✔
232
    });
233

234
    it('should skip null or undefined results', () => {
1✔
235
      const results = pluginManager.executeHook('nullHook');
1✔
236

237
      expect(mockPlugin.nullHook.calledOnce).to.be.true;
1✔
238
      expect(results).to.deep.equal({});
1✔
239
    });
240

241
    it('should merge results from multiple plugins', () => {
1✔
242
      const anotherPlugin = {
1✔
243
        testHook: sandbox.stub().returns({ key3: 'value3', key4: 'value4' })
244
      };
245

246
      pluginManager.register('anotherPlugin', anotherPlugin);
1✔
247

248
      const results = pluginManager.executeHook('testHook');
1✔
249

250
      expect(mockPlugin.testHook.calledOnce).to.be.true;
1✔
251
      expect(anotherPlugin.testHook.calledOnce).to.be.true;
1✔
252
      expect(results).to.deep.equal({
1✔
253
        result: 'success',
254
        key3: 'value3',
255
        key4: 'value4'
256
      });
257
    });
258

259
    it('should handle non-object results', () => {
1✔
260
      mockPlugin.testHook = sandbox.stub().returns('string result');
1✔
261

262
      const results = pluginManager.executeHook('testHook');
1✔
263

264
      expect(mockPlugin.testHook.calledOnce).to.be.true;
1✔
265
      expect(results).to.deep.equal({});
1✔
266
    });
267

268
    it('should handle plugins without the requested hook', () => {
1✔
269
      const results = pluginManager.executeHook('nonExistentHook');
1✔
270

271
      expect(results).to.deep.equal({});
1✔
272
    });
273

274
    it('should merge results from multiple object hooks', () => {
1✔
275
      const results = pluginManager.executeHook('objectHook');
1✔
276

277
      expect(mockPlugin.objectHook.calledOnce).to.be.true;
1✔
278
      expect(results).to.deep.equal({
1✔
279
        key1: 'value1',
280
        key2: 'value2'
281
      });
282
    });
283

284
    it('should handle errors during plugin filtering', () => {
1✔
285
      // Create a scenario where Array.from throws an error
286
      const originalArrayFrom = Array.from;
1✔
287
      Array.from = sandbox.stub().throws(new Error('Array.from error'));
1✔
288

289
      const results = pluginManager.executeHook('testHook');
1✔
290

291
      expect(logErrorStub.calledOnce).to.be.true;
1✔
292
      expect(logErrorStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Error in executeHookSync: Array.from error`);
1✔
293
      expect(results).to.deep.equal({});
1✔
294

295
      // Restore original Array.from
296
      Array.from = originalArrayFrom;
1✔
297
    });
298

299
    it('should handle synchronous hook functions', () => {
1✔
300
      const syncPlugin = {
1✔
301
        syncHook: sandbox.stub().returns({ syncKey: 'syncValue' })
302
      };
303

304
      pluginManager.register('syncPlugin', syncPlugin);
1✔
305

306
      const results = pluginManager.executeHook('syncHook');
1✔
307

308
      expect(syncPlugin.syncHook.calledOnce).to.be.true;
1✔
309
      expect(results).to.deep.equal({ syncKey: 'syncValue' });
1✔
310
    });
311

312
    it('should handle overwriting properties when merging results', () => {
1✔
313
      const plugin1 = {
1✔
314
        duplicateHook: sandbox.stub().returns({ key: 'value1' })
315
      };
316

317
      const plugin2 = {
1✔
318
        duplicateHook: sandbox.stub().returns({ key: 'value2' })
319
      };
320

321
      pluginManager.register('plugin1', plugin1);
1✔
322
      pluginManager.register('plugin2', plugin2);
1✔
323

324
      const results = pluginManager.executeHook('duplicateHook');
1✔
325

326
      expect(plugin1.duplicateHook.calledOnce).to.be.true;
1✔
327
      expect(plugin2.duplicateHook.calledOnce).to.be.true;
1✔
328

329
      // The last plugin's value should win in case of duplicate keys
330
      expect(results).to.deep.equal({ key: 'value2' });
1✔
331
    });
332

333
    it('should handle empty plugins map', () => {
1✔
334
      // Clear all plugins
335
      plugins.clear();
1✔
336

337
      const results = pluginManager.executeHook('testHook');
1✔
338

339
      expect(results).to.deep.equal({});
1✔
340
    });
341

342
    it('should handle complex nested object results', () => {
1✔
343
      const complexPlugin = {
1✔
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);
1✔
356

357
      const results = pluginManager.executeHook('complexHook');
1✔
358

359
      expect(complexPlugin.complexHook.calledOnce).to.be.true;
1✔
360
      expect(results).to.deep.equal({
1✔
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', () => {
1✔
372
      const promisePlugin = {
1✔
373
        promiseHook: sandbox.stub().returns(Promise.resolve({ promiseKey: 'promiseValue' }))
374
      };
375

376
      pluginManager.register('promisePlugin', promisePlugin);
1✔
377

378
      const results = pluginManager.executeHook('promiseHook');
1✔
379

380
      // Since executeHook is synchronous, it should treat the promise as an object
381
      expect(promisePlugin.promiseHook.calledOnce).to.be.true;
1✔
382
      expect(results).to.deep.equal({});
1✔
383
    });
384
  });
385

386
  describe('CONSTANTS', () => {
1✔
387
    it('should have the correct LOG_PRE_FIX value', () => {
1✔
388
      expect(CONSTANTS.LOG_PRE_FIX).to.equal('PubMatic-Plugin-Manager: ');
1✔
389
    });
390

391
    it('should be frozen', () => {
1✔
392
      expect(Object.isFrozen(CONSTANTS)).to.be.true;
1✔
393
    });
394

395
    it('should not allow modification of constants', () => {
1✔
396
      try {
1✔
397
        CONSTANTS.LOG_PRE_FIX = 'Modified prefix';
1✔
398
        // If we get here, the test should fail because the constant was modified
NEW
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);
1✔
403
        expect(CONSTANTS.LOG_PRE_FIX).to.equal('PubMatic-Plugin-Manager: ');
1✔
404
      }
405
    });
406
  });
407

408
  // Test browser compatibility
409
  describe('browser compatibility', () => {
1✔
410
    let originalMap;
411
    let originalObjectEntries;
412
    let originalObjectAssign;
413

414
    beforeEach(() => {
1✔
415
      // Store original implementations
416
      originalMap = global.Map;
3✔
417
      originalObjectEntries = Object.entries;
3✔
418
      originalObjectAssign = Object.assign;
3✔
419
    });
420

421
    afterEach(() => {
1✔
422
      // Restore original implementations
423
      global.Map = originalMap;
3✔
424
      Object.entries = originalObjectEntries;
3✔
425
      Object.assign = originalObjectAssign;
3✔
426
    });
427

428
    it('should handle browser environments where Map is not available', function() {
1✔
429
      // Skip this test if running in a real browser environment
430
      if (typeof window !== 'undefined' && window.Map) {
1✔
431
        this.skip();
1✔
NEW
432
        return;
×
433
      }
434

435
      // Mock a browser environment where Map is not available
NEW
436
      const MapBackup = global.Map;
×
NEW
437
      global.Map = undefined;
×
438

NEW
439
      try {
×
440
        // This should not throw an error
NEW
441
        expect(() => {
×
NEW
442
          const pm = PluginManager();
×
NEW
443
          pm.register('testPlugin', {});
×
444
        }).to.not.throw();
445
      } finally {
446
        // Restore Map
NEW
447
        global.Map = MapBackup;
×
448
      }
449
    });
450

451
    it('should handle browser environments where Object.entries is not available', function() {
1✔
452
      // Skip this test if running in a real browser environment
453
      if (typeof window !== 'undefined') {
1✔
454
        this.skip();
1✔
NEW
455
        return;
×
456
      }
457

458
      // Mock a browser environment where Object.entries is not available
NEW
459
      Object.entries = undefined;
×
460

461
      // Register a plugin
NEW
462
      pluginManager.register('testPlugin', mockPlugin);
×
463

464
      // This should not throw an error
NEW
465
      expect(() => {
×
NEW
466
        pluginManager.executeHook('testHook');
×
467
      }).to.not.throw();
468
    });
469

470
    it('should handle browser environments where Object.assign is not available', function() {
1✔
471
      // Skip this test if running in a real browser environment
472
      if (typeof window !== 'undefined') {
1✔
473
        this.skip();
1✔
NEW
474
        return;
×
475
      }
476

477
      // Mock a browser environment where Object.assign is not available
NEW
478
      Object.assign = undefined;
×
479

480
      // Register a plugin
NEW
481
      pluginManager.register('testPlugin', mockPlugin);
×
482

483
      // This should not throw an error
NEW
484
      expect(() => {
×
NEW
485
        pluginManager.executeHook('testHook');
×
486
      }).to.not.throw();
487
    });
488
  });
489
});
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc