text: import { expect } from 'chai';
  import * as utils from '../../../src/utils.js';
  import {
    spec,
    storage,
    DEFAULT_BIDDER_CONFIG,
    VERSION,
    common,
  } from '../../../modules/adbookpspBidAdapter.js';
  
  describe('adbookpsp bid adapter', () => {
    let sandbox;
  
    beforeEach(function () {
      sandbox = sinon.sandbox.create();
  
      sandbox
        .stub(common, 'generateUUID')
        .returns('54444444-5444-4444-9444-544444444444');
      sandbox.stub(common, 'getWindowDimensions').returns({
        innerWidth: 100,
        innerHeight: 100,
      });
    });
  
    afterEach(function () {
      sandbox.restore();
    });
  
    describe('isBidRequestValid()', () => {
      it('should return false when there is no banner in mediaTypes', () => {
        const bid = utils.deepClone(bannerBid);
        delete bid.mediaTypes.banner;
  
        expect(spec.isBidRequestValid(bid)).to.equal(false);
      });
  
      it('should return false when orgId and placementId is not defined', () => {
        const bid = utils.deepClone(bannerBid);
        delete bid.params.placementId;
        delete bid.params.orgId;
  
        expect(spec.isBidRequestValid(bid)).to.be.false;
      });
  
      it('should return true when orgId is set in config', () => {
        const bid = utils.deepClone(bannerBid);
  
        delete bid.params.placementId;
        delete bid.params.orgId;
  
        sandbox
          .stub(common, 'getConfig')
          .withArgs('adbookpsp.orgId')
          .returns('129576');
  
        expect(spec.isBidRequestValid(bid)).to.be.true;
      });
  
      it('should return true when required params found', () => {
        expect(spec.isBidRequestValid(bannerBid)).to.equal(true);
        expect(spec.isBidRequestValid(videoBid)).to.equal(true);
        expect(spec.isBidRequestValid(mixedBid)).to.equal(true);
      });
  
      it('should return false when sizes for banner are not specified', () => {
        const bid = utils.deepClone(bannerBid);
        delete bid.mediaTypes.banner.sizes;
  
        expect(spec.isBidRequestValid(bid)).to.equal(false);
      });
  
      it('should return false when sizes for banner are invalid', () => {
        const bid = utils.deepClone(bannerBid);
        delete bid.mediaTypes.banner.sizes;
  
        bid.mediaTypes.banner.sizes = [['123', 'foo']];
  
        expect(spec.isBidRequestValid(bid)).to.equal(false);
      });
  
      it('should return true if player size is set via playerSize', () => {
        expect(spec.isBidRequestValid(videoBid)).to.equal(true);
      });
  
      it('should return true if player size is set via w and h', () => {
        const bid = utils.deepClone(videoBid);
        delete bid.mediaTypes.video.playerSize;
  
        bid.mediaTypes.video.w = 400;
        bid.mediaTypes.video.h = 300;
  
        expect(spec.isBidRequestValid(bid)).to.equal(true);
      });
  
      it('should reutrn false if player size is not set', () => {
        const bid = utils.deepClone(videoBid);
        delete bid.mediaTypes.video.playerSize;
  
        expect(spec.isBidRequestValid(bid)).to.equal(false);
      });
    });
  
    describe('buildRequests()', () => {
      it('should build correct request for banner bid', () => {
        sandbox
          .stub(common, 'getConfig')
          .withArgs('adbookpsp.orgId')
          .returns(undefined)
          .withArgs('adbookpsp.exchangeUrl')
          .returns('https://ex.fattail.com/openrtb2');
  
        const requests = spec.buildRequests([bannerBid], bidderRequest);
  
        expect(requests).to.have.lengthOf(1);
        expect(requests[0]).to.deep.include({
          method: 'POST',
          url: 'https://ex.fattail.com/openrtb2',
          options: {
            contentType: 'application/json',
            withCredentials: true,
          },
        });
        expect(JSON.parse(requests[0].data)).to.deep.equal(bannerExchangeRequest);
      });
  
      it('should build correct request for video bid', () => {
        sandbox
          .stub(common, 'getConfig')
          .withArgs('adbookpsp')
          .returns(DEFAULT_BIDDER_CONFIG)
          .withArgs('adbookpsp.exchangeUrl')
          .returns(DEFAULT_BIDDER_CONFIG.exchangeUrl)
          .withArgs('adbookpsp.orgId')
          .returns(undefined);
  
        const requests = spec.buildRequests([videoBid], bidderRequest);
  
        expect(requests).to.have.lengthOf(1);
        expect(requests[0]).to.deep.include({
          method: 'POST',
          url: 'https://ex.fattail.com/openrtb2',
          options: {
            contentType: 'application/json',
            withCredentials: true,
          },
        });
        expect(JSON.parse(requests[0].data)).to.deep.include({
          ...videoExchangeRequest,
          ext: {
            adbook: {
              config: DEFAULT_BIDDER_CONFIG,
              version: {
                adapter: VERSION,
                prebid: '$prebid.version$',
              },
            },
          },
        });
      });
  
      it('should build correct request for video bid with w and h', () => {
        const bid = utils.deepClone(videoBid);
  
        delete bid.mediaTypes.video.playerSize;
  
        bid.mediaTypes.video.w = 400;
        bid.mediaTypes.video.h = 300;
  
        const [request] = spec.buildRequests([bid], bidderRequest);
        const requestData = JSON.parse(request.data);
  
        expect(requestData.imp[0].video.w).to.equal(400);
        expect(requestData.imp[0].video.h).to.equal(300);
      });
  
      it('should build correct request for video bid with both w, h and playerSize', () => {
        const bid = utils.deepClone(videoBid);
  
        bid.mediaTypes.video.w = 640;
        bid.mediaTypes.video.h = 480;
  
        const [request] = spec.buildRequests([bid], bidderRequest);
        const requestData = JSON.parse(request.data);
  
        expect(requestData.imp[0].video.w).to.equal(640);
        expect(requestData.imp[0].video.h).to.equal(480);
      });
  
      it('should build correct request for mixed bid', () => {
        sandbox
          .stub(common, 'getConfig')
          .withArgs('adbookpsp.orgId')
          .returns(undefined)
          .withArgs('adbookpsp.exchangeUrl')
          .returns('https://ex.fattail.com/openrtb2');
  
        const requests = spec.buildRequests([mixedBid], bidderRequest);
  
        expect(requests).to.have.lengthOf(1);
        expect(requests[0]).to.deep.include({
          method: 'POST',
          url: 'https://ex.fattail.com/openrtb2',
          options: {
            contentType: 'application/json',
            withCredentials: true,
          },
        });
        expect(JSON.parse(requests[0].data)).to.deep.include(
          mixedExchangeRequest
        );
      });
  
      it('should use orgId from config', () => {
        const bid = utils.deepClone(bannerBid);
  
        delete bid.params;
  
        sandbox
          .stub(common, 'getConfig')
          .withArgs('adbookpsp.orgId')
          .returns('129576');
  
        const requests = spec.buildRequests([bid], bidderRequest);
        const request = JSON.parse(requests[0].data);
  
        expect(request.imp[0].ext).to.deep.include({
          adbook: {
            orgId: '129576',
          },
        });
      });
  
      it('should use orgId from adUnit when orgId is also set in config', () => {
        const bid = utils.deepClone(bannerBid);
  
        delete bid.params.placementId;
  
        bid.params.orgId = 'adUnitOrgId';
  
        sandbox
          .stub(common, 'getConfig')
          .withArgs('adbookpsp.orgId')
          .returns('configOrgId');
  
        const requests = spec.buildRequests([bid], bidderRequest);
        const request = JSON.parse(requests[0].data);
  
        expect(request.imp[0].ext).to.deep.include({
          adbook: {
            orgId: 'adUnitOrgId',
          },
        });
      });
  
      it('should include in request GDPR options if available', () => {
        const request = utils.deepClone(bidderRequest);
  
        delete request.uspConsent;
  
        const requests = spec.buildRequests([bannerBid, mixedBid], request);
  
        expect(JSON.parse(requests[0].data)).to.deep.include({
          regs: {
            coppa: 0,
            ext: {
              gdpr: 1,
              gdprConsentString: 'gdprConsentString',
            },
          },
        });
      });
  
      it('should include in request USP (CPPA) options if available', () => {
        const request = utils.deepClone(bidderRequest);
  
        delete request.gdprConsent;
  
        const requests = spec.buildRequests([bannerBid, mixedBid], request);
  
        expect(JSON.parse(requests[0].data)).to.deep.include({
          regs: {
            coppa: 0,
            ext: {
              us_privacy: 'uspConsentString',
            },
          },
        });
      });
  
      it('should pass valid coppa flag based on config', () => {
        sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true);
  
        const request = utils.deepClone(bidderRequest);
  
        delete request.gdprConsent;
        delete request.uspConsent;
  
        const requests = spec.buildRequests([bannerBid, mixedBid], request);
  
        expect(JSON.parse(requests[0].data)).to.deep.include({
          regs: {
            coppa: 1,
          },
        });
      });
  
      it('should pass GDPR, USP (CCPA) and COPPA options', () => {
        sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true);
  
        const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest);
  
        expect(JSON.parse(requests[0].data)).to.deep.include({
          regs: {
            coppa: 1,
            ext: {
              gdpr: 1,
              gdprConsentString: 'gdprConsentString',
              us_privacy: 'uspConsentString',
            },
          },
        });
      });
  
      it('should generate and pass user id when is not present in cookie and local storage is not enabled', () => {
        sandbox.stub(storage, 'localStorageIsEnabled').returns(false);
        const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest);
        const rtbRequest = JSON.parse(requests[0].data);
  
        expect(rtbRequest.user.id).to.have.lengthOf(36);
      });
  
      it('should pass user id when is present in cookie', () => {
        sandbox.stub(storage, 'localStorageIsEnabled').returns(false);
        sandbox
          .stub(storage, 'getCookie')
          .returns('e35da6bb-f2f8-443b-aeff-3375bef45c9d');
        const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest);
        const rtbRequest = JSON.parse(requests[0].data);
  
        expect(rtbRequest.user.id).to.equal(
          'e35da6bb-f2f8-443b-aeff-3375bef45c9d'
        );
      });
  
      it('should pass user id if is present in local storage', () => {
        sandbox.stub(storage, 'localStorageIsEnabled').returns(true);
        sandbox
          .stub(storage, 'getDataFromLocalStorage')
          .returns('e35da6bb-f2f8-443b-aeff-3375bef45c9d');
  
        const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest);
        const rtbRequest = JSON.parse(requests[0].data);
        expect(rtbRequest.user.id).to.equal(
          'e35da6bb-f2f8-443b-aeff-3375bef45c9d'
        );
      });
  
      it('should regenerate user id if it is invalid', () => {
        sandbox.stub(storage, 'localStorageIsEnabled').returns(true);
        sandbox.stub(storage, 'getDataFromLocalStorage').returns('foo');
  
        const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest);
        const rtbRequest = JSON.parse(requests[0].data);
        expect(rtbRequest.user.id).to.have.lengthOf(36);
      });
  
      it('should pass schain if available', () => {
        const bid = utils.deepClone(bannerBid);
        const schain = {
          ver: '1.0',
          complete: 1,
          nodes: [
            {
              asi: 'exchange1.com',
              sid: '1234',
              hp: 1,
              rid: 'bid-request-1',
              name: 'publisher',
              domain: 'publisher.com',
            },
          ],
        };
  
        bid.schain = schain;
  
        const requests = spec.buildRequests([bid], bidderRequest);
  
        expect(JSON.parse(requests[0].data).source).to.deep.include({
          ext: {
            schain,
          },
        });
      });
  
      it('return empty array if there are no valid bid requests', () => {
        const requests = spec.buildRequests([], bidderRequest);
  
        expect(requests).to.deep.equal([]);
      });
  
      it('should prioritize device information set in config', () => {
        const ua =
          'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1';
  
        sandbox.stub(common, 'getConfig').withArgs('device').returns({
          ua,
        });
  
        const requests = spec.buildRequests([bannerBid], bidderRequest);
  
        expect(JSON.parse(requests[0].data).device.ua).to.equal(ua);
      });
  
      it('should include bidder config', () => {
        const bidderConfig = {
          bidTTL: 500,
          defaultCurrency: 'USD',
          exchangeUrl: 'https://exsb.fattail.com/openrtb2',
          winTrackingEnabled: true,
          winTrackingUrl: 'https://evsb.fattail.com/wins',
          orgId: '129576',
        };
        sandbox
          .stub(common, 'getConfig')
          .withArgs('adbookpsp')
          .returns(bidderConfig);
  
        const requests = spec.buildRequests([bannerBid], bidderRequest);
        const request = JSON.parse(requests[0].data);
  
        expect(request.ext).to.deep.include({
          adbook: {
            config: bidderConfig,
            version: {
              adapter: VERSION,
              prebid: '$prebid.version$',
            },
          },
        });
      });
  
      it('should use bidder video params if they are set', () => {
        const videoBidWithParams = utils.deepClone(videoBid);
        const bidderVideoParams = {
          api: [1, 2],
          mimes: ['video/mp4', 'video/x-flv'],
          playbackmethod: [3, 4],
          protocols: [5, 6],
          minduration: 10,
          maxduration: 30,
        };
        videoBidWithParams.params.video = bidderVideoParams;
  
        const requests = spec.buildRequests([videoBidWithParams], bidderRequest);
        const request = JSON.parse(requests[0].data);
  
        expect(request.imp[0]).to.deep.include({
          video: {
            ...bidderVideoParams,
            w: videoBidWithParams.mediaTypes.video.playerSize[0][0],
            h: videoBidWithParams.mediaTypes.video.playerSize[0][1],
          },
        });
      });
    });
  
    describe('interpretResponse()', () => {
      it('should correctly interpret valid response', () => {
        sandbox
          .stub(common, 'getConfig')
          .withArgs('adbookpsp.defaultCurrency')
          .returns(DEFAULT_BIDDER_CONFIG.defaultCurrency)
          .withArgs('adbookpsp.bidTTL')
          .returns(DEFAULT_BIDDER_CONFIG.bidTTL);
  
        const response = utils.deepClone(exchangeResponse);
        const bids = spec.interpretResponse(
          { body: response },
          { data: JSON.stringify(exchangeBidRequest) }
        );
  
        expect(bids).to.deep.equal([
          {
            bidderRequestId: '999ccceeee11',
            requestId: '9873kfse',
            bidId: 'bid123456',
            width: 300,
            height: 250,
            ttl: 300,
            cpm: 0.5,
            currency: 'USD',
            creativeId: '123456789',
            mediaType: 'banner',
            meta: {
              advertiserDomains: ['advertiser.com'],
              mediaType: 'banner',
              primaryCatId: 'IAB2-1',
              secondaryCatIds: ['IAB2-2', 'IAB2-3'],
            },
            netRevenue: true,
            nurl: 'http://win.example.url',
            adUnitCode: 'div-gpt-ad-837465923534-0',
            ad: '<div>ad</div>',
            adId: '5',
            adserverTargeting: {
              hb_ad_ord_adbookpsp: '0_0', // the value to the left of the underscore represents the index of the ad id and the number to the right represents the order index
              hb_adid_c_adbookpsp: '5',
              hb_deal_adbookpsp: 'werwetwerw',
              hb_liid_adbookpsp: '2342345',
              hb_ordid_adbookpsp: '567843',
            },
            referrer: 'http://prebid-test-page.io:8080/banner.html',
            lineItemId: '2342345',
          },
          {
            ad: '<VAST version="4.2" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.iab.com/VAST"></VAST>',
            adId: '10',
            adUnitCode: 'div-gpt-ad-837465923534-0',
            adserverTargeting: {
              hb_ad_ord_adbookpsp: '0_0',
              hb_adid_c_adbookpsp: '10',
              hb_deal_adbookpsp: 'dsfxcxcvxc',
              hb_liid_adbookpsp: '2121221',
              hb_ordid_adbookpsp: '5678234',
            },
            bidId: 'bid4321',
            bidderRequestId: '999ccceeee11',
            cpm: 0.45,
            creativeId: '543123',
            currency: 'USD',
            height: 250,
            lineItemId: '2121221',
            mediaType: 'video',
            meta: {
              advertiserDomains: ['advertiser.com', 'campaign.advertiser.com'],
              mediaType: 'video',
              primaryCatId: 'IAB2-3',
              secondaryCatIds: [],
            },
            netRevenue: true,
            nurl: 'http://win.example.url',
            referrer: 'http://prebid-test-page.io:8080/banner.html',
            requestId: '120kfeske',
            ttl: 300,
            vastXml:
              '<VAST version="4.2" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.iab.com/VAST"></VAST>',
            width: 300,
          },
        ]);
      });
  
      it('should place valid GAM targeting for all bids when multiple bids are present for multiple impressions', () => {
        const response = utils.deepClone(exchangeResponse);
  
        const bids = spec.interpretResponse(
          { body: response },
          { data: JSON.stringify(exchangeBidRequest) }
        );
  
        expect(bids).to.have.length(2);
        expect(bids[0].adserverTargeting).to.deep.equal({
          hb_ad_ord_adbookpsp: '0_0',
          hb_adid_c_adbookpsp: '5',
          hb_deal_adbookpsp: 'werwetwerw',
          hb_liid_adbookpsp: '2342345',
          hb_ordid_adbookpsp: '567843',
        });
        expect(bids[1].adserverTargeting).to.deep.equal({
          hb_ad_ord_adbookpsp: '0_0',
          hb_adid_c_adbookpsp: '10',
          hb_deal_adbookpsp: 'dsfxcxcvxc',
          hb_liid_adbookpsp: '2121221',
          hb_ordid_adbookpsp: '5678234',
        });
      });
  
      it('should place valid GAM targeting for all bids when multiple bids are present for single impression', () => {
        const response = utils.deepClone(exchangeResponse);
  
        response.seatbid[1].bid[0].impid = '9873kfse';
  
        const bids = spec.interpretResponse(
          { body: response },
          { data: JSON.stringify(exchangeBidRequest) }
        );
  
        expect(bids).to.have.length(2);
        for (const bid of bids) {
          expect(bid.adserverTargeting).to.deep.equal({
            hb_ad_ord_adbookpsp: '0_0,1_0',
            hb_adid_c_adbookpsp: '5,10',
            hb_deal_adbookpsp: 'werwetwerw,dsfxcxcvxc',
            hb_liid_adbookpsp: '2342345,2121221',
            hb_ordid_adbookpsp: '567843,5678234',
          });
        }
      });
  
      it('should return no bids if response id does not match bidderRequestId', () => {
        const body = utils.deepClone(exchangeResponse);
        body.id = '999';
  
        const bids = spec.interpretResponse(
          { body },
          { data: JSON.stringify(exchangeBidRequest) }
        );
  
        expect(bids).to.deep.equal([]);
      });
  
      it('should return no bids if response does not include seatbid', () => {
        const body = utils.deepClone(exchangeResponse);
        delete body.seatbid;
  
        const bids = spec.interpretResponse(
          { body },
          { data: JSON.stringify(exchangeBidRequest) }
        );
  
        expect(bids).to.deep.equal([]);
      });
  
      it('should return no bids if response does not include any bids', () => {
        const body = utils.deepClone(exchangeResponse);
        body.seatbid = [];
  
        const bids = spec.interpretResponse(
          { body },
          { data: JSON.stringify(exchangeBidRequest) }
        );
  
        expect(bids).to.deep.equal([]);
      });
  
      it('should exclude invalid video bids', () => {
        const body = utils.deepClone(exchangeResponse);
  
        body.seatbid.shift();
        body.seatbid[0].bid[0].adid = 34;
  
        const bids = spec.interpretResponse(
          { body },
          { data: JSON.stringify(exchangeBidRequest) }
        );
  
        expect(bids).to.deep.equal([]);
      });
  
      it('should exclude invalid banner bids', () => {
        const body = utils.deepClone(exchangeResponse);
        const request = utils.deepClone(exchangeBidRequest);
  
        body.seatbid.pop();
  
        delete body.seatbid[0].bid[0].w;
        delete body.seatbid[0].bid[0].h;
  
        request.imp[0].banner.format.push({ w: 300, h: 600 });
  
        const bids = spec.interpretResponse(
          { body },
          { data: JSON.stringify(request) }
        );
  
        expect(bids).to.deep.equal([]);
      });
  
      it('should not include invalid banner bids in targeting map', () => {
        const body = utils.deepClone(exchangeResponse);
        const request = utils.deepClone(exchangeBidRequest);
  
        body.seatbid[0].bid[0].h = '600';
  
        request.imp[0].banner.format.push({ w: 300, h: 600 });
  
        const bids = spec.interpretResponse(
          { body },
          { data: JSON.stringify(exchangeBidRequest) }
        );
  
        expect(bids[0].adserverTargeting).to.deep.equal({
          hb_ad_ord_adbookpsp: '0_0',
          hb_adid_c_adbookpsp: '10',
          hb_deal_adbookpsp: 'dsfxcxcvxc',
          hb_liid_adbookpsp: '2121221',
          hb_ordid_adbookpsp: '5678234',
        });
      });
  
      it('should not validate banner bid dimensions if bid request has single size', () => {
        const body = utils.deepClone(exchangeResponse);
        const request = utils.deepClone(exchangeBidRequest);
  
        delete body.seatbid[1];
        delete body.seatbid[0].bid[0].h;
        delete body.seatbid[0].bid[0].w;
  
        const bids = spec.interpretResponse(
          { body },
          { data: JSON.stringify(request) }
        );
  
        expect(bids.length).to.equal(1);
      });
    });
  
    describe('getUserSyncs()', () => {
      it('should return user syncs if there are included in the response and syncs are enabled', () => {
        const syncs = spec.getUserSyncs(
          {
            pixelEnabled: true,
            iframeEnabled: true,
          },
          [{ body: exchangeResponse }]
        );
  
        expect(syncs).to.deep.equal([
          {
            type: 'image',
            url: 'http://sometest.com/sync/1234567',
          },
          {
            type: 'iframe',
            url: 'http://sometest.com/sync/1234567',
          },
        ]);
      });
  
      it('should not return user syncs if syncs are disabled', () => {
        const syncs = spec.getUserSyncs(
          {
            pixelEnabled: false,
            iframeEnabled: false,
          },
          [{ body: exchangeResponse }]
        );
  
        expect(syncs).to.deep.equal([]);
      });
  
      it('should return image syncs if they are enabled', () => {
        const syncs = spec.getUserSyncs(
          {
            pixelEnabled: true,
            iframeEnabled: false,
          },
          [{ body: exchangeResponse }]
        );
  
        expect(syncs).to.deep.equal([
          {
            type: 'image',
            url: 'http://sometest.com/sync/1234567',
          },
        ]);
      });
  
      it('should return iframe syncs if they are enabled', () => {
        const syncs = spec.getUserSyncs(
          {
            pixelEnabled: false,
            iframeEnabled: true,
          },
          [{ body: exchangeResponse }]
        );
  
        expect(syncs).to.deep.equal([
          {
            type: 'iframe',
            url: 'http://sometest.com/sync/1234567',
          },
        ]);
      });
  
      it('should append COPPA status to sync url', () => {
        sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true);
        const syncs = spec.getUserSyncs(
          {
            pixelEnabled: false,
            iframeEnabled: true,
          },
          [{ body: utils.deepClone(exchangeResponse) }]
        );
  
        expect(syncs).to.deep.equal([
          {
            type: 'iframe',
            url: 'http://sometest.com/sync/1234567?coppa=1',
          },
        ]);
      });
  
      it('should append GDPR consent data to url', () => {
        sandbox.stub(common, 'getConfig').withArgs('coppa').returns(false);
        const syncs = spec.getUserSyncs(
          {
            pixelEnabled: false,
            iframeEnabled: true,
          },
          [{ body: utils.deepClone(exchangeResponse) }],
          { gdprApplies: true, consentString: 'gdprConsentString' }
        );
  
        expect(syncs).to.deep.equal([
          {
            type: 'iframe',
            url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString',
          },
        ]);
      });
  
      it('should append USP (CCPA) consent string to url', () => {
        const syncs = spec.getUserSyncs(
          {
            pixelEnabled: false,
            iframeEnabled: true,
          },
          [{ body: utils.deepClone(exchangeResponse) }],
          undefined,
          'uspConsentString'
        );
  
        expect(syncs).to.deep.equal([
          {
            type: 'iframe',
            url: 'http://sometest.com/sync/1234567?us_privacy=uspConsentString',
          },
        ]);
      });
  
      it('should append COPPA, GDPR and USP (CCPA) url params', () => {
        sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true);
        const syncs = spec.getUserSyncs(
          {
            pixelEnabled: true,
            iframeEnabled: true,
          },
          [{ body: utils.deepClone(exchangeResponse) }],
          { gdprApplies: true, consentString: 'gdprConsentString' },
          'uspConsentString'
        );
  
        expect(syncs).to.deep.equal([
          {
            type: 'image',
            url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1',
          },
          {
            type: 'iframe',
            url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1',
          },
        ]);
      });
  
      it('should respect url param syntax when appending params', () => {
        sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true);
  
        const response = utils.deepClone(exchangeResponse);
  
        response.ext.sync[0] = {
          type: 'image',
          url: 'http://sometest.com/sync/1234567?horseCount=4',
        };
  
        const syncs = spec.getUserSyncs(
          {
            pixelEnabled: true,
            iframeEnabled: false,
          },
          [{ body: response }],
          { gdprApplies: true, consentString: 'gdprConsentString' },
          'uspConsentString'
        );
  
        expect(syncs).to.deep.equal([
          {
            type: 'image',
            url: 'http://sometest.com/sync/1234567?horseCount=4&gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1',
          },
        ]);
      });
    });
  
    describe('onBidWon()', () => {
      it('should track win if win tracking is enabled', () => {
        const spy = sandbox.spy(utils, 'triggerPixel');
  
        sandbox
          .stub(common, 'getConfig')
          .withArgs('adbookpsp.winTrackingEnabled')
          .returns(true)
          .withArgs('adbookpsp.winTrackingUrl')
          .returns('https://ev.fattail.com/wins');
  
        spec.onBidWon({
          requestId: 'requestId',
          bidderRequestId: 'bidderRequestId',
          bidId: 'bidId',
        });
  
        expect(
          spy.calledWith(
            'https://ev.fattail.com/wins?impId=requestId&reqId=bidderRequestId&bidId=bidId'
          )
        ).to.equal(true);
      });
      it('should call bid.nurl if win tracking is enabled', () => {
        const spy = sandbox.spy(utils, 'triggerPixel');
  
        sandbox
          .stub(common, 'getConfig')
          .withArgs('adbookpsp.winTrackingEnabled')
          .returns(true)
          .withArgs('adbookpsp.winTrackingUrl')
          .returns('https://ev.fattail.com/wins');
  
        spec.onBidWon({
          requestId: 'requestId',
          bidderRequestId: 'bidderRequestId',
          bidId: 'bidId',
          nurl: 'http://win.example.url',
        });
  
        expect(spy.calledWith('http://win.example.url')).to.equal(true);
      });
      it('should not track win nor call bid.nurl if win tracking is disabled', () => {
        const spy = sandbox.spy(utils, 'triggerPixel');
  
        sandbox
          .stub(common, 'getConfig')
          .withArgs('adbookpsp.winTrackingEnabled')
          .returns(false)
          .withArgs('adbookpsp.winTrackingUrl')
          .returns('https://ev.fattail.com/wins');
  
        spec.onBidWon({
          requestId: 'requestId',
          bidderRequestId: 'bidderRequestId',
          bidId: 'bidId',
          nurl: 'http://win.example.url',
        });
  
        expect(spy.notCalled).to.equal(true);
      });
    });
  });
  
  const bidderRequest = {
    auctionId: 'aaccee333311',
    bidderRequestId: '999ccceeee11',
    timeout: 200,
    refererInfo: {
      referer: 'http://example-domain.com/foo',
    },
    gdprConsent: {
      gdprApplies: 1,
      consentString: 'gdprConsentString',
    },
    uspConsent: 'uspConsentString',
  };
  
  const bannerBid = {
    bidder: 'adbookpsp',
    params: {
      placementId: '12390123',
    },
    mediaTypes: {
      banner: {
        sizes: [
          [300, 250],
          [300, 600],
        ],
      },
    },
    adUnitCode: 'div-gpt-ad-837465923534-0',
    transactionId: 'sfsf89e-mck3-asf3-fe45-feksjfi123mfs',
    bidId: '9873kfse',
    bidderRequestId: '999ccceeee11',
    auctionId: 'aaccee333311',
    lineItemId: 123123123,
  };
  
  const bannerExchangeRequest = {
    id: '999ccceeee11',
    device: {
      h: 100,
      w: 100,
      js: true,
      ua: navigator.userAgent,
      dnt: 0,
    },
    regs: {
      coppa: 0,
      ext: {
        gdpr: 1,
        gdprConsentString: 'gdprConsentString',
        us_privacy: 'uspConsentString',
      },
    },
    site: {
      domain: location.hostname,
      page: location.href,
      ref: 'http://example-domain.com/foo',
    },
    source: {
      fd: 1,
      tid: 'aaccee333311',
    },
    tmax: 200,
    user: {
      gdprConsentString: 'gdprConsentString',
      id: '54444444-5444-4444-9444-544444444444',
    },
    imp: [
      {
        banner: {
          format: [
            {
              w: 300,
              h: 250,
            },
            {
              w: 300,
              h: 600,
            },
          ],
          w: 300,
          h: 250,
          topframe: 0,
          pos: 0,
        },
        ext: {
          adbook: {
            placementId: '12390123',
          },
        },
        id: '9873kfse',
        tagid: 'div-gpt-ad-837465923534-0',
      },
    ],
    ext: {
      adbook: {
        version: {
          adapter: VERSION,
          prebid: '$prebid.version$',
        },
      },
    },
  };
  
  const videoBid = {
    bidder: 'adbookpsp',
    params: {
      placementId: '129576',
    },
    mediaTypes: {
      video: {
        api: [1, 2, 4, 6],
        mimes: ['video/mp4'],
        playbackmethod: [2, 4, 6],
        playerSize: [[400, 300]],
        protocols: [3, 4, 7, 8, 10],
      },
    },
    adUnitCode: 'div-gpt-ad-9383743831-6',
    transactionId: 'aacc3fasf-fere-1335-8m1s-785393mc3fj',
    bidId: '120kfeske',
    bidderRequestId: '999ccceeee11',
    auctionId: 'aaccee333311',
    lineItemId: 321321321,
  };
  
  const videoExchangeRequest = {
    id: '999ccceeee11',
    device: {
      h: 100,
      w: 100,
      js: true,
      ua: navigator.userAgent,
      dnt: 0,
    },
    regs: {
      coppa: 0,
      ext: {
        gdpr: 1,
        gdprConsentString: 'gdprConsentString',
        us_privacy: 'uspConsentString',
      },
    },
    site: {
      domain: location.hostname,
      page: location.href,
      ref: 'http://example-domain.com/foo',
    },
    source: {
      fd: 1,
      tid: 'aaccee333311',
    },
    tmax: 200,
    user: {
      gdprConsentString: 'gdprConsentString',
      id: '54444444-5444-4444-9444-544444444444',
    },
    imp: [
      {
        video: {
          api: [1, 2, 4, 6],
          h: 300,
          mimes: ['video/mp4'],
          playbackmethod: [2, 4, 6],
          protocols: [3, 4, 7, 8, 10],
          w: 400,
        },
        ext: {
          adbook: {
            placementId: '129576',
          },
        },
        id: '120kfeske',
        tagid: 'div-gpt-ad-9383743831-6',
      },
    ],
    ext: {
      adbook: {
        version: {
          adapter: VERSION,
          prebid: '$prebid.version$',
        },
      },
    },
  };
  
  const mixedBid = {
    bidder: 'adbookpsp',
    params: {
      orgId: '129576',
    },
    mediaTypes: {
      banner: {
        sizes: [[300, 600]],
      },
      video: {
        mimes: ['video/mp4'],
        playerSize: [[300, 600]],
      },
    },
    adUnitCode: 'div-gpt-ad-9383743831-5',
    transactionId: 'aacc3fasf-fere-1335-8m1s-785393mc3fj',
    bidId: '120kfeske',
    bidderRequestId: '999ccceeee11',
    auctionId: 'aaccee333311',
    lineItemId: 12341234,
  };
  
  const mixedExchangeRequest = {
    id: '999ccceeee11',
    device: {
      h: 100,
      w: 100,
      js: true,
      ua: navigator.userAgent,
      dnt: 0,
    },
    regs: {
      coppa: 0,
      ext: {
        gdpr: 1,
        gdprConsentString: 'gdprConsentString',
        us_privacy: 'uspConsentString',
      },
    },
    site: {
      domain: location.hostname,
      page: location.href,
      ref: 'http://example-domain.com/foo',
    },
    source: {
      fd: 1,
      tid: 'aaccee333311',
    },
    tmax: 200,
    user: {
      gdprConsentString: 'gdprConsentString',
      id: '54444444-5444-4444-9444-544444444444',
    },
    imp: [
      {
        banner: {
          format: [
            {
              w: 300,
              h: 600,
            },
          ],
          w: 300,
          h: 600,
          topframe: 0,
          pos: 0,
        },
        video: {
          h: 600,
          mimes: ['video/mp4'],
          w: 300,
        },
        ext: {
          adbook: {
            orgId: '129576',
          },
        },
        id: '120kfeske',
        tagid: 'div-gpt-ad-9383743831-5',
      },
    ],
    ext: {
      adbook: {
        version: {
          adapter: VERSION,
          prebid: '$prebid.version$',
        },
      },
    },
  };
  
  const exchangeBidRequest = {
    id: '999ccceeee11',
    tmax: 200,
    imp: [
      {
        id: '9873kfse',
        banner: {
          format: [
            {
              w: 300,
              h: 250,
            },
          ],
        },
        video: {
          w: 300,
          h: 250,
        },
        tagid: 'div-gpt-ad-837465923534-0',
      },
      {
        id: '120kfeske',
        banner: {
          format: [
            {
              w: 300,
              h: 250,
            },
          ],
        },
        video: {
          w: 300,
          h: 250,
        },
        tagid: 'div-gpt-ad-837465923534-0',
      },
    ],
    source: {
      fd: 1,
      tid: 'aaccee333311',
    },
    site: {
      domain: location.hostname,
      page: location.href,
      ref: 'http://prebid-test-page.io:8080/banner.html',
    },
  };
  
  const exchangeResponse = {
    id: '999ccceeee11',
    seatbid: [
      {
        seat: 'adbookpsp',
        group: 0,
        bid: [
          {
            id: 'bid123456',
            w: 300,
            h: 250,
            impid: '9873kfse',
            price: 0.5,
            exp: 300,
            crid: '123456789',
            adm: '<div>ad</div>',
            adid: '5',
            dealid: 'werwetwerw',
            nurl: 'http://win.example.url',
            ext: {
              liid: '2342345',
              ordid: '567843',
            },
            cat: ['IAB2-1', 'IAB2-2', 'IAB2-3'],
            adomain: ['advertiser.com'],
          },
        ],
      },
      {
        seat: 'adbookpsp',
        group: 0,
        bid: [
          {
            id: 'bid4321',
            impid: '120kfeske',
            price: 0.45,
            exp: 300,
            crid: '543123',
            adm: '<VAST version="4.2" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.iab.com/VAST"></VAST>',
            adid: '10',
            dealid: 'dsfxcxcvxc',
            nurl: 'http://win.example.url',
            ext: {
              liid: '2121221',
              ordid: '5678234',
            },
            cat: ['IAB2-3'],
            adomain: ['advertiser.com', 'campaign.advertiser.com'],
          },
        ],
      },
    ],
    ext: {
      sync: [
        {
          type: 'image',
          url: 'http://sometest.com/sync/1234567',
        },
        {
          type: 'iframe',
          url: 'http://sometest.com/sync/1234567',
        },
      ],
    },
  };
  