From cbd3b732ab6a0f87ac3a514cadd3e536b818db60 Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Tue, 13 Jan 2026 18:28:11 +0530 Subject: [PATCH 01/14] RM-1476 : Prebid adapter for adsmartx --- modules/adsmartxBidAdapter.js | 325 ++++++++++++ modules/adsmartxBidAdapter.md | 73 +++ test/spec/modules/adsmartxBidAdapter_spec.js | 497 +++++++++++++++++++ 3 files changed, 895 insertions(+) create mode 100644 modules/adsmartxBidAdapter.js create mode 100644 modules/adsmartxBidAdapter.md create mode 100644 test/spec/modules/adsmartxBidAdapter_spec.js diff --git a/modules/adsmartxBidAdapter.js b/modules/adsmartxBidAdapter.js new file mode 100644 index 00000000000..741993683fa --- /dev/null +++ b/modules/adsmartxBidAdapter.js @@ -0,0 +1,325 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { deepAccess, logInfo, logWarn } from '../src/utils.js'; + +const BIDDER_CODE = 'adsmartx'; +const ENDPOINT_URL = 'https://ads.adsmartx.com/ads/rtb/prebid/js'; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_TTL = 60; + +// Store sync parameters from bid params for use in getUserSyncs +let syncParams = {}; + +/** + * 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 + */ +function getPublisherUserId(bidParams, bidderRequest) { + // Priority 1: Check bid params + if (bidParams?.sspUserId) { + logInfo('Using SSP user ID from bid params:', bidParams.sspUserId); + return bidParams.sspUserId; + } + + // Priority 2: Check ORTB2 user.id (standard first party data) + 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; +} + +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. + }; + } + + 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; + + 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); + } + // Add sspId and siteId if present in any bid params + const sspIdBid = bidderRequest.bids.find(bid => bid.params && 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 && 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) { + 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); + + // Extract and store sync parameters from first bid for use in getUserSyncs + if (validBidRequests && validBidRequests.length > 0) { + const firstBid = validBidRequests[0]; + if (firstBid.params) { + syncParams = { + sspId: firstBid.params.sspId, + siteId: firstBid.params.siteId, + // Get sspUserId with priority: bid params → ortb2.user.id + sspUserId: getPublisherUserId(firstBid.params, bidderRequest), + }; + logInfo('Stored sync parameters from bid params:', syncParams); + } + } + + 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); + } + }); + + 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) => { + logInfo('getUserSyncs called with options:', syncOptions); + + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + logWarn('User sync disabled: neither iframe nor pixel is enabled'); + return []; + } + + const syncs = []; + const syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + const syncUrl = 'https://ads.adsmartx.com/sync'; + + // Build query parameters for privacy signals + 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(','))); + } + + // Add custom sync parameters from bid params + if (syncParams.sspId) { + params.push('ssp_id=' + encodeURIComponent(syncParams.sspId)); + logInfo('Adding ssp_id to sync URL:', syncParams.sspId); + } + + if (syncParams.siteId) { + params.push('ssp_site_id=' + encodeURIComponent(syncParams.siteId)); + logInfo('Adding ssp_site_id to sync URL:', syncParams.siteId); + } + + if (syncParams.sspUserId) { + params.push('ssp_user_id=' + encodeURIComponent(syncParams.sspUserId)); + logInfo('Adding ssp_user_id to sync URL:', syncParams.sspUserId); + } + + // Add iframe_enabled flag + params.push('iframe_enabled=' + (syncOptions.iframeEnabled ? 'true' : 'false')); + + const queryString = params.length ? '?' + params.join('&') : ''; + const finalUrl = syncUrl + queryString; + + syncs.push({ + type: syncType, + url: finalUrl + }); + + logInfo('Returning user syncs:', syncs); + return syncs; +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); \ No newline at end of file diff --git a/modules/adsmartxBidAdapter.md b/modules/adsmartxBidAdapter.md new file mode 100644 index 00000000000..549967c6496 --- /dev/null +++ b/modules/adsmartxBidAdapter.md @@ -0,0 +1,73 @@ +# Overview + +Module Name : AdSmartX Bidder Adapter +Module Type : Bid Adapter +Maintainer : prebid@risemediatech.io + +# Description +Connects to AdSmartX Exchange for bids +AdSmartX supports Display & Video(Instream) currently. + +This adapter is maintained by Rise Media Technologies, the legal entity behind this implementation. Our official domain is risemediatech.io, which currently redirects to pubrise.ai for operational convenience. We also own the domain risemediatech.com. +Rise Media Technologies and PubRise are part of the same parent organization. +# 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/test/spec/modules/adsmartxBidAdapter_spec.js b/test/spec/modules/adsmartxBidAdapter_spec.js new file mode 100644 index 00000000000..b9fba3b6912 --- /dev/null +++ b/test/spec/modules/adsmartxBidAdapter_spec.js @@ -0,0 +1,497 @@ +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://dev-ads.risemediatech.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.ext).to.have.property('gdpr', 1); + expect(user.ext).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.ext).to.have.property('gdpr', 0); + expect(request.data.user.ext).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 not set mediaType 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].meta).to.not.have.property('mediaType'); + }); + + 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.not.empty; + }); + }); + + describe('getUserSyncs', () => { + it('should return null as user syncs are not implemented', () => { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], bidderRequest.gdprConsent, bidderRequest.uspConsent); + expect(syncs).to.be.null; + }); + }); +}); From f184a06e4e00925a9aef95a7f7bd42dec618e8ca Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Wed, 21 Jan 2026 18:28:11 +0530 Subject: [PATCH 02/14] RM-1476 : Improved unit test coverage --- test/spec/modules/adsmartxBidAdapter_spec.js | 694 ++++++++++++++++++- 1 file changed, 690 insertions(+), 4 deletions(-) diff --git a/test/spec/modules/adsmartxBidAdapter_spec.js b/test/spec/modules/adsmartxBidAdapter_spec.js index b9fba3b6912..3751a566228 100644 --- a/test/spec/modules/adsmartxBidAdapter_spec.js +++ b/test/spec/modules/adsmartxBidAdapter_spec.js @@ -172,7 +172,7 @@ describe('AdSmartX adapter', () => { const request = spec.buildRequests([validBidRequest], bidderRequest); expect(request).to.be.an('object'); expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://dev-ads.risemediatech.com/ads/rtb/prebid/js'); + expect(request.url).to.equal('https://ads.adsmartx.com/ads/rtb/prebid/js'); expect(request.data).to.be.an('object'); }); @@ -489,9 +489,695 @@ describe('AdSmartX adapter', () => { }); describe('getUserSyncs', () => { - it('should return null as user syncs are not implemented', () => { - const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], bidderRequest.gdprConsent, bidderRequest.uspConsent); - expect(syncs).to.be.null; + 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://ads.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 include custom sync parameters from bid params (sspId, siteId, sspUserId)', () => { + const bidWithParams = { + ...validBidRequest, + params: { + ...validBidRequest.params, + sspId: 'ssp123', + siteId: 'site456', + sspUserId: 'user789' + } + }; + + const testBidderRequest = { + ...bidderRequest, + bids: [bidWithParams] + }; + + // First build request to store sync params + spec.buildRequests([bidWithParams], testBidderRequest); + + // Then call getUserSyncs + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs[0].url).to.include('ssp_id=ssp123'); + expect(syncs[0].url).to.include('ssp_site_id=site456'); + expect(syncs[0].url).to.include('ssp_user_id=user789'); + }); + + 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 bidWithParams = { + ...validBidRequest, + params: { + ...validBidRequest.params, + sspId: 'ssp123', + siteId: 'site456', + sspUserId: 'user789' + } + }; + + const testBidderRequest = { + ...bidderRequest, + bids: [bidWithParams] + }; + + spec.buildRequests([bidWithParams], testBidderRequest); + + 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=ssp123'); + expect(syncs[0].url).to.include('ssp_site_id=site456'); + expect(syncs[0].url).to.include('ssp_user_id=user789'); + }); + + 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', () => { + const bidWithoutUserId = { + ...validBidRequest, + params: { + ...validBidRequest.params, + sspId: 'ssp123' + } + }; + + const bidderRequestWithOrtb2 = { + ...bidderRequest, + bids: [bidWithoutUserId], + ortb2: { + user: { + id: 'ortb2-user-id-123' + } + } + }; + + spec.buildRequests([bidWithoutUserId], bidderRequestWithOrtb2); + + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs[0].url).to.include('ssp_user_id=ortb2-user-id-123'); + }); + + it('should prioritize sspUserId from bid params over ortb2.user.id', () => { + const bidWithUserId = { + ...validBidRequest, + params: { + ...validBidRequest.params, + sspUserId: 'bid-param-user-id' + } + }; + + const bidderRequestWithOrtb2 = { + ...bidderRequest, + bids: [bidWithUserId], + ortb2: { + user: { + id: 'ortb2-user-id-123' + } + } + }; + + spec.buildRequests([bidWithUserId], bidderRequestWithOrtb2); + + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs[0].url).to.include('ssp_user_id=bid-param-user-id'); + expect(syncs[0].url).to.not.include('ortb2-user-id-123'); + }); + + it('should generate sync URL with no query parameters when no data is available', () => { + // Clear any stored sync params by not calling buildRequests + // This tests the edge case where getUserSyncs is called without any prior context + 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://ads.adsmartx.com/sync'); + 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 store sync parameters from first bid for getUserSyncs', () => { + const bidWithSyncParams = { + ...validBidRequest, + params: { + ...validBidRequest.params, + sspId: 'ssp789', + siteId: 'site012', + sspUserId: 'user345' + } + }; + + const testBidderRequest = { + ...bidderRequest, + bids: [bidWithSyncParams] + }; + + spec.buildRequests([bidWithSyncParams], testBidderRequest); + + // Verify params are stored by calling getUserSyncs + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs[0].url).to.include('ssp_id=ssp789'); + expect(syncs[0].url).to.include('ssp_site_id=site012'); + expect(syncs[0].url).to.include('ssp_user_id=user345'); + }); + + 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); }); }); }); From 972309303f6a229811347c89b025457eb87c7cf3 Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Thu, 22 Jan 2026 18:46:20 +0530 Subject: [PATCH 03/14] RM-1476 : Updated bidder documentation description --- modules/adsmartxBidAdapter.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/adsmartxBidAdapter.md b/modules/adsmartxBidAdapter.md index 549967c6496..9de3d217ad6 100644 --- a/modules/adsmartxBidAdapter.md +++ b/modules/adsmartxBidAdapter.md @@ -8,8 +8,7 @@ Maintainer : prebid@risemediatech.io Connects to AdSmartX Exchange for bids AdSmartX supports Display & Video(Instream) currently. -This adapter is maintained by Rise Media Technologies, the legal entity behind this implementation. Our official domain is risemediatech.io, which currently redirects to pubrise.ai for operational convenience. We also own the domain risemediatech.com. -Rise Media Technologies and PubRise are part of the same parent organization. +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 = [ From 9c9b88fffea417529a7eb8ac45ee5804f7aba613 Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Wed, 25 Feb 2026 16:51:35 +0530 Subject: [PATCH 04/14] RM-1476 : Review comments handled --- libraries/adsmartxUtils/bidderUtils.js | 279 +++++++++++++++ modules/adsmartxBidAdapter.js | 323 ++---------------- modules/adsmartxBidAdapter.md | 10 +- .../adsmartxUtils/bidderUtils_spec.js | 232 +++++++++++++ 4 files changed, 537 insertions(+), 307 deletions(-) create mode 100644 libraries/adsmartxUtils/bidderUtils.js create mode 100644 test/spec/libraries/adsmartxUtils/bidderUtils_spec.js diff --git a/libraries/adsmartxUtils/bidderUtils.js b/libraries/adsmartxUtils/bidderUtils.js new file mode 100644 index 00000000000..fb58441eb3c --- /dev/null +++ b/libraries/adsmartxUtils/bidderUtils.js @@ -0,0 +1,279 @@ +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 = { 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] }; + } + 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) { + 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 (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; + } + 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, and stores sync params. + * @param {Object} config - { converter, endpointUrl, getPublisherUserId } + * @param {Object} syncParamsRef - Mutable ref { current: {} } to store sync params for getUserSyncs + * @returns {function(Array, Object): Object} + */ +export function createBuildRequests(config, syncParamsRef) { + const { converter, endpointUrl, getPublisherUserId: getUserId } = config; + + return function buildRequests(validBidRequests, bidderRequest) { + logInfo('Building server request for valid bid requests:', validBidRequests); + + if (validBidRequests?.length > 0 && validBidRequests[0].params) { + const firstBid = validBidRequests[0]; + syncParamsRef.current = { + sspId: firstBid.params.sspId, + siteId: firstBid.params.siteId, + sspUserId: getUserId(firstBid.params, bidderRequest), + }; + logInfo('Stored sync parameters from bid params:', syncParamsRef.current); + } + + 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]; + 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 and custom params. + * @param {string} syncUrl - Base sync URL (e.g. 'https://ads.adsmartx.com/sync') + * @param {Object} syncParamsRef - Same ref passed to createBuildRequests, read as syncParamsRef.current + * @returns {function(Object, Array, Object, string, Object): Array} + */ +export function createGetUserSyncs(syncUrl, syncParamsRef) { + 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(','))); + } + + const syncParams = syncParamsRef.current || {}; + if (syncParams.sspId) { + params.push('ssp_id=' + encodeURIComponent(syncParams.sspId)); + logInfo('Adding ssp_id to sync URL:', syncParams.sspId); + } + if (syncParams.siteId) { + params.push('ssp_site_id=' + encodeURIComponent(syncParams.siteId)); + logInfo('Adding ssp_site_id to sync URL:', syncParams.siteId); + } + if (syncParams.sspUserId) { + params.push('ssp_user_id=' + encodeURIComponent(syncParams.sspUserId)); + logInfo('Adding ssp_user_id to sync URL:', syncParams.sspUserId); + } + 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:', syncs); + return syncs; + }; +} diff --git a/modules/adsmartxBidAdapter.js b/modules/adsmartxBidAdapter.js index 741993683fa..6380abb283b 100644 --- a/modules/adsmartxBidAdapter.js +++ b/modules/adsmartxBidAdapter.js @@ -1,316 +1,35 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { ortbConverter } from '../libraries/ortbConverter/converter.js'; -import { deepAccess, logInfo, logWarn } from '../src/utils.js'; +import { + getPublisherUserId, + 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://ads.adsmartx.com/sync'; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_TTL = 60; -// Store sync parameters from bid params for use in getUserSyncs -let syncParams = {}; +const syncParamsRef = { current: {} }; +const converter = createConverter({ defaultCurrency: DEFAULT_CURRENCY, defaultTtl: DEFAULT_TTL }); -/** - * 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 - */ -function getPublisherUserId(bidParams, bidderRequest) { - // Priority 1: Check bid params - if (bidParams?.sspUserId) { - logInfo('Using SSP user ID from bid params:', bidParams.sspUserId); - return bidParams.sspUserId; - } - - // Priority 2: Check ORTB2 user.id (standard first party data) - 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; -} +const isBidRequestValid = validateBidRequest; +const buildRequests = createBuildRequests( + { converter, endpointUrl: ENDPOINT_URL, getPublisherUserId }, + syncParamsRef +); +const getUserSyncs = createGetUserSyncs(SYNC_URL, syncParamsRef); -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. - }; - } - - 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; - - 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); - } - // Add sspId and siteId if present in any bid params - const sspIdBid = bidderRequest.bids.find(bid => bid.params && 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 && 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) { - 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); - - // Extract and store sync parameters from first bid for use in getUserSyncs - if (validBidRequests && validBidRequests.length > 0) { - const firstBid = validBidRequests[0]; - if (firstBid.params) { - syncParams = { - sspId: firstBid.params.sspId, - siteId: firstBid.params.siteId, - // Get sspUserId with priority: bid params → ortb2.user.id - sspUserId: getPublisherUserId(firstBid.params, bidderRequest), - }; - logInfo('Stored sync parameters from bid params:', syncParams); - } - } - - 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); - } - }); - - 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) => { - logInfo('getUserSyncs called with options:', syncOptions); - - if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { - logWarn('User sync disabled: neither iframe nor pixel is enabled'); - return []; - } - - const syncs = []; - const syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - const syncUrl = 'https://ads.adsmartx.com/sync'; - - // Build query parameters for privacy signals - 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(','))); - } - - // Add custom sync parameters from bid params - if (syncParams.sspId) { - params.push('ssp_id=' + encodeURIComponent(syncParams.sspId)); - logInfo('Adding ssp_id to sync URL:', syncParams.sspId); - } - - if (syncParams.siteId) { - params.push('ssp_site_id=' + encodeURIComponent(syncParams.siteId)); - logInfo('Adding ssp_site_id to sync URL:', syncParams.siteId); - } - - if (syncParams.sspUserId) { - params.push('ssp_user_id=' + encodeURIComponent(syncParams.sspUserId)); - logInfo('Adding ssp_user_id to sync URL:', syncParams.sspUserId); - } - - // Add iframe_enabled flag - params.push('iframe_enabled=' + (syncOptions.iframeEnabled ? 'true' : 'false')); - - const queryString = params.length ? '?' + params.join('&') : ''; - const finalUrl = syncUrl + queryString; - - syncs.push({ - type: syncType, - url: finalUrl + return interpretResponseUtil(serverResponse, request, { + defaultCurrency: DEFAULT_CURRENCY, + defaultTtl: DEFAULT_TTL, }); - - logInfo('Returning user syncs:', syncs); - return syncs; }; export const spec = { @@ -322,4 +41,4 @@ export const spec = { getUserSyncs, }; -registerBidder(spec); \ No newline at end of file +registerBidder(spec); diff --git a/modules/adsmartxBidAdapter.md b/modules/adsmartxBidAdapter.md index 9de3d217ad6..de3024a21f2 100644 --- a/modules/adsmartxBidAdapter.md +++ b/modules/adsmartxBidAdapter.md @@ -27,8 +27,8 @@ This adapter is maintained by Smart Exchange, the legal entity behind this imple params: { bidfloor: 0.001, testMode: 1, - sspId: 123456 - siteId: 987654 + sspId: 123456, + siteId: 987654, sspUserId: 'u1234' } } @@ -59,10 +59,10 @@ This adapter is maintained by Smart Exchange, the legal entity behind this imple { bidder: 'adsmartx', params: { - bidfloor: 0.001 + bidfloor: 0.001, testMode: 1, - sspId: 123456 - siteId: 987654 + sspId: 123456, + siteId: 987654, sspUserId: 'u1234' } } diff --git a/test/spec/libraries/adsmartxUtils/bidderUtils_spec.js b/test/spec/libraries/adsmartxUtils/bidderUtils_spec.js new file mode 100644 index 00000000000..9e668462324 --- /dev/null +++ b/test/spec/libraries/adsmartxUtils/bidderUtils_spec.js @@ -0,0 +1,232 @@ +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 syncParamsRef = { current: {} }; + const converter = createConverter(defaultConfig); + const buildRequests = createBuildRequests( + { converter, endpointUrl, getPublisherUserId }, + syncParamsRef + ); + + 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'); + }); + + it('stores sync params when first bid has params', () => { + const syncRef = { current: {} }; + const buildReq = createBuildRequests( + { + converter: createConverter(defaultConfig), + endpointUrl: 'https://x.com', + getPublisherUserId: () => 'stored-user', + }, + syncRef + ); + const validBidRequests = [ + { + bidId: 'b1', + mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, + params: { sspId: 's1', siteId: 'site1' }, + }, + ]; + buildReq(validBidRequests, {}); + expect(syncRef.current.sspId).to.equal('s1'); + expect(syncRef.current.siteId).to.equal('site1'); + expect(syncRef.current.sspUserId).to.equal('stored-user'); + }); + }); + + 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'; + const syncParamsRef = { current: {} }; + + it('returns empty array when iframe and pixel disabled', () => { + const getUserSyncs = createGetUserSyncs(syncUrl, syncParamsRef); + 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, syncParamsRef); + 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('appends ssp_id and ssp_site_id when syncParamsRef has them', () => { + const ref = { current: { sspId: 'ssp1', siteId: 'site1' } }; + const getUserSyncs = createGetUserSyncs(syncUrl, ref); + const result = getUserSyncs( + { iframeEnabled: true, pixelEnabled: false }, + [], + undefined, + undefined, + undefined + ); + expect(result[0].url).to.include('ssp_id=ssp1'); + expect(result[0].url).to.include('ssp_site_id=site1'); + }); + }); +}); From 595a2347f58674512614a8b9672d46ededece269 Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Wed, 25 Feb 2026 16:52:15 +0530 Subject: [PATCH 05/14] RM-1476 : Minor changes --- test/spec/modules/adsmartxBidAdapter_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/modules/adsmartxBidAdapter_spec.js b/test/spec/modules/adsmartxBidAdapter_spec.js index 3751a566228..9bc9dbaad94 100644 --- a/test/spec/modules/adsmartxBidAdapter_spec.js +++ b/test/spec/modules/adsmartxBidAdapter_spec.js @@ -403,7 +403,7 @@ describe('AdSmartX 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 +429,7 @@ describe('AdSmartX 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', () => { From d894733c8bb1a816020d17e978cf568d0067152b Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Wed, 25 Feb 2026 17:19:51 +0530 Subject: [PATCH 06/14] RM-1476 : Dedupe code for adapter risemediatech --- modules/risemediatechBidAdapter.js | 203 ++---------------- .../modules/risemediatechBidAdapter_spec.js | 4 +- 2 files changed, 20 insertions(+), 187 deletions(-) diff --git a/modules/risemediatechBidAdapter.js b/modules/risemediatechBidAdapter.js index e3709722ae0..e4ed12686a8 100644 --- a/modules/risemediatechBidAdapter.js +++ b/modules/risemediatechBidAdapter.js @@ -1,203 +1,36 @@ 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 { logInfo } from '../src/utils.js'; +import { + getPublisherUserId, + createConverter, + isBidRequestValid as validateBidRequest, + createBuildRequests, + interpretResponse as interpretResponseUtil, +} from '../libraries/adsmartxUtils/bidderUtils.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 syncParamsRef = { current: {} }; +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; +const isBidRequestValid = validateBidRequest; +const buildRequests = createBuildRequests( + { converter, endpointUrl: ENDPOINT_URL, getPublisherUserId }, + syncParamsRef +); - 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); - } - } - - 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; }; diff --git a/test/spec/modules/risemediatechBidAdapter_spec.js b/test/spec/modules/risemediatechBidAdapter_spec.js index d4d70017ceb..0aee4817a23 100644 --- a/test/spec/modules/risemediatechBidAdapter_spec.js +++ b/test/spec/modules/risemediatechBidAdapter_spec.js @@ -403,7 +403,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 +429,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', () => { From a80a4eb5cd32ea0534dfd23650658643b46b0012 Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Wed, 4 Mar 2026 09:32:28 +0530 Subject: [PATCH 07/14] Added only ssp_id for user sync flow --- libraries/adsmartxUtils/bidderUtils.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/adsmartxUtils/bidderUtils.js b/libraries/adsmartxUtils/bidderUtils.js index fb58441eb3c..c6508be004e 100644 --- a/libraries/adsmartxUtils/bidderUtils.js +++ b/libraries/adsmartxUtils/bidderUtils.js @@ -258,10 +258,6 @@ export function createGetUserSyncs(syncUrl, syncParamsRef) { params.push('ssp_id=' + encodeURIComponent(syncParams.sspId)); logInfo('Adding ssp_id to sync URL:', syncParams.sspId); } - if (syncParams.siteId) { - params.push('ssp_site_id=' + encodeURIComponent(syncParams.siteId)); - logInfo('Adding ssp_site_id to sync URL:', syncParams.siteId); - } if (syncParams.sspUserId) { params.push('ssp_user_id=' + encodeURIComponent(syncParams.sspUserId)); logInfo('Adding ssp_user_id to sync URL:', syncParams.sspUserId); From f228d1469a6872424339db6589dc6b3564638613 Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Wed, 4 Mar 2026 09:59:38 +0530 Subject: [PATCH 08/14] Fixed bugs --- modules/adsmartxBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/adsmartxBidAdapter.js b/modules/adsmartxBidAdapter.js index 6380abb283b..ab5610bf125 100644 --- a/modules/adsmartxBidAdapter.js +++ b/modules/adsmartxBidAdapter.js @@ -11,7 +11,7 @@ import { const BIDDER_CODE = 'adsmartx'; const ENDPOINT_URL = 'https://ads.adsmartx.com/ads/rtb/prebid/js'; -const SYNC_URL = 'https://ads.adsmartx.com/sync'; +const SYNC_URL = 'https://sync.adsmartx.com/sync'; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_TTL = 60; From 0b044a5045bdb7198063831bce6da4bd61a0cdc3 Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Fri, 6 Mar 2026 15:55:01 +0530 Subject: [PATCH 09/14] RM-1476 : handled all copilot review comments --- libraries/adsmartxUtils/bidderUtils.js | 50 +++---- modules/adsmartxBidAdapter.js | 7 +- modules/adsmartxBidAdapter.md | 6 +- modules/risemediatechBidAdapter.js | 7 +- test/spec/modules/adsmartxBidAdapter_spec.js | 140 +++---------------- 5 files changed, 47 insertions(+), 163 deletions(-) diff --git a/libraries/adsmartxUtils/bidderUtils.js b/libraries/adsmartxUtils/bidderUtils.js index c6508be004e..886ef880ae2 100644 --- a/libraries/adsmartxUtils/bidderUtils.js +++ b/libraries/adsmartxUtils/bidderUtils.js @@ -52,10 +52,10 @@ export function createConverter(config = {}) { } if (mediaTypes[BANNER]) { logInfo('Adding banner media type to impression:', mediaTypes[BANNER]); - imp.banner = { format: mediaTypes[BANNER].sizes.map(([w, h]) => ({ w, h })) }; + 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 = { ...mediaTypes[VIDEO] }; + imp.video = { ...(imp.video || {}), ...mediaTypes[VIDEO] }; } return imp; }, @@ -87,14 +87,17 @@ export function createConverter(config = {}) { } } + 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 = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; - request.user = { ext: { consent: bidderRequest.gdprConsent.consentString } }; + 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 = request.regs || {}; request.regs.ext = request.regs.ext || {}; request.regs.ext.us_privacy = bidderRequest.uspConsent; } @@ -118,6 +121,7 @@ export function isBidRequestValid(bid) { 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; @@ -131,27 +135,16 @@ export function isBidRequestValid(bid) { } /** - * Builds buildRequests function that uses the given converter and endpoint, and stores sync params. - * @param {Object} config - { converter, endpointUrl, getPublisherUserId } - * @param {Object} syncParamsRef - Mutable ref { current: {} } to store sync params for getUserSyncs + * Builds buildRequests function that uses the given converter and endpoint. + * @param {Object} config - { converter, endpointUrl } * @returns {function(Array, Object): Object} */ -export function createBuildRequests(config, syncParamsRef) { - const { converter, endpointUrl, getPublisherUserId: getUserId } = config; +export function createBuildRequests(config) { + const { converter, endpointUrl } = config; return function buildRequests(validBidRequests, bidderRequest) { logInfo('Building server request for valid bid requests:', validBidRequests); - if (validBidRequests?.length > 0 && validBidRequests[0].params) { - const firstBid = validBidRequests[0]; - syncParamsRef.current = { - sspId: firstBid.params.sspId, - siteId: firstBid.params.siteId, - sspUserId: getUserId(firstBid.params, bidderRequest), - }; - logInfo('Stored sync parameters from bid params:', syncParamsRef.current); - } - const request = converter.toORTB({ bidRequests: validBidRequests, bidderRequest }); logInfo('Converted to ORTB request:', request); return { @@ -227,12 +220,11 @@ export function interpretResponse(serverResponse, request, config = {}) { } /** - * Creates getUserSyncs function that builds sync URL with privacy and custom params. - * @param {string} syncUrl - Base sync URL (e.g. 'https://ads.adsmartx.com/sync') - * @param {Object} syncParamsRef - Same ref passed to createBuildRequests, read as syncParamsRef.current + * 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, syncParamsRef) { +export function createGetUserSyncs(syncUrl) { return function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { logInfo('getUserSyncs called with options:', syncOptions); if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { @@ -253,15 +245,7 @@ export function createGetUserSyncs(syncUrl, syncParamsRef) { params.push('gpp_sid=' + encodeURIComponent(gppConsent.applicableSections.join(','))); } - const syncParams = syncParamsRef.current || {}; - if (syncParams.sspId) { - params.push('ssp_id=' + encodeURIComponent(syncParams.sspId)); - logInfo('Adding ssp_id to sync URL:', syncParams.sspId); - } - if (syncParams.sspUserId) { - params.push('ssp_user_id=' + encodeURIComponent(syncParams.sspUserId)); - logInfo('Adding ssp_user_id to sync URL:', syncParams.sspUserId); - } + params.push('ssp_id=630141'); params.push('iframe_enabled=' + (syncOptions.iframeEnabled ? 'true' : 'false')); const queryString = params.length ? '?' + params.join('&') : ''; diff --git a/modules/adsmartxBidAdapter.js b/modules/adsmartxBidAdapter.js index ab5610bf125..934d22cabe2 100644 --- a/modules/adsmartxBidAdapter.js +++ b/modules/adsmartxBidAdapter.js @@ -1,7 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { - getPublisherUserId, createConverter, isBidRequestValid as validateBidRequest, createBuildRequests, @@ -15,15 +14,13 @@ const SYNC_URL = 'https://sync.adsmartx.com/sync'; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_TTL = 60; -const syncParamsRef = { current: {} }; const converter = createConverter({ defaultCurrency: DEFAULT_CURRENCY, defaultTtl: DEFAULT_TTL }); const isBidRequestValid = validateBidRequest; const buildRequests = createBuildRequests( - { converter, endpointUrl: ENDPOINT_URL, getPublisherUserId }, - syncParamsRef + { converter, endpointUrl: ENDPOINT_URL } ); -const getUserSyncs = createGetUserSyncs(SYNC_URL, syncParamsRef); +const getUserSyncs = createGetUserSyncs(SYNC_URL); const interpretResponse = (serverResponse, request) => { return interpretResponseUtil(serverResponse, request, { diff --git a/modules/adsmartxBidAdapter.md b/modules/adsmartxBidAdapter.md index de3024a21f2..93b3fa6993a 100644 --- a/modules/adsmartxBidAdapter.md +++ b/modules/adsmartxBidAdapter.md @@ -2,7 +2,7 @@ Module Name : AdSmartX Bidder Adapter Module Type : Bid Adapter -Maintainer : prebid@risemediatech.io +Maintainer : prebid@aidigital.com # Description Connects to AdSmartX Exchange for bids @@ -14,7 +14,7 @@ This adapter is maintained by Smart Exchange, the legal entity behind this imple var adUnits = [ { code: 'test-banner-div', - mediatypes: { + mediaTypes: { banner: { sizes:[ [320,50] @@ -42,7 +42,7 @@ This adapter is maintained by Smart Exchange, the legal entity behind this imple var videoAdUnit = [ { code: 'adsmartx', - mediatypes: { + mediaTypes: { video: { playerSize: [640, 480], // required context: 'instream', diff --git a/modules/risemediatechBidAdapter.js b/modules/risemediatechBidAdapter.js index e4ed12686a8..15c9cd84e38 100644 --- a/modules/risemediatechBidAdapter.js +++ b/modules/risemediatechBidAdapter.js @@ -2,7 +2,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { logInfo } from '../src/utils.js'; import { - getPublisherUserId, createConverter, isBidRequestValid as validateBidRequest, createBuildRequests, @@ -14,13 +13,11 @@ const ENDPOINT_URL = 'https://dev-ads.risemediatech.com/ads/rtb/prebid/js'; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_TTL = 60; -const syncParamsRef = { current: {} }; const converter = createConverter({ defaultCurrency: DEFAULT_CURRENCY, defaultTtl: DEFAULT_TTL }); const isBidRequestValid = validateBidRequest; const buildRequests = createBuildRequests( - { converter, endpointUrl: ENDPOINT_URL, getPublisherUserId }, - syncParamsRef + { converter, endpointUrl: ENDPOINT_URL } ); const interpretResponse = (serverResponse, request) => { @@ -32,7 +29,7 @@ const interpretResponse = (serverResponse, request) => { const getUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { logInfo('User syncs are not implemented in this adapter yet.'); - return null; + return []; }; export const spec = { diff --git a/test/spec/modules/adsmartxBidAdapter_spec.js b/test/spec/modules/adsmartxBidAdapter_spec.js index 9bc9dbaad94..79f1a21009a 100644 --- a/test/spec/modules/adsmartxBidAdapter_spec.js +++ b/test/spec/modules/adsmartxBidAdapter_spec.js @@ -179,8 +179,8 @@ describe('AdSmartX 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 +241,8 @@ describe('AdSmartX 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', () => { @@ -499,7 +499,7 @@ describe('AdSmartX adapter', () => { 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://ads.adsmartx.com/sync'); + expect(syncs[0].url).to.include('https://sync.adsmartx.com/sync'); }); it('should return image sync when only pixelEnabled is true', () => { @@ -584,30 +584,11 @@ describe('AdSmartX adapter', () => { expect(syncs[0].url).to.not.include('gpp='); }); - it('should include custom sync parameters from bid params (sspId, siteId, sspUserId)', () => { - const bidWithParams = { - ...validBidRequest, - params: { - ...validBidRequest.params, - sspId: 'ssp123', - siteId: 'site456', - sspUserId: 'user789' - } - }; - - const testBidderRequest = { - ...bidderRequest, - bids: [bidWithParams] - }; - - // First build request to store sync params - spec.buildRequests([bidWithParams], testBidderRequest); - - // Then call getUserSyncs + it('should always include hardcoded ssp_id in sync URL', () => { const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); - expect(syncs[0].url).to.include('ssp_id=ssp123'); - expect(syncs[0].url).to.include('ssp_site_id=site456'); - expect(syncs[0].url).to.include('ssp_user_id=user789'); + 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', () => { @@ -621,23 +602,6 @@ describe('AdSmartX adapter', () => { }); it('should include all consent parameters together', () => { - const bidWithParams = { - ...validBidRequest, - params: { - ...validBidRequest.params, - sspId: 'ssp123', - siteId: 'site456', - sspUserId: 'user789' - } - }; - - const testBidderRequest = { - ...bidderRequest, - bids: [bidWithParams] - }; - - spec.buildRequests([bidWithParams], testBidderRequest); - const gppConsent = { gppString: 'DBABLA~1YNN', applicableSections: [7] @@ -656,9 +620,7 @@ describe('AdSmartX adapter', () => { 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=ssp123'); - expect(syncs[0].url).to.include('ssp_site_id=site456'); - expect(syncs[0].url).to.include('ssp_user_id=user789'); + expect(syncs[0].url).to.include('ssp_id=630141'); }); it('should handle missing GDPR consentString gracefully', () => { @@ -672,59 +634,20 @@ describe('AdSmartX adapter', () => { }); it('should retrieve sspUserId from ortb2.user.id when not in bid params', () => { - const bidWithoutUserId = { - ...validBidRequest, - params: { - ...validBidRequest.params, - sspId: 'ssp123' - } - }; - - const bidderRequestWithOrtb2 = { - ...bidderRequest, - bids: [bidWithoutUserId], - ortb2: { - user: { - id: 'ortb2-user-id-123' - } - } - }; - - spec.buildRequests([bidWithoutUserId], bidderRequestWithOrtb2); - + // 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_user_id=ortb2-user-id-123'); + 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', () => { - const bidWithUserId = { - ...validBidRequest, - params: { - ...validBidRequest.params, - sspUserId: 'bid-param-user-id' - } - }; - - const bidderRequestWithOrtb2 = { - ...bidderRequest, - bids: [bidWithUserId], - ortb2: { - user: { - id: 'ortb2-user-id-123' - } - } - }; - - spec.buildRequests([bidWithUserId], bidderRequestWithOrtb2); - + // 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_user_id=bid-param-user-id'); - expect(syncs[0].url).to.not.include('ortb2-user-id-123'); + expect(syncs[0].url).to.include('ssp_id=630141'); + expect(syncs[0].url).to.not.include('ssp_user_id'); }); - it('should generate sync URL with no query parameters when no data is available', () => { - // Clear any stored sync params by not calling buildRequests - // This tests the edge case where getUserSyncs is called without any prior context + it('should always include ssp_id and iframe_enabled in sync URL', () => { const syncs = spec.getUserSyncs( { iframeEnabled: true, pixelEnabled: false }, [], @@ -735,7 +658,8 @@ describe('AdSmartX adapter', () => { expect(syncs).to.be.an('array').with.lengthOf(1); expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.include('https://ads.adsmartx.com/sync'); + 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'); }); }); @@ -830,29 +754,11 @@ describe('AdSmartX adapter', () => { expect(request.data.ext).to.have.property('siteId', 'site456'); }); - it('should store sync parameters from first bid for getUserSyncs', () => { - const bidWithSyncParams = { - ...validBidRequest, - params: { - ...validBidRequest.params, - sspId: 'ssp789', - siteId: 'site012', - sspUserId: 'user345' - } - }; - - const testBidderRequest = { - ...bidderRequest, - bids: [bidWithSyncParams] - }; - - spec.buildRequests([bidWithSyncParams], testBidderRequest); - - // Verify params are stored by calling getUserSyncs + 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=ssp789'); - expect(syncs[0].url).to.include('ssp_site_id=site012'); - expect(syncs[0].url).to.include('ssp_user_id=user345'); + 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', () => { From a81a61a9a32497df50390f4b16b1be564f41e2cf Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Fri, 6 Mar 2026 16:36:44 +0530 Subject: [PATCH 10/14] RM-1476 : Handled copilot review comments --- modules/adsmartxBidAdapter.js | 2 + .../adsmartxUtils/bidderUtils_spec.js | 43 +++---------------- 2 files changed, 9 insertions(+), 36 deletions(-) diff --git a/modules/adsmartxBidAdapter.js b/modules/adsmartxBidAdapter.js index 934d22cabe2..bba9482052a 100644 --- a/modules/adsmartxBidAdapter.js +++ b/modules/adsmartxBidAdapter.js @@ -31,6 +31,8 @@ const interpretResponse = (serverResponse, request) => { export const spec = { code: BIDDER_CODE, + // TODO: set gvlid once confirmed with AI Digital / AdSmartX team + gvlid: undefined, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, buildRequests, diff --git a/test/spec/libraries/adsmartxUtils/bidderUtils_spec.js b/test/spec/libraries/adsmartxUtils/bidderUtils_spec.js index 9e668462324..e2a115bc550 100644 --- a/test/spec/libraries/adsmartxUtils/bidderUtils_spec.js +++ b/test/spec/libraries/adsmartxUtils/bidderUtils_spec.js @@ -75,12 +75,8 @@ describe('AdSmartX bidderUtils', () => { describe('createBuildRequests and interpretResponse', () => { const endpointUrl = 'https://test.endpoint.com/ads'; - const syncParamsRef = { current: {} }; const converter = createConverter(defaultConfig); - const buildRequests = createBuildRequests( - { converter, endpointUrl, getPublisherUserId }, - syncParamsRef - ); + const buildRequests = createBuildRequests({ converter, endpointUrl }); it('buildRequests returns POST request with endpoint and compressed option', () => { const validBidRequests = [ @@ -97,29 +93,6 @@ describe('AdSmartX bidderUtils', () => { expect(result.options).to.deep.include({ endpointCompression: true }); expect(result.data).to.be.an('object'); }); - - it('stores sync params when first bid has params', () => { - const syncRef = { current: {} }; - const buildReq = createBuildRequests( - { - converter: createConverter(defaultConfig), - endpointUrl: 'https://x.com', - getPublisherUserId: () => 'stored-user', - }, - syncRef - ); - const validBidRequests = [ - { - bidId: 'b1', - mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, - params: { sspId: 's1', siteId: 'site1' }, - }, - ]; - buildReq(validBidRequests, {}); - expect(syncRef.current.sspId).to.equal('s1'); - expect(syncRef.current.siteId).to.equal('site1'); - expect(syncRef.current.sspUserId).to.equal('stored-user'); - }); }); describe('interpretResponse', () => { @@ -185,10 +158,9 @@ describe('AdSmartX bidderUtils', () => { describe('createGetUserSyncs', () => { const syncUrl = 'https://ads.example.com/sync'; - const syncParamsRef = { current: {} }; it('returns empty array when iframe and pixel disabled', () => { - const getUserSyncs = createGetUserSyncs(syncUrl, syncParamsRef); + const getUserSyncs = createGetUserSyncs(syncUrl); const result = getUserSyncs( { iframeEnabled: false, pixelEnabled: false }, [], @@ -200,7 +172,7 @@ describe('AdSmartX bidderUtils', () => { }); it('returns sync with URL containing gdpr and iframe_enabled', () => { - const getUserSyncs = createGetUserSyncs(syncUrl, syncParamsRef); + const getUserSyncs = createGetUserSyncs(syncUrl); const result = getUserSyncs( { iframeEnabled: true, pixelEnabled: false }, [], @@ -215,9 +187,8 @@ describe('AdSmartX bidderUtils', () => { expect(result[0].url).to.include('iframe_enabled=true'); }); - it('appends ssp_id and ssp_site_id when syncParamsRef has them', () => { - const ref = { current: { sspId: 'ssp1', siteId: 'site1' } }; - const getUserSyncs = createGetUserSyncs(syncUrl, ref); + it('always includes hardcoded ssp_id=630141 and no ssp_site_id', () => { + const getUserSyncs = createGetUserSyncs(syncUrl); const result = getUserSyncs( { iframeEnabled: true, pixelEnabled: false }, [], @@ -225,8 +196,8 @@ describe('AdSmartX bidderUtils', () => { undefined, undefined ); - expect(result[0].url).to.include('ssp_id=ssp1'); - expect(result[0].url).to.include('ssp_site_id=site1'); + expect(result[0].url).to.include('ssp_id=630141'); + expect(result[0].url).to.not.include('ssp_site_id'); }); }); }); From 81a24801e0a67bcaab8932640d84e1bf6fd93b28 Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Tue, 10 Mar 2026 13:22:36 +0530 Subject: [PATCH 11/14] RM-1476 : Handled prebid js PR review comments --- libraries/adsmartxUtils/bidderUtils.js | 6 +++++- modules/risemediatechBidAdapter.js | 2 -- test/spec/modules/adsmartxBidAdapter_spec.js | 2 +- test/spec/modules/risemediatechBidAdapter_spec.js | 14 +++++++------- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/libraries/adsmartxUtils/bidderUtils.js b/libraries/adsmartxUtils/bidderUtils.js index 886ef880ae2..597403b72e9 100644 --- a/libraries/adsmartxUtils/bidderUtils.js +++ b/libraries/adsmartxUtils/bidderUtils.js @@ -178,6 +178,10 @@ export function interpretResponse(serverResponse, request, config = {}) { 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, @@ -253,7 +257,7 @@ export function createGetUserSyncs(syncUrl) { type: syncOptions.iframeEnabled ? 'iframe' : 'image', url: syncUrl + queryString, }]; - logInfo('Returning user syncs:', syncs); + logInfo('Returning user syncs, type:', syncs[0]?.type); return syncs; }; } diff --git a/modules/risemediatechBidAdapter.js b/modules/risemediatechBidAdapter.js index 15c9cd84e38..32fb68ce900 100644 --- a/modules/risemediatechBidAdapter.js +++ b/modules/risemediatechBidAdapter.js @@ -1,6 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { logInfo } from '../src/utils.js'; import { createConverter, isBidRequestValid as validateBidRequest, @@ -28,7 +27,6 @@ const interpretResponse = (serverResponse, request) => { }; const getUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { - logInfo('User syncs are not implemented in this adapter yet.'); return []; }; diff --git a/test/spec/modules/adsmartxBidAdapter_spec.js b/test/spec/modules/adsmartxBidAdapter_spec.js index 79f1a21009a..b50599d57c6 100644 --- a/test/spec/modules/adsmartxBidAdapter_spec.js +++ b/test/spec/modules/adsmartxBidAdapter_spec.js @@ -484,7 +484,7 @@ describe('AdSmartX 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; }); }); diff --git a/test/spec/modules/risemediatechBidAdapter_spec.js b/test/spec/modules/risemediatechBidAdapter_spec.js index 0aee4817a23..a5d2b4e7369 100644 --- a/test/spec/modules/risemediatechBidAdapter_spec.js +++ b/test/spec/modules/risemediatechBidAdapter_spec.js @@ -179,8 +179,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 +241,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', () => { @@ -484,14 +484,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; }); }); }); From 089ee2a32806cf6cc3888c54083a8b6b24466e34 Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Tue, 14 Apr 2026 13:34:44 +0530 Subject: [PATCH 12/14] RM-1476 : Handled review comment to log a warning that risemediatech bid adapter is deprecated --- modules/risemediatechBidAdapter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/risemediatechBidAdapter.js b/modules/risemediatechBidAdapter.js index 32fb68ce900..d0220fa7611 100644 --- a/modules/risemediatechBidAdapter.js +++ b/modules/risemediatechBidAdapter.js @@ -6,6 +6,7 @@ import { 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'; @@ -14,7 +15,8 @@ const DEFAULT_TTL = 60; const converter = createConverter({ defaultCurrency: DEFAULT_CURRENCY, defaultTtl: DEFAULT_TTL }); -const isBidRequestValid = validateBidRequest; +const isBidRequestValid = false; +logWarn('Risemediatech Bid Adapter has been deprecated. Hence disabling this adapter by rejecting bid requests by default.'); const buildRequests = createBuildRequests( { converter, endpointUrl: ENDPOINT_URL } ); From 34fc43a9f3e97340aec8a85509bedeeac7850453 Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Tue, 14 Apr 2026 14:02:21 +0530 Subject: [PATCH 13/14] RM-1476: Added a function to disable the adapter. --- modules/risemediatechBidAdapter.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/risemediatechBidAdapter.js b/modules/risemediatechBidAdapter.js index d0220fa7611..2ac104e3b7c 100644 --- a/modules/risemediatechBidAdapter.js +++ b/modules/risemediatechBidAdapter.js @@ -2,7 +2,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { createConverter, - isBidRequestValid as validateBidRequest, createBuildRequests, interpretResponse as interpretResponseUtil, } from '../libraries/adsmartxUtils/bidderUtils.js'; @@ -15,8 +14,12 @@ const DEFAULT_TTL = 60; const converter = createConverter({ defaultCurrency: DEFAULT_CURRENCY, defaultTtl: DEFAULT_TTL }); -const isBidRequestValid = false; -logWarn('Risemediatech Bid Adapter has been deprecated. Hence disabling this adapter by rejecting bid requests by default.'); +export function disableAdapter() { + logWarn('Risemediatech Bid Adapter has been deprecated. Hence disabling this adapter by rejecting bid requests by default.'); + return false; +} + +const isBidRequestValid = disableAdapter(); const buildRequests = createBuildRequests( { converter, endpointUrl: ENDPOINT_URL } ); From 7844adc768cb1b69b3fe97fe9bfc9639ecdd61aa Mon Sep 17 00:00:00 2001 From: pritishmd-talentica Date: Tue, 14 Apr 2026 15:48:02 +0530 Subject: [PATCH 14/14] RM-1476 : Updated unit tests --- .../modules/risemediatechBidAdapter_spec.js | 129 +++--------------- 1 file changed, 19 insertions(+), 110 deletions(-) diff --git a/test/spec/modules/risemediatechBidAdapter_spec.js b/test/spec/modules/risemediatechBidAdapter_spec.js index a5d2b4e7369..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); + 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 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', () => { + 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); }); });