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

prebid / Prebid.js / 16621163569

30 Jul 2025 11:22AM UTC coverage: 96.257% (-0.005%) from 96.262%
16621163569

push

github

web-flow
fix video player size (#13691)

39380 of 48412 branches covered (81.34%)

9 of 9 new or added lines in 2 files covered. (100.0%)

32 existing lines in 16 files now uncovered.

195047 of 202632 relevant lines covered (96.26%)

83.45 hits per line

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

99.75
/test/spec/modules/tcfControl_spec.js
1
import {
2
  accessDeviceRule,
3
  ACTIVE_RULES,
4
  enrichEidsRule,
5
  fetchBidsRule,
6
  getGvlid,
7
  getGvlidFromAnalyticsAdapter,
8
  reportAnalyticsRule,
9
  setEnforcementConfig,
10
  STRICT_STORAGE_ENFORCEMENT,
11
  syncUserRule,
12
  transmitEidsRule,
13
  transmitPreciseGeoRule,
14
  ufpdRule,
15
  validateRules
16
} from 'modules/tcfControl.js';
17
import {config} from 'src/config.js';
18
import adapterManager, {gdprDataHandler} from 'src/adapterManager.js';
19
import * as utils from 'src/utils.js';
20
import {
21
  MODULE_TYPE_ANALYTICS,
22
  MODULE_TYPE_BIDDER,
23
  MODULE_TYPE_PREBID,
24
  MODULE_TYPE_UID
25
} from '../../../src/activities/modules.js';
26
import * as events from 'src/events.js';
27
import 'modules/appnexusBidAdapter.js'; // some tests expect this to be in the adapter registry
28
import {requestBids} from 'src/prebid.js';
29
import {hook} from '../../../src/hook.js';
30
import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../../../src/consentHandler.js';
31
import {activityParams} from '../../../src/activities/activityParams.js';
32
import { checkIfCredentialsAllowed } from '../../../modules/tcfControl.js';
33

34
describe('gdpr enforcement', function () {
1✔
35
  let nextFnSpy;
36
  let logWarnSpy;
37
  let gdprDataHandlerStub;
38
  const staticConfig = {
1✔
39
    cmpApi: 'static',
40
    timeout: 7500,
41
    consentData: {
42
      getTCData: {
43
        'tcString': 'COuqj-POu90rDBcBkBENAZCgAPzAAAPAACiQFwwBAABAA1ADEAbQC4YAYAAgAxAG0A',
44
        'cmpId': 92,
45
        'cmpVersion': 100,
46
        'tcfPolicyVersion': 2,
47
        'gdprApplies': true,
48
        'isServiceSpecific': true,
49
        'useNonStandardStacks': false,
50
        'purposeOneTreatment': false,
51
        'publisherCC': 'US',
52
        'cmpStatus': 'loaded',
53
        'eventStatus': 'tcloaded',
54
        'outOfBand': {
55
          'allowedVendors': {},
56
          'discloseVendors': {}
57
        },
58
        'purpose': {
59
          'consents': {
60
            '1': true,
61
            '2': true,
62
            '3': true,
63
            '7': true
64
          },
65
          'legitimateInterests': {
66
            '1': false,
67
            '2': true,
68
            '3': false
69
          }
70
        },
71
        'vendor': {
72
          'consents': {
73
            '1': true,
74
            '2': true,
75
            '3': false,
76
            '4': true,
77
            '5': false
78
          },
79
          'legitimateInterests': {
80
            '1': false,
81
            '2': true,
82
            '3': false,
83
            '4': false,
84
            '5': false
85
          }
86
        },
87
        'specialFeatureOptins': {
88
          '1': false,
89
          '2': false
90
        },
91
        'restrictions': {},
92
        'publisher': {
93
          'consents': {
94
            '1': false,
95
            '2': false,
96
            '3': false
97
          },
98
          'legitimateInterests': {
99
            '1': false,
100
            '2': false,
101
            '3': false
102
          },
103
          'customPurpose': {
104
            'consents': {},
105
            'legitimateInterests': {}
106
          }
107
        }
108
      }
109
    }
110
  };
111
  let gvlids, sandbox;
112

113
  function setupConsentData({gdprApplies = true, apiVersion = 2} = {}) {
52✔
114
    const cd = utils.deepClone(staticConfig);
52✔
115
    const consent = {
52✔
116
      vendorData: cd.consentData.getTCData,
117
      gdprApplies,
118
      apiVersion
119
    };
120
    sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(() => consent)
124✔
121
    return consent;
52✔
122
  }
123

124
  before(() => {
1✔
125
    hook.ready();
1✔
126
  });
127

128
  after(function () {
1✔
129
    requestBids.getHooks().remove();
1✔
130
  })
131

132
  function expectAllow(allow, ruleResult) {
133
    if (allow) {
54✔
134
      expect(ruleResult).to.not.exist;
35✔
135
    } else {
136
      sinon.assert.match(ruleResult, {allow: false});
19✔
137
    }
138
  }
139

140
  beforeEach(() => {
1✔
141
    sandbox = sinon.createSandbox();
148✔
142
    gvlids = {};
148✔
143
    sandbox.stub(GDPR_GVLIDS, 'get').callsFake((name) => ({gvlid: gvlids[name], modules: {}}));
148✔
144
  });
145

146
  afterEach(() => {
1✔
147
    sandbox.restore();
148✔
148
  })
149

150
  describe('deviceAccessRule', () => {
1✔
151
    afterEach(() => {
1✔
152
      config.resetConfig();
5✔
153
    });
154

155
    it('should not check for consent when enforcePurpose and enforceVendor are false', function () {
1✔
156
      Object.assign(gvlids, {
1✔
157
        appnexus: 1,
158
        rubicon: 5
159
      });
160
      setEnforcementConfig({
1✔
161
        gdpr: {
162
          rules: [{
163
            purpose: 'storage',
164
            enforcePurpose: false,
165
            enforceVendor: false,
166
            vendorExceptions: ['appnexus']
167
          }]
168
        }
169
      });
170
      setupConsentData();
1✔
171
      ['appnexus', 'rubicon'].forEach(bidder => expectAllow(true, accessDeviceRule(activityParams(MODULE_TYPE_BIDDER, bidder))));
2✔
172
    });
173

174
    it('should check consent for all vendors when enforcePurpose and enforceVendor are true', function () {
1✔
175
      Object.assign(gvlids, {
1✔
176
        appnexus: 1,
177
        rubicon: 3
178
      });
179
      setEnforcementConfig({
1✔
180
        gdpr: {
181
          rules: [{
182
            purpose: 'storage',
183
            enforcePurpose: true,
184
            enforceVendor: true,
185
          }]
186
        }
187
      });
188
      setupConsentData();
1✔
189
      Object.entries({
1✔
190
        appnexus: true,
191
        rubicon: false
192
      }).forEach(([bidder, isAllowed]) => {
2✔
193
        expectAllow(isAllowed, accessDeviceRule(activityParams(MODULE_TYPE_BIDDER, bidder)));
2✔
194
      })
195
    });
196

197
    it('should allow device access when gdprApplies is false and hasDeviceAccess flag is true', function () {
1✔
198
      gvlids.appnexus = 1;
1✔
199
      setEnforcementConfig({
1✔
200
        gdpr: {
201
          rules: [{
202
            purpose: 'storage',
203
            enforcePurpose: true,
204
            enforceVendor: true,
205
            vendorExceptions: []
206
          }]
207
        }
208
      });
209
      setupConsentData();
1✔
210
      expectAllow(true, accessDeviceRule(activityParams(MODULE_TYPE_BIDDER, 'appnexus')));
1✔
211
    });
212

213
    it('should use gvlMapping set by publisher', function() {
1✔
214
      config.setConfig({
1✔
215
        'gvlMapping': {
216
          'appnexus': 4
217
        }
218
      });
219
      setEnforcementConfig({
1✔
220
        gdpr: {
221
          rules: [{
222
            purpose: 'storage',
223
            enforcePurpose: true,
224
            enforceVendor: true,
225
            vendorExceptions: []
226
          }]
227
        }
228
      });
229
      setupConsentData();
1✔
230
      expectAllow(true, accessDeviceRule(activityParams(MODULE_TYPE_BIDDER, 'appnexus')));
1✔
231
    });
232

233
    it(`should not enforce consent for vendorless modules if ${STRICT_STORAGE_ENFORCEMENT} is not set`, () => {
1✔
234
      setEnforcementConfig({});
1✔
235
      setupConsentData();
1✔
236
      expectAllow(true, accessDeviceRule(activityParams(MODULE_TYPE_PREBID, 'mockCoreModule')));
1✔
237
    })
238
  });
239

240
  describe('syncUserRule', () => {
1✔
241
    it('should allow bidder to do user sync if consent is true', function () {
1✔
242
      setEnforcementConfig({
1✔
243
        gdpr: {
244
          rules: [{
245
            purpose: 'storage',
246
            enforcePurpose: false,
247
            enforceVendor: true,
248
            vendorExceptions: ['sampleBidder2']
249
          }]
250
        }
251
      });
252
      setupConsentData();
1✔
253
      Object.assign(gvlids, {
1✔
254
        sampleBidder1: 1,
255
        sampleBidder2: 2
256
      })
257
      Object.keys(gvlids).forEach(bidder => expect(syncUserRule(activityParams(MODULE_TYPE_BIDDER, bidder))).to.not.exist);
2✔
258
    });
259

260
    it('should not allow bidder to do user sync if user has denied consent', function () {
1✔
261
      setEnforcementConfig({
1✔
262
        gdpr: {
263
          rules: [{
264
            purpose: 'storage',
265
            enforcePurpose: false,
266
            enforceVendor: true,
267
            vendorExceptions: []
268
          }]
269
        }
270
      });
271
      setupConsentData();
1✔
272
      Object.assign(gvlids, {
1✔
273
        sampleBidder1: 1,
274
        sampleBidder2: 3
275
      })
276

277
      Object.entries({
1✔
278
        sampleBidder1: true,
279
        sampleBidder2: false
280
      }).forEach(([bidder, isAllowed]) => {
2✔
281
        expectAllow(isAllowed, syncUserRule(activityParams(MODULE_TYPE_BIDDER, bidder)));
2✔
282
      })
283
    });
284

285
    it('should not check vendor consent when enforceVendor is false', function () {
1✔
286
      setEnforcementConfig({
1✔
287
        gdpr: {
288
          rules: [{
289
            purpose: 'storage',
290
            enforcePurpose: true,
291
            enforceVendor: false,
292
            vendorExceptions: ['sampleBidder1']
293
          }]
294
        }
295
      });
296
      setupConsentData();
1✔
297
      Object.assign(gvlids, {
1✔
298
        sampleBidder1: 1,
299
        sampleBidder2: 3
300
      })
301
      Object.keys(gvlids).forEach(bidder => expect(syncUserRule(activityParams(MODULE_TYPE_BIDDER, bidder))).to.not.exist);
2✔
302
    });
303
  });
304
  describe('enrichEidsRule', () => {
1✔
305
    it('should allow user id module if consent is given', function () {
1✔
306
      setEnforcementConfig({
1✔
307
        gdpr: {
308
          rules: [{
309
            purpose: 'storage',
310
            enforcePurpose: false,
311
            enforceVendor: true,
312
            vendorExceptions: []
313
          }]
314
        }
315
      });
316
      setupConsentData();
1✔
317
      gvlids.sampleUserId = 1;
1✔
318
      expect(enrichEidsRule(activityParams(MODULE_TYPE_UID, 'sampleUserId'))).to.not.exist;
1✔
319
    });
320

321
    it('should allow userId module if gdpr not in scope', function () {
1✔
322
      gvlids.sampleUserId = 1;
1✔
323
      const consent = setupConsentData({gdprApplies: false});
1✔
324
      consent.vendorData.purpose.consents['1'] = false;
1✔
325
      expect(enrichEidsRule(activityParams(MODULE_TYPE_UID, 'sampleUserId'))).to.not.exist;
1✔
326
    });
327

328
    it('should not allow user id module if user denied consent', function () {
1✔
329
      setEnforcementConfig({
1✔
330
        gdpr: {
331
          rules: [{
332
            purpose: 'storage',
333
            enforcePurpose: false,
334
            enforceVendor: true,
335
            vendorExceptions: []
336
          }]
337
        }
338
      });
339
      setupConsentData();
1✔
340
      Object.assign(gvlids, {
1✔
341
        sampleUserId: 1,
342
        sampleUserId1: 3
343
      });
344
      Object.entries({
1✔
345
        sampleUserId: true,
346
        sampleUserId1: false
347
      }).forEach(([name, allow]) => {
2✔
348
        expectAllow(allow, enrichEidsRule(activityParams(MODULE_TYPE_UID, name)))
2✔
349
      });
350
    });
351
  });
352

353
  describe('fetchBidsRule', () => {
1✔
354
    afterEach(function () {
1✔
355
      config.resetConfig();
2✔
356
    });
357

358
    it('should block bidder which does not have consent and allow bidder which has consent (LI is established)', function () {
1✔
359
      setEnforcementConfig({
1✔
360
        gdpr: {
361
          rules: [{
362
            purpose: 'basicAds',
363
            enforcePurpose: true,
364
            enforceVendor: true,
365
            vendorExceptions: []
366
          }]
367
        }
368
      });
369
      const cd = setupConsentData()
1✔
370
      Object.assign(gvlids, {
1✔
371
        bidder_1: 4,
372
        bidder_2: 5,
373
      });
374
      Object.assign(cd.vendorData.vendor.legitimateInterests, {
1✔
375
        4: true,
376
        5: true,
377
      });
378

379
      ['bidder_1', 'bidder_2'].forEach(bidder => expect(fetchBidsRule(activityParams(MODULE_TYPE_BIDDER, bidder))).to.not.exist);
2✔
380
    });
381

382
    it('should block bidder which does not have consent and allow bidder which has consent (liTransparency is NOT established)', function() {
1✔
383
      setEnforcementConfig({
1✔
384
        gdpr: {
385
          rules: [{
386
            purpose: 'basicAds',
387
            enforcePurpose: true,
388
            enforceVendor: true,
389
            vendorExceptions: ['bidder_3']
390
          }]
391
        }
392
      });
393
      const consent = setupConsentData();
1✔
394
      consent.vendorData.purpose.legitimateInterests['2'] = false;
1✔
395
      Object.assign(gvlids, {
1✔
396
        bidder_1: 4,
397
        bidder_2: 5,
398
      })
399
      Object.entries({
1✔
400
        bidder_1: true,
401
        bidder_2: false,
402
        bidder_3: true
403
      }).forEach(([bidder, allowed]) => {
3✔
404
        expectAllow(allowed, fetchBidsRule(activityParams(MODULE_TYPE_BIDDER, bidder)));
3✔
405
      })
406
    });
407
  });
408

409
  describe('reportAnalyticsRule', () => {
1✔
410
    it('should block analytics adapter which does not have consent and allow the one(s) which have consent', function() {
1✔
411
      setEnforcementConfig({
1✔
412
        gdpr: {
413
          rules: [{
414
            purpose: 'measurement',
415
            enforcePurpose: true,
416
            enforceVendor: true,
417
            vendorExceptions: ['analyticsAdapter_B']
418
          }]
419
        }
420
      });
421

422
      Object.assign(gvlids, {
1✔
423
        analyticsAdapter_A: 3,
424
        analyticsAdapter_B: 5,
425
        analyticsAdapter_C: 1
426
      });
427

428
      setupConsentData()
1✔
429

430
      Object.entries({
1✔
431
        analyticsAdapter_A: false,
432
        analyticsAdapter_B: true,
433
        analyticsAdapter_C: true
434
      }).forEach(([adapter, allow]) => {
3✔
435
        expectAllow(allow, reportAnalyticsRule(activityParams(MODULE_TYPE_ANALYTICS, adapter)))
3✔
436
      })
437
    });
438
  });
439

440
  describe('transmitUfpdRule', () => {
1✔
441
    it('should allow when purpose 3 consent is given', () => {
1✔
442
      setEnforcementConfig({
1✔
443
        gdpr: {
444
          rules: [{
445
            purpose: 'personalizedAds',
446
            enforcePurpose: true,
447
            enforceVendor: true,
448
          }]
449
        }
450
      });
451
      Object.assign(gvlids, {
1✔
452
        mockBidder: 123
453
      });
454
      const consent = setupConsentData();
1✔
455
      consent.vendorData.purpose.consents[4] = true;
1✔
456
      consent.vendorData.vendor.consents[123] = true;
1✔
457
      expectAllow(true, ufpdRule(activityParams(MODULE_TYPE_BIDDER, 'mockBidder')));
1✔
458
    });
459

460
    it('should return deny by default when purpose 4 consent is withheld', () => {
1✔
461
      setEnforcementConfig({});
1✔
462
      Object.assign(gvlids, {
1✔
463
        mockBidder: 123
464
      });
465
      const consent = setupConsentData();
1✔
466
      consent.vendorData.purpose.consents[4] = true;
1✔
467
      consent.vendorData.vendor.consents[123] = false;
1✔
468
      expectAllow(false, ufpdRule(activityParams(MODULE_TYPE_BIDDER, 'mockBidder')))
1✔
469
    });
470
  });
471

472
  describe('transmitEidsRule', () => {
1✔
473
    const GVL_ID = 123;
1✔
474
    const BIDDER = 'mockBidder';
1✔
475
    let cd;
476
    const RULES_2_10 = {
1✔
477
      basicAds: 2,
478
      personalizedAds: 4,
479
      measurement: 7,
480
    }
481

482
    beforeEach(() => {
1✔
483
      cd = setupConsentData();
33✔
484
      cd.vendorData = {
33✔
485
        vendor: {
486
          consents: {},
487
          legitimateInterests: {},
488
        },
489
        purpose: {
490
          consents: {},
491
          legitimateInterests: {}
492
        }
493
      };
494
      Object.assign(gvlids, {
33✔
495
        [BIDDER]: GVL_ID
496
      });
497
    });
498

499
    function setVendorConsent(type = 'consents') {
17✔
500
      cd.vendorData.vendor[type][GVL_ID] = true;
17✔
501
    }
502

503
    function runRule() {
504
      return transmitEidsRule(activityParams(MODULE_TYPE_BIDDER, BIDDER));
33✔
505
    }
506

507
    describe('default behavior', () => {
1✔
508
      const CS_PURPOSES = [3, 4, 5, 6, 7, 8, 9, 10];
1✔
509
      const LI_PURPOSES = [2];
1✔
510
      const CONSENT_TYPES = ['consents', 'legitimateInterests'];
1✔
511

512
      describe('should deny if', () => {
1✔
513
        describe('config is default', () => {
1✔
514
          beforeEach(() => {
1✔
515
            setEnforcementConfig({});
7✔
516
          });
517

518
          it('no consent is given, of any type or for any vendor', () => {
1✔
519
            expectAllow(false, runRule());
1✔
520
          });
521

522
          CONSENT_TYPES.forEach(type => {
1✔
523
            it(`vendor ${type} is given, but no purpose has consent`, () => {
2✔
524
              setVendorConsent(type);
2✔
525
              expectAllow(false, runRule());
2✔
526
            });
527

528
            it(`${type} is given for purpose other than 2-10`, () => {
2✔
529
              setVendorConsent(type);
2✔
530
              cd.vendorData.purpose[type][1] = true;
2✔
531
              expectAllow(false, runRule());
2✔
532
            });
533

534
            LI_PURPOSES.forEach(purpose => {
2✔
535
              it(`purpose ${purpose} has ${type}, but vendor does not`, () => {
2✔
536
                cd.vendorData.purpose[type][purpose] = true;
2✔
537
                expectAllow(false, runRule());
2✔
538
              });
539
            });
540
          });
541
        });
542

543
        describe(`no consent is given`, () => {
1✔
544
          [
1✔
545
            {
546
              enforcePurpose: false,
547
            },
548
            {
549
              enforceVendor: false,
550
            },
551
            {
552
              enforcePurpose: false,
553
              enforceVendor: false,
554
            }
555
          ].forEach(t => {
556
            it(`config has ${JSON.stringify(t)} for each of ${Object.keys(RULES_2_10).join(', ')}`, () => {
3✔
557
              setEnforcementConfig({
3✔
558
                gdpr: {
559
                  rules: Object.keys(RULES_2_10).map(rule => Object.assign({
9✔
560
                    purpose: rule,
561
                    vendorExceptions: [],
562
                    enforcePurpose: true,
563
                    enforceVendor: true
564
                  }, t))
565
                }
566
              });
567
              expectAllow(false, runRule());
3✔
568
            });
569
          });
570
        });
571
      });
572

573
      describe('should allow if', () => {
1✔
574
        describe('config is default', () => {
1✔
575
          beforeEach(() => {
1✔
576
            setEnforcementConfig({});
10✔
577
          });
578
          LI_PURPOSES.forEach(purpose => {
1✔
579
            it(`purpose ${purpose} has LI, vendor has LI`, () => {
1✔
580
              setVendorConsent('legitimateInterests');
1✔
581
              cd.vendorData.purpose.legitimateInterests[purpose] = true;
1✔
582
              expectAllow(true, runRule());
1✔
583
            });
584
          });
585

586
          LI_PURPOSES.concat(CS_PURPOSES).forEach(purpose => {
1✔
587
            it(`purpose ${purpose} has consent, vendor has consent`, () => {
9✔
588
              setVendorConsent();
9✔
589
              cd.vendorData.purpose.consents[purpose] = true;
9✔
590
              expectAllow(true, runRule());
9✔
591
            });
592
          });
593
        });
594

595
        Object.keys(RULES_2_10).forEach(rule => {
1✔
596
          it(`no consent given, but '${rule}' config has a vendor exception`, () => {
3✔
597
            setEnforcementConfig({
3✔
598
              gdpr: {
599
                rules: [
600
                  {
601
                    purpose: rule,
602
                    enforceVendor: false,
603
                    enforcePurpose: false,
604
                    vendorExceptions: [BIDDER]
605
                  }
606
                ]
607
              }
608
            });
609
            expectAllow(true, runRule());
3✔
610
          });
611

612
          it(`vendor consent is missing, but '${rule}' config has a softVendorException`, () => {
3✔
613
            setEnforcementConfig({
3✔
614
              gdpr: {
615
                rules: [
616
                  {
617
                    purpose: rule,
618
                    enforceVendor: false,
619
                    enforcePurpose: false,
620
                    softVendorExceptions: [BIDDER]
621
                  }
622
                ]
623
              }
624
            });
625
            cd.vendorData.purpose.consents[RULES_2_10[rule]] = true;
3✔
626
            expectAllow(true, runRule());
3✔
627
          })
628
        });
629
      });
630
    });
631

632
    describe('with eidsRequireP4consent', () => {
1✔
633
      function setupPAdsRule(cfg = {}) {
7✔
634
        setEnforcementConfig({
7✔
635
          gdpr: {
636
            rules: [
637
              Object.assign({
638
                purpose: 'personalizedAds',
639
                eidsRequireP4Consent: true,
640
                enforcePurpose: true,
641
                enforceVendor: true,
642
              }, cfg)
643
            ]
644
          }
645
        })
646
      }
647
      describe('allows when', () => {
1✔
648
        Object.entries({
1✔
649
          'purpose 4 consent is given'() {
650
            setupPAdsRule();
1✔
651
            setVendorConsent();
1✔
652
            cd.vendorData.purpose.consents[4] = true
1✔
653
          },
654
          'enforcePurpose is false, with vendor consent given'() {
655
            setupPAdsRule({enforcePurpose: false});
1✔
656
            setVendorConsent();
1✔
657
          },
658
          'enforceVendor is false, with purpose consent given'() {
659
            setupPAdsRule({enforceVendor: false});
1✔
660
            cd.vendorData.purpose.consents[4] = true;
1✔
661
          },
662
          'vendor is excepted'() {
663
            setupPAdsRule({vendorExceptions: [BIDDER]});
1✔
664
          },
665
          'vendor is softly excepted, with purpose consent given'() {
666
            setupPAdsRule({softVendorExceptions: [BIDDER]});
1✔
667
            cd.vendorData.purpose.consents[4] = true;
1✔
668
          }
669
        }).forEach(([t, setup]) => {
5✔
670
          it(t, () => {
5✔
671
            setup();
5✔
672
            expectAllow(true, runRule());
5✔
673
          });
674
        });
675
      });
676
      describe('denies when', () => {
1✔
677
        Object.entries({
1✔
678
          'purpose 4 consent is not given'() {
679
            setupPAdsRule();
1✔
680
            setVendorConsent();
1✔
681
          },
682
          'vendor consent is not given'() {
683
            setupPAdsRule();
1✔
684
            cd.vendorData.purpose.consents[4] = true
1✔
685
          },
686
        }).forEach(([t, setup]) => {
2✔
687
          it(t, () => {
2✔
688
            setup();
2✔
689
            expectAllow(false, runRule());
2✔
690
          })
691
        })
692
      })
693
    })
694
  });
695

696
  describe('transmitPreciseGeoRule', () => {
1✔
697
    const BIDDER = 'mockBidder';
1✔
698
    let cd;
699

700
    function runRule() {
701
      return transmitPreciseGeoRule(activityParams(MODULE_TYPE_BIDDER, BIDDER))
2✔
702
    }
703

704
    beforeEach(() => {
1✔
705
      cd = setupConsentData();
2✔
706
      setEnforcementConfig({
2✔
707
        gdpr: {
708
          rules: [{
709
            purpose: 'transmitPreciseGeo',
710
            enforcePurpose: true,
711
            enforceVendor: false
712
          }]
713
        }
714
      })
715
    });
716

717
    it('should allow when special feature 1 consent is given', () => {
1✔
718
      cd.vendorData.specialFeatureOptins[1] = true;
1✔
719
      expectAllow(true, runRule());
1✔
720
    })
721
    it('should deny when configured, but consent is missing', () => {
1✔
722
      cd.vendorData.specialFeatureOptins[1] = false;
1✔
723
      expectAllow(false, runRule());
1✔
724
    });
725
  });
726

727
  describe('validateRules', function () {
1✔
728
    const createGdprRule = (purposeName = 'storage', enforcePurpose = true, enforceVendor = true, vendorExceptions = [], softVendorExceptions = []) => ({
65!
729
      purpose: purposeName,
730
      enforcePurpose,
731
      enforceVendor,
732
      vendorExceptions,
733
      softVendorExceptions,
734
    });
735

736
    const consentData = {
1✔
737
      vendorData: staticConfig.consentData.getTCData,
738
      apiVersion: 2,
739
      gdprApplies: true
740
    };
741

742
    // Bidder = 'bidderB' doesn't have vendorConsent
743
    const vendorBlockedModule = 'bidderB';
1✔
744
    const vendorBlockedGvlId = 3;
1✔
745

746
    const consentDataWithPurposeConsentFalse = utils.deepClone(consentData);
1✔
747
    consentDataWithPurposeConsentFalse.vendorData.purpose.consents['1'] = false;
1✔
748

749
    describe('when the vendor has a softVendorException', () => {
1✔
750
      const gdprRule = createGdprRule('storage', true, true, [], [vendorBlockedModule]);
1✔
751

752
      it('should return false if general consent was not given', () => {
1✔
753
        const isAllowed = validateRules(gdprRule, consentDataWithPurposeConsentFalse, vendorBlockedModule, vendorBlockedGvlId);
1✔
754
        expect(isAllowed).to.be.false;
1✔
755
      })
756
      it('should return true if general consent was given', () => {
1✔
757
        const isAllowed = validateRules(gdprRule, consentData, vendorBlockedModule, vendorBlockedGvlId);
1✔
758
        expect(isAllowed).to.be.true;
1✔
759
      })
760
    })
761

762
    describe('first party modules', () => {
1✔
763
      Object.entries({
1✔
764
        'storage': {
765
          purposeNo: 1,
766
          allowsLI: false
767
        },
768
        'basicAds': {
769
          purposeNo: 2,
770
          allowsLI: true
771
        },
772
        'measurement': {
773
          purposeNo: 7,
774
          allowsLI: true
775
        },
776
        'personalizedAds': {
777
          purposeNo: 4,
778
          allowsLI: false
779
        },
780
      }).forEach(([purpose, {purposeNo, allowsLI}]) => {
4✔
781
        describe(`purpose ${purpose}`, () => {
4✔
782
          let consent;
783
          beforeEach(() => {
4✔
784
            consent = utils.deepClone(consentData);
12✔
785
          })
786
          const rule = createGdprRule(purpose);
4✔
787
          Object.entries({
4✔
788
            'allowed': true,
789
            'not allowed': false
790
          }).forEach(([t, consentGiven]) => {
8✔
791
            it(`should be ${t} when publisher is ${t}`, () => {
8✔
792
              consent.vendorData.publisher.consents[purposeNo] = consentGiven;
8✔
793
              consent.vendorData.publisher.legitimateInterests[purposeNo] = false;
8✔
794
              const actual = validateRules(rule, consent, 'mockModule', VENDORLESS_GVLID);
8✔
795
              expect(actual).to.equal(consentGiven);
8✔
796
            });
797
          })
798
          it(`should ${allowsLI ? '' : 'NOT '}be allowed when publisher consent is not given, but LI is`, () => {
4✔
799
            consent.vendorData.publisher.consents[purposeNo] = false;
4✔
800
            consent.vendorData.publisher.legitimateInterests[purposeNo] = true;
4✔
801
            const actual = validateRules(rule, consent, 'mockModule', VENDORLESS_GVLID);
4✔
802
            expect(actual).to.equal(allowsLI);
4✔
803
          })
804
        })
805
      })
806
    })
807

808
    describe('validateRules', function () {
1✔
809
      Object.entries({
1✔
810
        '1 (which does not consider LI)': [1, 'storage', false],
811
        '2 (which does consider LI)': [2, 'basicAds', true]
812
      }).forEach(([t, [purposeNo, purpose, allowsLI]]) => {
2✔
813
        describe(`for purpose ${t}`, () => {
2✔
814
          Object.entries({
2✔
815
            'enforcePurpose=true, enforceVendor=true': [true, true],
816
            'enforcePurpose=true, enforceVendor=false': [true, false],
817
            'enforcePurpose=false, enforceVendor=true': [false, true],
818
            'enforcePurpose=false, enforceVendor=false': [false, false],
819
          }).forEach(([t, [enforcePurpose, enforceVendor]]) => {
8✔
820
            describe(`with ${t}`, () => {
8✔
821
              let rule;
822
              beforeEach(() => {
8✔
823
                rule = createGdprRule(purpose, enforcePurpose, enforceVendor, []);
60✔
824
              });
825

826
              ['consents', 'legitimateInterests'].forEach(ctype => {
8✔
827
                Object.entries({
16✔
828
                  'purpose=true, vendor=true': [true, true, true],
829
                  'purpose=true, vendor=false': [true, false, !enforceVendor],
830
                  'purpose=false, vendor=true': [false, true, !enforcePurpose],
831
                  'purpose=false, vendor=false': [false, false, !enforcePurpose && !enforceVendor]
24✔
832
                }).forEach(([t, [purposeConsent, vendorConsent, expected]]) => {
64✔
833
                  describe(`when ${ctype} for ${t}`, () => {
64✔
834
                    let consentData;
835
                    beforeEach(() => {
64✔
836
                      consentData = {
60✔
837
                        vendorData: {
838
                          purpose: {
839
                            [ctype]: {
840
                              [purposeNo]: purposeConsent,
841
                            }
842
                          },
843
                          vendor: {
844
                            [ctype]: {
845
                              123: vendorConsent,
846
                            }
847
                          }
848
                        }
849
                      }
850
                    });
851
                    if (allowsLI || ctype !== 'legitimateInterests') {
64✔
852
                      it(`should return ${expected}`, () => {
48✔
853
                        const allowed = validateRules(rule, consentData, 'mockVendor', 123);
48✔
854
                        expect(allowed).to.eql(expected);
48✔
855
                      })
856
                    } else if (enforceVendor || enforcePurpose) {
16✔
857
                      it(`should return false (LI is irrelevant)`, () => {
12✔
858
                        const allowed = validateRules(rule, consentData, 'mockVendor', 123);
12✔
859
                        expect(allowed).to.be.false;
12✔
860
                      })
861
                    }
862
                  })
863
                })
864
              })
865
            })
866
          })
867
        })
868
      })
869
    });
870
  })
871

872
  describe('setEnforcementConfig', function () {
1✔
873
    let sandbox;
874
    const DEFAULT_RULES = [{
1✔
875
      purpose: 'storage',
876
      enforcePurpose: true,
877
      enforceVendor: true,
878
      vendorExceptions: []
879
    }, {
880
      purpose: 'basicAds',
881
      enforcePurpose: true,
882
      enforceVendor: true,
883
      vendorExceptions: []
884
    }];
885
    beforeEach(function () {
1✔
886
      sandbox = sinon.createSandbox();
4✔
887
      logWarnSpy = sandbox.spy(utils, 'logWarn');
4✔
888
    });
889
    afterEach(function () {
1✔
890
      config.resetConfig();
4✔
891
      sandbox.restore();
4✔
892
    });
893

894
    it('should enforce TCF2 Purpose1 and Purpose 2 if no "rules" found in the config', function () {
1✔
895
      setEnforcementConfig({
1✔
896
        gdpr: {
897
          cmpApi: 'iab',
898
          timeout: 5000
899
        }
900
      });
901

902
      expect(logWarnSpy.calledOnce).to.equal(true);
1✔
903
      expect(ACTIVE_RULES.purpose[1]).to.deep.equal(DEFAULT_RULES[0]);
1✔
904
      expect(ACTIVE_RULES.purpose[2]).to.deep.equal(DEFAULT_RULES[1]);
1✔
905
    });
906

907
    it('should enforce TCF2 Purpose 2 also if only Purpose 1 is defined in "rules"', function () {
1✔
908
      const purpose1RuleDefinedInConfig = {
1✔
909
        purpose: 'storage',
910
        enforcePurpose: false,
911
        enforceVendor: true,
912
        vendorExceptions: ['bidderA']
913
      }
914
      setEnforcementConfig({
1✔
915
        gdpr: {
916
          rules: [purpose1RuleDefinedInConfig]
917
        }
918
      });
919

920
      expect(ACTIVE_RULES.purpose[1]).to.deep.equal(purpose1RuleDefinedInConfig);
1✔
921
      expect(ACTIVE_RULES.purpose[2]).to.deep.equal(DEFAULT_RULES[1]);
1✔
922
    });
923

924
    it('should enforce TCF2 Purpose 1 also if only Purpose 2 is defined in "rules"', function () {
1✔
925
      const purpose2RuleDefinedInConfig = {
1✔
926
        purpose: 'basicAds',
927
        enforcePurpose: false,
928
        enforceVendor: true,
929
        vendorExceptions: ['bidderA']
930
      }
931
      setEnforcementConfig({
1✔
932
        gdpr: {
933
          rules: [purpose2RuleDefinedInConfig]
934
        }
935
      });
936

937
      expect(ACTIVE_RULES.purpose[1]).to.deep.equal(DEFAULT_RULES[0]);
1✔
938
      expect(ACTIVE_RULES.purpose[2]).to.deep.equal(purpose2RuleDefinedInConfig);
1✔
939
    });
940

941
    it('should use the "rules" defined in config if a definition found', function() {
1✔
942
      const rules = [{
1✔
943
        purpose: 'storage',
944
        enforcePurpose: false,
945
        enforceVendor: false
946
      }, {
947
        purpose: 'basicAds',
948
        enforcePurpose: false,
949
        enforceVendor: false
950
      }]
951
      setEnforcementConfig({gdpr: { rules }});
1✔
952
      expect(ACTIVE_RULES.purpose[1]).to.deep.equal(rules[0]);
1✔
953
      expect(ACTIVE_RULES.purpose[2]).to.deep.equal(rules[1]);
1✔
954
    });
955
  });
956

957
  describe('TCF2FinalResults', function() {
1✔
958
    let sandbox;
959
    beforeEach(function() {
1✔
960
      sandbox = sinon.createSandbox();
1✔
961
      sandbox.spy(events, 'emit');
1✔
962
    });
963
    afterEach(function() {
1✔
964
      config.resetConfig();
1✔
965
      sandbox.restore();
1✔
966
    });
967
    it('should emit TCF2 enforcement data on auction end', function() {
1✔
968
      const rules = [{
1✔
969
        purpose: 'storage',
970
        enforcePurpose: false,
971
        enforceVendor: false
972
      }, {
973
        purpose: 'basicAds',
974
        enforcePurpose: false,
975
        enforceVendor: false
976
      }]
977
      setEnforcementConfig({gdpr: { rules }});
1✔
978

979
      events.emit('auctionEnd', {})
1✔
980

981
      // Assertions
982
      sinon.assert.calledWith(events.emit.getCall(1), 'tcf2Enforcement', sinon.match.object);
1✔
983
    })
984
  });
985

986
  describe('gvlid resolution', () => {
1✔
987
    let sandbox;
988
    beforeEach(function() {
1✔
989
      sandbox = sinon.createSandbox();
17✔
990
    });
991

992
    afterEach(function() {
1✔
993
      sandbox.restore();
17✔
994
      config.resetConfig();
17✔
995
    });
996

997
    describe('getGvlid', function() {
1✔
998
      const MOCK_MODULE = 'moduleA';
1✔
999
      let entry;
1000

1001
      beforeEach(function() {
1✔
1002
        entry = {modules: {}};
14✔
1003
        GDPR_GVLIDS.get.resetHistory();
14✔
1004
        GDPR_GVLIDS.get.callsFake((mod) => mod === MOCK_MODULE ? entry : {modules: {}});
14✔
1005
      });
1006

1007
      it('should return "null" if called without passing any argument', function() {
1✔
1008
        const gvlid = getGvlid();
1✔
1009
        expect(gvlid).to.equal(null);
1✔
1010
      });
1011

1012
      it('should return "null" if no GVL ID was registered', function() {
1✔
1013
        const gvlid = getGvlid('type', MOCK_MODULE);
1✔
1014
        expect(gvlid).to.equal(null);
1✔
1015
      });
1016

1017
      it('should return null if the wrong GVL ID was registered', () => {
1✔
1018
        entry = {gvlid: 123};
1✔
1019
        expect(getGvlid('type', 'someOtherModule')).to.equal(null);
1✔
1020
      })
1021

1022
      Object.entries({
1✔
1023
        'without fallback': null,
UNCOV
1024
        'with fallback': () => 'shouldBeIgnored'
×
1025
      }).forEach(([t, fallbackFn]) => {
2✔
1026
        describe(t, () => {
2✔
1027
          it('should return the GVL ID from gvlMapping if it is defined in setConfig', function() {
2✔
1028
            config.setConfig({
2✔
1029
              gvlMapping: {
1030
                [MOCK_MODULE]: 1
1031
              }
1032
            });
1033

1034
            entry = {gvlid: 2};
2✔
1035

1036
            const gvlid = getGvlid('type', MOCK_MODULE, fallbackFn);
2✔
1037
            expect(gvlid).to.equal(1);
2✔
1038
          });
1039

1040
          it('should return the GVL ID that was registered', function() {
2✔
1041
            entry = {gvlid: 7};
2✔
1042
            expect(getGvlid('type', MOCK_MODULE, fallbackFn)).to.equal(7);
2✔
1043
          });
1044

1045
          it('should return VENDORLESS_GVLID for core modules', () => {
2✔
1046
            entry = {gvlid: 123};
2✔
1047
            expect(getGvlid(MODULE_TYPE_PREBID, MOCK_MODULE, fallbackFn)).to.equal(VENDORLESS_GVLID);
2✔
1048
          });
1049

1050
          describe('multiple GVL IDs are found', () => {
2✔
1051
            it('should use bidder over others', () => {
2✔
1052
              entry = {modules: {[MODULE_TYPE_BIDDER]: 123, [MODULE_TYPE_UID]: 321}};
2✔
1053
              expect(getGvlid(MODULE_TYPE_UID, MOCK_MODULE, fallbackFn)).to.equal(123);
2✔
1054
            });
1055
            it('should use uid over analytics', () => {
2✔
1056
              entry = {modules: {[MODULE_TYPE_UID]: 123, [MODULE_TYPE_ANALYTICS]: 321}};
2✔
1057
              expect(getGvlid(MODULE_TYPE_ANALYTICS, MOCK_MODULE, fallbackFn)).to.equal(123);
2✔
1058
            })
1059
          })
1060
        })
1061
      })
1062

1063
      it('should use fallbackFn if no other lookup produces a gvl id', () => {
1✔
1064
        expect(getGvlid('type', MOCK_MODULE, () => 321)).to.equal(321);
1✔
1065
      });
1066
    });
1067

1068
    describe('getGvlidFromAnalyticsConfig', () => {
1✔
1069
      let getAnalyticsAdapter, adapter, adapterEntry;
1070

1071
      beforeEach(() => {
1✔
1072
        adapter = {};
3✔
1073
        adapterEntry = {
3✔
1074
          adapter
1075
        };
1076
        getAnalyticsAdapter = sandbox.stub(adapterManager, 'getAnalyticsAdapter');
3✔
1077
        getAnalyticsAdapter.withArgs('analytics').returns(adapterEntry);
3✔
1078
      });
1079

1080
      it('should return gvlid from adapter if defined', () => {
1✔
1081
        adapter.gvlid = 321;
1✔
1082
        expect(getGvlidFromAnalyticsAdapter('analytics')).to.equal(321);
1✔
1083
      });
1084

1085
      it('should invoke adapter.gvlid if it\'s a function', () => {
1✔
1086
        adapter.gvlid = (cfg) => cfg.k
1✔
1087
        const cfg = {k: 231};
1✔
1088
        expect(getGvlidFromAnalyticsAdapter('analytics', cfg)).to.eql(231);
1✔
1089
      });
1090

1091
      it('should not choke if adapter gvlid fn throws', () => {
1✔
1092
        adapter.gvlid = () => { throw new Error(); };
1✔
1093
        expect(getGvlidFromAnalyticsAdapter('analytics')).to.not.be.ok;
1✔
1094
      });
1095
    });
1096
  })
1097
  describe('checkIfCredentialsAllowed', () => {
1✔
1098
    it('should not allow access credentials for lack of purpose consent 1', () => {
1✔
1099
      const logWarn = sinon.spy(utils, 'logWarn');
1✔
1100
      const rules = [{
1✔
1101
        purpose: 'storage',
1102
        enforcePurpose: true,
1103
        enforceVendor: false
1104
      }]
1105
      setEnforcementConfig({gdpr: {rules}});
1✔
1106
      const consent = setupConsentData({gdprApplies: false});
1✔
1107
      consent.vendorData.purpose.consents['1'] = false;
1✔
1108
      const nextSpy = sinon.spy();
1✔
1109
      const options = {
1✔
1110
        withCredentials: true
1111
      }
1112

1113
      checkIfCredentialsAllowed(nextSpy, options);
1✔
1114

1115
      sinon.assert.calledWith(nextSpy, {withCredentials: false});
1✔
1116
      expect(logWarn.calledOnce).to.equal(true);
1✔
1117
      logWarn.restore();
1✔
1118
    })
1119
  })
1120
});
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