From 328de7c339494b3fc68d427e0622e78d43e5d2a1 Mon Sep 17 00:00:00 2001
From: hasanideepak <80700939+hasanideepak@users.noreply.github.com>
Date: Fri, 7 Jun 2024 00:06:25 +0530
Subject: [PATCH] RelevateHealth Bid Adapter : Initial release (#11640)
* added bid adapter for relevatehealth
* made suggested changes by reviewer
* solved indentation issues as suggested
* solved indentation issues again as suggested
* solved indentation issues mentioned in ci/circleci
* removed trailing spaces mentioned in ci/circleci
* removed trailing spaces and added line at the end mentioned in ci/circleci
---
modules/relevatehealthBidAdapter.js | 160 ++++++++++++
modules/relevatehealthBidAdapter.md | 40 +++
.../modules/relevatehealthBidAdapter_spec.js | 239 ++++++++++++++++++
3 files changed, 439 insertions(+)
create mode 100644 modules/relevatehealthBidAdapter.js
create mode 100644 modules/relevatehealthBidAdapter.md
create mode 100644 test/spec/modules/relevatehealthBidAdapter_spec.js
diff --git a/modules/relevatehealthBidAdapter.js b/modules/relevatehealthBidAdapter.js
new file mode 100644
index 000000000000..1d60e7f67ccb
--- /dev/null
+++ b/modules/relevatehealthBidAdapter.js
@@ -0,0 +1,160 @@
+import {
+ registerBidder
+} from '../src/adapters/bidderFactory.js';
+import {
+ BANNER
+} from '../src/mediaTypes.js';
+import {
+ deepAccess,
+ generateUUID,
+ isArray,
+ logError
+} from '../src/utils.js';
+const BIDDER_CODE = 'relevatehealth';
+const ENDPOINT_URL = 'https://rtb.relevate.health/prebid/relevate';
+
+function buildRequests(bidRequests, bidderRequest) {
+ const requests = [];
+ // Loop through each bid request
+ bidRequests.forEach(bid => {
+ // Construct the bid request object
+ const request = {
+ id: generateUUID(),
+ placementId: bid.params.placement_id,
+ imp: [{
+ id: bid.bidId,
+ banner: getBanner(bid),
+ bidfloor: getFloor(bid)
+ }],
+ site: getSite(bidderRequest),
+ user: buildUser(bid)
+ };
+ // Get uspConsent from bidderRequest
+ if (bidderRequest && bidderRequest.uspConsent) {
+ request.us_privacy = bidderRequest.uspConsent;
+ }
+ // Get GPP Consent from bidderRequest
+ if (bidderRequest?.gppConsent?.gppString) {
+ request.gpp = bidderRequest.gppConsent.gppString;
+ request.gpp_sid = bidderRequest.gppConsent.applicableSections;
+ } else if (bidderRequest?.ortb2?.regs?.gpp) {
+ request.gpp = bidderRequest.ortb2.regs.gpp;
+ request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid;
+ }
+ // Get coppa compliance from bidderRequest
+ if (bidderRequest?.ortb2?.regs?.coppa) {
+ request.coppa = 1;
+ }
+ // Push the constructed bid request to the requests array
+ requests.push(request);
+ });
+ // Return the array of bid requests
+ return {
+ method: 'POST',
+ url: ENDPOINT_URL,
+ data: JSON.stringify(requests),
+ options: {
+ contentType: 'application/json',
+ }
+ };
+}
+// Format the response as per the standards
+function interpretResponse(bidResponse, bidRequest) {
+ let resp = [];
+ if (bidResponse && bidResponse.body) {
+ try {
+ let bids = bidResponse.body.seatbid && bidResponse.body.seatbid[0] ? bidResponse.body.seatbid[0].bid : [];
+ if (bids) {
+ bids.forEach(bidObj => {
+ let newBid = formatResponse(bidObj);
+ newBid.mediaType = BANNER;
+ resp.push(newBid);
+ });
+ }
+ } catch (err) {
+ logError(err);
+ }
+ }
+ return resp;
+}
+// Function to check if Bid is valid
+function isBidRequestValid(bid) {
+ return !!(bid.params.placement_id && bid.params.user_id);
+}
+// Function to get banner details
+function getBanner(bid) {
+ if (deepAccess(bid, 'mediaTypes.banner')) {
+ // Fetch width and height from MediaTypes object, if not provided in bid params
+ if (deepAccess(bid, 'mediaTypes.banner.sizes') && !bid.params.height && !bid.params.width) {
+ let sizes = deepAccess(bid, 'mediaTypes.banner.sizes');
+ if (isArray(sizes) && sizes.length > 0) {
+ return {
+ h: sizes[0][1],
+ w: sizes[0][0]
+ };
+ }
+ } else {
+ return {
+ h: bid.params.height,
+ w: bid.params.width
+ };
+ }
+ }
+}
+// Function to get bid_floor
+function getFloor(bid) {
+ if (bid.params && bid.params.bid_floor) {
+ return bid.params.bid_floor;
+ } else {
+ return 0;
+ }
+}
+// Function to get site details
+function getSite(bidderRequest) {
+ let site = {};
+ if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page) {
+ site.name = bidderRequest.refererInfo.domain;
+ } else {
+ site.name = '';
+ }
+ return site;
+}
+// Function to format response
+function formatResponse(bid) {
+ return {
+ requestId: bid && bid.impid ? bid.impid : undefined,
+ cpm: bid && bid.price ? bid.price : 0.0,
+ width: bid && bid.w ? bid.w : 0,
+ height: bid && bid.h ? bid.h : 0,
+ ad: bid && bid.adm ? bid.adm : '',
+ meta: {
+ advertiserDomains: bid && bid.adomain ? bid.adomain : []
+ },
+ creativeId: bid && bid.crid ? bid.crid : undefined,
+ netRevenue: false,
+ currency: bid && bid.cur ? bid.cur : 'USD',
+ ttl: 300,
+ dealId: bid && bid.dealId ? bid.dealId : undefined
+ };
+}
+// Function to build the user object
+function buildUser(bid) {
+ if (bid && bid.params) {
+ return {
+ id: bid.params.user_id && typeof bid.params.user_id == 'string' ? bid.params.user_id : '',
+ buyeruid: localStorage.getItem('adx_profile_guid') ? localStorage.getItem('adx_profile_guid') : '',
+ keywords: bid.params.keywords && typeof bid.params.keywords == 'string' ? bid.params.keywords : '',
+ customdata: bid.params.customdata && typeof bid.params.customdata == 'string' ? bid.params.customdata : ''
+ };
+ }
+}
+// Export const spec
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: BANNER,
+ isBidRequestValid,
+ buildRequests,
+ interpretResponse
+}
+
+registerBidder(spec);
diff --git a/modules/relevatehealthBidAdapter.md b/modules/relevatehealthBidAdapter.md
new file mode 100644
index 000000000000..432e4fcec02e
--- /dev/null
+++ b/modules/relevatehealthBidAdapter.md
@@ -0,0 +1,40 @@
+# Overview
+
+```
+Module Name: relevatehealth Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: marketingops@relevatehealth.com
+```
+
+# Description
+
+relevatehealth currently supports the BANNER type ads through prebid js
+
+Module that connects to relevatehealth's demand sources.
+
+# Banner Test Request
+```
+ var adUnits = [
+ {
+ code: 'banner-ad',
+ mediaTypes: {
+ banner: {
+ sizes: [[160, 600]],
+ }
+ }
+ bids: [
+ {
+ bidder: 'relevatehealth',
+ params: {
+ placement_id: 110011, // Required parameter
+ user_id: '1111111' // Required parameter
+ width: 160, // Optional parameter
+ height: 600, // Optional parameter
+ domain: '', // Optional parameter
+ bid_floor: 0.5 // Optional parameter
+ }
+ }
+ ]
+ }
+ ];
+```
diff --git a/test/spec/modules/relevatehealthBidAdapter_spec.js b/test/spec/modules/relevatehealthBidAdapter_spec.js
new file mode 100644
index 000000000000..ef974bc3ac1d
--- /dev/null
+++ b/test/spec/modules/relevatehealthBidAdapter_spec.js
@@ -0,0 +1,239 @@
+import {
+ expect
+} from 'chai';
+import {
+ spec
+} from '../../../modules/relevatehealthBidAdapter.js';
+import * as utils from '../../../src/utils.js';
+
+describe('relevatehealth adapter', function() {
+ let request;
+ let bannerResponse, invalidResponse;
+
+ beforeEach(function() {
+ request = [{
+ bidder: 'relevatehealth',
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [160, 600]
+ ]
+ }
+ },
+ params: {
+ placement_id: 110011,
+ user_id: '11211',
+ width: 160,
+ height: 600,
+ domain: '',
+ bid_floor: 0.5
+ }
+ }];
+ bannerResponse = {
+ 'body': {
+ 'id': 'a48b79e7-7104-4213-99f3-55f3234f2e54',
+ 'seatbid': [{
+ 'bid': [{
+ 'id': '3d7dd6dc-7665-4cdc-96a4-5ea192df32b8',
+ 'impid': '285b9c53b2c662',
+ 'price': 20.68,
+ 'adid': '3389',
+ 'adm': "",
+ 'adomain': ['google.com'],
+ 'iurl': 'https://rtb.relevate.health/prebid/relevate',
+ 'cid': '1431/3389',
+ 'crid': '3389',
+ 'w': 160,
+ 'h': 600,
+ 'cat': ['IAB1-15']
+ }],
+ 'seat': '00001',
+ 'group': 0
+ }],
+ 'cur': 'USD',
+ 'bidid': 'BIDDER_1276'
+ }
+ };
+ invalidResponse = {
+ 'body': {
+ 'id': 'a48b79e7-7104-4213-99f3-55f3234f2e54',
+ 'seatbid': [{
+ 'bid': [{
+ 'id': '3d7dd6dc-7665-4cdc-96a4-5ea192df32b8',
+ 'impid': '285b9c53b2c662',
+ 'price': 20.68,
+ 'adid': '3389',
+ 'adm': 'invalid response',
+ 'adomain': ['google.com'],
+ 'iurl': 'https://rtb.relevate.health/prebid/relevate',
+ 'cid': '1431/3389',
+ 'crid': '3389',
+ 'w': 160,
+ 'h': 600,
+ 'cat': ['IAB1-15']
+ }],
+ 'seat': '00001',
+ 'group': 0
+ }],
+ 'cur': 'USD',
+ 'bidid': 'BIDDER_1276'
+ }
+ };
+ });
+
+ describe('validations', function() {
+ it('isBidValid : placement_id and user_id are passed', function() {
+ let bid = {
+ bidder: 'relevatehealth',
+ params: {
+ placement_id: 110011,
+ user_id: '11211'
+ }
+ },
+ isValid = spec.isBidRequestValid(bid);
+ expect(isValid).to.equals(true);
+ });
+ it('isBidValid : placement_id and user_id are not passed', function() {
+ let bid = {
+ bidder: 'relevatehealth',
+ params: {
+ width: 160,
+ height: 600,
+ domain: '',
+ bid_floor: 0.5
+ }
+ },
+ isValid = spec.isBidRequestValid(bid);
+ expect(isValid).to.equals(false);
+ });
+ it('isBidValid : placement_id is passed but user_id is not passed', function() {
+ let bid = {
+ bidder: 'relevatehealth',
+ params: {
+ placement_id: 110011,
+ width: 160,
+ height: 600,
+ domain: '',
+ bid_floor: 0.5
+ }
+ },
+ isValid = spec.isBidRequestValid(bid);
+ expect(isValid).to.equals(false);
+ });
+ it('isBidValid : user_id is passed but placement_id is not passed', function() {
+ let bid = {
+ bidder: 'relevatehealth',
+ params: {
+ width: 160,
+ height: 600,
+ domain: '',
+ bid_floor: 0.5,
+ user_id: '11211'
+ }
+ },
+ isValid = spec.isBidRequestValid(bid);
+ expect(isValid).to.equals(false);
+ });
+ });
+ describe('Validate Request', function() {
+ it('Immutable bid request validate', function() {
+ let _Request = utils.deepClone(request),
+ bidRequest = spec.buildRequests(request);
+ expect(request).to.deep.equal(_Request);
+ });
+ it('Validate bidder connection', function() {
+ let _Request = spec.buildRequests(request);
+ expect(_Request.url).to.equal('https://rtb.relevate.health/prebid/relevate');
+ expect(_Request.method).to.equal('POST');
+ expect(_Request.options.contentType).to.equal('application/json');
+ });
+ it('Validate bid request : Impression', function() {
+ let _Request = spec.buildRequests(request);
+ let data = JSON.parse(_Request.data);
+ expect(data[0].imp[0].id).to.equal(request[0].bidId);
+ expect(data[0].placementId).to.equal(110011);
+ });
+ it('Validate bid request : ad size', function() {
+ let _Request = spec.buildRequests(request);
+ let data = JSON.parse(_Request.data);
+ expect(data[0].imp[0].banner).to.be.a('object');
+ expect(data[0].imp[0].banner.w).to.equal(160);
+ expect(data[0].imp[0].banner.h).to.equal(600);
+ });
+ it('Validate bid request : user object', function() {
+ let _Request = spec.buildRequests(request);
+ let data = JSON.parse(_Request.data);
+ expect(data[0].user).to.be.a('object');
+ expect(data[0].user.id).to.be.a('string');
+ });
+ it('Validate bid request : CCPA Check', function() {
+ let bidRequest = {
+ uspConsent: '1NYN'
+ };
+ let _Request = spec.buildRequests(request, bidRequest);
+ let data = JSON.parse(_Request.data);
+ expect(data[0].us_privacy).to.equal('1NYN');
+ });
+ });
+ describe('Validate response ', function() {
+ it('Validate bid response : valid bid response', function() {
+ let bResponse = spec.interpretResponse(bannerResponse, request);
+ expect(bResponse).to.be.an('array').with.length.above(0);
+ expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid);
+ expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w);
+ expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h);
+ expect(bResponse[0].currency).to.equal('USD');
+ expect(bResponse[0].netRevenue).to.equal(false);
+ expect(bResponse[0].mediaType).to.equal('banner');
+ expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['google.com']);
+ expect(bResponse[0].ttl).to.equal(300);
+ expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid);
+ expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId);
+ });
+ it('Invalid bid response check ', function() {
+ let bRequest = spec.buildRequests(request);
+ let response = spec.interpretResponse(invalidResponse, bRequest);
+ expect(response[0].ad).to.equal('invalid response');
+ });
+ });
+ describe('GPP and coppa', function() {
+ it('Request params check with GPP Consent', function() {
+ let bidderReq = {
+ gppConsent: {
+ gppString: 'gpp-string-test',
+ applicableSections: [5]
+ }
+ };
+ let _Request = spec.buildRequests(request, bidderReq);
+ let data = JSON.parse(_Request.data);
+ expect(data[0].gpp).to.equal('gpp-string-test');
+ expect(data[0].gpp_sid[0]).to.equal(5);
+ });
+ it('Request params check with GPP Consent read from ortb2', function() {
+ let bidderReq = {
+ ortb2: {
+ regs: {
+ gpp: 'gpp-test-string',
+ gpp_sid: [5]
+ }
+ }
+ };
+ let _Request = spec.buildRequests(request, bidderReq);
+ let data = JSON.parse(_Request.data);
+ expect(data[0].gpp).to.equal('gpp-test-string');
+ expect(data[0].gpp_sid[0]).to.equal(5);
+ });
+ it(' Bid request should have coppa flag if its true', () => {
+ let bidderReq = {
+ ortb2: {
+ regs: {
+ coppa: 1
+ }
+ }
+ };
+ let _Request = spec.buildRequests(request, bidderReq);
+ let data = JSON.parse(_Request.data);
+ expect(data[0].coppa).to.equal(1);
+ });
+ });
+});