diff --git a/modules/ssmasBidAdapter.js b/modules/ssmasBidAdapter.js new file mode 100644 index 00000000000..9ed1ec90dd8 --- /dev/null +++ b/modules/ssmasBidAdapter.js @@ -0,0 +1,126 @@ +import { BANNER } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { triggerPixel, deepSetValue } from '../src/utils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import {config} from '../src/config.js'; + +export const SSMAS_CODE = 'ssmas'; +const SSMAS_SERVER = 'ads.ssmas.com'; +export const SSMAS_ENDPOINT = `http://${SSMAS_SERVER}/ortb`; +const SYNC_URL = `http://sync.ssmas.com/user_sync`; +export const SSMAS_REQUEST_METHOD = 'POST'; +const GDPR_VENDOR_ID = 1183; + +export const ssmasOrtbConverter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + mediaType: BANNER, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'ext.placementId', bidRequest.params.placementId); + return imp; + }, +}); + +export const spec = { + code: SSMAS_CODE, + supportedMediaTypes: [BANNER], + gvlid: GDPR_VENDOR_ID, + + isBidRequestValid: (bid) => { + return !!bid.params.placementId && !!bid.bidId && bid.bidder === SSMAS_CODE; + }, + + buildRequests: (bidRequests, bidderRequest) => { + const data = ssmasOrtbConverter.toORTB({ bidRequests, bidderRequest }); + + const options = { + contentType: 'application/json', + withCredentials: false, + }; + + data.imp && data.imp.forEach(imp => { + if (imp.ext && imp.ext.placementId) { + imp.tagId = imp.ext.placementId; + } + }); + + data.regs = data.regs || {}; + data.regs.ext = data.regs.ext || {}; + + if (bidderRequest.gdprConsent) { + data.regs.ext.consent = bidderRequest.gdprConsent.consentString; + data.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + if (bidderRequest.uspConsent) { + data.regs.ext.consent = bidderRequest.uspConsent.consentString; + data.regs.ext.ccpa = 1; + } + if (config.getConfig('coppa') === true) { + data.regs.coppa = 1; + } + + return [ + { + method: SSMAS_REQUEST_METHOD, + url: SSMAS_ENDPOINT, + data, + options, + }, + ]; + }, + + interpretResponse: (serverResponse, bidRequest) => { + const bids = ssmasOrtbConverter.fromORTB({ + response: serverResponse.body, + request: bidRequest.data, + }).bids; + + return bids.filter((bid) => { + return bid.cpm > 0; + }); + }, + + onBidWon: (bid) => { + if (bid.burl) { + triggerPixel(bid.burl); + } + }, + + getUserSyncs: ( + syncOptions, + serverResponses, + gdprConsent, + uspConsent + ) => { + const syncs = []; + + let params = ['pbjs=1']; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params.push(`gdpr=${Boolean(gdprConsent.gdprApplies)}&gdpr_consent=${ + gdprConsent.consentString + }`); + } else { + params.push(`gdpr_consent=${gdprConsent.consentString}`); + } + } + + if (uspConsent && uspConsent.consentString) { + params.push(`ccpa_consent=${uspConsent.consentString}`); + } + + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + syncs.push({ + type: 'image', + url: `${SYNC_URL}?${params.join('&')}` + }); + } + return syncs; + }, +}; + +registerBidder(spec); diff --git a/modules/ssmasBidAdapter.md b/modules/ssmasBidAdapter.md new file mode 100644 index 00000000000..1b15764a54d --- /dev/null +++ b/modules/ssmasBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +Module Name: SSMas Bidder Adapter +Module Type: Bidder Adapter +Maintainer: hzchen.work@gmail.com + +# Description + +Module that connects to Sem Seo & Mas header bidding endpoint to fetch bids. +Supports Banner +Supported currencies: EUR + +Required parameters: +- placement id + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'ssmas', + params: { + placementId: "10336" + } + }] + } +]; +``` diff --git a/test/spec/modules/ssmasBidAdapter_spec.js b/test/spec/modules/ssmasBidAdapter_spec.js new file mode 100644 index 00000000000..26c6f60da4b --- /dev/null +++ b/test/spec/modules/ssmasBidAdapter_spec.js @@ -0,0 +1,244 @@ +import { expect } from 'chai'; +import { spec, SSMAS_CODE, SSMAS_ENDPOINT, SSMAS_REQUEST_METHOD } from 'modules/ssmasBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import * as utils from 'src/utils.js'; + +describe('ssmasBidAdapter', function () { + const bid = { + bidder: SSMAS_CODE, + adUnitCode: 'adunit-code', + sizes: [[300, 250]], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + params: { + placementId: '1' + } + }; + + const bidderRequest = { + 'bidderCode': SSMAS_CODE, + 'auctionId': 'd912faa2-174f-4636-b755-7396a0a964d8', + 'bidderRequestId': '109db5a5f5c6788', + 'bids': [ + bid + ], + 'auctionStart': 1684799653734, + 'timeout': 20000, + 'metrics': {}, + 'ortb2': { + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/noadserver/basic_noadserver.html', + 'ref': 'http://localhost:9999/integrationExamples/noadserver/' + }, + 'device': { + 'w': 1536, + 'h': 711, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0', + 'language': 'es' + } + }, + 'start': 1684799653737 + }; + + describe('Build Requests', () => { + it('Check bid request', function () { + const request = spec.buildRequests([bid], bidderRequest); + expect(request[0].method).to.equal(SSMAS_REQUEST_METHOD); + expect(request[0].url).to.equal(SSMAS_ENDPOINT); + }); + }); + + describe('register adapter functions', () => { + const adapter = newBidder(spec); + it('is registered', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('validate bid request building', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('test bad bid request', function () { + // empty bid + expect(spec.isBidRequestValid({bidId: '', params: {}})).to.be.false; + + // empty bidId + bid.bidId = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; + + // empty placementId + bid.bidId = '1231'; + bid.params.placementId = ''; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('check bid request bidder is Sem Seo & Mas', function() { + const invalidBid = { + ...bid, bidder: 'invalidBidder' + }; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('interpretResponse', function () { + let bidOrtbResponse = { + 'id': 'aa02e2fe-56d9-4713-88f9-d8672ceae8ab', + 'seatbid': [ + { + 'bid': [ + { + 'id': '0001', + 'impid': '3919400af0b73e8', + 'price': 7.01, + 'adid': null, + 'nurl': null, + 'adm': '', + 'adomain': [ + 'ssmas.com' + ], + 'iurl': null, + 'cid': null, + 'crid': '3547894', + 'attr': [], + 'api': 0, + 'protocol': 0, + 'dealid': null, + 'h': 600, + 'w': 300, + 'cat': null, + 'ext': null, + 'builder': { + 'id': '0001', + 'adid': null, + 'impid': '3919400af0b73e8', + 'adomainList': [ + 'ssmas.com' + ], + 'attrList': [] + }, + 'adomainList': [ + 'ssmas.com' + ], + 'attrList': [] + } + ], + 'seat': null, + 'group': 0 + } + ], + 'bidid': '408731cc-c018-4976-bfc6-89f9c61e97a0', + 'cur': 'EUR', + 'nbr': -1 + }; + let bidResponse = { + 'mediaType': 'banner', + 'ad': '', + 'requestId': '37c658fe8ba57b', + 'seatBidId': '0001', + 'cpm': 10, + 'currency': 'EUR', + 'width': 300, + 'height': 250, + 'dealId': null, + 'creative_id': '3547894', + 'creativeId': '3547894', + 'ttl': 30, + 'netRevenue': true, + 'meta': { + 'advertiserDomains': [ + 'ssmas.com' + ] + } + }; + let bidRequest = { + 'imp': [ + { + 'ext': { + 'tid': '937db9c3-c22d-4454-b786-fcad76a349e5', + 'data': { + 'pbadslot': 'test-div' + } + }, + 'id': '3919400af0b73e8', + 'banner': { + 'topframe': 1, + 'format': [ + { + 'w': 300, + 'h': 600 + } + ] + } + }, + { + 'ext': { + 'tid': '0c0d3d1b-0ad0-4786-896d-24c15fc6531d', + 'data': { + 'pbadslot': 'test-div2' + } + }, + 'id': '3919400af0b73e8', + 'banner': { + 'topframe': 1, + 'format': [ + { + 'w': 300, + 'h': 600 + } + ] + } + } + ], + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/noadserver/basic_noadserver.html', + 'ref': 'http://localhost:9999/integrationExamples/noadserver/', + 'id': 1, + 'ext': { + 'placementId': 13144370 + } + }, + 'device': { + 'w': 1536, + 'h': 711, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0', + 'language': 'es' + }, + 'id': '8cc2f4b0-084d-4f40-acfa-5bec2023b1ab', + 'test': 0, + 'tmax': 20000, + 'source': { + 'tid': '8cc2f4b0-084d-4f40-acfa-5bec2023b1ab' + } + } + }); + + describe('test onBidWon function', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('exists and is a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + it('should return nothing', function () { + var response = spec.onBidWon({}); + expect(response).to.be.an('undefined') + expect(utils.triggerPixel.called).to.equal(false); + }); + }); +});