From 59dd4e07ddcd88768f25a5c0d4d12edeaa118d98 Mon Sep 17 00:00:00 2001 From: adxpremium <55161519+adxpremium@users.noreply.github.com> Date: Thu, 18 Nov 2021 10:41:06 +0100 Subject: [PATCH] LuponMedia Bid Adapter: add adapter back to prebid master (#7602) * LuponMedia BidAdapter 5.X * manually try to kick off circleci tests * kick off tests * kick off tests * Update luponmediaBidAdapter.js using isFn from utils Co-authored-by: adxpremium Co-authored-by: Chris Huie --- modules/luponmediaBidAdapter.js | 570 ++++++++++++++++++ .../spec/modules/luponmediaBidAdapter_spec.js | 412 +++++++++++++ 2 files changed, 982 insertions(+) create mode 100755 modules/luponmediaBidAdapter.js create mode 100755 test/spec/modules/luponmediaBidAdapter_spec.js diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js new file mode 100755 index 00000000000..897dc3c8825 --- /dev/null +++ b/modules/luponmediaBidAdapter.js @@ -0,0 +1,570 @@ +import {isArray, logMessage, deepAccess, logWarn, parseSizesInput, deepSetValue, generateUUID, isEmpty, logError, _each, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {config} from '../src/config.js'; +import {BANNER} from '../src/mediaTypes.js'; +import { ajax } from '../src/ajax.js'; + +const BIDDER_CODE = 'luponmedia'; +const ENDPOINT_URL = 'https://rtb.adxpremium.services/openrtb2/auction'; + +const DIGITRUST_PROP_NAMES = { + PREBID_SERVER: { + id: 'id', + keyv: 'keyv' + } +}; + +var sizeMap = { + 1: '468x60', + 2: '728x90', + 5: '120x90', + 7: '125x125', + 8: '120x600', + 9: '160x600', + 10: '300x600', + 13: '200x200', + 14: '250x250', + 15: '300x250', + 16: '336x280', + 17: '240x400', + 19: '300x100', + 31: '980x120', + 32: '250x360', + 33: '180x500', + 35: '980x150', + 37: '468x400', + 38: '930x180', + 39: '750x100', + 40: '750x200', + 41: '750x300', + 42: '2x4', + 43: '320x50', + 44: '300x50', + 48: '300x300', + 53: '1024x768', + 54: '300x1050', + 55: '970x90', + 57: '970x250', + 58: '1000x90', + 59: '320x80', + 60: '320x150', + 61: '1000x1000', + 64: '580x500', + 65: '640x480', + 66: '930x600', + 67: '320x480', + 68: '1800x1000', + 72: '320x320', + 73: '320x160', + 78: '980x240', + 79: '980x300', + 80: '980x400', + 83: '480x300', + 85: '300x120', + 90: '548x150', + 94: '970x310', + 95: '970x100', + 96: '970x210', + 101: '480x320', + 102: '768x1024', + 103: '480x280', + 105: '250x800', + 108: '320x240', + 113: '1000x300', + 117: '320x100', + 125: '800x250', + 126: '200x600', + 144: '980x600', + 145: '980x150', + 152: '1000x250', + 156: '640x320', + 159: '320x250', + 179: '250x600', + 195: '600x300', + 198: '640x360', + 199: '640x200', + 213: '1030x590', + 214: '980x360', + 221: '1x1', + 229: '320x180', + 230: '2000x1400', + 232: '580x400', + 234: '6x6', + 251: '2x2', + 256: '480x820', + 257: '400x600', + 258: '500x200', + 259: '998x200', + 264: '970x1000', + 265: '1920x1080', + 274: '1800x200', + 278: '320x500', + 282: '320x400', + 288: '640x380', + 548: '500x1000', + 550: '980x480', + 552: '300x200', + 558: '640x640' +}; + +_each(sizeMap, (item, key) => sizeMap[item] = key); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.siteId && bid.params.keyId); // TODO: check for siteId and keyId + }, + buildRequests: function (bidRequests, bidderRequest) { + const bRequest = { + method: 'POST', + url: ENDPOINT_URL, + data: null, + options: {}, + bidderRequest + }; + + let currentImps = []; + + for (let i = 0, len = bidRequests.length; i < len; i++) { + let newReq = newOrtbBidRequest(bidRequests[i], bidderRequest, currentImps); + currentImps = newReq.imp; + bRequest.data = JSON.stringify(newReq); + } + + return bRequest; + }, + interpretResponse: (response, request) => { + const bidResponses = []; + var respCur = 'USD'; + let parsedRequest = JSON.parse(request.data); + let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : ''; + try { + if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { + // Supporting multiple bid responses for same adSize + respCur = response.body.cur || respCur; + response.body.seatbid.forEach(seatbidder => { + seatbidder.bid && + isArray(seatbidder.bid) && + seatbidder.bid.forEach(bid => { + let newBid = { + requestId: bid.impid, + cpm: (parseFloat(bid.price) || 0).toFixed(2), + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.id, + dealId: bid.dealid, + currency: respCur, + netRevenue: false, + ttl: 300, + referrer: parsedReferrer, + ad: bid.adm + }; + + bidResponses.push(newBid); + }); + }); + } + } catch (error) { + logError(error); + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + let allUserSyncs = []; + if (!hasSynced && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + responses.forEach(csResp => { + if (csResp.body && csResp.body.ext && csResp.body.ext.usersyncs) { + try { + let response = csResp.body.ext.usersyncs + let bidders = response.bidder_status; + for (let synci in bidders) { + let thisSync = bidders[synci]; + if (thisSync.no_cookie) { + let url = thisSync.usersync.url; + let type = thisSync.usersync.type; + + if (!url) { + logError(`No sync url for bidder luponmedia.`); + } else if ((type === 'image' || type === 'redirect') && syncOptions.pixelEnabled) { + logMessage(`Invoking image pixel user sync for luponmedia`); + allUserSyncs.push({type: 'image', url: url}); + } else if (type == 'iframe' && syncOptions.iframeEnabled) { + logMessage(`Invoking iframe user sync for luponmedia`); + allUserSyncs.push({type: 'iframe', url: url}); + } else { + logError(`User sync type "${type}" not supported for luponmedia`); + } + } + } + } catch (e) { + logError(e); + } + } + }); + } else { + logWarn('Luponmedia: Please enable iframe/pixel based user sync.'); + } + + hasSynced = true; + return allUserSyncs; + }, + onBidWon: bid => { + const bidString = JSON.stringify(bid); + spec.sendWinningsToServer(bidString); + }, + sendWinningsToServer: data => { + let mutation = `mutation {createWin(input: {win: {eventData: "${window.btoa(data)}"}}) {win {createTime } } }`; + let dataToSend = JSON.stringify({ query: mutation }); + + ajax('https://analytics.adxpremium.services/graphql', null, dataToSend, { + contentType: 'application/json', + method: 'POST' + }); + } +}; + +export function hasValidSupplyChainParams(schain) { + let isValid = false; + const requiredFields = ['asi', 'sid', 'hp']; + if (!schain.nodes) return isValid; + isValid = schain.nodes.reduce((status, node) => { + if (!status) return status; + return requiredFields.every(field => node[field]); + }, true); + if (!isValid) logError('LuponMedia: required schain params missing'); + return isValid; +} + +var hasSynced = false; + +export function resetUserSync() { + hasSynced = false; +} + +export function masSizeOrdering(sizes) { + const MAS_SIZE_PRIORITY = [15, 2, 9]; + + return sizes.sort((first, second) => { + // sort by MAS_SIZE_PRIORITY priority order + const firstPriority = MAS_SIZE_PRIORITY.indexOf(first); + const secondPriority = MAS_SIZE_PRIORITY.indexOf(second); + + if (firstPriority > -1 || secondPriority > -1) { + if (firstPriority === -1) { + return 1; + } + if (secondPriority === -1) { + return -1; + } + return firstPriority - secondPriority; + } + + // and finally ascending order + return first - second; + }); +} + +function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { + bidRequest.startTime = new Date().getTime(); + + const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); + + let bannerSizes = []; + + if (bannerParams && bannerParams.sizes) { + const sizes = parseSizesInput(bannerParams.sizes); + + // get banner sizes in form [{ w: , h: }, ...] + const format = sizes.map(size => { + const [ width, height ] = size.split('x'); + const w = parseInt(width, 10); + const h = parseInt(height, 10); + return { w, h }; + }); + + bannerSizes = format; + } + + const data = { + id: bidRequest.transactionId, + test: config.getConfig('debug') ? 1 : 0, + source: { + tid: bidRequest.transactionId + }, + tmax: config.getConfig('timeout') || 1500, + imp: currentImps.concat([{ + id: bidRequest.bidId, + secure: 1, + ext: { + [bidRequest.bidder]: bidRequest.params + }, + banner: { + format: bannerSizes + } + }]), + ext: { + prebid: { + targeting: { + includewinners: true, + // includebidderkeys always false for openrtb + includebidderkeys: false + } + } + }, + user: { + } + } + + let bidFloor; + if (isFn(bidRequest.getFloor) && !config.getConfig('disableFloors')) { + let floorInfo; + try { + floorInfo = bidRequest.getFloor({ + currency: 'USD', + mediaType: 'video', + size: parseSizes(bidRequest, 'video') + }); + } catch (e) { + logError('LuponMedia: getFloor threw an error: ', e); + } + bidFloor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined; + } else { + bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); + } + if (!isNaN(bidFloor)) { + data.imp[0].bidfloor = bidFloor; + } + + appendSiteAppDevice(data, bidRequest, bidderRequest); + + const digiTrust = _getDigiTrustQueryParams(bidRequest, 'PREBID_SERVER'); + if (digiTrust) { + deepSetValue(data, 'user.ext.digitrust', digiTrust); + } + + if (bidderRequest.gdprConsent) { + // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module + let gdprApplies; + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + + deepSetValue(data, 'regs.ext.gdpr', gdprApplies); + deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + if (bidderRequest.uspConsent) { + deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + // Set user uuid + deepSetValue(data, 'user.id', generateUUID()); + + // set crumbs + if (bidRequest.crumbs && bidRequest.crumbs.pubcid) { + deepSetValue(data, 'user.buyeruid', bidRequest.crumbs.pubcid); + } else { + deepSetValue(data, 'user.buyeruid', generateUUID()); + } + + if (bidRequest.userId && typeof bidRequest.userId === 'object' && + (bidRequest.userId.tdid || bidRequest.userId.pubcid || bidRequest.userId.lipb || bidRequest.userId.idl_env)) { + deepSetValue(data, 'user.ext.eids', []); + + if (bidRequest.userId.tdid) { + data.user.ext.eids.push({ + source: 'adserver.org', + uids: [{ + id: bidRequest.userId.tdid, + ext: { + rtiPartner: 'TDID' + } + }] + }); + } + + if (bidRequest.userId.pubcid) { + data.user.ext.eids.push({ + source: 'pubcommon', + uids: [{ + id: bidRequest.userId.pubcid, + }] + }); + } + + // support liveintent ID + if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { + data.user.ext.eids.push({ + source: 'liveintent.com', + uids: [{ + id: bidRequest.userId.lipb.lipbid + }] + }); + + data.user.ext.tpid = { + source: 'liveintent.com', + uid: bidRequest.userId.lipb.lipbid + }; + + if (Array.isArray(bidRequest.userId.lipb.segments) && bidRequest.userId.lipb.segments.length) { + deepSetValue(data, 'rp.target.LIseg', bidRequest.userId.lipb.segments); + } + } + + // support identityLink (aka LiveRamp) + if (bidRequest.userId.idl_env) { + data.user.ext.eids.push({ + source: 'liveramp.com', + uids: [{ + id: bidRequest.userId.idl_env + }] + }); + } + } + + if (config.getConfig('coppa') === true) { + deepSetValue(data, 'regs.coppa', 1); + } + + if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) { + deepSetValue(data, 'source.ext.schain', bidRequest.schain); + } + + const siteData = Object.assign({}, bidRequest.params.inventory, config.getConfig('fpd.context')); + const userData = Object.assign({}, bidRequest.params.visitor, config.getConfig('fpd.user')); + + if (!isEmpty(siteData) || !isEmpty(userData)) { + const bidderData = { + bidders: [ bidderRequest.bidderCode ], + config: { + fpd: {} + } + }; + + if (!isEmpty(siteData)) { + bidderData.config.fpd.site = siteData; + } + + if (!isEmpty(userData)) { + bidderData.config.fpd.user = userData; + } + + deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); + } + + const pbAdSlot = deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + if (typeof pbAdSlot === 'string' && pbAdSlot) { + deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); + } + + return data; +} + +function _getDigiTrustQueryParams(bidRequest = {}, endpointName) { + if (!endpointName || !DIGITRUST_PROP_NAMES[endpointName]) { + return null; + } + const propNames = DIGITRUST_PROP_NAMES[endpointName]; + + function getDigiTrustId() { + const bidRequestDigitrust = deepAccess(bidRequest, 'userId.digitrustid.data'); + if (bidRequestDigitrust) { + return bidRequestDigitrust; + } + + let digiTrustUser = (window.DigiTrust && (config.getConfig('digiTrustId') || window.DigiTrust.getUser({member: 'T9QSFKPDN9'}))); + return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null; + } + + let digiTrustId = getDigiTrustId(); + // Verify there is an ID and this user has not opted out + if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) { + return null; + } + + const digiTrustQueryParams = { + [propNames.id]: digiTrustId.id, + [propNames.keyv]: digiTrustId.keyv + }; + if (propNames.pref) { + digiTrustQueryParams[propNames.pref] = 0; + } + return digiTrustQueryParams; +} + +function _getPageUrl(bidRequest, bidderRequest) { + let pageUrl = config.getConfig('pageUrl'); + if (bidRequest.params.referrer) { + pageUrl = bidRequest.params.referrer; + } else if (!pageUrl) { + pageUrl = bidderRequest.refererInfo.referer; + } + return bidRequest.params.secure ? pageUrl.replace(/^http:/i, 'https:') : pageUrl; +} + +function appendSiteAppDevice(data, bidRequest, bidderRequest) { + if (!data) return; + + // ORTB specifies app OR site + if (typeof config.getConfig('app') === 'object') { + data.app = config.getConfig('app'); + } else { + data.site = { + page: _getPageUrl(bidRequest, bidderRequest) + } + } + if (typeof config.getConfig('device') === 'object') { + data.device = config.getConfig('device'); + } +} + +/** + * @param sizes + * @returns {*} + */ +function mapSizes(sizes) { + return parseSizesInput(sizes) + // map sizes while excluding non-matches + .reduce((result, size) => { + let mappedSize = parseInt(sizeMap[size], 10); + if (mappedSize) { + result.push(mappedSize); + } + return result; + }, []); +} + +function parseSizes(bid, mediaType) { + let params = bid.params; + if (mediaType === 'video') { + let size = []; + if (params.video && params.video.playerWidth && params.video.playerHeight) { + size = [ + params.video.playerWidth, + params.video.playerHeight + ]; + } else if (Array.isArray(deepAccess(bid, 'mediaTypes.video.playerSize')) && bid.mediaTypes.video.playerSize.length === 1) { + size = bid.mediaTypes.video.playerSize[0]; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0 && Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1) { + size = bid.sizes[0]; + } + return size; + } + + // Deprecated: temp legacy support + let sizes = []; + if (Array.isArray(params.sizes)) { + sizes = params.sizes; + } else if (typeof deepAccess(bid, 'mediaTypes.banner.sizes') !== 'undefined') { + sizes = mapSizes(bid.mediaTypes.banner.sizes); + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizes = mapSizes(bid.sizes) + } else { + logWarn('LuponMedia: no sizes are setup or found'); + } + + return masSizeOrdering(sizes); +} + +registerBidder(spec); diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js new file mode 100755 index 00000000000..8aeecc87c98 --- /dev/null +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -0,0 +1,412 @@ +import { resetUserSync, spec, hasValidSupplyChainParams } from 'modules/luponmediaBidAdapter.js'; +const ENDPOINT_URL = 'https://rtb.adxpremium.services/openrtb2/auction'; + +describe('luponmediaBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 12345, + 'keyId': '4o2c4' + }, + 'adUnitCode': 'test-div', + 'sizes': [[300, 250]], + 'bidId': 'g1987234bjkads', + 'bidderRequestId': '290348ksdhkas89324', + 'auctionId': '20384rlek235', + }; + + it('should return true when required params are found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + 'siteId': 12345 + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests = [ + { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 303522, + 'keyId': '4o2c4' + }, + 'crumbs': { + 'pubcid': '8d8b16cb-1383-4a0f-b4bb-0be28464d974' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1533155193780-2', + 'transactionId': '585d96a5-bd93-4a89-b8ea-0f546f3aaa82', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '268a30af10dd6f', + 'bidderRequestId': '140411b5010a2a', + 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'novi.ba', + 'sid': '199424', + 'hp': 1 + } + ] + } + } + ]; + + let bidderRequest = { + 'bidderCode': 'luponmedia', + 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', + 'bidderRequestId': '140411b5010a2a', + 'bids': [ + { + 'bidder': 'luponmedia', + 'params': { + 'siteId': 303522, + 'keyId': '4o2c4' + }, + 'crumbs': { + 'pubcid': '8d8b16cb-1383-4a0f-b4bb-0be28464d974' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1533155193780-2', + 'transactionId': '585d96a5-bd93-4a89-b8ea-0f546f3aaa82', + 'sizes': [ + [ + 300, + 250 + ] + ], + 'bidId': '268a30af10dd6f', + 'bidderRequestId': '140411b5010a2a', + 'auctionId': '7376c117-b7aa-49f5-a661-488543deeefd', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'novi.ba', + 'sid': '199424', + 'hp': 1 + } + ] + } + } + ], + 'auctionStart': 1587413920820, + 'timeout': 2000, + 'refererInfo': { + 'referer': 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines', + 'reachedTop': true, + 'numIframes': 0, + 'stack': [ + 'https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines' + ] + }, + 'start': 1587413920835 + }; + + it('sends bid request to ENDPOINT via POST', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + let dynRes = JSON.parse(requests.data); + expect(requests.url).to.equal(ENDPOINT_URL); + expect(requests.method).to.equal('POST'); + expect(requests.data).to.equal('{"id":"585d96a5-bd93-4a89-b8ea-0f546f3aaa82","test":0,"source":{"tid":"585d96a5-bd93-4a89-b8ea-0f546f3aaa82","ext":{"schain":{"ver":"1.0","complete":1,"nodes":[{"asi":"novi.ba","sid":"199424","hp":1}]}}},"tmax":1500,"imp":[{"id":"268a30af10dd6f","secure":1,"ext":{"luponmedia":{"siteId":303522,"keyId":"4o2c4"}},"banner":{"format":[{"w":300,"h":250}]}}],"ext":{"prebid":{"targeting":{"includewinners":true,"includebidderkeys":false}}},"user":{"id":"' + dynRes.user.id + '","buyeruid":"8d8b16cb-1383-4a0f-b4bb-0be28464d974"},"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}'); + }); + }); + + describe('interpretResponse', function () { + it('should get correct banner bid response', function () { + let response = { + 'id': '4776d680-15a2-45c3-bad5-db6bebd94a06', + 'seatbid': [ + { + 'bid': [ + { + 'id': '2a122246ef72ea', + 'impid': '2a122246ef72ea', + 'price': 0.43, + 'adm': ' ', + 'adid': '56380110', + 'cid': '44724710', + 'crid': '443801010', + 'w': 300, + 'h': 250, + 'ext': { + 'prebid': { + 'targeting': { + 'hb_bidder': 'luponmedia', + 'hb_pb': '0.40', + 'hb_size': '300x250' + }, + 'type': 'banner' + } + } + } + ], + 'seat': 'luponmedia' + } + ], + 'cur': 'USD', + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'ok', + 'bidder_status': [] + } + } + }; + + let expectedResponse = [ + { + 'requestId': '2a122246ef72ea', + 'cpm': '0.43', + 'width': 300, + 'height': 250, + 'creativeId': '443801010', + 'currency': 'USD', + 'dealId': '23425', + 'netRevenue': false, + 'ttl': 300, + 'referrer': '', + 'ad': ' ' + } + ]; + + let bidderRequest = { + 'data': '{"site":{"page":"https://novi.ba/clanak/176067/fast-car-beginner-s-guide-to-tuning-turbo-engines"}}' + }; + + let result = spec.interpretResponse({ body: response }, bidderRequest); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function () { + let noBidResponse = []; + + let noBidBidderRequest = { + 'data': '{"site":{"page":""}}' + } + let noBidResult = spec.interpretResponse({ body: noBidResponse }, noBidBidderRequest); + expect(noBidResult.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + const bidResponse1 = { + 'body': { + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'ok', + 'bidder_status': [ + { + 'bidder': 'luponmedia', + 'no_cookie': true, + 'usersync': { + 'url': 'https://adxpremium.services/api/usersync', + 'type': 'redirect' + } + }, + { + 'bidder': 'luponmedia', + 'no_cookie': true, + 'usersync': { + 'url': 'https://adxpremium.services/api/iframeusersync', + 'type': 'iframe' + } + } + ] + } + } + } + }; + + const bidResponse2 = { + 'body': { + 'ext': { + 'responsetimemillis': { + 'luponmedia': 233 + }, + 'tmaxrequest': 1500, + 'usersyncs': { + 'status': 'no_cookie', + 'bidder_status': [] + } + } + } + }; + + it('should use a sync url from first response (pixel and iframe)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, [bidResponse1, bidResponse2]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://adxpremium.services/api/usersync' + }, + { + type: 'iframe', + url: 'https://adxpremium.services/api/iframeusersync' + } + ]); + }); + + it('handle empty response (e.g. timeout)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + + it('returns empty syncs when not pixel enabled and not iframe enabled', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: false }, [bidResponse1]); + expect(syncs).to.deep.equal([]); + }); + + it('returns pixel syncs when pixel enabled and not iframe enabled', function() { + resetUserSync(); + + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: false }, [bidResponse1]); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: 'https://adxpremium.services/api/usersync' + } + ]); + }); + + it('returns iframe syncs when not pixel enabled and iframe enabled', function() { + resetUserSync(); + + const syncs = spec.getUserSyncs({ pixelEnabled: false, iframeEnabled: true }, [bidResponse1]); + expect(syncs).to.deep.equal([ + { + type: 'iframe', + url: 'https://adxpremium.services/api/iframeusersync' + } + ]); + }); + }); + + describe('hasValidSupplyChainParams', function () { + it('returns true if schain is valid', function () { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'novi.ba', + 'sid': '199424', + 'hp': 1 + } + ] + }; + + const checkSchain = hasValidSupplyChainParams(schain); + expect(checkSchain).to.equal(true); + }); + + it('returns false if schain is invalid', function () { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'invalid': 'novi.ba' + } + ] + }; + + const checkSchain = hasValidSupplyChainParams(schain); + expect(checkSchain).to.equal(false); + }); + }); + + describe('onBidWon', function () { + const bidWonEvent = { + 'bidderCode': 'luponmedia', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '105bbf8c54453ff', + 'requestId': '934b8752185955', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.364, + 'creativeId': '443801010', + 'currency': 'USD', + 'netRevenue': false, + 'ttl': 300, + 'referrer': '', + 'ad': '', + 'auctionId': '926a8ea3-3dd4-4bf2-95ab-c85c2ce7e99b', + 'responseTimestamp': 1598527728026, + 'requestTimestamp': 1598527727629, + 'bidder': 'luponmedia', + 'adUnitCode': 'div-gpt-ad-1533155193780-5', + 'timeToRespond': 397, + 'size': '300x250', + 'status': 'rendered' + }; + + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'sendWinningsToServer') + }) + + afterEach(() => { + ajaxStub.restore() + }) + + it('calls luponmedia\'s callback endpoint', () => { + const result = spec.onBidWon(bidWonEvent); + expect(result).to.equal(undefined); + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.deep.equal(JSON.stringify(bidWonEvent)); + }); + }); +});