From 2d765118b7a8b89169ef024d126212ef4a2cff01 Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Mon, 25 Aug 2025 11:49:01 -0400 Subject: [PATCH 01/13] Add AlvaDS Bid Adapter for banner and video - Implement alvadsBidAdapter module supporting OpenRTB 2.5 POST requests - Supports dynamic endpoints via bid params or default endpoint - Handles banner and video media types with fallback sizes - Parses responses to set mediaType and extract vastXml/vastUrl for video - Includes example test parameters for banner and video - Default netRevenue: true, TTL: 300 - Callback support for onTimeout and onBidWon --- modules/alvadsBidAdapter.js | 147 ++++++++++++++++++++++++ modules/alvadsBidAdapter.md | 123 ++++++++++++++++++++ test/spec/alvadsBidAdapter_spec.js | 173 +++++++++++++++++++++++++++++ 3 files changed, 443 insertions(+) create mode 100644 modules/alvadsBidAdapter.js create mode 100644 modules/alvadsBidAdapter.md create mode 100644 test/spec/alvadsBidAdapter_spec.js diff --git a/modules/alvadsBidAdapter.js b/modules/alvadsBidAdapter.js new file mode 100644 index 00000000000..d4e9d6a9dd6 --- /dev/null +++ b/modules/alvadsBidAdapter.js @@ -0,0 +1,147 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import * as utils from '../src/utils.js'; + +const BIDDER_CODE = 'alvads'; +const ENDPOINT_BANNER = 'https://helios-ads-qa-core.ssidevops.com/decision/openrtb'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + isBidRequestValid: (bid) => { + return Boolean( + bid.params && + bid.params.publisherId && + (bid.mediaTypes?.banner ? bid.params.tagid : true) + ); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bid => { + const imps = []; + + // Banner + if (bid.mediaTypes?.banner) { + const sizes = utils.parseSizesInput(bid.mediaTypes.banner.sizes || bid.sizes) + .map(s => { + const parts = s.split('x').map(Number); + return { w: parts[0], h: parts[1] }; + }); + + sizes.forEach(size => { + imps.push({ + id: bid.bidId, + banner: { w: size.w, h: size.h }, + tagid: bid.params.tagid, + bidfloor: bid.params.bidfloor || 0, + ext: { userId: bid.params.userId } + }); + }); + } + + // Video + if (bid.mediaTypes?.video) { + const wh = (bid.mediaTypes.video.playerSize && bid.mediaTypes.video.playerSize[0]) || [1280, 720]; + imps.push({ + id: bid.bidId, + video: { w: wh[0], h: wh[1] }, + tagid: bid.params.tagid, + bidfloor: bid.params.bidfloor || 0, + ext: { userId: bid.params.userId } + }); + } + + // Payload OpenRTB por bid + const payload = { + id: 'REQ-OPENRTB-' + Date.now(), + site: { + page: bidderRequest.refererInfo.page, + ref: document.referrer || bidderRequest.refererInfo.page, + publisher: { id: bid.params.publisherId } + }, + imp: imps, + device: { + ua: navigator.userAgent, + ip: bid.params.ip || '0.0.0.0', + geo: bid.params.geo || {} + }, + user: { + id: bid.params.userId || utils.generateUUID(), + buyeruid: utils.generateUUID() + }, + regs: { + gpp: '', + gpp_sid: [], + ext: { + gdpr: bidderRequest.gdprConsent ? 1 : 0, + us_privacy: bidderRequest.uspConsent || '1YNN' + } + }, + ext: { + user_fingerprint: utils.generateUUID() + } + }; + + const endpoint = bid.params.endpoint || ENDPOINT_BANNER; + + return { + method: 'POST', + url: endpoint, + data: JSON.stringify(payload), + options: { contentType: 'application/json', withCredentials: false }, + bid: bid + }; + }); + }, + + interpretResponse: (serverResponse) => { + const bidResponses = []; + const body = serverResponse.body; + + // --- Banners OpenRTB --- + if (body && body.seatbid) { + body.seatbid.forEach(seat => { + seat.bid.forEach(bid => { + const isVideo = bid.adm && bid.adm.includes(' { + utils.logWarn('Timeout bids ALVA:', timeoutData); + }, + + onBidWon: (bid) => { + utils.logInfo('Bid winner ALVA:', bid); + } +}; + +registerBidder(spec); diff --git a/modules/alvadsBidAdapter.md b/modules/alvadsBidAdapter.md new file mode 100644 index 00000000000..359048f3b82 --- /dev/null +++ b/modules/alvadsBidAdapter.md @@ -0,0 +1,123 @@ +# Overview +**Module Name:** alvadsBidAdapter +**Module Type:** bidder +**Maintainer:** alvads@oyealva.com + +--- + +# Description +The **Alva Bid Adapter** allows publishers to connect their banner and video inventory with the Alva demand platform. + +- **Bidder Code:** `alvads` +- **Supported Media Types:** `banner`, `video` +- **Protocols:** OpenRTB 2.5 via POST for both banner and video +- **Dynamic Endpoints:** The adapter can use a default endpoint or a custom endpoint provided in the bid params. + +--- + +# Test Parameters + +## Banner Example + +```javascript +var adUnits = [{ + code: 'div-banner', + mediaTypes: { + banner: { + sizes: [[300, 250], [320, 100]] + } + }, + bids: [{ + bidder: 'alvads', + params: { + publisherId: 'pub-123', // required + tagid: 'tag-456', // required for banner + bidfloor: 0.50, // optional + userId: '+59165352182', // optional + endpoint: 'https://custom-endpoint.com/openrtb' // optional, overrides default + } + }] +}]; +``` + +## Video Example + +```javascript +var adUnits = [{ + code: 'video-ad', + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 360]] + } + }, + bids: [{ + bidder: 'alvads', + params: { + publisherId: 'pub-123', // required + bidfloor: 0.5, // optional + userId: '+59165352182', // optional + endpoint: 'https://custom-endpoint.com/video' // optional, overrides default + } + }] +}]; +``` + +--- + +# Request Information + +### Banner / Video +- **Endpoint:** + ``` + https://helios-ads-qa-core.ssidevops.com/decision/openrtb + ``` +- **Method:** `POST` +- **Payload:** OpenRTB 2.5 request containing `site`, `device`, `user`, `regs`, `imp`. +- **Dynamic Endpoint:** The request URL can be overridden by bid.params.endpoint. + + +# Response Information + +### Banner +The response is standard OpenRTB with `seatbid`. Example: + +```json +{ + "id": "response-id", + "seatbid": [{ + "bid": [{ + "impid": "imp-123", + "price": 0.50, + "adm": "
Creative
", + "crid": "creative-1", + "w": 320, + "h": 100, + "ext": { + "vast_url": "http://example.com/vast.xml" + }, + "adomain": ["example.com"] + }] + }], + "cur": "USD" +} + +``` +# Interpretation: + +If adm contains , the adapter sets mediaType: 'video' and includes vastXml & vastUrl. + +Otherwise, mediaType: 'banner' and ad contains the HTML. + + +# Additional Details + +- **Defaults:** + - `netRevenue = true` + - `ttl = 300` + - Banner fallback size: `320x100` + - Video fallback size: `1280x720` + +- **Callbacks:** + - `onTimeout` → logs timeout events + - `onBidWon` → logs winning bid diff --git a/test/spec/alvadsBidAdapter_spec.js b/test/spec/alvadsBidAdapter_spec.js new file mode 100644 index 00000000000..e5a8aa885a8 --- /dev/null +++ b/test/spec/alvadsBidAdapter_spec.js @@ -0,0 +1,173 @@ +import { expect } from 'chai'; +import { spec } from 'modules/alvadsBidAdapter.js'; + +describe('ALVADS Bid Adapter', function() { + const bannerBid = { + bidId: 'banner-1', + mediaTypes: { banner: { sizes: [[320, 100]] } }, + params: { + publisherId: 'D7DACCE3-C23D-4AB9-8FE6-9FF41BF32F8F', + tagid: 'zone-001', + bidfloor: 0.1, + userId: 'user-001' + } + }; + + const videoBid = { + bidId: 'video-1', + mediaTypes: { video: { playerSize: [[1280, 720]] } }, + params: { + publisherId: 'D7DACCE3-C23D-4AB9-8FE6-9FF41BF32F8F', + bidfloor: 0.5, + userId: 'user-002', + language: 'en', + count: 1 + } + }; + + const bidderRequestBanner = { + refererInfo: { page: "http://localhost:1200" }, + gdprConsent: true, + uspConsent: '1YNN' + }; + const bidderRequestVideo = { + refererInfo: { page: "https://instagram.com" }, + gdprConsent: true, + uspConsent: '1YNN' + }; + + // ----------------------------- + describe('isBidRequestValid', function() { + it('validates banner bid requests', function() { + expect(spec.isBidRequestValid(bannerBid)).to.be.true; + }); + + it('validates video bid requests', function() { + expect(spec.isBidRequestValid(videoBid)).to.be.true; + }); + + it('rejects invalid bid requests', function() { + expect(spec.isBidRequestValid({})).to.be.false; + }); + }); + + // ----------------------------- + describe('buildRequests', function() { + it('uses default endpoint if none provided', function() { + const requests = spec.buildRequests([bannerBid], bidderRequestBanner); + expect(requests[0].url).to.equal('https://helios-ads-qa-core.ssidevops.com/decision/openrtb'); + }); + + it('uses custom endpoint from bid params', function() { + const customBid = { + ...bannerBid, + params: { ...bannerBid.params, endpoint: 'https://helios-ads-qa-core.ssidevops.com/decision/openrtb' } + }; + const requests = spec.buildRequests([customBid], bidderRequestBanner); + expect(requests[0].url).to.equal('https://helios-ads-qa-core.ssidevops.com/decision/openrtb'); + }); + + it('builds correct banner request payload', function() { + const requests = spec.buildRequests([bannerBid], bidderRequestBanner); + const request = requests[0]; + const data = JSON.parse(request.data); + + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].banner).to.deep.equal({ w: 320, h: 100 }); + expect(data.imp[0].tagid).to.equal(bannerBid.params.tagid); + expect(data.site.publisher.id).to.equal(bannerBid.params.publisherId); + }); + + it('builds correct video request payload', function() { + const requests = spec.buildRequests([videoBid], bidderRequestVideo); + const request = requests[0]; + const data = JSON.parse(request.data); + + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].video).to.deep.equal({ w: 1280, h: 720 }); + expect(data.imp[0].tagid).to.equal(videoBid.params.tagid); + }); + }); + + // ----------------------------- + describe('interpretResponse', function() { + it('returns empty array if no bids', function() { + const serverResponse = { body: { seatbid: [] } }; + const result = spec.interpretResponse(serverResponse, { bid: bannerBid }); + expect(result).to.have.lengthOf(0); + }); + + it('interprets banner bid response', function() { + const serverResponse = { + body: { + seatbid: [ + { bid: [{ impid: 'banner-1', price: 1.2, w: 320, h: 100, crid: 'c1', adm: '
ad
' }] } + ], + cur: 'USD' + } + }; + + const result = spec.interpretResponse(serverResponse, { bid: bannerBid }); + expect(result).to.have.lengthOf(1); + const r = result[0]; + expect(r.mediaType).to.equal('banner'); + expect(r.cpm).to.equal(1.2); + expect(r.ad).to.equal('
ad
'); + expect(r.width).to.equal(320); + expect(r.height).to.equal(100); + expect(r.creativeId).to.equal('c1'); + }); + + it('interprets video bid response', function() { + const serverResponse = { + body: { + seatbid: [ + { bid: [{ impid: 'video-1', price: 2.5, w: 1280, h: 720, crid: 'v1', adm: '', ext: { vast_url: 'http://vast.url' }, adomain: ['example.com'] }] } + ], + cur: 'USD' + } + }; + + const result = spec.interpretResponse(serverResponse, { bid: videoBid }); + expect(result).to.have.lengthOf(1); + const r = result[0]; + expect(r.mediaType).to.equal('video'); + expect(r.cpm).to.equal(2.5); + expect(r.vastXml).to.equal(''); + expect(r.vastUrl).to.equal('http://vast.url'); + expect(r.width).to.equal(1280); + expect(r.height).to.equal(720); + expect(r.creativeId).to.equal('v1'); + expect(r.meta.advertiserDomains).to.deep.equal(['example.com']); + }); + }); + + // ----------------------------- + describe('onTimeout', function() { + it('calls logWarn with timeout data', function() { + const logs = []; + const original = spec.onTimeout; + spec.onTimeout = (data) => logs.push(data); + + spec.onTimeout({ bidId: 'timeout-1' }); + expect(logs).to.have.lengthOf(1); + expect(logs[0].bidId).to.equal('timeout-1'); + + spec.onTimeout = original; + }); + }); + + describe('onBidWon', function() { + it('calls logInfo with bid won', function() { + const logs = []; + const original = spec.onBidWon; + spec.onBidWon = (bid) => logs.push(bid); + + spec.onBidWon({ bidId: 'won-1' }); + expect(logs).to.have.lengthOf(1); + expect(logs[0].bidId).to.equal('won-1'); + + spec.onBidWon = original; + }); + }); +}); From bb819be8859cbf1161ccbb047b16a51291d46d70 Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Mon, 25 Aug 2025 14:10:02 -0400 Subject: [PATCH 02/13] fixed test --- test/spec/alvadsBidAdapter_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/spec/alvadsBidAdapter_spec.js b/test/spec/alvadsBidAdapter_spec.js index e5a8aa885a8..1fc95cd9921 100644 --- a/test/spec/alvadsBidAdapter_spec.js +++ b/test/spec/alvadsBidAdapter_spec.js @@ -85,7 +85,6 @@ describe('ALVADS Bid Adapter', function() { expect(data.imp).to.have.lengthOf(1); expect(data.imp[0].video).to.deep.equal({ w: 1280, h: 720 }); - expect(data.imp[0].tagid).to.equal(videoBid.params.tagid); }); }); From 9b22914eb94e912c2256d550f0c254f947bccd38 Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Wed, 27 Aug 2025 08:27:45 -0400 Subject: [PATCH 03/13] alvads --- test/spec/alvadsBidAdapter_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/spec/alvadsBidAdapter_spec.js b/test/spec/alvadsBidAdapter_spec.js index 1fc95cd9921..a61551e47c4 100644 --- a/test/spec/alvadsBidAdapter_spec.js +++ b/test/spec/alvadsBidAdapter_spec.js @@ -87,7 +87,6 @@ describe('ALVADS Bid Adapter', function() { expect(data.imp[0].video).to.deep.equal({ w: 1280, h: 720 }); }); }); - // ----------------------------- describe('interpretResponse', function() { it('returns empty array if no bids', function() { From b298c79c25400a24adf86581212fbbc741a0d671 Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Wed, 27 Aug 2025 10:56:23 -0400 Subject: [PATCH 04/13] removed application/json header --- modules/alvadsBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/alvadsBidAdapter.js b/modules/alvadsBidAdapter.js index d4e9d6a9dd6..ac3fc5bb131 100644 --- a/modules/alvadsBidAdapter.js +++ b/modules/alvadsBidAdapter.js @@ -87,7 +87,7 @@ export const spec = { method: 'POST', url: endpoint, data: JSON.stringify(payload), - options: { contentType: 'application/json', withCredentials: false }, + options: { withCredentials: false }, bid: bid }; }); From 014113e186406bb047d54a64ded68950c575e03b Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Wed, 27 Aug 2025 13:24:48 -0400 Subject: [PATCH 05/13] removed application/json header --- modules/alvadsBidAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/alvadsBidAdapter.js b/modules/alvadsBidAdapter.js index ac3fc5bb131..c03e9efc91d 100644 --- a/modules/alvadsBidAdapter.js +++ b/modules/alvadsBidAdapter.js @@ -80,7 +80,6 @@ export const spec = { user_fingerprint: utils.generateUUID() } }; - const endpoint = bid.params.endpoint || ENDPOINT_BANNER; return { From f0143eddecf7bd2facefdd445685d5e7d9807186 Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Tue, 2 Sep 2025 08:27:15 -0400 Subject: [PATCH 06/13] ready for review --- modules/alvadsBidAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/alvadsBidAdapter.js b/modules/alvadsBidAdapter.js index c03e9efc91d..0e5b4b06566 100644 --- a/modules/alvadsBidAdapter.js +++ b/modules/alvadsBidAdapter.js @@ -18,7 +18,6 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { return validBidRequests.map(bid => { const imps = []; - // Banner if (bid.mediaTypes?.banner) { const sizes = utils.parseSizesInput(bid.mediaTypes.banner.sizes || bid.sizes) From 1b1077e475babf277574f7e96712192fc94041d8 Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Tue, 2 Sep 2025 09:16:43 -0400 Subject: [PATCH 07/13] ready for review --- modules/alvadsBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/alvadsBidAdapter.js b/modules/alvadsBidAdapter.js index 0e5b4b06566..c03e9efc91d 100644 --- a/modules/alvadsBidAdapter.js +++ b/modules/alvadsBidAdapter.js @@ -18,6 +18,7 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { return validBidRequests.map(bid => { const imps = []; + // Banner if (bid.mediaTypes?.banner) { const sizes = utils.parseSizesInput(bid.mediaTypes.banner.sizes || bid.sizes) From bc81fb82ad0974def5bdde0ed6e8de8e5d00dcf7 Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Tue, 2 Sep 2025 09:25:39 -0400 Subject: [PATCH 08/13] ready for review --- modules/alvadsBidAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/alvadsBidAdapter.js b/modules/alvadsBidAdapter.js index c03e9efc91d..0e5b4b06566 100644 --- a/modules/alvadsBidAdapter.js +++ b/modules/alvadsBidAdapter.js @@ -18,7 +18,6 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { return validBidRequests.map(bid => { const imps = []; - // Banner if (bid.mediaTypes?.banner) { const sizes = utils.parseSizesInput(bid.mediaTypes.banner.sizes || bid.sizes) From b3946772af4cdebfc5b35d3f789340469aa1cd27 Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Thu, 4 Sep 2025 08:33:59 -0400 Subject: [PATCH 09/13] added some methods --- modules/alvadsBidAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/alvadsBidAdapter.js b/modules/alvadsBidAdapter.js index 0e5b4b06566..8cb5e416226 100644 --- a/modules/alvadsBidAdapter.js +++ b/modules/alvadsBidAdapter.js @@ -129,7 +129,6 @@ export const spec = { }); }); } - return bidResponses; }, From 08b00fd46ff01c9a8b3f30f4a06b6efa27d35959 Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Thu, 4 Sep 2025 16:00:18 -0400 Subject: [PATCH 10/13] added some methods --- modules/alvadsBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/alvadsBidAdapter.js b/modules/alvadsBidAdapter.js index 8cb5e416226..4a2e0492e92 100644 --- a/modules/alvadsBidAdapter.js +++ b/modules/alvadsBidAdapter.js @@ -132,6 +132,7 @@ export const spec = { return bidResponses; }, + onTimeout: (timeoutData) => { utils.logWarn('Timeout bids ALVA:', timeoutData); }, From 20981e7ccb619b4aa61525e34d20b5bb0847013d Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Thu, 4 Sep 2025 16:01:16 -0400 Subject: [PATCH 11/13] added some methods --- modules/alvadsBidAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/alvadsBidAdapter.js b/modules/alvadsBidAdapter.js index 4a2e0492e92..8cb5e416226 100644 --- a/modules/alvadsBidAdapter.js +++ b/modules/alvadsBidAdapter.js @@ -132,7 +132,6 @@ export const spec = { return bidResponses; }, - onTimeout: (timeoutData) => { utils.logWarn('Timeout bids ALVA:', timeoutData); }, From 667dae7a2b399e3d8741f00f467b6034946c5db3 Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Thu, 11 Sep 2025 09:57:48 -0400 Subject: [PATCH 12/13] ready for review --- modules/alvadsBidAdapter.js | 35 ++++++++++++++++++++++------------- modules/alvadsBidAdapter.md | 12 ++++++++++++ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/modules/alvadsBidAdapter.js b/modules/alvadsBidAdapter.js index 8cb5e416226..03f7c4ac287 100644 --- a/modules/alvadsBidAdapter.js +++ b/modules/alvadsBidAdapter.js @@ -1,22 +1,30 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; - +import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'alvads'; const ENDPOINT_BANNER = 'https://helios-ads-qa-core.ssidevops.com/decision/openrtb'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: ['banner', 'video'], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: (bid) => { return Boolean( bid.params && bid.params.publisherId && - (bid.mediaTypes?.banner ? bid.params.tagid : true) + (bid.mediaTypes?.[BANNER] ? bid.params.tagid : true) ); }, buildRequests: function(validBidRequests, bidderRequest) { return validBidRequests.map(bid => { + const floorInfo = (typeof bid.getFloor === 'function') + ? bid.getFloor({ + currency: 'USD', + mediaType: bid.mediaTypes?.banner ? BANNER : VIDEO, + size: '*' + }) + : { floor: 0, currency: 'USD' }; + const imps = []; // Banner if (bid.mediaTypes?.banner) { @@ -31,7 +39,8 @@ export const spec = { id: bid.bidId, banner: { w: size.w, h: size.h }, tagid: bid.params.tagid, - bidfloor: bid.params.bidfloor || 0, + bidfloor: floorInfo.floor, + bidfloorcur: floorInfo.currency, ext: { userId: bid.params.userId } }); }); @@ -44,7 +53,8 @@ export const spec = { id: bid.bidId, video: { w: wh[0], h: wh[1] }, tagid: bid.params.tagid, - bidfloor: bid.params.bidfloor || 0, + bidfloor: floorInfo.floor, + bidfloorcur: floorInfo.currency, ext: { userId: bid.params.userId } }); } @@ -54,14 +64,12 @@ export const spec = { id: 'REQ-OPENRTB-' + Date.now(), site: { page: bidderRequest.refererInfo.page, - ref: document.referrer || bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.ref, publisher: { id: bid.params.publisherId } }, imp: imps, device: { - ua: navigator.userAgent, - ip: bid.params.ip || '0.0.0.0', - geo: bid.params.geo || {} + ua: navigator.userAgent }, user: { id: bid.params.userId || utils.generateUUID(), @@ -72,7 +80,7 @@ export const spec = { gpp_sid: [], ext: { gdpr: bidderRequest.gdprConsent ? 1 : 0, - us_privacy: bidderRequest.uspConsent || '1YNN' + ...(bidderRequest.uspConsent && { us_privacy: bidderRequest.uspConsent }) } }, ext: { @@ -108,13 +116,14 @@ export const spec = { creativeId: bid.crid || bid.id, currency: body.cur || 'USD', netRevenue: true, - ttl: 300 + ttl: 300, + meta: { advertiserDomains: bid.adomain || [] } }; if (isVideo) { bidResponses.push({ ...common, - mediaType: 'video', + mediaType: VIDEO, vastXml: bid.adm, vastUrl: bid.ext && bid.ext.vast_url ? bid.ext.vast_url : undefined, meta: { advertiserDomains: bid.adomain || [] } @@ -122,7 +131,7 @@ export const spec = { } else { bidResponses.push({ ...common, - mediaType: 'banner', + mediaType: BANNER, ad: bid.adm }); } diff --git a/modules/alvadsBidAdapter.md b/modules/alvadsBidAdapter.md index 359048f3b82..b85d6df968d 100644 --- a/modules/alvadsBidAdapter.md +++ b/modules/alvadsBidAdapter.md @@ -12,6 +12,18 @@ The **Alva Bid Adapter** allows publishers to connect their banner and video inv - **Supported Media Types:** `banner`, `video` - **Protocols:** OpenRTB 2.5 via POST for both banner and video - **Dynamic Endpoints:** The adapter can use a default endpoint or a custom endpoint provided in the bid params. +- **Price Floors:** Supported via `bid.getFloor()`. If configured, the adapter will send `bidfloor` and `bidfloorcur` per impression. + +--- +# Parameters + +| Parameter | Required | Description | +|------------ |---------------- |------------ | +| publisherId | Yes | Publisher ID assigned by Alva | +| tagid | Banner only | Required for banner impressions | +| bidfloor | No | Optional; adapter supports floors module via `bid.getFloor()` | +| userId | No | Optional; used for user identification | +| endpoint | No | Optional; overrides default endpoint | --- From 7094206b06c41d54834cacca01000f2e910af1b6 Mon Sep 17 00:00:00 2001 From: Samuel Alejandro Maldonado Garcia Date: Thu, 25 Sep 2025 09:51:49 -0400 Subject: [PATCH 13/13] fixes --- modules/alvadsBidAdapter.js | 6 +- test/spec/modules/alvadsBidAdapter_spec.js | 171 +++++++++++++++++++++ 2 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 test/spec/modules/alvadsBidAdapter_spec.js diff --git a/modules/alvadsBidAdapter.js b/modules/alvadsBidAdapter.js index 03f7c4ac287..7a56ba549a5 100644 --- a/modules/alvadsBidAdapter.js +++ b/modules/alvadsBidAdapter.js @@ -79,8 +79,7 @@ export const spec = { gpp: '', gpp_sid: [], ext: { - gdpr: bidderRequest.gdprConsent ? 1 : 0, - ...(bidderRequest.uspConsent && { us_privacy: bidderRequest.uspConsent }) + gdpr: Number(bidderRequest.gdprConsent?.gdprApplies) } }, ext: { @@ -93,8 +92,7 @@ export const spec = { method: 'POST', url: endpoint, data: JSON.stringify(payload), - options: { withCredentials: false }, - bid: bid + options: { withCredentials: false } }; }); }, diff --git a/test/spec/modules/alvadsBidAdapter_spec.js b/test/spec/modules/alvadsBidAdapter_spec.js new file mode 100644 index 00000000000..a61551e47c4 --- /dev/null +++ b/test/spec/modules/alvadsBidAdapter_spec.js @@ -0,0 +1,171 @@ +import { expect } from 'chai'; +import { spec } from 'modules/alvadsBidAdapter.js'; + +describe('ALVADS Bid Adapter', function() { + const bannerBid = { + bidId: 'banner-1', + mediaTypes: { banner: { sizes: [[320, 100]] } }, + params: { + publisherId: 'D7DACCE3-C23D-4AB9-8FE6-9FF41BF32F8F', + tagid: 'zone-001', + bidfloor: 0.1, + userId: 'user-001' + } + }; + + const videoBid = { + bidId: 'video-1', + mediaTypes: { video: { playerSize: [[1280, 720]] } }, + params: { + publisherId: 'D7DACCE3-C23D-4AB9-8FE6-9FF41BF32F8F', + bidfloor: 0.5, + userId: 'user-002', + language: 'en', + count: 1 + } + }; + + const bidderRequestBanner = { + refererInfo: { page: "http://localhost:1200" }, + gdprConsent: true, + uspConsent: '1YNN' + }; + const bidderRequestVideo = { + refererInfo: { page: "https://instagram.com" }, + gdprConsent: true, + uspConsent: '1YNN' + }; + + // ----------------------------- + describe('isBidRequestValid', function() { + it('validates banner bid requests', function() { + expect(spec.isBidRequestValid(bannerBid)).to.be.true; + }); + + it('validates video bid requests', function() { + expect(spec.isBidRequestValid(videoBid)).to.be.true; + }); + + it('rejects invalid bid requests', function() { + expect(spec.isBidRequestValid({})).to.be.false; + }); + }); + + // ----------------------------- + describe('buildRequests', function() { + it('uses default endpoint if none provided', function() { + const requests = spec.buildRequests([bannerBid], bidderRequestBanner); + expect(requests[0].url).to.equal('https://helios-ads-qa-core.ssidevops.com/decision/openrtb'); + }); + + it('uses custom endpoint from bid params', function() { + const customBid = { + ...bannerBid, + params: { ...bannerBid.params, endpoint: 'https://helios-ads-qa-core.ssidevops.com/decision/openrtb' } + }; + const requests = spec.buildRequests([customBid], bidderRequestBanner); + expect(requests[0].url).to.equal('https://helios-ads-qa-core.ssidevops.com/decision/openrtb'); + }); + + it('builds correct banner request payload', function() { + const requests = spec.buildRequests([bannerBid], bidderRequestBanner); + const request = requests[0]; + const data = JSON.parse(request.data); + + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].banner).to.deep.equal({ w: 320, h: 100 }); + expect(data.imp[0].tagid).to.equal(bannerBid.params.tagid); + expect(data.site.publisher.id).to.equal(bannerBid.params.publisherId); + }); + + it('builds correct video request payload', function() { + const requests = spec.buildRequests([videoBid], bidderRequestVideo); + const request = requests[0]; + const data = JSON.parse(request.data); + + expect(data.imp).to.have.lengthOf(1); + expect(data.imp[0].video).to.deep.equal({ w: 1280, h: 720 }); + }); + }); + // ----------------------------- + describe('interpretResponse', function() { + it('returns empty array if no bids', function() { + const serverResponse = { body: { seatbid: [] } }; + const result = spec.interpretResponse(serverResponse, { bid: bannerBid }); + expect(result).to.have.lengthOf(0); + }); + + it('interprets banner bid response', function() { + const serverResponse = { + body: { + seatbid: [ + { bid: [{ impid: 'banner-1', price: 1.2, w: 320, h: 100, crid: 'c1', adm: '
ad
' }] } + ], + cur: 'USD' + } + }; + + const result = spec.interpretResponse(serverResponse, { bid: bannerBid }); + expect(result).to.have.lengthOf(1); + const r = result[0]; + expect(r.mediaType).to.equal('banner'); + expect(r.cpm).to.equal(1.2); + expect(r.ad).to.equal('
ad
'); + expect(r.width).to.equal(320); + expect(r.height).to.equal(100); + expect(r.creativeId).to.equal('c1'); + }); + + it('interprets video bid response', function() { + const serverResponse = { + body: { + seatbid: [ + { bid: [{ impid: 'video-1', price: 2.5, w: 1280, h: 720, crid: 'v1', adm: '', ext: { vast_url: 'http://vast.url' }, adomain: ['example.com'] }] } + ], + cur: 'USD' + } + }; + + const result = spec.interpretResponse(serverResponse, { bid: videoBid }); + expect(result).to.have.lengthOf(1); + const r = result[0]; + expect(r.mediaType).to.equal('video'); + expect(r.cpm).to.equal(2.5); + expect(r.vastXml).to.equal(''); + expect(r.vastUrl).to.equal('http://vast.url'); + expect(r.width).to.equal(1280); + expect(r.height).to.equal(720); + expect(r.creativeId).to.equal('v1'); + expect(r.meta.advertiserDomains).to.deep.equal(['example.com']); + }); + }); + + // ----------------------------- + describe('onTimeout', function() { + it('calls logWarn with timeout data', function() { + const logs = []; + const original = spec.onTimeout; + spec.onTimeout = (data) => logs.push(data); + + spec.onTimeout({ bidId: 'timeout-1' }); + expect(logs).to.have.lengthOf(1); + expect(logs[0].bidId).to.equal('timeout-1'); + + spec.onTimeout = original; + }); + }); + + describe('onBidWon', function() { + it('calls logInfo with bid won', function() { + const logs = []; + const original = spec.onBidWon; + spec.onBidWon = (bid) => logs.push(bid); + + spec.onBidWon({ bidId: 'won-1' }); + expect(logs).to.have.lengthOf(1); + expect(logs[0].bidId).to.equal('won-1'); + + spec.onBidWon = original; + }); + }); +});