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

prebid / Prebid.js / 20279026889

16 Dec 2025 06:45PM UTC coverage: 96.205% (+0.001%) from 96.204%
20279026889

push

github

web-flow
Update PR review and testing guidelines in AGENTS.md (#14268)

Clarify PR review guidelines and testing instructions.

41439 of 50987 branches covered (81.27%)

207294 of 215472 relevant lines covered (96.2%)

71.29 hits per line

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

96.13
/test/spec/modules/id5AnalyticsAdapter_spec.js
1
import adapterManager from '../../../src/adapterManager.js';
2
import id5AnalyticsAdapter from '../../../modules/id5AnalyticsAdapter.js';
3
import {expect} from 'chai';
4
import * as events from '../../../src/events.js';
5
import {EVENTS} from '../../../src/constants.js';
6
import {generateUUID} from '../../../src/utils.js';
7
import {server} from '../../mocks/xhr.js';
8
import {getGlobal} from '../../../src/prebidGlobal.js';
9
import {enrichEidsRule} from "../../../modules/tcfControl.ts";
10
import * as utils from '../../../src/utils.js';
11

12
const CONFIG_URL = 'https://api.id5-sync.com/analytics/12349/pbjs';
1✔
13
const INGEST_URL = 'https://test.me/ingest';
1✔
14

15
describe('ID5 analytics adapter', () => {
1✔
16
  let config;
17

18
  beforeEach(() => {
1✔
19
    // to enforce tcfControl initialization when running in single test mode
20
    expect(enrichEidsRule).to.exist
11✔
21
    config = {
11✔
22
      options: {
23
        partnerId: 12349,
24
        compressionDisabled: true
25
      }
26
    };
27
  });
28

29
  it('registers itself with the adapter manager', () => {
1✔
30
    const adapter = adapterManager.getAnalyticsAdapter('id5Analytics');
1✔
31
    expect(adapter).to.exist;
1✔
32
    expect(adapter.gvlid).to.be.a('number');
1✔
33
    expect(adapter.adapter).to.equal(id5AnalyticsAdapter);
1✔
34
  });
35

36
  it('tolerates undefined or empty config', () => {
1✔
37
    id5AnalyticsAdapter.enableAnalytics(undefined);
1✔
38
    id5AnalyticsAdapter.enableAnalytics({});
1✔
39
  });
40

41
  it('calls configuration endpoint', () => {
1✔
42
    server.respondWith('GET', CONFIG_URL, [200,
1✔
43
      {
44
        'Content-Type': 'application/json',
45
        'Access-Control-Allow-Origin': '*'
46
      },
47
      `{ "sampling": 0, "ingestUrl": "${INGEST_URL}" }`
48
    ]);
49
    id5AnalyticsAdapter.enableAnalytics(config);
1✔
50
    server.respond();
1✔
51

52
    expect(server.requests).to.have.length(1);
1✔
53

54
    id5AnalyticsAdapter.disableAnalytics();
1✔
55
  });
56

57
  it('does not call configuration endpoint when partner id is missing', () => {
1✔
58
    id5AnalyticsAdapter.enableAnalytics({});
1✔
59
    server.respond();
1✔
60

61
    expect(server.requests).to.have.length(0);
1✔
62

63
    id5AnalyticsAdapter.disableAnalytics();
1✔
64
  });
65

66
  describe('after configuration', () => {
1✔
67
    let auction;
68

69
    beforeEach(() => {
1✔
70
      server.respondWith('GET', CONFIG_URL, [200,
7✔
71
        {
72
          'Content-Type': 'application/json',
73
          'Access-Control-Allow-Origin': '*'
74
        },
75
        `{ "sampling": 1, "ingestUrl": "${INGEST_URL}" }`
76
      ]);
77

78
      server.respondWith('POST', INGEST_URL, [200,
7✔
79
        {
80
          'Content-Type': 'application/json',
81
          'Access-Control-Allow-Origin': '*'
82
        },
83
        ''
84
      ]);
85

86
      auction = {
7✔
87
        auctionId: generateUUID(),
88
        adUnits: [{
89
          'code': 'user-728',
90
          mediaTypes: {
91
            banner: {
92
              sizes: [[300, 250], [300, 600], [728, 90]]
93
            }
94
          },
95
          adUnitCodes: ['user-728']
96
        }],
97
      };
98
    });
99

100
    afterEach(() => {
1✔
101
      id5AnalyticsAdapter.disableAnalytics();
7✔
102
    });
103

104
    it('sends auction end events to the backend', () => {
1✔
105
      id5AnalyticsAdapter.enableAnalytics(config);
1✔
106
      server.respond();
1✔
107
      events.emit(EVENTS.AUCTION_END, auction);
1✔
108
      server.respond();
1✔
109

110
      // Why 3? 1: config, 2: tcfEnforcement, 3: auctionEnd
111
      // tcfEnforcement? yes, tcfControl module emits in reaction to auctionEnd
112
      expect(server.requests).to.have.length(3);
1✔
113

114
      const body1 = JSON.parse(server.requests[1].requestBody);
1✔
115
      expect(body1.source).to.equal('pbjs');
1✔
116
      expect(body1.event).to.equal('tcf2Enforcement');
1✔
117
      expect(body1.partnerId).to.equal(12349);
1✔
118
      expect(body1.meta).to.be.a('object');
1✔
119
      expect(body1.meta.pbjs).to.equal(getGlobal().version);
1✔
120
      expect(body1.meta.sampling).to.equal(1);
1✔
121
      expect(body1.meta.tz).to.be.a('number');
1✔
122

123
      const body2 = JSON.parse(server.requests[2].requestBody);
1✔
124
      expect(body2.source).to.equal('pbjs');
1✔
125
      expect(body2.event).to.equal('auctionEnd');
1✔
126
      expect(body2.partnerId).to.equal(12349);
1✔
127
      expect(body2.meta).to.be.a('object');
1✔
128
      expect(body2.meta.pbjs).to.equal(getGlobal().version);
1✔
129
      expect(body2.meta.sampling).to.equal(1);
1✔
130
      expect(body2.meta.tz).to.be.a('number');
1✔
131
      expect(body2.payload).to.eql(auction);
1✔
132
    });
133

134
    it('compresses large events with gzip when enabled', async function() {
1✔
135
      // turn ON compression
136
      config.options.compressionDisabled = false;
1✔
137

138
      const longCode = 'x'.repeat(2048);
1✔
139
      auction.adUnits[0].code = longCode;
1✔
140
      auction.adUnits[0].adUnitCodes = [longCode];
1✔
141

142
      id5AnalyticsAdapter.enableAnalytics(config);
1✔
143
      server.respond();
1✔
144
      events.emit(EVENTS.AUCTION_END, auction);
1✔
145
      server.respond();
1✔
146

147
      // Wait as gzip stream is async, we need to wait until it is processed.  3 requests: config, tcf2Enforcement, auctionEnd
148
      await waitForRequests(3);
1✔
149
      const eventReq = server.requests[2];
1✔
150
      if (utils.isGzipCompressionSupported()) {
1!
151
        expect(eventReq.requestHeaders['Content-Encoding']).to.equal('gzip');
1✔
152
        expect(eventReq.requestBody).to.be.instanceof(Uint8Array);
1✔
153
      } else {    // compression is not supported in some test browsers, so we expect the event to be uncompressed.
154
        expect(eventReq.requestHeaders['Content-Encoding']).to.be.undefined;
×
155
        const body = JSON.parse(eventReq.requestBody);
×
156
        expect(body.event).to.equal(EVENTS.AUCTION_END);
×
157
      }
158
    });
159

160
    it('does not repeat already sent events on new events', () => {
1✔
161
      id5AnalyticsAdapter.enableAnalytics(config);
1✔
162
      server.respond();
1✔
163
      events.emit(EVENTS.AUCTION_END, auction);
1✔
164
      server.respond();
1✔
165
      events.emit(EVENTS.BID_WON, auction);
1✔
166
      server.respond();
1✔
167

168
      // Why 4? 1: config, 2: tcfEnforcement, 3: auctionEnd 4: bidWon
169
      expect(server.requests).to.have.length(4);
1✔
170

171
      const body1 = JSON.parse(server.requests[1].requestBody);
1✔
172
      expect(body1.event).to.equal('tcf2Enforcement');
1✔
173

174
      const body2 = JSON.parse(server.requests[2].requestBody);
1✔
175
      expect(body2.event).to.equal('auctionEnd');
1✔
176

177
      const body3 = JSON.parse(server.requests[3].requestBody);
1✔
178
      expect(body3.event).to.equal('bidWon');
1✔
179
    })
180

181
    it('filters unwanted IDs from the events it sends', () => {
1✔
182
      auction.adUnits[0].bids = [{
1✔
183
        'bidder': 'appnexus',
184
        'params': {
185
          'placementId': '16618951'
186
        },
187
        'userId': {
188
          'criteoId': '_h_y_19IMUhMZG1TOTRReHFNc29TekJ3TzQ3elhnRU81ayUyQjhiRkdJJTJGaTFXJTJCdDRnVmN4S0FETUhQbXdmQWg0M3g1NWtGbGolMkZXalclMkJvWjJDOXFDSk1HU3ZKaVElM0QlM0Q',
189
          'id5id': {
190
            'uid': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*FSycZQy7v7zWXiKbEpPEWoB3_UiWdPGzh554ncYDvOkAAA3rajiR0yNrFAU7oDTu',
191
            'ext': {'linkType': 1}
192
          },
193
          'tdid': '888a6042-8f99-483b-aa26-23c44bc9166b'
194
        },
195
        'userIdAsEids': [{
196
          'source': 'criteo.com',
197
          'uids': [{
198
            'id': '_h_y_19IMUhMZG1TOTRReHFNc29TekJ3TzQ3elhnRU81ayUyQjhiRkdJJTJGaTFXJTJCdDRnVmN4S0FETUhQbXdmQWg0M3g1NWtGbGolMkZXalclMkJvWjJDOXFDSk1HU3ZKaVElM0QlM0Q',
199
            'atype': 1
200
          }]
201
        }, {
202
          'source': 'id5-sync.com',
203
          'uids': [{
204
            'id': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*FSycZQy7v7zWXiKbEpPEWoB3_UiWdPGzh554ncYDvOkAAA3rajiR0yNrFAU7oDTu',
205
            'atype': 1,
206
            'ext': {'linkType': 1}
207
          }]
208
        }]
209
      }];
210

211
      auction.bidderRequests = [{
1✔
212
        'bidderCode': 'appnexus',
213
        'auctionId': 'e8d15df4-d89c-44c9-8b36-812f75cbf227',
214
        'bidderRequestId': '1451a3c759c60359',
215
        'bids': [
216
          {
217
            'bidder': 'appnexus',
218
            'params': {
219
              'placementId': '16824712'
220
            },
221
            'userId': {
222
              'id5id': {
223
                'uid': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*CmuuahP8jbPJGRCUDdT2VZ8wz0eJM8O8mNlKktlEjuYAABFEjc2c9faqDencf2hR',
224
                'ext': {
225
                  'linkType': 1
226
                }
227
              },
228
              'sharedid': {
229
                'id': '01F6J4T72MRFYVWTN65WFA0H7N',
230
                'third': '01F6J4T72MRFYVWTN65WFA0H7N'
231
              },
232
              'tdid': '0e45f56b-ad09-4c91-b090-8bd03e0d0754'
233
            },
234
            'userIdAsEids': [
235
              {
236
                'source': 'id5-sync.com',
237
                'uids': [
238
                  {
239
                    'id': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*CmuuahP8jbPJGRCUDdT2VZ8wz0eJM8O8mNlKktlEjuYAABFEjc2c9faqDencf2hR',
240
                    'atype': 1,
241
                    'ext': {
242
                      'linkType': 1
243
                    }
244
                  }
245
                ]
246
              },
247
              {
248
                'source': 'sharedid.org',
249
                'uids': [
250
                  {
251
                    'id': '01F6J4T72MRFYVWTN65WFA0H7N',
252
                    'atype': 1,
253
                    'ext': {
254
                      'third': '01F6J4T72MRFYVWTN65WFA0H7N'
255
                    }
256
                  }
257
                ]
258
              },
259
              {
260
                'source': 'adserver.org',
261
                'uids': [
262
                  {
263
                    'id': '0e45f56b-ad09-4c91-b090-8bd03e0d0754',
264
                    'atype': 1,
265
                    'ext': {
266
                      'rtiPartner': 'TDID'
267
                    }
268
                  }
269
                ]
270
              }
271
            ],
272
            'ortb2Imp': {
273
              'ext': {
274
                'data': {
275
                  'adserver': {
276
                    'name': 'gam',
277
                    'adslot': '/6783/Kiwi/portail'
278
                  },
279
                  'pbadslot': '/6783/Kiwi/portail'
280
                }
281
              }
282
            },
283
            'adUnitCode': 'btf_leaderboard',
284
            'transactionId': '3ce8216e-7898-4a22-86ba-01519b62bfce',
285
            'sizes': [
286
              [
287
                728,
288
                90
289
              ]
290
            ],
291
            'bidId': '146661c05209a56e',
292
            'bidderRequestId': '1451a3c759c60359',
293
            'auctionId': 'e8d15df4-d89c-44c9-8b36-812f75cbf227',
294
            'src': 'client',
295
            'bidRequestsCount': 2,
296
            'bidderRequestsCount': 2,
297
            'bidderWinsCount': 0
298
          }
299
        ],
300
        'auctionStart': 1621959214757,
301
        'timeout': 2000,
302
        'refererInfo': {
303
          'referer': 'https://www.blog.com/?pbjs_debug=true',
304
          'reachedTop': true,
305
          'isAmp': false,
306
          'numIframes': 0,
307
          'stack': [
308
            'https://www.blog.com/?pbjs_debug=true'
309
          ],
310
          'canonicalUrl': null
311
        },
312
        'gdprConsent': {
313
          'consentString': 'CPGw1WAPGw1WAAHABBENBbCsAP_AAH_AAAAAH3tf_X__b3_j-_59__t0eY1f9_7_v-0zjhfdt-8N2f_X_L8X42M7vF36pq4KuR4Eu3LBIQdlHOHcTUmw6okVrTPsbk2Mr7NKJ7PEmnMbe2dYGH9_n93TuZKY7__8___z__-v_v____f_r-3_3__59X---_e_V399zLv9__3__9gfaASYal8AF2JY4Mk0aVQogQhWEh0AoAKKAYWiawgZXBTsrgI9QQMAEJqAjAiBBiCjFgEAAgEASERASAHggEQBEAgABACpAQgAI2AQWAFgYBAAKAaFiBFAEIEhBkcFRymBARItFBPZWAJRd7GmEIZRYAUCj-iowEShBAsDISFg4AAA.f_gAD_gAAAAA',
314
          'vendorData': {
315
            'cmpId': 7,
316
            'cmpVersion': 1,
317
            'gdprApplies': true,
318
            'tcfPolicyVersion': 2,
319
            'eventStatus': 'useractioncomplete',
320
            'cmpStatus': 'loaded',
321
            'listenerId': 47,
322
            'tcString': 'CPGw1WAPGw1WAAHABBENBbCsAP_AAH_AAAAAH3tf_X__b3_j-_59__t0eY1f9_7_v-0zjhfdt-8N2f_X_L8X42M7vF36pq4KuR4Eu3LBIQdlHOHcTUmw6okVrTPsbk2Mr7NKJ7PEmnMbe2dYGH9_n93TuZKY7__8___z__-v_v____f_r-3_3__59X---_e_V399zLv9__3__9gfaASYal8AF2JY4Mk0aVQogQhWEh0AoAKKAYWiawgZXBTsrgI9QQMAEJqAjAiBBiCjFgEAAgEASERASAHggEQBEAgABACpAQgAI2AQWAFgYBAAKAaFiBFAEIEhBkcFRymBARItFBPZWAJRd7GmEIZRYAUCj-iowEShBAsDISFg4AAA.f_gAD_gAAAAA',
323
          },
324
          'gdprApplies': true,
325
          'addtlConsent': '1~7.12.35.62.66.70.89.93.108.122.144.149.153.162.167.184.196.221.241.253.259.272.311.317.323.326.338.348.350.415.440.448.449.482.486.491.494.495.540.571.574.585.587.588.590.725.733.780.817.839.864.867.932.938.981.986.1031.1033.1051.1092.1097.1126.1127.1170.1171.1186.1201.1204.1205.1211.1215.1230.1232.1236.1248.1276.1290.1301.1313.1344.1364.1365.1415.1419.1428.1449.1451.1509.1558.1564.1570.1577.1591.1651.1669.1712.1716.1720.1721.1725.1733.1753.1765.1799.1810.1834.1842.1870.1878.1889.1896.1911.1922.1929.2012.2072.2078.2079.2109.2177.2202.2253.2290.2299.2316.2357.2373.2526.2531.2571.2572.2575.2628.2663.2677.2776.2778.2779.2985.3033.3052.3154',
326
          'apiVersion': 2
327
        },
328
        'start': 1621959214763
329
      }];
330

331
      auction.bidsReceived = [{
1✔
332
        'bidderCode': 'appnexus',
333
        'width': 728,
334
        'height': 90,
335
        'statusMessage': 'Bid available',
336
        'adId': '99e7838aa7f1c4f',
337
        'requestId': '21e0b32208ee9a',
338
        'mediaType': 'banner',
339
        'source': 'client',
340
        'cpm': 0.020601,
341
        'creativeId': 209272535,
342
        'currency': 'USD',
343
        'netRevenue': true,
344
        'ttl': 300,
345
        'adUnitCode': 'user-728',
346
        'appnexus': {
347
          'buyerMemberId': 11563
348
        },
349
        'meta': {
350
          'advertiserId': 4388779
351
        },
352
        'ad': 'stuff i am not interested in',
353
        'originalCpm': 0.020601,
354
        'originalCurrency': 'USD',
355
        'auctionId': 'c7694dbb-a583-4a73-a933-b16f1f821ba4',
356
        // Make sure cleanup is resilient
357
        'someNullObject': null,
358
        'someUndefinedProperty': undefined
359
      }];
360

361
      id5AnalyticsAdapter.enableAnalytics(config);
1✔
362
      server.respond();
1✔
363
      events.emit(EVENTS.AUCTION_END, auction);
1✔
364
      server.respond();
1✔
365

366
      expect(server.requests).to.have.length(3);
1✔
367

368
      const body = JSON.parse(server.requests[2].requestBody);
1✔
369
      expect(body.event).to.equal('auctionEnd');
1✔
370
      expect(body.payload.adUnits[0].bids[0].userId).to.eql({
1✔
371
        'criteoId': '__ID5_REDACTED__',
372
        'id5id': {
373
          'uid': '__ID5_REDACTED__',
374
          'ext': {
375
            'linkType': 1
376
          }
377
        },
378
        'tdid': '__ID5_REDACTED__'
379
      });
380
      expect(body.payload.bidderRequests[0].bids[0].userId).to.eql({
1✔
381
        'sharedid': '__ID5_REDACTED__',
382
        'id5id': {
383
          'uid': '__ID5_REDACTED__',
384
          'ext': {
385
            'linkType': 1
386
          }
387
        },
388
        'tdid': '__ID5_REDACTED__'
389
      });
390
      body.payload.adUnits[0].bids[0].userIdAsEids.forEach((userId) => {
1✔
391
        expect(userId.uids[0].id).to.equal('__ID5_REDACTED__');
2✔
392
        if (userId.uids[0].ext) {
2✔
393
          expect(userId.uids[0].ext).to.equal('__ID5_REDACTED__');
1✔
394
        }
395
      });
396
      body.payload.bidderRequests[0].bids[0].userIdAsEids.forEach((userId) => {
1✔
397
        expect(userId.uids[0].id).to.equal('__ID5_REDACTED__');
3✔
398
        if (userId.uids[0].ext) {
3✔
399
          expect(userId.uids[0].ext).to.equal('__ID5_REDACTED__');
3✔
400
        }
401
      });
402
      expect(body.payload.bidsReceived[0].ad).to.equal(undefined);
1✔
403
      expect(body.payload.bidsReceived[0].requestId).to.equal('21e0b32208ee9a');
1✔
404
    });
405

406
    it('can override events to collect if configured to do so', () => {
1✔
407
      server.respondWith('GET', CONFIG_URL, [200,
1✔
408
        {
409
          'Content-Type': 'application/json',
410
          'Access-Control-Allow-Origin': '*'
411
        },
412
        `{ "sampling": 1, "ingestUrl": "${INGEST_URL}", "eventsToTrack": ["tcf2Enforcement"] }`
413
      ]);
414
      id5AnalyticsAdapter.enableAnalytics(config);
1✔
415
      server.respond();
1✔
416
      events.emit(EVENTS.AUCTION_END, auction);
1✔
417
      server.respond();
1✔
418

419
      expect(server.requests).to.have.length(2);
1✔
420
      const body1 = JSON.parse(server.requests[1].requestBody);
1✔
421
      expect(body1.event).to.equal('tcf2Enforcement');
1✔
422
    });
423

424
    it('can extend cleanup rules from server side', () => {
1✔
425
      auction.bidsReceived = [{
1✔
426
        'bidderCode': 'appnexus',
427
        'width': 728,
428
        'height': 90,
429
        'statusMessage': 'Bid available',
430
        'adId': '99e7838aa7f1c4f',
431
        'requestId': '21e0b32208ee9a',
432
        'mediaType': 'banner',
433
        'source': 'client',
434
        'cpm': 0.020601,
435
        'creativeId': 209272535,
436
        'currency': 'USD',
437
        'netRevenue': true,
438
        'ttl': 300,
439
        'adUnitCode': 'user-728',
440
        'appnexus': {
441
          'buyerMemberId': 11563
442
        },
443
        'meta': {
444
          'advertiserId': 4388779
445
        },
446
        'ad': 'stuff i am not interested in',
447
        'originalCpm': 0.020601,
448
        'originalCurrency': 'USD',
449
        'auctionId': 'c7694dbb-a583-4a73-a933-b16f1f821ba4'
450
      }, {
451
        'bidderCode': 'ix',
452
        'width': 728,
453
        'height': 90,
454
        'statusMessage': 'Bid available',
455
        'adId': '228f725de4a9ff09',
456
        'requestId': '225a42b4a8ec7287',
457
        'mediaType': 'banner',
458
        'source': 'client',
459
        'cpm': 0.06,
460
        'netRevenue': true,
461
        'currency': 'USD',
462
        'creativeId': '8838044',
463
        'ad': 'lots of HTML code',
464
        'ttl': 300,
465
        'meta': {
466
          'networkId': 85,
467
          'brandId': 822,
468
          'brandName': 'Microsoft Brands',
469
          'advertiserDomains': [
470
            'microsoftstore.com'
471
          ]
472
        },
473
        'originalCpm': 0.06,
474
        'originalCurrency': 'USD',
475
        'auctionId': 'fe28ce44-61bb-4ed8-be3c-3e801dfddcb9',
476
        'responseTimestamp': 1621954632648,
477
        'requestTimestamp': 1621954632498,
478
        'bidder': 'ix',
479
        'adUnitCode': 'sticky_footer',
480
        'timeToRespond': 150,
481
        'pbLg': '0.00',
482
        'pbCg': '0.06',
483
        'size': '728x90',
484
        'adserverTargeting': {
485
          'hb_bidder': 'ix',
486
        }
487
      }];
488
      server.respondWith('GET', CONFIG_URL, [200,
1✔
489
        {
490
          'Content-Type': 'application/json',
491
          'Access-Control-Allow-Origin': '*'
492
        },
493
        `{ "sampling": 1, "ingestUrl": "${INGEST_URL}", "additionalCleanupRules": {"auctionEnd": [{"match":["bidsReceived", "*", "requestId"],"apply":"erase"}]} }`
494
      ]);
495
      id5AnalyticsAdapter.enableAnalytics(config);
1✔
496
      server.respond();
1✔
497
      events.emit(EVENTS.AUCTION_END, auction);
1✔
498
      server.respond();
1✔
499

500
      expect(server.requests).to.have.length(3);
1✔
501
      const body = JSON.parse(server.requests[2].requestBody);
1✔
502
      expect(body.event).to.equal('auctionEnd');
1✔
503
      expect(body.payload.bidsReceived[0].requestId).to.equal(undefined);
1✔
504
      expect(body.payload.bidsReceived[1].requestId).to.equal(undefined);
1✔
505
      expect(body.payload.bidsReceived[0].bidderCode).to.equal('appnexus');
1✔
506
      expect(body.payload.bidsReceived[1].bidderCode).to.equal('ix');
1✔
507
    });
508

509
    it('can replace cleanup rules from server side', () => {
1✔
510
      auction.bidsReceived = [{
1✔
511
        'meta': {
512
          'advertiserId': 4388779
513
        }
514
      }]
515
      auction.adUnits[0].bids = [{
1✔
516
        'bidder': 'appnexus',
517
        'userId': {
518
          'id5id': {
519
            'uid': 'ID5-ZHMOQ99ulpk687Fd9xVwzxMsYtkQIJnI-qm3iWdtww!ID5*FSycZQy7v7zWXiKbEpPEWoB3_UiWdPGzh554ncYDvOkAAA3rajiR0yNrFAU7oDTu',
520
            'ext': {'linkType': 1}
521
          }
522
        }
523
      }];
524
      server.respondWith('GET', CONFIG_URL, [200,
1✔
525
        {
526
          'Content-Type': 'application/json',
527
          'Access-Control-Allow-Origin': '*'
528
        },
529
        `{ "sampling": 1, "ingestUrl": "${INGEST_URL}", "replaceCleanupRules":true, "additionalCleanupRules": {"auctionEnd": [{"match":["bidsReceived", "*", "meta"],"apply":"erase"}]} }`
530
      ]);
531
      id5AnalyticsAdapter.enableAnalytics(config);
1✔
532
      server.respond();
1✔
533
      events.emit(EVENTS.AUCTION_END, auction);
1✔
534
      server.respond();
1✔
535

536
      expect(server.requests).to.have.length(3);
1✔
537
      const body = JSON.parse(server.requests[2].requestBody);
1✔
538
      expect(body.event).to.equal('auctionEnd');
1✔
539
      expect(body.payload.bidsReceived[0].meta).to.equal(undefined);    // new rule
1✔
540
      expect(body.payload.adUnits[0].bids[0].userId.id5id.uid).to.equal(auction.adUnits[0].bids[0].userId.id5id.uid); // old, overridden rule
1✔
541
    });
542

543
    // helper to wait until server has received at least `expected` requests
544
    async function waitForRequests(expected = 3, timeout = 2000, interval = 10) {
1!
545
      return new Promise((resolve, reject) => {
1✔
546
        const start = Date.now();
1✔
547
        const timer = setInterval(() => {
1✔
548
          if (server.requests.length >= expected) {
1!
549
            clearInterval(timer);
1✔
550
            resolve();
1✔
551
          } else if (Date.now() - start > timeout) {
×
552
            clearInterval(timer);
×
553
            reject(new Error('Timed out waiting for requests: expected ' + expected));
×
554
          }
555
        }, interval);
556
      });
557
    }
558
  });
559
});
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