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`);
});
});