diff --git a/libraries/adsmartxUtils/bidderUtils.js b/libraries/adsmartxUtils/bidderUtils.js
new file mode 100644
index 00000000000..597403b72e9
--- /dev/null
+++ b/libraries/adsmartxUtils/bidderUtils.js
@@ -0,0 +1,263 @@
+import { BANNER, VIDEO } from '../../src/mediaTypes.js';
+import { ortbConverter } from '../ortbConverter/converter.js';
+import { deepAccess, logInfo, logWarn } from '../../src/utils.js';
+
+const DEFAULT_CURRENCY = 'USD';
+const DEFAULT_TTL = 60;
+
+/**
+ * Get publisher user ID with priority:
+ * 1. Bid params (sspUserId)
+ * 2. ORTB2 first party data (ortb2.user.id)
+ * @param {Object} bidParams - Bid parameters from first bid
+ * @param {Object} bidderRequest - Bidder request object containing ortb2
+ * @returns {string|null} Publisher user ID if found, null otherwise
+ */
+export function getPublisherUserId(bidParams, bidderRequest) {
+ if (bidParams?.sspUserId) {
+ logInfo('Using SSP user ID from bid params:', bidParams.sspUserId);
+ return bidParams.sspUserId;
+ }
+ const ortb2UserId = deepAccess(bidderRequest, 'ortb2.user.id');
+ if (ortb2UserId) {
+ logInfo('Using SSP user ID from ORTB2 user.id:', ortb2UserId);
+ return ortb2UserId;
+ }
+ logInfo('No SSP user ID found in bid params or ORTB2');
+ return null;
+}
+
+/**
+ * Creates ORTB converter with shared imp/request logic.
+ * @param {Object} config - { defaultCurrency, defaultTtl }
+ * @returns {Object} ortbConverter instance
+ */
+export function createConverter(config = {}) {
+ const currency = config.defaultCurrency ?? DEFAULT_CURRENCY;
+ const ttl = config.defaultTtl ?? DEFAULT_TTL;
+
+ return ortbConverter({
+ context: {
+ netRevenue: true,
+ ttl,
+ currency,
+ },
+ imp(buildImp, bidRequest, context) {
+ logInfo('Building impression object for bidRequest:', bidRequest);
+ const imp = buildImp(bidRequest, context);
+ const { mediaTypes } = bidRequest;
+ if (bidRequest.params?.bidfloor) {
+ logInfo('Setting bid floor for impression:', bidRequest.params.bidfloor);
+ imp.bidfloor = bidRequest.params.bidfloor;
+ }
+ if (mediaTypes[BANNER]) {
+ logInfo('Adding banner media type to impression:', mediaTypes[BANNER]);
+ imp.banner = { ...(imp.banner || {}), format: mediaTypes[BANNER].sizes.map(([w, h]) => ({ w, h })) };
+ } else if (mediaTypes[VIDEO]) {
+ logInfo('Adding video media type to impression:', mediaTypes[VIDEO]);
+ imp.video = { ...(imp.video || {}), ...mediaTypes[VIDEO] };
+ }
+ return imp;
+ },
+ request(buildRequest, imps, bidderRequest, context) {
+ logInfo('Building server request with impressions:', imps);
+ const request = buildRequest(imps, bidderRequest, context);
+ request.cur = [currency];
+ request.tmax = bidderRequest.timeout;
+ request.test = bidderRequest.test || 0;
+
+ if (Array.isArray(bidderRequest.bids)) {
+ const hasTestMode = bidderRequest.bids.some(bid => bid.params?.testMode === 1);
+ if (hasTestMode) {
+ request.ext = request.ext || {};
+ request.ext.test = 1;
+ logInfo('Test mode detected in bid params, setting test flag in request:', request.ext.test);
+ }
+ const sspIdBid = bidderRequest.bids.find(bid => bid.params?.sspId);
+ if (sspIdBid) {
+ request.ext = request.ext || {};
+ request.ext.sspId = sspIdBid.params.sspId;
+ logInfo('sspId detected in bid params, setting sspId in request:', request.ext.sspId);
+ }
+ const siteIdBid = bidderRequest.bids.find(bid => bid.params?.siteId);
+ if (siteIdBid) {
+ request.ext = request.ext || {};
+ request.ext.siteId = siteIdBid.params.siteId;
+ logInfo('siteId detected in bid params, setting siteId in request:', request.ext.siteId);
+ }
+ }
+
+ if (bidderRequest.gdprConsent || bidderRequest.uspConsent) {
+ request.regs = request.regs || {};
+ request.user = request.user || {};
+ }
+ if (bidderRequest.gdprConsent) {
+ logInfo('Adding GDPR consent information to request:', bidderRequest.gdprConsent);
+ request.regs.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0;
+ request.user.consent = bidderRequest.gdprConsent.consentString;
+ }
+ if (bidderRequest.uspConsent) {
+ logInfo('Adding USP consent information to request:', bidderRequest.uspConsent);
+ request.regs.ext = request.regs.ext || {};
+ request.regs.ext.us_privacy = bidderRequest.uspConsent;
+ }
+ return request;
+ },
+ });
+}
+
+/**
+ * Validates the bid request (video mimes/sizes, etc.).
+ * @param {Object} bid - The bid request object.
+ * @returns {boolean} True if the bid request is valid.
+ */
+export function isBidRequestValid(bid) {
+ logInfo('Validating bid request:', bid);
+ const { mediaTypes } = bid;
+
+ if (mediaTypes?.[VIDEO]) {
+ const video = mediaTypes[VIDEO];
+ if (!video.mimes || !Array.isArray(video.mimes) || video.mimes.length === 0) {
+ logWarn('Invalid video bid request: Missing or invalid mimes.');
+ return false;
+ }
+ // w and h are optional; if provided they must be positive
+ if (video.w != null && video.w <= 0) {
+ logWarn('Invalid video bid request: Invalid width.');
+ return false;
+ }
+ if (video.h != null && video.h <= 0) {
+ logWarn('Invalid video bid request: Invalid height.');
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Builds buildRequests function that uses the given converter and endpoint.
+ * @param {Object} config - { converter, endpointUrl }
+ * @returns {function(Array, Object): Object}
+ */
+export function createBuildRequests(config) {
+ const { converter, endpointUrl } = config;
+
+ return function buildRequests(validBidRequests, bidderRequest) {
+ logInfo('Building server request for valid bid requests:', validBidRequests);
+
+ const request = converter.toORTB({ bidRequests: validBidRequests, bidderRequest });
+ logInfo('Converted to ORTB request:', request);
+ return {
+ method: 'POST',
+ url: endpointUrl,
+ data: request,
+ options: { endpointCompression: true },
+ };
+ };
+}
+
+/**
+ * Interprets the server response and extracts bid information.
+ * @param {Object} serverResponse - The response from the server.
+ * @param {Object} request - The original request sent to the server.
+ * @param {Object} config - { defaultCurrency, defaultTtl }
+ * @returns {Array} Array of bid objects.
+ */
+export function interpretResponse(serverResponse, request, config = {}) {
+ const defaultCurrency = config.defaultCurrency ?? DEFAULT_CURRENCY;
+ const defaultTtl = config.defaultTtl ?? DEFAULT_TTL;
+
+ logInfo('Interpreting server response:', serverResponse);
+ const bidResp = serverResponse?.body;
+ if (!bidResp || !Array.isArray(bidResp.seatbid)) {
+ logWarn('Server response is empty, invalid, or does not contain seatbid array.');
+ return [];
+ }
+
+ const responses = [];
+ bidResp.seatbid.forEach(seatbid => {
+ if (!Array.isArray(seatbid.bid) || seatbid.bid.length === 0) return;
+ const bid = seatbid.bid[0];
+ if (!bid.impid || bid.price == null) {
+ logWarn('Skipping bid with missing impid or price, bidId:', bid.id);
+ return;
+ }
+ logInfo('Processing bid response:', bid);
+ const bidResponse = {
+ requestId: bid.impid,
+ cpm: bid.price,
+ currency: bidResp.cur || defaultCurrency,
+ width: bid.w,
+ height: bid.h,
+ ad: bid.adm,
+ creativeId: bid.crid,
+ netRevenue: true,
+ ttl: defaultTtl,
+ meta: { advertiserDomains: bid.adomain || [] },
+ };
+
+ switch (bid.mtype) {
+ case 1:
+ bidResponse.mediaType = BANNER;
+ break;
+ case 2:
+ bidResponse.mediaType = VIDEO;
+ bidResponse.vastXml = bid.adm;
+ break;
+ default:
+ if (bid.mtype != null) {
+ logWarn('Unknown media type: ', bid.mtype, ' for bidId: ', bid.id);
+ } else {
+ logWarn('Bid response does not contain media type for bidId: ', bid.id);
+ }
+ bidResponse.mediaType = BANNER;
+ break;
+ }
+
+ if (bid.dealid) bidResponse.dealId = bid.dealid;
+ logInfo('Interpreted response:', bidResponse, ' for bidId: ', bid.id);
+ responses.push(bidResponse);
+ });
+
+ logInfo('Interpreted bid responses:', responses);
+ return responses;
+}
+
+/**
+ * Creates getUserSyncs function that builds sync URL with privacy params.
+ * @param {string} syncUrl - Base sync URL (e.g. 'https://sync.adsmartx.com/sync')
+ * @returns {function(Object, Array, Object, string, Object): Array}
+ */
+export function createGetUserSyncs(syncUrl) {
+ return function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) {
+ logInfo('getUserSyncs called with options:', syncOptions);
+ if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) {
+ logWarn('User sync disabled: neither iframe nor pixel is enabled');
+ return [];
+ }
+
+ const params = [];
+ if (gdprConsent) {
+ params.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0));
+ params.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''));
+ }
+ if (uspConsent) {
+ params.push('us_privacy=' + encodeURIComponent(uspConsent));
+ }
+ if (gppConsent?.gppString && gppConsent?.applicableSections?.length) {
+ params.push('gpp=' + encodeURIComponent(gppConsent.gppString));
+ params.push('gpp_sid=' + encodeURIComponent(gppConsent.applicableSections.join(',')));
+ }
+
+ params.push('ssp_id=630141');
+ params.push('iframe_enabled=' + (syncOptions.iframeEnabled ? 'true' : 'false'));
+
+ const queryString = params.length ? '?' + params.join('&') : '';
+ const syncs = [{
+ type: syncOptions.iframeEnabled ? 'iframe' : 'image',
+ url: syncUrl + queryString,
+ }];
+ logInfo('Returning user syncs, type:', syncs[0]?.type);
+ return syncs;
+ };
+}
diff --git a/modules/adsmartxBidAdapter.js b/modules/adsmartxBidAdapter.js
new file mode 100644
index 00000000000..bba9482052a
--- /dev/null
+++ b/modules/adsmartxBidAdapter.js
@@ -0,0 +1,43 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
+import {
+ createConverter,
+ isBidRequestValid as validateBidRequest,
+ createBuildRequests,
+ interpretResponse as interpretResponseUtil,
+ createGetUserSyncs,
+} from '../libraries/adsmartxUtils/bidderUtils.js';
+
+const BIDDER_CODE = 'adsmartx';
+const ENDPOINT_URL = 'https://ads.adsmartx.com/ads/rtb/prebid/js';
+const SYNC_URL = 'https://sync.adsmartx.com/sync';
+const DEFAULT_CURRENCY = 'USD';
+const DEFAULT_TTL = 60;
+
+const converter = createConverter({ defaultCurrency: DEFAULT_CURRENCY, defaultTtl: DEFAULT_TTL });
+
+const isBidRequestValid = validateBidRequest;
+const buildRequests = createBuildRequests(
+ { converter, endpointUrl: ENDPOINT_URL }
+);
+const getUserSyncs = createGetUserSyncs(SYNC_URL);
+
+const interpretResponse = (serverResponse, request) => {
+ return interpretResponseUtil(serverResponse, request, {
+ defaultCurrency: DEFAULT_CURRENCY,
+ defaultTtl: DEFAULT_TTL,
+ });
+};
+
+export const spec = {
+ code: BIDDER_CODE,
+ // TODO: set gvlid once confirmed with AI Digital / AdSmartX team
+ gvlid: undefined,
+ supportedMediaTypes: [BANNER, VIDEO],
+ isBidRequestValid,
+ buildRequests,
+ interpretResponse,
+ getUserSyncs,
+};
+
+registerBidder(spec);
diff --git a/modules/adsmartxBidAdapter.md b/modules/adsmartxBidAdapter.md
new file mode 100644
index 00000000000..93b3fa6993a
--- /dev/null
+++ b/modules/adsmartxBidAdapter.md
@@ -0,0 +1,72 @@
+# Overview
+
+Module Name : AdSmartX Bidder Adapter
+Module Type : Bid Adapter
+Maintainer : prebid@aidigital.com
+
+# Description
+Connects to AdSmartX Exchange for bids
+AdSmartX supports Display & Video(Instream) currently.
+
+This adapter is maintained by Smart Exchange, the legal entity behind this implementation. Our official domain is [AI Digital](https://www.aidigital.com/).
+# Sample Ad Unit : Banner
+```
+ var adUnits = [
+ {
+ code: 'test-banner-div',
+ mediaTypes: {
+ banner: {
+ sizes:[
+ [320,50]
+ ]
+ }
+ },
+ bids:[
+ {
+ bidder: 'adsmartx',
+ params: {
+ bidfloor: 0.001,
+ testMode: 1,
+ sspId: 123456,
+ siteId: 987654,
+ sspUserId: 'u1234'
+ }
+ }
+ ]
+ }
+ ]
+```
+
+# Sample Ad Unit : Video
+```
+ var videoAdUnit = [
+ {
+ code: 'adsmartx',
+ mediaTypes: {
+ video: {
+ playerSize: [640, 480], // required
+ context: 'instream',
+ mimes: ['video/mp4','video/webm'],
+ minduration: 5,
+ maxduration: 30,
+ startdelay: 30,
+ maxseq: 2,
+ poddur: 30,
+ protocols: [1,3,4],
+ }
+ },
+ bids:[
+ {
+ bidder: 'adsmartx',
+ params: {
+ bidfloor: 0.001,
+ testMode: 1,
+ sspId: 123456,
+ siteId: 987654,
+ sspUserId: 'u1234'
+ }
+ }
+ ]
+ }
+ ]
+```
diff --git a/modules/risemediatechBidAdapter.js b/modules/risemediatechBidAdapter.js
index e3709722ae0..2ac104e3b7c 100644
--- a/modules/risemediatechBidAdapter.js
+++ b/modules/risemediatechBidAdapter.js
@@ -1,205 +1,38 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
-import { ortbConverter } from '../libraries/ortbConverter/converter.js';
-import { logInfo, logWarn } from '../src/utils.js';
+import {
+ createConverter,
+ createBuildRequests,
+ interpretResponse as interpretResponseUtil,
+} from '../libraries/adsmartxUtils/bidderUtils.js';
+import { logWarn } from '../src/utils.js';
const BIDDER_CODE = 'risemediatech';
const ENDPOINT_URL = 'https://dev-ads.risemediatech.com/ads/rtb/prebid/js';
const DEFAULT_CURRENCY = 'USD';
const DEFAULT_TTL = 60;
-const converter = ortbConverter({
- context: {
- netRevenue: true,
- ttl: DEFAULT_TTL,
- currency: DEFAULT_CURRENCY,
- },
- imp(buildImp, bidRequest, context) {
- logInfo('Building impression object for bidRequest:', bidRequest);
- const imp = buildImp(bidRequest, context);
- const { mediaTypes } = bidRequest;
- if (bidRequest.params) {
- if (bidRequest.params.bidfloor) {
- logInfo('Setting bid floor for impression:', bidRequest.params.bidfloor);
- imp.bidfloor = bidRequest.params.bidfloor;
- }
- }
- if (mediaTypes[BANNER]) {
- logInfo('Adding banner media type to impression:', mediaTypes[BANNER]);
- imp.banner = { format: mediaTypes[BANNER].sizes.map(([w, h]) => ({ w, h })) };
- } else if (mediaTypes[VIDEO]) {
- logInfo('Adding video media type to impression:', mediaTypes[VIDEO]);
- imp.video = {
- ...mediaTypes[VIDEO],
- // all video parameters are mapped.
- };
- }
+const converter = createConverter({ defaultCurrency: DEFAULT_CURRENCY, defaultTtl: DEFAULT_TTL });
- return imp;
- },
- request(buildRequest, imps, bidderRequest, context) {
- logInfo('Building server request with impressions:', imps);
- const request = buildRequest(imps, bidderRequest, context);
- request.cur = [DEFAULT_CURRENCY];
- request.tmax = bidderRequest.timeout;
- request.test = bidderRequest.test || 0;
+export function disableAdapter() {
+ logWarn('Risemediatech Bid Adapter has been deprecated. Hence disabling this adapter by rejecting bid requests by default.');
+ return false;
+}
- if (Array.isArray(bidderRequest.bids)) {
- const hasTestMode = bidderRequest.bids.some(bid => bid.params && bid.params.testMode === 1);
- if (hasTestMode) {
- request.ext = request.ext || {};
- request.ext.test = 1;
- logInfo('Test mode detected in bid params, setting test flag in request:', request.ext.test);
- }
- }
+const isBidRequestValid = disableAdapter();
+const buildRequests = createBuildRequests(
+ { converter, endpointUrl: ENDPOINT_URL }
+);
- if (bidderRequest.gdprConsent) {
- logInfo('Adding GDPR consent information to request:', bidderRequest.gdprConsent);
- request.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } };
- request.user = { ext: { consent: bidderRequest.gdprConsent.consentString } };
- }
-
- if (bidderRequest.uspConsent) {
- logInfo('Adding USP consent information to request:', bidderRequest.uspConsent);
- request.regs = request.regs || {};
- request.regs.ext = request.regs.ext || {};
- request.regs.ext.us_privacy = bidderRequest.uspConsent;
- }
-
- return request;
- },
-});
-
-/**
- * Validates the bid request.
- * @param {Object} bid - The bid request object.
- * @returns {boolean} True if the bid request is valid.
- */
-const isBidRequestValid = (bid) => {
- logInfo('Validating bid request:', bid);
-
- const { mediaTypes } = bid;
-
- // Validate video-specific fields if mediaTypes includes VIDEO
- if (mediaTypes?.[VIDEO]) {
- const video = mediaTypes[VIDEO];
-
- if (!video.mimes || !Array.isArray(video.mimes) || video.mimes.length === 0) {
- logWarn('Invalid video bid request: Missing or invalid mimes.');
- return false;
- }
- if (video.w != null && video.w <= 0) {
- logWarn('Invalid video bid request: Invalid width.');
- return false;
- }
- if (video.h != null && video.h <= 0) {
- logWarn('Invalid video bid request: Invalid height.');
- return false;
- }
- }
-
- return true;
-};
-
-/**
- * Builds the server request for the bid.
- * @param {Array} validBidRequests - Array of valid bid requests.
- * @param {Object} bidderRequest - Additional information about the bid request.
- * @returns {Object} Server request object.
- */
-const buildRequests = (validBidRequests, bidderRequest) => {
- logInfo('Building server request for valid bid requests:', validBidRequests);
- const request = converter.toORTB({ bidRequests: validBidRequests, bidderRequest });
- logInfo('Converted to ORTB request:', request);
- return {
- method: 'POST',
- url: ENDPOINT_URL,
- data: request,
- options: {
- endpointCompression: true
- },
- };
-};
-
-/**
- * Interprets the server response and extracts bid information.
- * @param {Object} serverResponse - The response from the server.
- * @param {Object} request - The original request sent to the server.
- * @returns {Array} Array of bid objects.
- */
const interpretResponse = (serverResponse, request) => {
- logInfo('Interpreting server response:', serverResponse);
-
- const bidResp = serverResponse && serverResponse.body;
- if (!bidResp || !Array.isArray(bidResp.seatbid)) {
- logWarn('Server response is empty, invalid, or does not contain seatbid array.');
- return [];
- }
-
- const responses = [];
- bidResp.seatbid.forEach(seatbid => {
- if (Array.isArray(seatbid.bid) && seatbid.bid.length > 0) {
- const bid = seatbid.bid[0];
- logInfo('Processing bid response:', bid);
- const bidResponse = {
- requestId: bid.impid,
- cpm: bid.price,
- currency: bidResp.cur || DEFAULT_CURRENCY,
- width: bid.w,
- height: bid.h,
- ad: bid.adm,
- creativeId: bid.crid,
- netRevenue: true,
- ttl: DEFAULT_TTL,
- meta: {
- advertiserDomains: bid.adomain || [],
- }
- }
-
- // Set media type based on bid.mtype
- if (bid.mtype == null) {
- logWarn('Bid response does not contain media type for bidId: ', bid.id);
- bidResponse.mediaType = BANNER;
- }
- switch (bid.mtype) {
- case 1:
- bidResponse.mediaType = BANNER;
- break;
- case 2:
- bidResponse.mediaType = VIDEO;
- bidResponse.vastXml = bid.adm;
- break;
- default:
- logWarn('Unknown media type: ', bid.mtype, ' for bidId: ', bid.id);
- break;
- }
-
- // set dealId if present
- if (bid.dealid) {
- bidResponse.dealId = bid.dealid;
- }
- logInfo('Interpreted response:', bidResponse, ' for bidId: ', bid.id);
- responses.push(bidResponse);
- }
+ return interpretResponseUtil(serverResponse, request, {
+ defaultCurrency: DEFAULT_CURRENCY,
+ defaultTtl: DEFAULT_TTL,
});
-
- logInfo('Interpreted bid responses:', responses);
- return responses;
};
-/**
- * Handles user syncs for GDPR, CCPA, and GPP compliance.
- * @param {Object} syncOptions - Options for user sync.
- * @param {Array} serverResponses - Server responses.
- * @param {Object} gdprConsent - GDPR consent information.
- * @param {Object} uspConsent - CCPA consent information.
- * @param {Object} gppConsent - GPP consent information.
- * @returns {Array} Array of user sync objects.
- */
const getUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => {
- // return [{ type, url }];
- logInfo('User syncs are not implemented in this adapter yet.');
- return null;
+ return [];
};
export const spec = {
diff --git a/test/spec/libraries/adsmartxUtils/bidderUtils_spec.js b/test/spec/libraries/adsmartxUtils/bidderUtils_spec.js
new file mode 100644
index 00000000000..e2a115bc550
--- /dev/null
+++ b/test/spec/libraries/adsmartxUtils/bidderUtils_spec.js
@@ -0,0 +1,203 @@
+import { expect } from 'chai';
+import {
+ getPublisherUserId,
+ createConverter,
+ isBidRequestValid,
+ createBuildRequests,
+ interpretResponse,
+ createGetUserSyncs,
+} from '../../../../libraries/adsmartxUtils/bidderUtils.js';
+import { BANNER, VIDEO } from '../../../../src/mediaTypes.js';
+
+describe('AdSmartX bidderUtils', () => {
+ const defaultConfig = { defaultCurrency: 'USD', defaultTtl: 60 };
+
+ describe('getPublisherUserId', () => {
+ it('returns sspUserId from bid params when present', () => {
+ const bidParams = { sspUserId: 'user-from-params' };
+ const bidderRequest = {};
+ expect(getPublisherUserId(bidParams, bidderRequest)).to.equal('user-from-params');
+ });
+
+ it('returns ortb2.user.id when sspUserId not in params', () => {
+ const bidParams = {};
+ const bidderRequest = { ortb2: { user: { id: 'ortb2-user-id' } } };
+ expect(getPublisherUserId(bidParams, bidderRequest)).to.equal('ortb2-user-id');
+ });
+
+ it('returns null when neither source has user id', () => {
+ expect(getPublisherUserId({}, {})).to.equal(null);
+ expect(getPublisherUserId({}, { ortb2: {} })).to.equal(null);
+ });
+ });
+
+ describe('createConverter', () => {
+ it('returns a converter that produces valid ORTB structure', () => {
+ const converter = createConverter(defaultConfig);
+ expect(converter).to.be.an('object');
+ expect(converter.toORTB).to.be.a('function');
+ });
+ });
+
+ describe('isBidRequestValid', () => {
+ it('returns true for valid banner bid', () => {
+ const bid = { mediaTypes: { [BANNER]: { sizes: [[300, 250]] } } };
+ expect(isBidRequestValid(bid)).to.equal(true);
+ });
+
+ it('returns true for valid video bid with mimes and sizes', () => {
+ const bid = {
+ mediaTypes: {
+ [VIDEO]: { mimes: ['video/mp4'], w: 640, h: 480 },
+ },
+ };
+ expect(isBidRequestValid(bid)).to.equal(true);
+ });
+
+ it('returns false for video bid with empty mimes', () => {
+ const bid = {
+ mediaTypes: {
+ [VIDEO]: { mimes: [], w: 640, h: 480 },
+ },
+ };
+ expect(isBidRequestValid(bid)).to.equal(false);
+ });
+
+ it('returns false for video bid with invalid width', () => {
+ const bid = {
+ mediaTypes: {
+ [VIDEO]: { mimes: ['video/mp4'], w: 0, h: 480 },
+ },
+ };
+ expect(isBidRequestValid(bid)).to.equal(false);
+ });
+ });
+
+ describe('createBuildRequests and interpretResponse', () => {
+ const endpointUrl = 'https://test.endpoint.com/ads';
+ const converter = createConverter(defaultConfig);
+ const buildRequests = createBuildRequests({ converter, endpointUrl });
+
+ it('buildRequests returns POST request with endpoint and compressed option', () => {
+ const validBidRequests = [
+ {
+ bidId: 'bid1',
+ mediaTypes: { [BANNER]: { sizes: [[300, 250]] } },
+ params: {},
+ },
+ ];
+ const bidderRequest = { timeout: 3000 };
+ const result = buildRequests(validBidRequests, bidderRequest);
+ expect(result.method).to.equal('POST');
+ expect(result.url).to.equal(endpointUrl);
+ expect(result.options).to.deep.include({ endpointCompression: true });
+ expect(result.data).to.be.an('object');
+ });
+ });
+
+ describe('interpretResponse', () => {
+ it('returns empty array when body or seatbid missing', () => {
+ expect(interpretResponse(undefined, {})).to.deep.equal([]);
+ expect(interpretResponse({ body: {} }, {})).to.deep.equal([]);
+ expect(interpretResponse({ body: { seatbid: null } }, {})).to.deep.equal([]);
+ });
+
+ it('maps seatbid to bids with mediaType from mtype', () => {
+ const serverResponse = {
+ body: {
+ cur: 'USD',
+ seatbid: [
+ {
+ bid: [
+ {
+ impid: 'imp1',
+ price: 2.5,
+ w: 300,
+ h: 250,
+ adm: '
Ad
',
+ crid: 'c1',
+ adomain: ['example.com'],
+ mtype: 1,
+ },
+ ],
+ },
+ ],
+ },
+ };
+ const bids = interpretResponse(serverResponse, {}, defaultConfig);
+ expect(bids).to.have.lengthOf(1);
+ expect(bids[0].requestId).to.equal('imp1');
+ expect(bids[0].mediaType).to.equal(BANNER);
+ expect(bids[0].currency).to.equal('USD');
+ });
+
+ it('defaults unknown or null mtype to BANNER', () => {
+ const serverResponse = {
+ body: {
+ seatbid: [
+ {
+ bid: [
+ {
+ impid: 'imp1',
+ price: 1,
+ w: 300,
+ h: 250,
+ adm: 'Ad
',
+ crid: 'c1',
+ mtype: 999,
+ },
+ ],
+ },
+ ],
+ },
+ };
+ const bids = interpretResponse(serverResponse, {}, defaultConfig);
+ expect(bids[0].mediaType).to.equal(BANNER);
+ });
+ });
+
+ describe('createGetUserSyncs', () => {
+ const syncUrl = 'https://ads.example.com/sync';
+
+ it('returns empty array when iframe and pixel disabled', () => {
+ const getUserSyncs = createGetUserSyncs(syncUrl);
+ const result = getUserSyncs(
+ { iframeEnabled: false, pixelEnabled: false },
+ [],
+ undefined,
+ undefined,
+ undefined
+ );
+ expect(result).to.deep.equal([]);
+ });
+
+ it('returns sync with URL containing gdpr and iframe_enabled', () => {
+ const getUserSyncs = createGetUserSyncs(syncUrl);
+ const result = getUserSyncs(
+ { iframeEnabled: true, pixelEnabled: false },
+ [],
+ { gdprApplies: true, consentString: 'consent1' },
+ undefined,
+ undefined
+ );
+ expect(result).to.have.lengthOf(1);
+ expect(result[0].type).to.equal('iframe');
+ expect(result[0].url).to.include(syncUrl);
+ expect(result[0].url).to.include('gdpr=1');
+ expect(result[0].url).to.include('iframe_enabled=true');
+ });
+
+ it('always includes hardcoded ssp_id=630141 and no ssp_site_id', () => {
+ const getUserSyncs = createGetUserSyncs(syncUrl);
+ const result = getUserSyncs(
+ { iframeEnabled: true, pixelEnabled: false },
+ [],
+ undefined,
+ undefined,
+ undefined
+ );
+ expect(result[0].url).to.include('ssp_id=630141');
+ expect(result[0].url).to.not.include('ssp_site_id');
+ });
+ });
+});
diff --git a/test/spec/modules/adsmartxBidAdapter_spec.js b/test/spec/modules/adsmartxBidAdapter_spec.js
new file mode 100644
index 00000000000..b50599d57c6
--- /dev/null
+++ b/test/spec/modules/adsmartxBidAdapter_spec.js
@@ -0,0 +1,1089 @@
+import { expect } from 'chai';
+import { spec } from 'modules/adsmartxBidAdapter.js';
+
+describe('AdSmartX adapter', () => {
+ const validBidRequest = {
+ bidder: 'adsmartx',
+ params: {
+ publisherId: '12345',
+ adSlot: '/1234567/adunit',
+ },
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250], [728, 90]],
+ },
+ },
+ bidId: '1abc',
+ auctionId: '2def',
+ };
+
+ const bidderRequest = {
+ refererInfo: {
+ page: 'https://example.com',
+ },
+ timeout: 3000,
+ gdprConsent: {
+ gdprApplies: true,
+ consentString: 'consent123',
+ },
+ uspConsent: '1YNN',
+ };
+
+ const serverResponse = {
+ body: {
+ id: '2def',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1abc',
+ impid: '1abc',
+ price: 1.5,
+ adm: 'Ad
',
+ w: 300,
+ h: 250,
+ crid: 'creative123',
+ adomain: ['example.com'],
+ },
+ ],
+ },
+ ],
+ },
+ };
+
+ describe('isBidRequestValid', () => {
+ it('should return true for valid bid request', () => {
+ expect(spec.isBidRequestValid(validBidRequest)).to.equal(true);
+ });
+
+ it('should return false for invalid video bid request', () => {
+ const invalidVideoRequest = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: [],
+ },
+ },
+ };
+ expect(spec.isBidRequestValid(invalidVideoRequest)).to.equal(false);
+ });
+
+ it('should return false for video bid request with missing mimes', () => {
+ const invalidVideoRequest = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ w: 640,
+ h: 480
+ // mimes missing
+ }
+ }
+ };
+ expect(spec.isBidRequestValid(invalidVideoRequest)).to.equal(false);
+ });
+
+ it('should return false for video request with invalid mimes (not an array)', () => {
+ const invalidBid = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: 'video/mp4', // Not an array
+ w: 640,
+ h: 480
+ }
+ }
+ };
+ expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
+ });
+
+ it('should return false for video request with empty mimes array', () => {
+ const invalidBid = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: [],
+ w: 640,
+ h: 480
+ }
+ }
+ };
+ expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
+ });
+
+ it('should return false for video request with width <= 0', () => {
+ const invalidBid = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4'],
+ w: 0,
+ h: 480
+ }
+ }
+ };
+ expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
+ });
+
+ it('should return false for video request with height <= 0', () => {
+ const invalidBid = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4'],
+ w: 640,
+ h: -10
+ }
+ }
+ };
+ expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
+ });
+
+ it('should return false for video bid request with invalid width', () => {
+ const invalidVideoRequest = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4'],
+ w: 0,
+ h: 480
+ }
+ }
+ };
+ expect(spec.isBidRequestValid(invalidVideoRequest)).to.equal(false);
+ });
+
+ it('should return false for video bid request with invalid height', () => {
+ const invalidVideoRequest = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4'],
+ w: 640,
+ h: 0
+ }
+ }
+ };
+ expect(spec.isBidRequestValid(invalidVideoRequest)).to.equal(false);
+ });
+ });
+
+ describe('buildRequests', () => {
+ it('should build a valid server request', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ expect(request).to.be.an('object');
+ expect(request.method).to.equal('POST');
+ expect(request.url).to.equal('https://ads.adsmartx.com/ads/rtb/prebid/js');
+ expect(request.data).to.be.an('object');
+ });
+
+ it('should include GDPR and USP consent in the request', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const { regs, user } = request.data;
+ expect(regs).to.have.property('gdpr', 1);
+ expect(user).to.have.property('consent', 'consent123');
+ expect(regs.ext).to.have.property('us_privacy', '1YNN');
+ });
+
+ it('should include banner impressions in the request', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const { imp } = request.data;
+ expect(imp).to.be.an('array');
+ expect(imp[0]).to.have.property('banner');
+ expect(imp[0].banner).to.have.property('format').with.lengthOf(2);
+ });
+
+ it('should set request.test to 0 if bidderRequest.test is not provided', () => {
+ const request = spec.buildRequests([validBidRequest], { ...bidderRequest });
+ expect(request.data.test).to.equal(0);
+ });
+
+ it('should set request.test to bidderRequest.test if provided', () => {
+ const testBidderRequest = { ...bidderRequest, test: 1 };
+ const request = spec.buildRequests([validBidRequest], testBidderRequest);
+ expect(request.data.test).to.equal(1);
+ });
+
+ it('should build a video impression if only video mediaType is present', () => {
+ const videoBidRequest = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4'],
+ w: 640,
+ h: 480
+ }
+ },
+ params: {
+ ...validBidRequest.params,
+ mimes: ['video/mp4'],
+ minduration: 5,
+ maxduration: 30,
+ startdelay: 0,
+ maxseq: 1,
+ poddur: 60,
+ protocols: [2, 3]
+ }
+ };
+ const request = spec.buildRequests([videoBidRequest], bidderRequest);
+ const { imp } = request.data;
+ expect(imp[0]).to.have.property('video');
+ expect(imp[0]).to.not.have.property('banner');
+ expect(imp[0].video).to.include({ w: 640, h: 480 });
+ expect(imp[0].video.mimes).to.include('video/mp4');
+ });
+
+ it('should set gdpr to 0 if gdprApplies is false', () => {
+ const noGdprBidderRequest = {
+ ...bidderRequest,
+ gdprConsent: {
+ gdprApplies: false,
+ consentString: 'consent123'
+ }
+ };
+ const request = spec.buildRequests([validBidRequest], noGdprBidderRequest);
+ expect(request.data.regs).to.have.property('gdpr', 0);
+ expect(request.data.user).to.have.property('consent', 'consent123');
+ });
+
+ it('should set regs and regs.ext to {} if not already set when only USP consent is present', () => {
+ const onlyUspBidderRequest = {
+ ...bidderRequest,
+ gdprConsent: undefined,
+ uspConsent: '1YNN'
+ };
+ const request = spec.buildRequests([validBidRequest], onlyUspBidderRequest);
+ expect(request.data.regs).to.be.an('object');
+ expect(request.data.regs.ext).to.be.an('object');
+ expect(request.data.regs.ext).to.have.property('us_privacy', '1YNN');
+ });
+ });
+
+ describe('interpretResponse', () => {
+ it('should interpret the server response correctly', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(serverResponse, request);
+ expect(bids).to.be.an('array').with.lengthOf(1);
+ const bid = bids[0];
+ expect(bid).to.have.property('requestId', '1abc');
+ expect(bid).to.have.property('cpm', 1.5);
+ expect(bid).to.have.property('width', 300);
+ expect(bid).to.have.property('height', 250);
+ expect(bid).to.have.property('creativeId', 'creative123');
+ expect(bid).to.have.property('currency', 'USD');
+ expect(bid).to.have.property('netRevenue', true);
+ expect(bid).to.have.property('ttl', 60);
+ });
+
+ it('should return an empty array if no bids are present', () => {
+ const emptyResponse = { body: { seatbid: [] } };
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(emptyResponse, request);
+ expect(bids).to.be.an('array').with.lengthOf(0);
+ });
+
+ it('should interpret multiple seatbids as multiple bids', () => {
+ const multiSeatbidResponse = {
+ body: {
+ id: '2def',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1abc',
+ impid: '1abc',
+ price: 1.5,
+ adm: 'Ad1
',
+ w: 300,
+ h: 250,
+ crid: 'creative123',
+ adomain: ['example.com'],
+ mtype: 1
+ },
+ ],
+ },
+ {
+ bid: [
+ {
+ id: '2bcd',
+ impid: '2bcd',
+ price: 2.0,
+ adm: 'Ad2
',
+ w: 728,
+ h: 90,
+ crid: 'creative456',
+ adomain: ['another.com'],
+ mtype: 2
+ },
+ ],
+ },
+ ],
+ },
+ };
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(multiSeatbidResponse, request);
+ expect(bids).to.be.an('array').with.lengthOf(2);
+ expect(bids[0]).to.have.property('requestId', '1abc');
+ expect(bids[1]).to.have.property('requestId', '2bcd');
+ expect(bids[0].mediaType).to.equal('banner');
+ expect(bids[1].mediaType).to.equal('video');
+ expect(bids[0]).to.have.property('cpm', 1.5);
+ expect(bids[1]).to.have.property('cpm', 2.0);
+ });
+
+ it('should set mediaType to banner if mtype is missing', () => {
+ const responseNoMtype = {
+ body: {
+ id: '2def',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1abc',
+ impid: '1abc',
+ price: 1.5,
+ adm: 'Ad
',
+ w: 300,
+ h: 250,
+ crid: 'creative123',
+ adomain: ['example.com']
+ // mtype missing
+ }
+ ]
+ }
+ ]
+ }
+ };
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(responseNoMtype, request);
+ expect(bids[0].mediaType).to.equal('banner');
+ });
+
+ it('should set meta.advertiserDomains to an empty array if adomain is missing', () => {
+ const responseWithoutAdomain = {
+ body: {
+ id: '2def',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1abc',
+ impid: '1abc',
+ price: 1.5,
+ adm: 'Ad
',
+ w: 300,
+ h: 250,
+ crid: 'creative123'
+ // adomain is missing
+ }
+ ]
+ }
+ ]
+ }
+ };
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(responseWithoutAdomain, request);
+ expect(bids[0].meta.advertiserDomains).to.be.an('array').that.is.empty;
+ });
+
+ it('should return an empty array and warn if server response is undefined', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(undefined, request);
+ expect(bids).to.be.an('array').that.is.empty;
+ });
+
+ it('should return an empty array and warn if server response body is missing', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse({}, request);
+ expect(bids).to.be.an('array').that.is.empty;
+ });
+
+ it('should return bids from converter if present', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(serverResponse, request);
+ expect(bids).to.be.an('array').with.lengthOf(1);
+ });
+
+ it('should log a warning and default mediaType to banner for unknown mtype', () => {
+ const responseWithUnknownMtype = {
+ body: {
+ id: '2def',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1abc',
+ impid: '1abc',
+ price: 1.5,
+ adm: 'Ad
',
+ w: 300,
+ h: 250,
+ crid: 'creative123',
+ adomain: ['example.com'],
+ mtype: 999, // Unknown mtype
+ },
+ ],
+ },
+ ],
+ },
+ };
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(responseWithUnknownMtype, request);
+ expect(bids).to.be.an('array').with.lengthOf(1);
+ expect(bids[0].mediaType).to.equal('banner');
+ });
+
+ it('should include dealId if present in the bid response', () => {
+ const responseWithDealId = {
+ body: {
+ id: '2def',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1abc',
+ impid: '1abc',
+ price: 1.5,
+ adm: 'Ad
',
+ w: 300,
+ h: 250,
+ crid: 'creative123',
+ adomain: ['example.com'],
+ dealid: 'deal123',
+ },
+ ],
+ },
+ ],
+ },
+ };
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(responseWithDealId, request);
+ expect(bids).to.be.an('array').with.lengthOf(1);
+ expect(bids[0]).to.have.property('dealId', 'deal123');
+ });
+
+ it('should handle bids with missing price gracefully', () => {
+ const responseWithoutPrice = {
+ body: {
+ id: '2def',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1abc',
+ impid: '1abc',
+ adm: 'Ad
',
+ w: 300,
+ h: 250,
+ crid: 'creative123',
+ adomain: ['example.com'],
+ },
+ ],
+ },
+ ],
+ },
+ };
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(responseWithoutPrice, request);
+ expect(bids).to.be.an('array').that.is.empty;
+ });
+ });
+
+ describe('getUserSyncs', () => {
+ it('should return empty array if neither iframe nor pixel is enabled', () => {
+ const syncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: false }, [], bidderRequest.gdprConsent, bidderRequest.uspConsent);
+ expect(syncs).to.be.an('array').that.is.empty;
+ });
+
+ it('should return iframe sync when iframeEnabled is true', () => {
+ const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: false }, []);
+ expect(syncs).to.be.an('array').with.lengthOf(1);
+ expect(syncs[0]).to.have.property('type', 'iframe');
+ expect(syncs[0]).to.have.property('url');
+ expect(syncs[0].url).to.include('https://sync.adsmartx.com/sync');
+ });
+
+ it('should return image sync when only pixelEnabled is true', () => {
+ const syncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, []);
+ expect(syncs).to.be.an('array').with.lengthOf(1);
+ expect(syncs[0]).to.have.property('type', 'image');
+ expect(syncs[0]).to.have.property('url');
+ });
+
+ it('should include GDPR consent parameters in sync URL', () => {
+ const syncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ [],
+ { gdprApplies: true, consentString: 'consent123' }
+ );
+ expect(syncs[0].url).to.include('gdpr=1');
+ expect(syncs[0].url).to.include('gdpr_consent=consent123');
+ });
+
+ it('should include gdpr=0 when gdprApplies is false', () => {
+ const syncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ [],
+ { gdprApplies: false, consentString: 'consent123' }
+ );
+ expect(syncs[0].url).to.include('gdpr=0');
+ expect(syncs[0].url).to.include('gdpr_consent=consent123');
+ });
+
+ it('should include USP consent parameter in sync URL', () => {
+ const syncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ [],
+ undefined,
+ '1YNN'
+ );
+ expect(syncs[0].url).to.include('us_privacy=1YNN');
+ });
+
+ it('should include GPP consent parameters in sync URL', () => {
+ const gppConsent = {
+ gppString: 'DBABLA~1YNN',
+ applicableSections: [7, 8]
+ };
+ const syncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ [],
+ undefined,
+ undefined,
+ gppConsent
+ );
+ expect(syncs[0].url).to.include('gpp=DBABLA~1YNN');
+ expect(syncs[0].url).to.include('gpp_sid=7%2C8');
+ });
+
+ it('should not include GPP if gppString is missing', () => {
+ const gppConsent = {
+ applicableSections: [7, 8]
+ };
+ const syncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ [],
+ undefined,
+ undefined,
+ gppConsent
+ );
+ expect(syncs[0].url).to.not.include('gpp=');
+ });
+
+ it('should not include GPP if applicableSections is empty', () => {
+ const gppConsent = {
+ gppString: 'DBABLA~1YNN',
+ applicableSections: []
+ };
+ const syncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ [],
+ undefined,
+ undefined,
+ gppConsent
+ );
+ expect(syncs[0].url).to.not.include('gpp=');
+ });
+
+ it('should always include hardcoded ssp_id in sync URL', () => {
+ const syncs = spec.getUserSyncs({ iframeEnabled: true }, []);
+ expect(syncs[0].url).to.include('ssp_id=630141');
+ expect(syncs[0].url).to.not.include('ssp_site_id');
+ expect(syncs[0].url).to.not.include('ssp_user_id');
+ });
+
+ it('should include iframe_enabled flag in sync URL', () => {
+ const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: false }, []);
+ expect(syncs[0].url).to.include('iframe_enabled=true');
+ });
+
+ it('should set iframe_enabled=false when only pixel is enabled', () => {
+ const syncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, []);
+ expect(syncs[0].url).to.include('iframe_enabled=false');
+ });
+
+ it('should include all consent parameters together', () => {
+ const gppConsent = {
+ gppString: 'DBABLA~1YNN',
+ applicableSections: [7]
+ };
+
+ const syncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ [],
+ { gdprApplies: true, consentString: 'consent123' },
+ '1YNN',
+ gppConsent
+ );
+
+ expect(syncs[0].url).to.include('gdpr=1');
+ expect(syncs[0].url).to.include('gdpr_consent=consent123');
+ expect(syncs[0].url).to.include('us_privacy=1YNN');
+ expect(syncs[0].url).to.include('gpp=');
+ expect(syncs[0].url).to.include('gpp_sid=7');
+ expect(syncs[0].url).to.include('ssp_id=630141');
+ });
+
+ it('should handle missing GDPR consentString gracefully', () => {
+ const syncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ [],
+ { gdprApplies: true }
+ );
+ expect(syncs[0].url).to.include('gdpr=1');
+ expect(syncs[0].url).to.include('gdpr_consent=');
+ });
+
+ it('should retrieve sspUserId from ortb2.user.id when not in bid params', () => {
+ // sspUserId is no longer forwarded to the sync URL; ssp_id is hardcoded
+ const syncs = spec.getUserSyncs({ iframeEnabled: true }, []);
+ expect(syncs[0].url).to.include('ssp_id=630141');
+ expect(syncs[0].url).to.not.include('ssp_user_id');
+ });
+
+ it('should prioritize sspUserId from bid params over ortb2.user.id', () => {
+ // sspUserId is no longer forwarded to the sync URL; ssp_id is hardcoded
+ const syncs = spec.getUserSyncs({ iframeEnabled: true }, []);
+ expect(syncs[0].url).to.include('ssp_id=630141');
+ expect(syncs[0].url).to.not.include('ssp_user_id');
+ });
+
+ it('should always include ssp_id and iframe_enabled in sync URL', () => {
+ const syncs = spec.getUserSyncs(
+ { iframeEnabled: true, pixelEnabled: false },
+ [],
+ undefined,
+ undefined,
+ undefined
+ );
+
+ expect(syncs).to.be.an('array').with.lengthOf(1);
+ expect(syncs[0].type).to.equal('iframe');
+ expect(syncs[0].url).to.include('https://sync.adsmartx.com/sync');
+ expect(syncs[0].url).to.include('ssp_id=630141');
+ expect(syncs[0].url).to.include('iframe_enabled=true');
+ });
+ });
+
+ describe('buildRequests - additional scenarios', () => {
+ it('should set ext.test to 1 when testMode=1 is in bid params', () => {
+ const bidWithTestMode = {
+ ...validBidRequest,
+ params: {
+ ...validBidRequest.params,
+ testMode: 1
+ }
+ };
+
+ const testBidderRequest = {
+ ...bidderRequest,
+ bids: [bidWithTestMode]
+ };
+
+ const request = spec.buildRequests([bidWithTestMode], testBidderRequest);
+ expect(request.data).to.have.property('ext');
+ expect(request.data.ext).to.have.property('test', 1);
+ });
+
+ it('should not set ext.test when testMode is not present', () => {
+ const testBidderRequest = {
+ ...bidderRequest,
+ bids: [validBidRequest]
+ };
+ const request = spec.buildRequests([validBidRequest], testBidderRequest);
+ if (request.data.ext) {
+ expect(request.data.ext).to.not.have.property('test');
+ }
+ });
+
+ it('should include sspId in request.ext when present in bid params', () => {
+ const bidWithSspId = {
+ ...validBidRequest,
+ params: {
+ ...validBidRequest.params,
+ sspId: 'ssp123'
+ }
+ };
+
+ const testBidderRequest = {
+ ...bidderRequest,
+ bids: [bidWithSspId]
+ };
+
+ const request = spec.buildRequests([bidWithSspId], testBidderRequest);
+ expect(request.data).to.have.property('ext');
+ expect(request.data.ext).to.have.property('sspId', 'ssp123');
+ });
+
+ it('should include siteId in request.ext when present in bid params', () => {
+ const bidWithSiteId = {
+ ...validBidRequest,
+ params: {
+ ...validBidRequest.params,
+ siteId: 'site456'
+ }
+ };
+
+ const testBidderRequest = {
+ ...bidderRequest,
+ bids: [bidWithSiteId]
+ };
+
+ const request = spec.buildRequests([bidWithSiteId], testBidderRequest);
+ expect(request.data).to.have.property('ext');
+ expect(request.data.ext).to.have.property('siteId', 'site456');
+ });
+
+ it('should include both sspId and siteId in request.ext when both present', () => {
+ const bidWithBothIds = {
+ ...validBidRequest,
+ params: {
+ ...validBidRequest.params,
+ sspId: 'ssp123',
+ siteId: 'site456'
+ }
+ };
+
+ const testBidderRequest = {
+ ...bidderRequest,
+ bids: [bidWithBothIds]
+ };
+
+ const request = spec.buildRequests([bidWithBothIds], testBidderRequest);
+ expect(request.data).to.have.property('ext');
+ expect(request.data.ext).to.have.property('sspId', 'ssp123');
+ expect(request.data.ext).to.have.property('siteId', 'site456');
+ });
+
+ it('should always include hardcoded ssp_id in sync URL regardless of bid params', () => {
+ const syncs = spec.getUserSyncs({ iframeEnabled: true }, []);
+ expect(syncs[0].url).to.include('ssp_id=630141');
+ expect(syncs[0].url).to.not.include('ssp_site_id');
+ expect(syncs[0].url).to.not.include('ssp_user_id');
+ });
+
+ it('should include bidfloor in impression when present in bid params', () => {
+ const bidWithFloor = {
+ ...validBidRequest,
+ params: {
+ ...validBidRequest.params,
+ bidfloor: 0.5
+ }
+ };
+
+ const request = spec.buildRequests([bidWithFloor], bidderRequest);
+ expect(request.data.imp[0]).to.have.property('bidfloor', 0.5);
+ });
+
+ it('should not include bidfloor when not present in bid params', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ expect(request.data.imp[0]).to.not.have.property('bidfloor');
+ });
+
+ it('should set request.tmax to bidderRequest.timeout', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ expect(request.data).to.have.property('tmax', 3000);
+ });
+
+ it('should set request.cur to [USD]', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ expect(request.data).to.have.property('cur').that.deep.equals(['USD']);
+ });
+
+ it('should enable endpoint compression in options', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ expect(request.options).to.have.property('endpointCompression', true);
+ });
+
+ it('should handle empty validBidRequests array gracefully', () => {
+ const request = spec.buildRequests([], bidderRequest);
+ expect(request).to.be.an('object');
+ expect(request.data.imp).to.be.an('array').that.is.empty;
+ });
+
+ it('should handle bidderRequest without GDPR consent', () => {
+ const noBidderRequest = {
+ ...bidderRequest,
+ gdprConsent: undefined,
+ uspConsent: undefined
+ };
+ const request = spec.buildRequests([validBidRequest], noBidderRequest);
+ expect(request.data).to.not.have.property('regs');
+ expect(request.data).to.not.have.property('user');
+ });
+ });
+
+ describe('interpretResponse - additional scenarios', () => {
+ it('should set mediaType to video and include vastXml when mtype is 2', () => {
+ const videoResponse = {
+ body: {
+ id: '2def',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1abc',
+ impid: '1abc',
+ price: 2.5,
+ adm: '...',
+ w: 640,
+ h: 480,
+ crid: 'video-creative-123',
+ adomain: ['video-example.com'],
+ mtype: 2
+ }
+ ]
+ }
+ ]
+ }
+ };
+
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(videoResponse, request);
+
+ expect(bids).to.be.an('array').with.lengthOf(1);
+ expect(bids[0]).to.have.property('mediaType', 'video');
+ expect(bids[0]).to.have.property('vastXml', '...');
+ });
+
+ it('should set mediaType to banner when mtype is 1', () => {
+ const bannerResponse = {
+ body: {
+ id: '2def',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1abc',
+ impid: '1abc',
+ price: 1.5,
+ adm: 'Banner Ad
',
+ w: 300,
+ h: 250,
+ crid: 'banner-creative-123',
+ adomain: ['banner-example.com'],
+ mtype: 1
+ }
+ ]
+ }
+ ]
+ }
+ };
+
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(bannerResponse, request);
+
+ expect(bids).to.be.an('array').with.lengthOf(1);
+ expect(bids[0]).to.have.property('mediaType', 'banner');
+ expect(bids[0]).to.not.have.property('vastXml');
+ });
+
+ it('should use custom currency from response if provided', () => {
+ const responseWithCurrency = {
+ body: {
+ id: '2def',
+ cur: 'EUR',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1abc',
+ impid: '1abc',
+ price: 1.5,
+ adm: 'Ad
',
+ w: 300,
+ h: 250,
+ crid: 'creative123',
+ adomain: ['example.com']
+ }
+ ]
+ }
+ ]
+ }
+ };
+
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(responseWithCurrency, request);
+
+ expect(bids[0]).to.have.property('currency', 'EUR');
+ });
+
+ it('should not include dealId if not present in bid response', () => {
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(serverResponse, request);
+
+ expect(bids[0]).to.not.have.property('dealId');
+ });
+
+ it('should return empty array if seatbid is not an array', () => {
+ const invalidResponse = {
+ body: {
+ id: '2def',
+ seatbid: 'invalid'
+ }
+ };
+
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(invalidResponse, request);
+
+ expect(bids).to.be.an('array').that.is.empty;
+ });
+
+ it('should skip seatbid entries with empty bid arrays', () => {
+ const responseWithEmptyBids = {
+ body: {
+ id: '2def',
+ seatbid: [
+ { bid: [] },
+ {
+ bid: [
+ {
+ id: '1abc',
+ impid: '1abc',
+ price: 1.5,
+ adm: 'Ad
',
+ w: 300,
+ h: 250,
+ crid: 'creative123',
+ adomain: ['example.com']
+ }
+ ]
+ }
+ ]
+ }
+ };
+
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(responseWithEmptyBids, request);
+
+ expect(bids).to.be.an('array').with.lengthOf(1);
+ });
+
+ it('should handle seatbid with non-array bid property', () => {
+ const invalidSeatbidResponse = {
+ body: {
+ id: '2def',
+ seatbid: [
+ { bid: 'invalid' }
+ ]
+ }
+ };
+
+ const request = spec.buildRequests([validBidRequest], bidderRequest);
+ const bids = spec.interpretResponse(invalidSeatbidResponse, request);
+
+ expect(bids).to.be.an('array').that.is.empty;
+ });
+ });
+
+ describe('isBidRequestValid - additional scenarios', () => {
+ it('should return true for valid video bid with all required fields', () => {
+ const validVideoBid = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4', 'video/webm'],
+ w: 640,
+ h: 480
+ }
+ }
+ };
+
+ expect(spec.isBidRequestValid(validVideoBid)).to.equal(true);
+ });
+
+ it('should return true for video bid without width and height (optional)', () => {
+ const videoBidNoSize = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4']
+ }
+ }
+ };
+
+ expect(spec.isBidRequestValid(videoBidNoSize)).to.equal(true);
+ });
+
+ it('should return true for banner-only bid', () => {
+ expect(spec.isBidRequestValid(validBidRequest)).to.equal(true);
+ });
+
+ it('should return true for bid with both banner and video', () => {
+ const multiMediaBid = {
+ ...validBidRequest,
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]]
+ },
+ video: {
+ mimes: ['video/mp4'],
+ w: 640,
+ h: 480
+ }
+ }
+ };
+
+ expect(spec.isBidRequestValid(multiMediaBid)).to.equal(true);
+ });
+
+ it('should return true for video with negative width when width is null', () => {
+ const videoBid = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4'],
+ w: null,
+ h: 480
+ }
+ }
+ };
+
+ expect(spec.isBidRequestValid(videoBid)).to.equal(true);
+ });
+
+ it('should return false for video with width exactly 0', () => {
+ const videoBid = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4'],
+ w: 0,
+ h: 480
+ }
+ }
+ };
+
+ expect(spec.isBidRequestValid(videoBid)).to.equal(false);
+ });
+
+ it('should return false for video with negative width', () => {
+ const videoBid = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4'],
+ w: -100,
+ h: 480
+ }
+ }
+ };
+
+ expect(spec.isBidRequestValid(videoBid)).to.equal(false);
+ });
+
+ it('should return false for video with negative height', () => {
+ const videoBid = {
+ ...validBidRequest,
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4'],
+ w: 640,
+ h: -100
+ }
+ }
+ };
+
+ expect(spec.isBidRequestValid(videoBid)).to.equal(false);
+ });
+ });
+});
diff --git a/test/spec/modules/risemediatechBidAdapter_spec.js b/test/spec/modules/risemediatechBidAdapter_spec.js
index d4d70017ceb..de9ff2ac32e 100644
--- a/test/spec/modules/risemediatechBidAdapter_spec.js
+++ b/test/spec/modules/risemediatechBidAdapter_spec.js
@@ -1,5 +1,7 @@
import { expect } from 'chai';
-import { spec } from 'modules/risemediatechBidAdapter.js';
+import sinon from 'sinon';
+import { spec, disableAdapter } from 'modules/risemediatechBidAdapter.js';
+import * as utils from 'src/utils.js';
describe('RiseMediaTech adapter', () => {
const validBidRequest = {
@@ -51,119 +53,26 @@ describe('RiseMediaTech adapter', () => {
},
};
- describe('isBidRequestValid', () => {
- it('should return true for valid bid request', () => {
- expect(spec.isBidRequestValid(validBidRequest)).to.equal(true);
- });
-
- it('should return false for invalid video bid request', () => {
- const invalidVideoRequest = {
- ...validBidRequest,
- mediaTypes: {
- video: {
- mimes: [],
- },
- },
- };
- expect(spec.isBidRequestValid(invalidVideoRequest)).to.equal(false);
- });
-
- it('should return false for video bid request with missing mimes', () => {
- const invalidVideoRequest = {
- ...validBidRequest,
- mediaTypes: {
- video: {
- w: 640,
- h: 480
- // mimes missing
- }
- }
- };
- expect(spec.isBidRequestValid(invalidVideoRequest)).to.equal(false);
- });
-
- it('should return false for video request with invalid mimes (not an array)', () => {
- const invalidBid = {
- ...validBidRequest,
- mediaTypes: {
- video: {
- mimes: 'video/mp4', // Not an array
- w: 640,
- h: 480
- }
- }
- };
- expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
- });
-
- it('should return false for video request with empty mimes array', () => {
- const invalidBid = {
- ...validBidRequest,
- mediaTypes: {
- video: {
- mimes: [],
- w: 640,
- h: 480
- }
- }
- };
- expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
- });
-
- it('should return false for video request with width <= 0', () => {
- const invalidBid = {
- ...validBidRequest,
- mediaTypes: {
- video: {
- mimes: ['video/mp4'],
- w: 0,
- h: 480
- }
- }
- };
- expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
+ describe('disableAdapter', () => {
+ it('should log a deprecation warning', () => {
+ const warnStub = sinon.stub(utils, 'logWarn');
+ try {
+ disableAdapter();
+ expect(warnStub.calledOnce).to.be.true;
+ expect(warnStub.firstCall.args[0]).to.include('deprecated');
+ } finally {
+ warnStub.restore();
+ }
});
- it('should return false for video request with height <= 0', () => {
- const invalidBid = {
- ...validBidRequest,
- mediaTypes: {
- video: {
- mimes: ['video/mp4'],
- w: 640,
- h: -10
- }
- }
- };
- expect(spec.isBidRequestValid(invalidBid)).to.equal(false);
- });
-
- it('should return false for video bid request with invalid width', () => {
- const invalidVideoRequest = {
- ...validBidRequest,
- mediaTypes: {
- video: {
- mimes: ['video/mp4'],
- w: 0,
- h: 480
- }
- }
- };
- expect(spec.isBidRequestValid(invalidVideoRequest)).to.equal(false);
+ it('should return false', () => {
+ expect(disableAdapter()).to.equal(false);
});
+ });
- it('should return false for video bid request with invalid height', () => {
- const invalidVideoRequest = {
- ...validBidRequest,
- mediaTypes: {
- video: {
- mimes: ['video/mp4'],
- w: 640,
- h: 0
- }
- }
- };
- expect(spec.isBidRequestValid(invalidVideoRequest)).to.equal(false);
+ describe('isBidRequestValid', () => {
+ it('should be false because the adapter is disabled/deprecated', () => {
+ expect(spec.isBidRequestValid).to.equal(false);
});
});
@@ -179,8 +88,8 @@ describe('RiseMediaTech adapter', () => {
it('should include GDPR and USP consent in the request', () => {
const request = spec.buildRequests([validBidRequest], bidderRequest);
const { regs, user } = request.data;
- expect(regs.ext).to.have.property('gdpr', 1);
- expect(user.ext).to.have.property('consent', 'consent123');
+ expect(regs).to.have.property('gdpr', 1);
+ expect(user).to.have.property('consent', 'consent123');
expect(regs.ext).to.have.property('us_privacy', '1YNN');
});
@@ -241,8 +150,8 @@ describe('RiseMediaTech adapter', () => {
}
};
const request = spec.buildRequests([validBidRequest], noGdprBidderRequest);
- expect(request.data.regs.ext).to.have.property('gdpr', 0);
- expect(request.data.user.ext).to.have.property('consent', 'consent123');
+ expect(request.data.regs).to.have.property('gdpr', 0);
+ expect(request.data.user).to.have.property('consent', 'consent123');
});
it('should set regs and regs.ext to {} if not already set when only USP consent is present', () => {
@@ -403,7 +312,7 @@ describe('RiseMediaTech adapter', () => {
expect(bids).to.be.an('array').with.lengthOf(1);
});
- it('should log a warning and not set mediaType for unknown mtype', () => {
+ it('should log a warning and default mediaType to banner for unknown mtype', () => {
const responseWithUnknownMtype = {
body: {
id: '2def',
@@ -429,7 +338,7 @@ describe('RiseMediaTech adapter', () => {
const request = spec.buildRequests([validBidRequest], bidderRequest);
const bids = spec.interpretResponse(responseWithUnknownMtype, request);
expect(bids).to.be.an('array').with.lengthOf(1);
- expect(bids[0].meta).to.not.have.property('mediaType');
+ expect(bids[0].mediaType).to.equal('banner');
});
it('should include dealId if present in the bid response', () => {
@@ -484,14 +393,14 @@ describe('RiseMediaTech adapter', () => {
};
const request = spec.buildRequests([validBidRequest], bidderRequest);
const bids = spec.interpretResponse(responseWithoutPrice, request);
- expect(bids).to.be.an('array').that.is.not.empty;
+ expect(bids).to.be.an('array').that.is.empty;
});
});
describe('getUserSyncs', () => {
- it('should return null as user syncs are not implemented', () => {
+ it('should return empty array as user syncs are not implemented', () => {
const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], bidderRequest.gdprConsent, bidderRequest.uspConsent);
- expect(syncs).to.be.null;
+ expect(syncs).to.be.an('array').that.is.empty;
});
});
});