diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index f9e14c42483..f4a39637fd2 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -338,7 +338,6 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder url: encodeURIComponent(topWindowUrl), uqs: getTopWindowQueryParams(), cb: Date.now(), - bidFloor: bidFloor, bidId: bidId, referrer: bidderRequest.refererInfo.ref, adUnitCode: adUnitCode, @@ -368,6 +367,9 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder ...uniqueRequestData }; + if (bidFloor) { + data.bidFloor = bidFloor; + } // backward compatible userId generators if (bid.userIdAsEids?.length > 0) { appendUserIdsAsEidsToRequestPayload(data, bid.userIdAsEids); diff --git a/libraries/vidazooUtils/vidazooTypes.ts b/libraries/vidazooUtils/vidazooTypes.ts index cd29cbb033c..b8a24cd40d3 100644 --- a/libraries/vidazooUtils/vidazooTypes.ts +++ b/libraries/vidazooUtils/vidazooTypes.ts @@ -12,12 +12,14 @@ export interface VidazooBaseBidderParams { /** * The minimum bid value desired. Adapter will not respond with bids lower than this value */ - bidFloor: number; + bidFloor?: number; /** * Placement id on platform. */ - placementId?: number; + /** + * Custom parameters for the request + */ ext?: Ext; /** * Subdomain define subdomain in the bid request URL diff --git a/modules/copper6sspBidAdapter.d.ts b/modules/copper6sspBidAdapter.d.ts new file mode 100644 index 00000000000..6d2244bb0ae --- /dev/null +++ b/modules/copper6sspBidAdapter.d.ts @@ -0,0 +1,33 @@ +import { Ext } from '../libraries/vidazooUtils/vidazooTypes.ts'; + +interface Copper6SSPCommonParams { + bidFloor?: number; + ext?: Ext; + subDomain?: string; +} + +/** Current documented params */ +interface Copper6SSPModernParams extends Copper6SSPCommonParams { + cId: string; + pId: string; + placementId?: never; + endpointId?: never; +} + +/** Previously documented legacy params */ +interface Copper6SSPLegacyParams extends Copper6SSPCommonParams { + placementId: string; + endpointId: string; + cId?: never; + pId?: never; +} + +export type Copper6SSPBidRequestParams = + | Copper6SSPModernParams + | Copper6SSPLegacyParams; + +declare module '../src/adUnits' { + interface BidderParams { + copper6ssp: Copper6SSPBidRequestParams; + } +} diff --git a/modules/copper6sspBidAdapter.js b/modules/copper6sspBidAdapter.js index e05ed241cc6..a91e16ad263 100644 --- a/modules/copper6sspBidAdapter.js +++ b/modules/copper6sspBidAdapter.js @@ -1,21 +1,88 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { + extractCID, + extractPID, + onBidWon, + createUserSyncGetter, + createBuildRequestsFn, + createInterpretResponseFn +} from '../libraries/vidazooUtils/bidderUtils.js'; +/** + * @typedef {import('./copper6sspBidAdapter.d.ts').Copper6SSPBidRequestParams} Copper6SSPBidRequestParams + */ + +const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'copper6ssp'; -const AD_URL = 'https://endpoint.copper6.com/pbjs'; -const SYNC_URL = 'https://сsync.copper6.com'; +const BIDDER_VERSION = '1.0.0'; const GVLID = 1356; +const DEFAULT_CID = "600000000000000000000cc6"; +const DEFAULT_PID = "600000000000000000000dc6"; +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.copper6.com`; +} + +function createUniqueRequestData(hashUrl, bid) { + const { auctionId, transactionId } = bid; + return { + auctionId, + transactionId + }; +} + +function legacySupport(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, allowSingleRequest = false) { + const buildFunction = createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, allowSingleRequest); + return function legacyHandler(validBidRequests, bidderRequest) { + const modifiedRequests = validBidRequests.map(request => { + if (!request.params?.cId) { + if (request.params?.placementId) { + request.params.cId = request.params.placementId; + } else { + request.params.cId = DEFAULT_CID; + } + delete request.params.placementId; + } + if (!request.params?.pId) { + if (request.params?.endpointId) { + request.params.pId = request.params.endpointId; + } else { + request.params.pId = DEFAULT_PID; + } + } + return request; + }); + return buildFunction(modifiedRequests, bidderRequest); + }; +} + +const buildRequests = legacySupport(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, false); +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.copper6.com/api/sync/iframe', + imageSyncUrl: 'https://sync.copper6.com/api/sync/image' +}); + +function isBidRequestValid(bid) { + const p = bid.params || {}; + const hasNew = extractCID(p) && extractPID(p); + const hasLegacy = p.placementId && p.endpointId; + return !!(hasNew || hasLegacy); +} export const spec = { code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], - - isBidRequestValid: isBidRequestValid(), - buildRequests: buildRequests(AD_URL), + isBidRequestValid, + buildRequests, interpretResponse, - getUserSyncs: getUserSyncs(SYNC_URL) + getUserSyncs, + onBidWon, }; registerBidder(spec); diff --git a/modules/copper6sspBidAdapter.md b/modules/copper6sspBidAdapter.md index a414187022d..6393432e174 100755 --- a/modules/copper6sspBidAdapter.md +++ b/modules/copper6sspBidAdapter.md @@ -1,79 +1,35 @@ # Overview -``` -Module Name: Copper6SSP Bidder Adapter -Module Type: Copper6SSP Bidder Adapter -Maintainer: info@copper6.com -``` +**Module Name:** Copper6 Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** operations@copper6.com # Description -Connects to Copper6SSP exchange for bids. -Copper6SSP bid adapter supports Banner, Video (instream and outstream) and Native. +Module that connects to Copper6's demand sources. # Test Parameters -``` - var adUnits = [ - // Will return static test banner - { - code: 'adunit1', - mediaTypes: { - banner: { - sizes: [ [300, 250], [320, 50] ], - } - }, - bids: [ - { - bidder: 'copper6ssp', - params: { - placementId: 'testBanner', - } - } - ] - }, - { - code: 'addunit2', - mediaTypes: { - video: { - playerSize: [ [640, 480] ], - context: 'instream', - minduration: 5, - maxduration: 60, - } - }, - bids: [ - { - bidder: 'copper6ssp', - params: { - placementId: 'testVideo', - } - } - ] - }, - { - code: 'addunit3', - mediaTypes: { - native: { - title: { - required: true - }, - body: { - required: true - }, - icon: { - required: true, - size: [64, 64] - } - } - }, - bids: [ - { - bidder: 'copper6ssp', - params: { - placementId: 'testNative', - } - } - ] + +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'copper6ssp', + params: { + cId: '562524b21b1c1f08117667f9', + pId: '59ac17c192832d0016683fe3', + bidFloor: 0.0001, + ext: { + // custom params that were recomended to add by a partner + } } - ]; -``` \ No newline at end of file + } + ] + } +]; +``` diff --git a/test/spec/modules/copper6sspBidAdapter_spec.js b/test/spec/modules/copper6sspBidAdapter_spec.js index f21c670b5ee..27fa3e9f14e 100644 --- a/test/spec/modules/copper6sspBidAdapter_spec.js +++ b/test/spec/modules/copper6sspBidAdapter_spec.js @@ -1,514 +1,1306 @@ import { expect } from 'chai'; -import { spec } from '../../../modules/copper6sspBidAdapter.js'; -import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; -import { getUniqueIdentifierStr } from '../../../src/utils.js'; - -const bidder = 'copper6ssp'; - -describe('Copper6SSPBidAdapter', function () { - const userIdAsEids = [{ - source: 'test.org', - uids: [{ - id: '01**********', - atype: 1, - ext: { - third: '01***********' - } - }] - }]; - const bids = [ - { - bidId: getUniqueIdentifierStr(), - bidder: bidder, - mediaTypes: { - [BANNER]: { - sizes: [[300, 250]], - battr: [1, 3] - } - }, - params: { - placementId: 'testBanner' - }, - userIdAsEids - }, - { - bidId: getUniqueIdentifierStr(), - bidder: bidder, - mediaTypes: { - [VIDEO]: { - playerSize: [[300, 300]], - minduration: 5, - maxduration: 60, - battr: [1, 3] - } - }, - params: { - placementId: 'testVideo' - }, - userIdAsEids - }, - { - bidId: getUniqueIdentifierStr(), - bidder: bidder, - mediaTypes: { - [NATIVE]: { - native: { - title: { - required: true - }, - body: { - required: true - }, - icon: { - required: true, - size: [64, 64] - } - } - } - }, - params: { - placementId: 'testNative' - }, - userIdAsEids +import { + spec as adapter, + createDomain, + storage, +} from '../../../modules/copper6sspBidAdapter.js'; +import { newBidder } from '../../../src/adapters/bidderFactory.js'; +import * as ajax from '../../../src/ajax.js'; +import * as activityRules from 'src/activities/rules.js'; +import { hook } from '../../../src/hook.js'; +import * as utils from 'src/utils.js'; +import { version } from 'package.json'; +import { useFakeTimers } from 'sinon'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import { config } from '../../../src/config.js'; +import { + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, +} from '../../../libraries/vidazooUtils/bidderUtils.js'; +import { getGlobal } from '../../../src/prebidGlobal.js'; + +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; + +const SUB_DOMAIN = 'exchange'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' } - ]; - - const invalidBid = { - bidId: getUniqueIdentifierStr(), - bidder: bidder, - mediaTypes: { - [BANNER]: { - sizes: [[300, 250]] - } - }, - params: { + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789' + } + } +}; +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 } - }; + } +}; - const bidderRequest = { - uspConsent: '1---', - gdprConsent: { - consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', - vendorData: {} +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] }, - refererInfo: { - referer: 'https://test.com', - page: 'https://test.com' - }, - ortb2: { - device: { - w: 1512, - h: 982, - language: 'en-UK' + 'browsers': [ + { 'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0'] }, + { 'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119'] }, + { 'brand': 'Chromium', 'version': ['109', '0', '5414', '119'] } + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: { fiftyonedegrees_deviceId: '17595-133085-133468-18092' }, +}; + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'gppString': 'gpp_string', + 'gppSid': [7], + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + }, + 'ortb2': { + 'site': { + 'content': { + 'language': 'en' } }, - timeout: 500 - }; + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7], + 'coppa': 0 + }, + 'device': ORTB2_DEVICE, + } +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['bidder.copper6.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + +const ORTB2_OBJ = { + "device": ORTB2_DEVICE, + "regs": { "coppa": 0, "gpp": "gpp_string", "gpp_sid": [7] }, + "site": { "content": { "language": "en" } } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, { decodeSearchAsString: true }); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('copper6sspBidAdapter', function () { + before(() => config.resetConfig()); + after(() => config.resetConfig()); + + describe('validate spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); - describe('isBidRequestValid', function () { - it('Should return true if there are bidId, params and key parameters present', function () { - expect(spec.isBidRequestValid(bids[0])).to.be.true; + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); }); - it('Should return false if at least one of parameters is not present', function () { - expect(spec.isBidRequestValid(invalidBid)).to.be.false; + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); }); }); - describe('buildRequests', function () { - let serverRequest = spec.buildRequests(bids, bidderRequest); - - it('Creates a ServerRequest object with method, URL and data', function () { - expect(serverRequest).to.exist; - expect(serverRequest.method).to.exist; - expect(serverRequest.url).to.exist; - expect(serverRequest.data).to.exist; - }); - - it('Returns POST method', function () { - expect(serverRequest.method).to.equal('POST'); - }); - - it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://endpoint.copper6.com/pbjs'); - }); - - it('Returns general data valid', function () { - const data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys( - 'deviceWidth', - 'deviceHeight', - 'device', - 'language', - 'secure', - 'host', - 'page', - 'placements', - 'coppa', - 'ccpa', - 'gdpr', - 'tmax', - 'bcat', - 'badv', - 'bapp' - ); - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.language).to.be.a('string'); - expect(data.secure).to.be.within(0, 1); - expect(data.host).to.be.a('string'); - expect(data.page).to.be.a('string'); - expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('object'); - expect(data.ccpa).to.be.a('string'); - expect(data.tmax).to.be.a('number'); - expect(data.placements).to.have.lengthOf(3); - }); - - it('Returns valid placements', function () { - const { placements } = serverRequest.data; - for (let i = 0, len = placements.length; i < len; i++) { - const placement = placements[i]; - expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); - expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); - expect(placement.bidId).to.be.a('string'); - expect(placement.schain).to.be.an('object'); - expect(placement.bidfloor).to.exist.and.to.equal(0); - expect(placement.type).to.exist.and.to.equal('publisher'); - expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); - - switch (placement.adFormat) { - case BANNER: - expect(placement.sizes).to.be.an('array'); - expect(placement.battr).to.deep.equal([1, 3]); - break; - case VIDEO: - expect(placement.playerSize).to.be.an('array'); - expect(placement.minduration).to.be.an('number'); - expect(placement.maxduration).to.be.an('number'); - expect(placement.battr).to.deep.equal([1, 3]); - break; - case NATIVE: - expect(placement.native).to.be.an('object'); - break; + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' } + }); + expect(isValid).to.be.true; + }); + + it('should validate legacy placementId and endpointId params', function () { + const isValid = adapter.isBidRequestValid({ + params: { + placementId: 'legacy-cid', + endpointId: 'legacy-pid' + } + }); + expect(isValid).to.be.true; + }); + + it('should reject legacy params when only placementId is provided', function () { + const isValid = adapter.isBidRequestValid({ + params: { + placementId: 'legacy-cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should reject legacy params when only endpointId is provided', function () { + const isValid = adapter.isBidRequestValid({ + params: { + endpointId: 'legacy-pid' + } + }); + expect(isValid).to.be.false; + }); + }); + + describe('param shapes', function () { + const commonParams = { + bidFloor: 0.1, + subDomain: SUB_DOMAIN, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' } + }; + + describe('modern params (cId/pId)', function () { + it('should validate with only cId and pId', function () { + expect(adapter.isBidRequestValid({ + params: { + cId: '59db6b3b4ffaa70004f45cdc', + pId: '59ac17c192832d0011283fe3', + } + })).to.be.true; + }); + + it('should validate with optional bidFloor, ext, and subDomain', function () { + expect(adapter.isBidRequestValid({ + params: { + cId: '59db6b3b4ffaa70004f45cdc', + pId: '59ac17c192832d0011283fe3', + ...commonParams, + } + })).to.be.true; + }); + + it('should not require bidFloor', function () { + expect(adapter.isBidRequestValid({ + params: { + cId: '59db6b3b4ffaa70004f45cdc', + pId: '59ac17c192832d0011283fe3', + } + })).to.be.true; + }); }); - it('Returns valid endpoints', function () { - const bids = [ - { - bidId: getUniqueIdentifierStr(), - bidder: bidder, + describe('legacy params (placementId/endpointId)', function () { + it('should validate string placementId and endpointId without cId/pId', function () { + expect(adapter.isBidRequestValid({ + params: { + placementId: '59db6b3b4ffaa70004f45cdc', + endpointId: '59ac17c192832d0011283fe3', + } + })).to.be.true; + }); + + it('should validate with optional bidFloor, ext, and subDomain', function () { + expect(adapter.isBidRequestValid({ + params: { + placementId: '59db6b3b4ffaa70004f45cdc', + endpointId: '59ac17c192832d0011283fe3', + ...commonParams, + } + })).to.be.true; + }); + + it('should build requests from legacy string ids', function () { + const requests = adapter.buildRequests([{ + ...LEGACY_BID, + params: { + placementId: '59db6b3b4ffaa70004f45cdc', + endpointId: '59ac17c192832d0011283fe3', + ...commonParams, + } + }], BIDDER_REQUEST); + + expect(requests).to.have.length(1); + expect(requests[0].url).to.equal(`${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`); + expect(requests[0].data.publisherId).to.equal('59ac17c192832d0011283fe3'); + expect(requests[0].data.bidFloor).to.equal(0.1); + }); + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + getGlobal().bidderSettings = { + copper6ssp: { + storageAllowed: true + } + }; + sandbox = sinon.createSandbox(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + prebidVersion: version, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + { 'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0'] }, + { 'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119'] }, + { 'brand': 'Chromium', 'version': ['109', '0', '5414', '119'] } + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + device: ORTB2_DEVICE, + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), mediaTypes: { - [BANNER]: { - sizes: [[300, 250]] + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 } }, - params: { - endpointId: 'testBanner', - }, - userIdAsEids + gpid: '', + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + ortb2: ORTB2_OBJ, + userData: [], + coppa: 0 } - ]; + }); + }); - const serverRequest = spec.buildRequests(bids, bidderRequest); - - const { placements } = serverRequest.data; - for (let i = 0, len = placements.length; i < len; i++) { - const placement = placements[i]; - expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); - expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); - expect(placement.bidId).to.be.a('string'); - expect(placement.schain).to.be.an('object'); - expect(placement.bidfloor).to.exist.and.to.equal(0); - expect(placement.type).to.exist.and.to.equal('network'); - expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); - - switch (placement.adFormat) { - case BANNER: - expect(placement.sizes).to.be.an('array'); - break; - case VIDEO: - expect(placement.playerSize).to.be.an('array'); - expect(placement.minduration).to.be.an('number'); - expect(placement.maxduration).to.be.an('number'); - break; - case NATIVE: - expect(placement.native).to.be.an('object'); - break; + it('should build banner request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], + usPrivacy: 'consent_string', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + { 'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0'] }, + { 'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119'] }, + { 'brand': 'Chromium', 'version': ['109', '0', '5414', '119'] } + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + device: ORTB2_DEVICE, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + gpid: '0123456789', + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + ortb2Imp: BID.ortb2Imp, + ortb2: ORTB2_OBJ, + userData: [], + coppa: 0 } - } + }); }); - it('Returns data with gdprConsent and without uspConsent', function () { - delete bidderRequest.uspConsent; - serverRequest = spec.buildRequests(bids, bidderRequest); - const data = serverRequest.data; - expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('object'); - expect(data.gdpr).to.have.property('consentString'); - expect(data.gdpr).to.not.have.property('vendorData'); - expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); - expect(data.ccpa).to.not.exist; - delete bidderRequest.gdprConsent; - }); - - it('Returns data with uspConsent and without gdprConsent', function () { - bidderRequest.uspConsent = '1---'; - delete bidderRequest.gdprConsent; - serverRequest = spec.buildRequests(bids, bidderRequest); - const data = serverRequest.data; - expect(data.ccpa).to.exist; - expect(data.ccpa).to.be.a('string'); - expect(data.ccpa).to.equal(bidderRequest.uspConsent); - expect(data.gdpr).to.not.exist; + after(function () { + getGlobal().bidderSettings = {}; + sandbox.restore(); }); }); + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); - describe('gpp consent', function () { - it('bidderRequest.gppConsent', () => { - bidderRequest.gppConsent = { - gppString: 'abc123', - applicableSections: [8] - }; - - const serverRequest = spec.buildRequests(bids, bidderRequest); - const data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.property('gpp'); - expect(data).to.have.property('gpp_sid'); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.copper6.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }]); + }); - delete bidderRequest.gppConsent; + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.copper6.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0' + }]); }); - it('bidderRequest.ortb2.regs.gpp', () => { - bidderRequest.ortb2 = bidderRequest.ortb2 || {}; - bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; - bidderRequest.ortb2.regs.gpp = 'abc123'; - bidderRequest.ortb2.regs.gpp_sid = [8]; + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE]); - const serverRequest = spec.buildRequests(bids, bidderRequest); - const data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.property('gpp'); - expect(data).to.have.property('gpp_sid'); + expect(result).to.deep.equal([{ + 'url': 'https://sync.copper6.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=0', + 'type': 'image' + }]); + }); - expect(bidderRequest).to.have.property('ortb2'); + it('should have valid user sync with coppa on response', function () { + config.setConfig({ + coppa: 1 + }); + const result = adapter.getUserSyncs({ iframeEnabled: true }, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.copper6.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=&coppa=1' + }]); }); - }); - describe('interpretResponse', function () { - it('Should interpret banner response', function () { - const banner = { - body: [{ - mediaType: 'banner', - width: 300, - height: 250, - cpm: 0.4, - ad: 'Test', - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1', - meta: { - advertiserDomains: ['google.com'], - advertiserId: 1234 - } - }] + it('should generate url with consent data', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'consent_string' }; - const bannerResponses = spec.interpretResponse(banner); - expect(bannerResponses).to.be.an('array').that.is.not.empty; - const dataItem = bannerResponses[0]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); - expect(dataItem.requestId).to.equal(banner.body[0].requestId); - expect(dataItem.cpm).to.equal(banner.body[0].cpm); - expect(dataItem.width).to.equal(banner.body[0].width); - expect(dataItem.height).to.equal(banner.body[0].height); - expect(dataItem.ad).to.equal(banner.body[0].ad); - expect(dataItem.ttl).to.equal(banner.body[0].ttl); - expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal(banner.body[0].currency); - expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); - }); - it('Should interpret video response', function () { - const video = { - body: [{ - vastUrl: 'test.com', - mediaType: 'video', - cpm: 0.5, - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1', - meta: { - advertiserDomains: ['google.com'], - advertiserId: 1234 - } - }] + const uspConsent = 'usp_string'; + const gppConsent = { + gppString: 'gpp_string', + applicableSections: [7] }; - const videoResponses = spec.interpretResponse(video); - expect(videoResponses).to.be.an('array').that.is.not.empty; - - const dataItem = videoResponses[0]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.5); - expect(dataItem.vastUrl).to.equal('test.com'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); - expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); - }); - it('Should interpret native response', function () { - const native = { - body: [{ - mediaType: 'native', - native: { - clickUrl: 'test.com', - title: 'Test', - image: 'test.com', - impressionTrackers: ['test.com'], - }, - ttl: 120, - cpm: 0.4, - requestId: '23fhj33i987f', - creativeId: '2', - netRevenue: true, - currency: 'USD', - meta: { - advertiserDomains: ['google.com'], - advertiserId: 1234 - } - }] + + const result = adapter.getUserSyncs({ pixelEnabled: true }, [SERVER_RESPONSE], gdprConsent, uspConsent, gppConsent); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.copper6.com/api/sync/image/?cid=testcid123&gdpr=1&gdpr_consent=consent_string&us_privacy=usp_string&coppa=1&gpp=gpp_string&gpp_sid=7', + 'type': 'image' + }]); + }); + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({ price: 1, ad: '' }); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({ price: null, ad: 'great ad' }); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['bidder.copper6.com'], + agencyName: 'Agency Name', }; - const nativeResponses = spec.interpretResponse(native); - expect(nativeResponses).to.be.an('array').that.is.not.empty; - - const dataItem = nativeResponses[0]; - expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); - expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.native.clickUrl).to.equal('test.com'); - expect(dataItem.native.title).to.equal('Test'); - expect(dataItem.native.image).to.equal('test.com'); - expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; - expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); - expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); - }); - it('Should return an empty array if invalid banner response is passed', function () { - const invBanner = { - body: [{ - width: 300, - cpm: 0.4, - ad: 'Test', - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['bidder.copper6.com'], + agencyName: 'Agency Name' + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['bidder.copper6.com'] + } + }); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); + + describe('user id system', function () { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return { lipbid: id }; + case 'id5id': + return { uid: id }; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId }; - const serverResponses = spec.interpretResponse(invBanner); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - it('Should return an empty array if invalid video response is passed', function () { - const invVideo = { - body: [{ - mediaType: 'video', - cpm: 0.5, - requestId: '23fhj33i987f', - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + // testing bid.userIdAsEids handling + it("should include user ids from bid.userIdAsEids (length=1)", function() { + const bid = utils.deepClone(BID); + bid.userIdAsEids = [ + { + "source": "audigent.com", + "uids": [{ "id": "fakeidi6j6dlc6e" }] + } + ]; + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.audigent.com']).to.equal("fakeidi6j6dlc6e"); + }); + it("should include user ids from bid.userIdAsEids (length=2)", function() { + const bid = utils.deepClone(BID); + bid.userIdAsEids = [ + { + "source": "audigent.com", + "uids": [{ "id": "fakeidi6j6dlc6e" }] + }, + { + "source": "rwdcntrl.net", + "uids": [{ "id": "fakeid6f35197d5c", "atype": 1 }] + } + ]; + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.audigent.com']).to.equal("fakeidi6j6dlc6e"); + expect(requests[0].data['uid.rwdcntrl.net']).to.equal("fakeid6f35197d5c"); + }); + // testing user.ext.eid handling + it("should include user ids from user.ext.eid (length=1)", function() { + const bid = utils.deepClone(BID); + bid.user = { + ext: { + eids: [ + { + "source": "pubcid.org", + "uids": [{ "id": "fakeid8888dlc6e" }] + } + ] + } }; - const serverResponses = spec.interpretResponse(invVideo); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - it('Should return an empty array if invalid native response is passed', function () { - const invNative = { - body: [{ - mediaType: 'native', - clickUrl: 'test.com', - title: 'Test', - impressionTrackers: ['test.com'], - ttl: 120, - requestId: '23fhj33i987f', - creativeId: '2', - netRevenue: true, - currency: 'USD', - }] + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.pubcid.org']).to.equal("fakeid8888dlc6e"); + }); + it("should include user ids from user.ext.eid (length=2)", function() { + const bid = utils.deepClone(BID); + bid.user = { + ext: { + eids: [ + { + "source": "pubcid.org", + "uids": [{ "id": "fakeid8888dlc6e" }] + }, + { + "source": "adserver.org", + "uids": [{ "id": "fakeid495ff1" }] + } + ] + } }; - const serverResponses = spec.interpretResponse(invNative); - expect(serverResponses).to.be.an('array').that.is.empty; - }); - it('Should return an empty array if invalid response is passed', function () { - const invalid = { - body: [{ - ttl: 120, - creativeId: '2', - netRevenue: true, - currency: 'USD', - dealId: '1' - }] + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data['uid.pubcid.org']).to.equal("fakeid8888dlc6e"); + expect(requests[0].data['uid.adserver.org']).to.equal("fakeid495ff1"); + }); + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({ 'c_id': '1' }); + const pid = extractPID({ 'p_id': '1' }); + const subDomain = extractSubDomain({ 'sub_domain': 'prebid' }); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({ 'cID': '1' }); + const pid = extractPID({ 'Pid': '2' }); + const subDomain = extractSubDomain({ 'subDOMAIN': 'prebid' }); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('unique deal id', function () { + before(function () { + getGlobal().bidderSettings = { + copper6ssp: { + storageAllowed: true + } }; - const serverResponses = spec.interpretResponse(invalid); - expect(serverResponses).to.be.an('array').that.is.empty; + }); + after(function () { + getGlobal().bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(storage, key, 0); + }); + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(storage, key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(storage, key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200); }); }); - describe('getUserSyncs', function() { - it('Should return array of objects with proper sync config , include GDPR', function() { - const syncData = spec.getUserSyncs({ pixelEnabled: true }, {}, { - consentString: 'ALL', - gdprApplies: true, - }, undefined); - expect(syncData).to.be.an('array').which.is.not.empty; - expect(syncData[0]).to.be.an('object'); - expect(syncData[0].type).to.be.a('string'); - expect(syncData[0].type).to.equal('image'); - expect(syncData[0].url).to.be.a('string'); - expect(syncData[0].url).to.equal('https://сsync.copper6.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0'); - }); - it('Should return array of objects with proper sync config , include CCPA', function() { - const syncData = spec.getUserSyncs({ pixelEnabled: true }, {}, {}, '1---'); - expect(syncData).to.be.an('array').which.is.not.empty; - expect(syncData[0]).to.be.an('object'); - expect(syncData[0].type).to.be.a('string'); - expect(syncData[0].type).to.equal('image'); - expect(syncData[0].url).to.be.a('string'); - expect(syncData[0].url).to.equal('https://сsync.copper6.com/image?pbjs=1&ccpa_consent=1---&coppa=0'); - }); - it('Should return array of objects with proper sync config , include GPP', function() { - const syncData = spec.getUserSyncs({ pixelEnabled: true }, {}, {}, undefined, { - gppString: 'abc123', - applicableSections: [8] + describe('storage utils', function () { + before(function () { + getGlobal().bidderSettings = { + copper6ssp: { + storageAllowed: true + } + }; + }); + after(function () { + getGlobal().bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now }); - expect(syncData).to.be.an('array').which.is.not.empty; - expect(syncData[0]).to.be.an('object'); - expect(syncData[0].type).to.be.a('string'); - expect(syncData[0].type).to.equal('image'); - expect(syncData[0].url).to.be.a('string'); - expect(syncData[0].url).to.equal('https://сsync.copper6.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0'); + setStorageItem(storage, 'myKey', 2020); + const { value, created } = getStorageItem(storage, 'myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman'; + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem(storage, 'myExternalKey'); + expect(item).to.be.equal(value); }); + + it('should parse JSON value', function () { + const data = JSON.stringify({ event: 'send' }); + const { event } = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); +}); +// Supporting legacy adapter requests + +const LEGACY_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'placementId': '59db6b3b4ffaa70004f45cdc', + 'endpointId': '59ac17c192832d0011283fe3', + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789' + } + } +}; + +const LEGACY_BID_NO_PARAMS = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': {}, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789' + } + } +}; +const LEGACY_VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'auctionId': 'auction_id', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'placementId': '635509f7ff6642d368cb9837', + 'endpointId': '59ac17c192832d0011283fe3', + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + } +}; + +describe('build requests for legacy adapter calls', function () { + let sandbox; + before(function () { + getGlobal().bidderSettings = { + copper6ssp: { + storageAllowed: true + } + }; + sandbox = sinon.createSandbox(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([LEGACY_VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + prebidVersion: version, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + { 'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0'] }, + { 'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119'] }, + { 'brand': 'Chromium', 'version': ['109', '0', '5414', '119'] } + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + device: ORTB2_DEVICE, + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + }, + gpid: '', + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + ortb2: ORTB2_OBJ, + userData: [], + coppa: 0 + } + }); + }); + + it('should build banner request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([LEGACY_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], + usPrivacy: 'consent_string', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + { 'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0'] }, + { 'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119'] }, + { 'brand': 'Chromium', 'version': ['109', '0', '5414', '119'] } + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + device: ORTB2_DEVICE, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + gpid: '0123456789', + uqs: getTopWindowQueryParams(), + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + ortb2Imp: BID.ortb2Imp, + ortb2: ORTB2_OBJ, + userData: [], + coppa: 0 + } + }); + }); + + it('should build banner request for each size with default cId,pId', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([LEGACY_BID_NO_PARAMS], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/600000000000000000000cc6`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], + usPrivacy: 'consent_string', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + auctionId: 'auction_id', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + { 'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0'] }, + { 'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119'] }, + { 'brand': 'Chromium', 'version': ['109', '0', '5414', '119'] } + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + device: ORTB2_DEVICE, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '600000000000000000000dc6', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + gpid: '0123456789', + uqs: getTopWindowQueryParams(), + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + ortb2Imp: BID.ortb2Imp, + ortb2: ORTB2_OBJ, + userData: [], + coppa: 0 + } + }); + }); + + after(function () { + getGlobal().bidderSettings = {}; + sandbox.restore(); + }); +}); + +describe('auction path', function () { + let sandbox; + let ajaxStub; + let addBidResponse; + let done; + let onTimelyResponse; + let bidder; + + before(function () { + hook.ready(); + }); + + beforeEach(function () { + sandbox = sinon.createSandbox(); + sandbox.stub(activityRules, 'isActivityAllowed').callsFake(() => true); + ajaxStub = sandbox.stub(ajax, 'ajax').callsFake((url, callbacks) => { + callbacks.success(JSON.stringify({ results: [] }), { getResponseHeader: sandbox.stub() }); + }); + addBidResponse = sandbox.stub(); + addBidResponse.reject = sandbox.stub(); + done = sandbox.stub(); + onTimelyResponse = sandbox.stub(); + bidder = newBidder(adapter); + getGlobal().bidderSettings = { + copper6ssp: { + storageAllowed: true + } + }; + config.setConfig({ bidderTimeout: 3000 }); + sandbox.stub(Date, 'now').returns(1000); + }); + + afterEach(function () { + getGlobal().bidderSettings = {}; + config.resetConfig(); + sandbox.restore(); + }); + + function callBidsWith(bids) { + const bidderRequest = { + ...BIDDER_REQUEST, + bids, + bidderRequestId: 'auction-req-1', + }; + bidder.callBids( + bidderRequest, + addBidResponse, + done, + ajaxStub, + onTimelyResponse, + config.callbackWithBidder(adapter.code) + ); + } + + it('should send request for legacy placementId/endpointId params after validation', function () { + callBidsWith([LEGACY_BID]); + + expect(ajaxStub.calledOnce).to.be.true; + expect(ajaxStub.firstCall.args[0]).to.equal(`${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`); + + const postData = JSON.parse(ajaxStub.firstCall.args[2]); + expect(postData.publisherId).to.equal('59ac17c192832d0011283fe3'); + }); + + it('should send request for modern cId/pId params after validation', function () { + callBidsWith([BID]); + + expect(ajaxStub.calledOnce).to.be.true; + expect(ajaxStub.firstCall.args[0]).to.equal(`${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`); + }); + + it('should not send request when legacy params are incomplete', function () { + callBidsWith([{ + ...LEGACY_BID, + params: { placementId: '59db6b3b4ffaa70004f45cdc' } + }]); + + expect(ajaxStub.called).to.be.false; + }); + + it('should not send request when modern params are incomplete', function () { + callBidsWith([{ + ...BID, + params: { cId: '59db6b3b4ffaa70004f45cdc' } + }]); + + expect(ajaxStub.called).to.be.false; + }); + + it('should filter invalid bids before buildRequests and only send valid legacy bids', function () { + callBidsWith([ + LEGACY_BID, + { + ...LEGACY_BID, + bidId: 'invalid-legacy-bid', + params: { placementId: '59db6b3b4ffaa70004f45cdc' } + } + ]); + + expect(ajaxStub.calledOnce).to.be.true; + expect(ajaxStub.firstCall.args[0]).to.equal(`${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`); }); });