Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rediads Bid Adapter : initial release #12525

Merged
merged 11 commits into from
Dec 17, 2024
102 changes: 102 additions & 0 deletions modules/rediadsBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { ortbConverter } from '../libraries/ortbConverter/converter.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { deepSetValue, logWarn, logError } from '../src/utils.js';
import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';

const BIDDER_CODE = 'rediads';
const ENDPOINT_URL = 'https://bidding.rediads.com/openrtb2/auction';
const STAGING_ENDPOINT_URL = 'https://stagingbidding.rediads.com/openrtb2/auction';
const DEFAULT_CURRENCY = 'USD';
const LOG_PREFIX = 'Rediads: ';

const MEDIA_TYPES = {
[BANNER]: 1,
[VIDEO]: 2,
[NATIVE]: 4,
};

const converter = ortbConverter({
context: {
netRevenue: true,
ttl: 300,
currency: DEFAULT_CURRENCY,
},
bidResponse(buildBidResponse, bid, context) {
let mediaType = 'banner'; // Default media type

if (bid.vastXml || bid.vastUrl || (bid.adm && bid.adm.startsWith('<VAST'))) {
mediaType = 'video';
} else if (bid.adm && bid.adm.includes('"native"') && bid.adm.includes('"assets"')) {
mediaType = 'native';
}

bid.mediaType = mediaType;
bid.mtype = MEDIA_TYPES[mediaType];

if (bid.mediaType === BANNER) {
bid.ad = bid.adm;
}
return buildBidResponse(bid, context);
},
});

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [NATIVE, BANNER, VIDEO],
isBidRequestValid: function (bid) {
let isValid = false;
const accountID = bid?.params?.account_id
if (accountID && typeof accountID === 'string') {
isValid = true;
} else {
logError(`${LOG_PREFIX} account_id is missing from params or is not of type "string"`)
}
return isValid;
},
buildRequests(bidRequests, bidderRequest) {
const params = bidRequests[0]?.params || {};
const siteContent = bidRequests[0]?.site?.content || null;
let data = {};
let FINAL_ENDPOINT_URL = params.endpoint || ENDPOINT_URL
try {
data = converter.toORTB({ bidRequests, bidderRequest });
const testBidsRequested = location.hash.includes('rediads-test-bids');
const stagingEnvRequested = location.hash.includes('rediads-staging');

if (stagingEnvRequested) {
FINAL_ENDPOINT_URL = STAGING_ENDPOINT_URL;
}
deepSetValue(data, 'ext.rediads.params', params);
deepSetValue(data, 'site.content', siteContent);

if (testBidsRequested) {
deepSetValue(data, 'test', 1);
logWarn(`${LOG_PREFIX} test bids are enabled as rediads-test-bids is present in page URL hash.`)
}
} catch (err) {
logError(`${LOG_PREFIX} encountered an error while building bid requests :: ${err}`)
}

return [
{
method: 'POST',
url: FINAL_ENDPOINT_URL,
data,
},
];
},
interpretResponse(response, request) {
let bids = [];
try {
bids = converter.fromORTB({
response: response.body,
request: request.data,
}).bids
} catch (err) {
logError(`${LOG_PREFIX} encountered an error while processing bid responses :: ${err}`)
}
return bids;
},
};

registerBidder(spec);
38 changes: 38 additions & 0 deletions modules/rediadsBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Overview

```
Module Name: RediAds Bid Adapter
Module Type: Bidder Adapter
Maintainer: [email protected]
```

# Description

Connect to RediAds exchange for bids.

The RediAds adapter requires setup and approval.
Please reach out to [email protected] for more information.

# Test Parameters
```
var adUnits = [
{
code: 'test-div',
mediaTypes: {
banner: {
sizes: [[300, 250]]
}
},
bids: [
{
bidder: "rediads",
params: {
account_id: '123',
site: 'rediads.com',
slot: '321'
}
}
]
}
];
```
133 changes: 133 additions & 0 deletions test/spec/modules/rediadsBidAdapter_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { expect } from 'chai';
import { spec } from '../../../modules/rediadsBidAdapter';

describe('rediads Bid Adapter', function () {
const BIDDER_CODE = 'rediads';
const STAGING_ENDPOINT_URL = 'https://stagingbidding.rediads.com/openrtb2/auction';

const bidRequest = {
bidder: BIDDER_CODE,
params: {
account_id: '12345',
},
mediaTypes: {
banner: {
sizes: [[300, 250], [728, 90]],
},
},
adUnitCode: 'adunit-code',
bidId: '2ab03f1234',
auctionId: '123456789',
};

const bidderRequest = {
bidderCode: BIDDER_CODE,
refererInfo: {
referer: 'http://example.com',
},
};

const resetHash = (originalHash) => {
// Reset the hash, ensuring no trailing #
if (originalHash) {
location.hash = originalHash;
} else {
history.replaceState(null, '', location.pathname + location.search);
}
}

describe('isBidRequestValid', function () {
it('should return true for valid bid requests', function () {
expect(spec.isBidRequestValid(bidRequest)).to.equal(true);
});

it('should return false if account_id is missing', function () {
const invalidBid = { ...bidRequest, params: {} };
expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
});
});

describe('buildRequests', function () {
it('should build a valid request with correct data', function () {
const requests = spec.buildRequests([bidRequest], bidderRequest);
expect(requests).to.be.an('array').that.is.not.empty;

const request = requests[0];
expect(request.method).to.equal('POST');
expect(request.url).that.is.not.empty;
expect(request.data).to.have.property('ext');
expect(request.data.ext.rediads.params).to.deep.equal(bidRequest.params);
});

it('should include test flag if testBidsRequested is true', function () {
const originalHash = location.hash;
location.hash = '#rediads-test-bids';

const requests = spec.buildRequests([bidRequest], bidderRequest);
expect(requests[0].data.test).to.equal(1);

resetHash(originalHash);
});

it('should set staging environtment if stagingEnvRequested is true', function () {
const originalHash = location.hash;
location.hash = '#rediads-staging';

const requests = spec.buildRequests([bidRequest], bidderRequest);
expect(requests[0].url).to.equal(STAGING_ENDPOINT_URL);

resetHash(originalHash);
});
});

describe('interpretResponse', function () {
it('should interpret and return valid bid responses for banner bid', function () {
const serverResponse = {
body: {
seatbid: [
{
bid: [
{
price: 1.23,
impid: '2ab03f1234',
adm: '<div>Ad</div>',
crid: 'creative123',
w: 300,
h: 250,
},
],
},
],
},
};
const requestObj = spec.buildRequests([bidRequest], bidderRequest);
const bids = spec.interpretResponse(serverResponse, requestObj[0]);
expect(bids).to.be.an('array').that.is.not.empty;

const bid = bids[0];
expect(bid).to.include({
requestId: '2ab03f1234',
cpm: 1.23,
creativeId: 'creative123',
width: 300,
height: 250,
ad: '<div>Ad</div>',
});
expect(bid.mediaType).to.equal('banner');
});

it('should return an empty array for invalid responses', function () {
const invalidResponse = { body: {} };
const updatedBidRequest = {...bidRequest, params: undefined}
const requestObj = spec.buildRequests([updatedBidRequest], bidderRequest);
const bids = spec.interpretResponse(invalidResponse, requestObj[0]);
expect(bids).to.be.an('array').that.is.empty;
});
});

describe('Miscellaneous', function () {
it('should support multiple media types', function () {
expect(spec.supportedMediaTypes).to.include.members(['banner', 'native', 'video']);
});
});
});
Loading