From 6425d0b2b40fb849fccf76fcd500b695d70c9044 Mon Sep 17 00:00:00 2001 From: adtech-sky Date: Wed, 14 Feb 2024 08:33:05 +0100 Subject: [PATCH 1/5] Opsco bid adapter init commit --- modules/opscoBidAdapter.js | 109 ++++++++++ modules/opscoBidAdapter.md | 0 test/spec/modules/opscoBidAdapter_spec.js | 232 ++++++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 modules/opscoBidAdapter.js create mode 100644 modules/opscoBidAdapter.md create mode 100644 test/spec/modules/opscoBidAdapter_spec.js diff --git a/modules/opscoBidAdapter.js b/modules/opscoBidAdapter.js new file mode 100644 index 00000000000..07b1c61ba23 --- /dev/null +++ b/modules/opscoBidAdapter.js @@ -0,0 +1,109 @@ +import {deepAccess, deepSetValue, logInfo} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const ENDPOINT = 'https://dev-exchange-npid.ops.co/pbjs'; +const BIDDER_CODE = 'opsco'; +const DEFAULT_BID_TTL = 300; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => !!(bid.params && bid.params.placementId && bid.params.publisherId && bid.mediaTypes?.banner?.sizes), + + buildRequests: (validBidRequests, bidderRequest) => { + const { publisherId } = validBidRequests[0].params; + const siteId = validBidRequests[0].params.siteId; + + const payload = { + id: bidderRequest.bidderRequestId, + imp: buildOpenRtbImps(validBidRequests), + site: { + id: siteId, + publisher: { id: publisherId }, + domain: bidderRequest.refererInfo?.domain, + page: bidderRequest.refererInfo?.page, + ref: bidderRequest.refererInfo?.ref, + }, + test: isTest(validBidRequests[0]) + }; + + attachParams(payload, 'schain', bidderRequest); + attachParams(payload, 'gdprConsent', bidderRequest); + attachParams(payload, 'uspConsent', bidderRequest); + attachParams(payload, 'userIdAsEids', validBidRequests[0]); + + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(payload), + }; + }, + + interpretResponse: (response) => (response.body?.bids || []).map(({ bidId, cpm, width, height, creativeId, currency, netRevenue, ttl, ad, mediaType }) => ({ + requestId: bidId, + cpm, + width, + height, + creativeId, + currency: currency || DEFAULT_CURRENCY, + netRevenue: netRevenue || DEFAULT_NET_REVENUE, + ttl: ttl || DEFAULT_BID_TTL, + ad, + mediaType + })), + + getUserSyncs: (syncOptions, serverResponses) => { + logInfo('opsco.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); + + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return []; + } + + let syncs = []; + + serverResponses.forEach(resp => { + const userSync = deepAccess(resp, 'body.ext.usersync'); + if (userSync) { + const syncDetails = Object.values(userSync).flatMap(value => value.syncs || []); + syncDetails.forEach(syncDetail => { + const type = syncDetail.type === 'iframe' ? 'iframe' : 'image'; + if ((type === 'iframe' && syncOptions.iframeEnabled) || (type === 'image' && syncOptions.pixelEnabled)) { + syncs.push({ type, url: syncDetail.url }); + } + }); + } + }); + + logInfo('opsco.getUserSyncs result=%o', syncs); + return syncs; + } +}; + +function buildOpenRtbImps(validBidRequests) { + const placementId = validBidRequests[0].params.placementId; + return validBidRequests.map(bidRequest => ({ + id: bidRequest.bidId, + banner: { format: extractSizes(bidRequest) }, + ext: { placementId } + })); +} + +function extractSizes(bidRequest) { + return (bidRequest.mediaTypes?.banner?.sizes || []).map(([width, height]) => ({ w: width, h: height })); +} + +function attachParams(payload, paramName, request) { + if (request[paramName]) { + deepSetValue(payload, `source.ext.${paramName}`, request[paramName]); + } +} + +function isTest(validBidRequest) { + return validBidRequest.params?.test === true; +} + +registerBidder(spec); diff --git a/modules/opscoBidAdapter.md b/modules/opscoBidAdapter.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/spec/modules/opscoBidAdapter_spec.js b/test/spec/modules/opscoBidAdapter_spec.js new file mode 100644 index 00000000000..e062f8386cd --- /dev/null +++ b/test/spec/modules/opscoBidAdapter_spec.js @@ -0,0 +1,232 @@ +import {expect} from 'chai'; +import {spec} from 'modules/opscoBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory.js'; + +describe('opscoBidAdapter', function () { + const adapter = newBidder(spec); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('isBidRequestValid', function () { + const validBid = { + bidder: 'opsco', + params: { + placementId: '123', + publisherId: '456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + it('should return true when required params are present', function () { + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + + it('should return false when placementId is missing', function () { + const invalidBid = {...validBid}; + delete invalidBid.params.placementId; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('should return false when publisherId is missing', function () { + const invalidBid = {...validBid}; + delete invalidBid.params.publisherId; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('should return false when mediaTypes.banner.sizes is missing', function () { + const invalidBid = {...validBid}; + delete invalidBid.mediaTypes.banner.sizes; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('should return false when mediaTypes.banner is missing', function () { + const invalidBid = {...validBid}; + delete invalidBid.mediaTypes.banner; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('should return false when bid params are missing', function () { + const invalidBid = {bidder: 'opsco'}; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + + it('should return false when bid params are empty', function () { + const invalidBid = {bidder: 'opsco', params: {}}; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + const validBid = { + bidder: 'opsco', + params: { + placementId: '123', + publisherId: '456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + const bidderRequest = { + bidderRequestId: 'bid123', + refererInfo: { + domain: 'example.com', + page: 'https://example.com/page', + ref: 'https://referrer.com' + }, + gdprConsent: { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true + }, + uspConsent: '1YYY' + }; + + const expectedPayload = { + id: 'bid123', + imp: [{ + id: 'bid123', + banner: {format: [{w: 300, h: 250}]}, + ext: {placementId: '123'} + }], + site: { + id: '456', + publisher: {id: '123'}, + domain: 'example.com', + page: 'https://example.com/page', + ref: 'https://referrer.com', + }, + test: false, + gdprConsent: {consentString: 'GDPR_CONSENT_STRING', gdprApplies: true}, + uspConsent: '1YYY' + }; + + it('should build a valid POST request with proper payload', function () { + const request = spec.buildRequests([validBid], bidderRequest); + expect(request.data).to.equal(JSON.stringify(expectedPayload)); + }); + + it('should send GDPR consent in the payload if present', function () { + const request = spec.buildRequests([validBid], bidderRequest); + expect(JSON.parse(request.data).gdprConsent).to.deep.equal(expectedPayload.gdprConsent); + }); + + it('should send CCPA in the payload if present', function () { + const ccpa = '1YYY'; + bidderRequest.uspConsent = ccpa; + const request = spec.buildRequests([validBid], bidderRequest); + expect(JSON.parse(request.data).uspConsent).to.equal(ccpa); + }); + + it('should send eids in the payload if present', function () { + const eids = {data: [{source: 'test', uids: [{id: '123', ext: {}}]}]}; + bidderRequest.userIdAsEids = eids; + const request = spec.buildRequests([validBid], bidderRequest); + expect(JSON.parse(request.data).userIdsAsEids).to.deep.equal(eids); + }); + + it('should send schain in the payload if present', function () { + const schain = {'ver': '1.0', 'complete': 1, 'nodes': [{'asi': 'exchange1.com', 'sid': '1234', 'hp': 1}]}; + bidderRequest.schain = schain; + const request = spec.buildRequests([validBid], bidderRequest); + expect(JSON.parse(request.data).source.ext.schain).to.deep.equal(schain); + }); + }); + + describe('interpretResponse', function () { + // Test cases for interpretResponse + }); + + describe('getUserSyncs', function () { + // Test cases for getUserSyncs + }); + + describe('Helper functions', function () { + const validBidRequest = { + bidId: 'bid1', + params: { + placementId: '123' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + + const schainRequest = { + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{'asi': 'exchange1.com', 'sid': '1234', 'hp': 1}] + } + }; + + describe('buildOpenRtbImps', function () { + it('should build valid OpenRTB imps', function () { + const validBidRequests = [validBidRequest]; + const expectedImps = [{ + id: 'bid1', + banner: {format: [{w: 300, h: 250}]}, + ext: {placementId: '123'} + }]; + + const result = spec.buildOpenRtbImps(validBidRequests); + expect(result).to.deep.equal(expectedImps); + }); + }); + + describe('extractSizes', function () { + it('should extract sizes from bid request', function () { + const bidRequest = { + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + } + }; + + const expectedSizes = [{w: 300, h: 250}, {w: 728, h: 90}]; + const result = spec.extractSizes(bidRequest); + expect(result).to.deep.equal(expectedSizes); + }); + }); + + describe('attachParams', function () { + it('should attach parameters to the payload', function () { + const payload = {}; + const paramName = 'schain'; + + spec.attachParams(payload, paramName, schainRequest); + expect(payload.source.ext.schain).to.deep.equal(schainRequest.schain); + }); + }); + + describe('isTest', function () { + it('should correctly identify test mode', function () { + const result = spec.isTest({params: {test: true}}); + expect(result).to.be.true; + }); + + it('should return false when test mode is not enabled', function () { + const result = spec.isTest({params: {test: false}}); + expect(result).to.be.false; + }); + + it('should return false when test param is not present', function () { + const result = spec.isTest({}); + expect(result).to.be.true; + }); + }); + }); +}); From bf9a86c92f63c18fcf6dbdac4433d9a3ab9d1238 Mon Sep 17 00:00:00 2001 From: adtech-sky Date: Mon, 19 Feb 2024 10:35:35 +0100 Subject: [PATCH 2/5] Opsco bid adapter banner implementation --- modules/opscoBidAdapter.js | 113 ++++++---- modules/opscoBidAdapter.md | 36 +++ test/spec/modules/opscoBidAdapter_spec.js | 258 ++++++++++++---------- 3 files changed, 246 insertions(+), 161 deletions(-) diff --git a/modules/opscoBidAdapter.js b/modules/opscoBidAdapter.js index 07b1c61ba23..bfb98d0a650 100644 --- a/modules/opscoBidAdapter.js +++ b/modules/opscoBidAdapter.js @@ -1,8 +1,8 @@ -import {deepAccess, deepSetValue, logInfo} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import {deepAccess, deepSetValue, isArray, logInfo} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; -const ENDPOINT = 'https://dev-exchange-npid.ops.co/pbjs'; +const ENDPOINT = 'https://dev-exchange-npid.ops.co/openrtb2/auction'; const BIDDER_CODE = 'opsco'; const DEFAULT_BID_TTL = 300; const DEFAULT_CURRENCY = 'USD'; @@ -12,29 +12,58 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], - isBidRequestValid: (bid) => !!(bid.params && bid.params.placementId && bid.params.publisherId && bid.mediaTypes?.banner?.sizes), + isBidRequestValid: (bid) => !!(bid.params && + bid.params.placementId && + bid.params.publisherId && + bid.mediaTypes?.banner?.sizes && + Array.isArray(bid.mediaTypes?.banner?.sizes)), buildRequests: (validBidRequests, bidderRequest) => { - const { publisherId } = validBidRequests[0].params; - const siteId = validBidRequests[0].params.siteId; + const {publisherId, placementId, siteId} = validBidRequests[0].params; const payload = { id: bidderRequest.bidderRequestId, - imp: buildOpenRtbImps(validBidRequests), + imp: validBidRequests.map(bidRequest => ({ + id: bidRequest.bidId, + banner: {format: extractSizes(bidRequest)}, + ext: { + opsco: { + placementId: placementId, + publisherId: publisherId, + } + } + })), site: { id: siteId, - publisher: { id: publisherId }, + publisher: {id: publisherId}, domain: bidderRequest.refererInfo?.domain, page: bidderRequest.refererInfo?.page, ref: bidderRequest.refererInfo?.ref, }, - test: isTest(validBidRequests[0]) + test: 1 }; - attachParams(payload, 'schain', bidderRequest); - attachParams(payload, 'gdprConsent', bidderRequest); - attachParams(payload, 'uspConsent', bidderRequest); - attachParams(payload, 'userIdAsEids', validBidRequests[0]); + if (isTest(validBidRequests[0])) { + payload.test = 1; + } + + if (bidderRequest.gdprConsent) { + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(payload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + const eids = deepAccess(validBidRequests[0], 'userIdAsEids'); + if (eids && eids.length !== 0) { + deepSetValue(payload, 'user.ext.eids', eids); + } + + const schainData = deepAccess(validBidRequests[0], 'schain.nodes'); + if (isArray(schainData) && schainData.length > 0) { + deepSetValue(payload, 'source.ext.schain', validBidRequests[0].schain); + } + + if (bidderRequest.uspConsent) { + deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } return { method: 'POST', @@ -43,28 +72,35 @@ export const spec = { }; }, - interpretResponse: (response) => (response.body?.bids || []).map(({ bidId, cpm, width, height, creativeId, currency, netRevenue, ttl, ad, mediaType }) => ({ - requestId: bidId, - cpm, - width, - height, - creativeId, - currency: currency || DEFAULT_CURRENCY, - netRevenue: netRevenue || DEFAULT_NET_REVENUE, - ttl: ttl || DEFAULT_BID_TTL, - ad, - mediaType - })), + interpretResponse: (serverResponse) => { + const response = (serverResponse || {}).body; + const bidResponses = response?.seatbid?.[0]?.bid?.map(bid => ({ + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + ad: bid.adm, + ttl: typeof bid.exp === 'number' ? bid.exp : DEFAULT_BID_TTL, + creativeId: bid.crid, + netRevenue: DEFAULT_NET_REVENUE, + currency: DEFAULT_CURRENCY, + meta: {advertiserDomains: bid?.adomain || []}, + mediaType: bid.mediaType || bid.mtype + })) || []; + + if (!bidResponses.length) { + logInfo('opsco.interpretResponse :: No valid responses'); + } + + return bidResponses; + }, getUserSyncs: (syncOptions, serverResponses) => { logInfo('opsco.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); - if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { return []; } - let syncs = []; - serverResponses.forEach(resp => { const userSync = deepAccess(resp, 'body.ext.usersync'); if (userSync) { @@ -72,7 +108,7 @@ export const spec = { syncDetails.forEach(syncDetail => { const type = syncDetail.type === 'iframe' ? 'iframe' : 'image'; if ((type === 'iframe' && syncOptions.iframeEnabled) || (type === 'image' && syncOptions.pixelEnabled)) { - syncs.push({ type, url: syncDetail.url }); + syncs.push({type, url: syncDetail.url}); } }); } @@ -83,23 +119,8 @@ export const spec = { } }; -function buildOpenRtbImps(validBidRequests) { - const placementId = validBidRequests[0].params.placementId; - return validBidRequests.map(bidRequest => ({ - id: bidRequest.bidId, - banner: { format: extractSizes(bidRequest) }, - ext: { placementId } - })); -} - function extractSizes(bidRequest) { - return (bidRequest.mediaTypes?.banner?.sizes || []).map(([width, height]) => ({ w: width, h: height })); -} - -function attachParams(payload, paramName, request) { - if (request[paramName]) { - deepSetValue(payload, `source.ext.${paramName}`, request[paramName]); - } + return (bidRequest.mediaTypes?.banner?.sizes || []).map(([width, height]) => ({w: width, h: height})); } function isTest(validBidRequest) { diff --git a/modules/opscoBidAdapter.md b/modules/opscoBidAdapter.md index e69de29bb2d..edba9dd8aed 100644 --- a/modules/opscoBidAdapter.md +++ b/modules/opscoBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Opsco Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@ops.co +``` + +# Description + +Module that connects to Opscos's demand sources. + +# Test Parameters + +## Banner + +``` +var adUnits = [ + { + code: 'test-ad', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'opsco', + params: { + placementId: '1234', + publisherId: '9876', + } + }], + test: 1 + } +]; +``` diff --git a/test/spec/modules/opscoBidAdapter_spec.js b/test/spec/modules/opscoBidAdapter_spec.js index e062f8386cd..38cacff8f82 100644 --- a/test/spec/modules/opscoBidAdapter_spec.js +++ b/test/spec/modules/opscoBidAdapter_spec.js @@ -65,168 +65,196 @@ describe('opscoBidAdapter', function () { }); describe('buildRequests', function () { - const validBid = { - bidder: 'opsco', - params: { - placementId: '123', - publisherId: '456' - }, - mediaTypes: { - banner: { - sizes: [[300, 250]] + let validBid, bidderRequest; + + beforeEach(function () { + validBid = { + bidder: 'opsco', + params: { + placementId: '123', + publisherId: '456' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } } - } - }; + }; - const bidderRequest = { - bidderRequestId: 'bid123', - refererInfo: { - domain: 'example.com', - page: 'https://example.com/page', - ref: 'https://referrer.com' - }, - gdprConsent: { - consentString: 'GDPR_CONSENT_STRING', - gdprApplies: true - }, - uspConsent: '1YYY' - }; + bidderRequest = { + bidderRequestId: 'bid123', + refererInfo: { + domain: 'example.com', + page: 'https://example.com/page', + ref: 'https://referrer.com' + }, + gdprConsent: { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true + }, + }; + }); - const expectedPayload = { - id: 'bid123', - imp: [{ - id: 'bid123', - banner: {format: [{w: 300, h: 250}]}, - ext: {placementId: '123'} - }], - site: { - id: '456', - publisher: {id: '123'}, - domain: 'example.com', - page: 'https://example.com/page', - ref: 'https://referrer.com', - }, - test: false, - gdprConsent: {consentString: 'GDPR_CONSENT_STRING', gdprApplies: true}, - uspConsent: '1YYY' - }; + it('should return true when banner sizes are defined', function () { + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); - it('should build a valid POST request with proper payload', function () { - const request = spec.buildRequests([validBid], bidderRequest); - expect(request.data).to.equal(JSON.stringify(expectedPayload)); + it('should return false when banner sizes are invalid', function () { + const invalidSizes = [ + '2:1', + undefined, + 123, + 'undefined' + ]; + + invalidSizes.forEach((sizes) => { + validBid.mediaTypes.banner.sizes = sizes; + expect(spec.isBidRequestValid(validBid)).to.be.false; + }); }); it('should send GDPR consent in the payload if present', function () { const request = spec.buildRequests([validBid], bidderRequest); - expect(JSON.parse(request.data).gdprConsent).to.deep.equal(expectedPayload.gdprConsent); + expect(JSON.parse(request.data).user.ext.consent).to.deep.equal('GDPR_CONSENT_STRING'); }); it('should send CCPA in the payload if present', function () { const ccpa = '1YYY'; bidderRequest.uspConsent = ccpa; const request = spec.buildRequests([validBid], bidderRequest); - expect(JSON.parse(request.data).uspConsent).to.equal(ccpa); + expect(JSON.parse(request.data).regs.ext.us_privacy).to.equal(ccpa); }); it('should send eids in the payload if present', function () { const eids = {data: [{source: 'test', uids: [{id: '123', ext: {}}]}]}; - bidderRequest.userIdAsEids = eids; + validBid.userIdAsEids = eids; const request = spec.buildRequests([validBid], bidderRequest); - expect(JSON.parse(request.data).userIdsAsEids).to.deep.equal(eids); + expect(JSON.parse(request.data).user.ext.eids).to.deep.equal(eids); }); it('should send schain in the payload if present', function () { const schain = {'ver': '1.0', 'complete': 1, 'nodes': [{'asi': 'exchange1.com', 'sid': '1234', 'hp': 1}]}; - bidderRequest.schain = schain; + validBid.schain = schain; const request = spec.buildRequests([validBid], bidderRequest); expect(JSON.parse(request.data).source.ext.schain).to.deep.equal(schain); }); - }); - - describe('interpretResponse', function () { - // Test cases for interpretResponse - }); - describe('getUserSyncs', function () { - // Test cases for getUserSyncs + it('should correctly identify test mode', function () { + validBid.params.test = true; + const request = spec.buildRequests([validBid], bidderRequest); + expect(JSON.parse(request.data).test).to.equal(1); + }); }); - describe('Helper functions', function () { - const validBidRequest = { - bidId: 'bid1', - params: { - placementId: '123' - }, - mediaTypes: { - banner: { - sizes: [[300, 250]] - } + describe('interpretResponse', function () { + const validResponse = { + body: { + seatbid: [ + { + bid: [ + { + impid: 'bid1', + price: 1.5, + w: 300, + h: 250, + crid: 'creative1', + currency: 'USD', + netRevenue: true, + ttl: 300, + adm: '
Ad content
', + mtype: 1 + }, + { + impid: 'bid2', + price: 2.0, + w: 728, + h: 90, + crid: 'creative2', + currency: 'USD', + netRevenue: true, + ttl: 300, + adm: '
Ad content
', + mtype: 1 + } + ] + } + ] } }; - const schainRequest = { - schain: { - 'ver': '1.0', - 'complete': 1, - 'nodes': [{'asi': 'exchange1.com', 'sid': '1234', 'hp': 1}] + const emptyResponse = { + body: { + seatbid: [] } }; - describe('buildOpenRtbImps', function () { - it('should build valid OpenRTB imps', function () { - const validBidRequests = [validBidRequest]; - const expectedImps = [{ - id: 'bid1', - banner: {format: [{w: 300, h: 250}]}, - ext: {placementId: '123'} - }]; - - const result = spec.buildOpenRtbImps(validBidRequests); - expect(result).to.deep.equal(expectedImps); + it('should return an array of bid objects with valid response', function () { + const interpretedBids = spec.interpretResponse(validResponse); + const expectedBids = validResponse.body.seatbid[0].bid; + expect(interpretedBids).to.have.lengthOf(expectedBids.length); + expectedBids.forEach((expectedBid, index) => { + expect(interpretedBids[index]).to.have.property('requestId', expectedBid.impid); + expect(interpretedBids[index]).to.have.property('cpm', expectedBid.price); + expect(interpretedBids[index]).to.have.property('width', expectedBid.w); + expect(interpretedBids[index]).to.have.property('height', expectedBid.h); + expect(interpretedBids[index]).to.have.property('creativeId', expectedBid.crid); + expect(interpretedBids[index]).to.have.property('currency', expectedBid.currency); + expect(interpretedBids[index]).to.have.property('netRevenue', expectedBid.netRevenue); + expect(interpretedBids[index]).to.have.property('ttl', expectedBid.ttl); + expect(interpretedBids[index]).to.have.property('ad', expectedBid.adm); + expect(interpretedBids[index]).to.have.property('mediaType', expectedBid.mtype); }); }); - describe('extractSizes', function () { - it('should extract sizes from bid request', function () { - const bidRequest = { - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] + it('should return an empty array with empty response', function () { + const interpretedBids = spec.interpretResponse(emptyResponse); + expect(interpretedBids).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function () { + const RESPONSE = { + body: { + ext: { + usersync: { + sovrn: { + syncs: [{type: 'iframe', url: 'https://sovrn.com/iframe_sync'}] + }, + appnexus: { + syncs: [{type: 'image', url: 'https://appnexus.com/image_sync'}] } } - }; + } + } + }; - const expectedSizes = [{w: 300, h: 250}, {w: 728, h: 90}]; - const result = spec.extractSizes(bidRequest); - expect(result).to.deep.equal(expectedSizes); - }); + it('should return empty array if no options are provided', function () { + const opts = spec.getUserSyncs({}); + expect(opts).to.be.an('array').that.is.empty; }); - describe('attachParams', function () { - it('should attach parameters to the payload', function () { - const payload = {}; - const paramName = 'schain'; - - spec.attachParams(payload, paramName, schainRequest); - expect(payload.source.ext.schain).to.deep.equal(schainRequest.schain); - }); + it('should return empty array if neither iframe nor pixel is enabled', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(opts).to.be.an('array').that.is.empty; }); - describe('isTest', function () { - it('should correctly identify test mode', function () { - const result = spec.isTest({params: {test: true}}); - expect(result).to.be.true; - }); + it('should return syncs only for iframe sync type', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [RESPONSE]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync.sovrn.syncs[0].url); + }); - it('should return false when test mode is not enabled', function () { - const result = spec.isTest({params: {test: false}}); - expect(result).to.be.false; - }); + it('should return syncs only for pixel sync types', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [RESPONSE]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal(RESPONSE.body.ext.usersync.appnexus.syncs[0].url); + }); - it('should return false when test param is not present', function () { - const result = spec.isTest({}); - expect(result).to.be.true; - }); + it('should return syncs when both iframe and pixel are enabled', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [RESPONSE]); + expect(opts.length).to.equal(2); }); }); }); From 40aae572db49bf61fd1c06984015fec0c225d361 Mon Sep 17 00:00:00 2001 From: adtech-sky Date: Mon, 19 Feb 2024 10:58:21 +0100 Subject: [PATCH 3/5] Changing test parameter --- modules/opscoBidAdapter.js | 1 - modules/opscoBidAdapter.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/opscoBidAdapter.js b/modules/opscoBidAdapter.js index bfb98d0a650..f1e760091cc 100644 --- a/modules/opscoBidAdapter.js +++ b/modules/opscoBidAdapter.js @@ -40,7 +40,6 @@ export const spec = { page: bidderRequest.refererInfo?.page, ref: bidderRequest.refererInfo?.ref, }, - test: 1 }; if (isTest(validBidRequests[0])) { diff --git a/modules/opscoBidAdapter.md b/modules/opscoBidAdapter.md index edba9dd8aed..b5e1015a325 100644 --- a/modules/opscoBidAdapter.md +++ b/modules/opscoBidAdapter.md @@ -28,9 +28,9 @@ var adUnits = [ params: { placementId: '1234', publisherId: '9876', + test: true } }], - test: 1 } ]; ``` From ab8aad37402dcc763f6c1e091a23e7c9f4c6b970 Mon Sep 17 00:00:00 2001 From: adtech-sky Date: Mon, 19 Feb 2024 16:09:34 +0100 Subject: [PATCH 4/5] Changing endpoint --- modules/opscoBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/opscoBidAdapter.js b/modules/opscoBidAdapter.js index f1e760091cc..87d00f14de0 100644 --- a/modules/opscoBidAdapter.js +++ b/modules/opscoBidAdapter.js @@ -2,7 +2,7 @@ import {deepAccess, deepSetValue, isArray, logInfo} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -const ENDPOINT = 'https://dev-exchange-npid.ops.co/openrtb2/auction'; +const ENDPOINT = 'https://exchange.ops.co/openrtb2/auction'; const BIDDER_CODE = 'opsco'; const DEFAULT_BID_TTL = 300; const DEFAULT_CURRENCY = 'USD'; From 9cb7fb02d18625a7e99c9efa8d0e892305b8b0af Mon Sep 17 00:00:00 2001 From: adtech-sky Date: Wed, 29 May 2024 17:33:58 +0200 Subject: [PATCH 5/5] Retrieving placement Id from bid request params --- modules/opscoBidAdapter.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/opscoBidAdapter.js b/modules/opscoBidAdapter.js index 87d00f14de0..2ad14227804 100644 --- a/modules/opscoBidAdapter.js +++ b/modules/opscoBidAdapter.js @@ -19,7 +19,11 @@ export const spec = { Array.isArray(bid.mediaTypes?.banner?.sizes)), buildRequests: (validBidRequests, bidderRequest) => { - const {publisherId, placementId, siteId} = validBidRequests[0].params; + if (!validBidRequests || !bidderRequest) { + return; + } + + const {publisherId, siteId} = validBidRequests[0].params; const payload = { id: bidderRequest.bidderRequestId, @@ -28,7 +32,7 @@ export const spec = { banner: {format: extractSizes(bidRequest)}, ext: { opsco: { - placementId: placementId, + placementId: bidRequest.params.placementId, publisherId: publisherId, } }