From 3f3c4324289fca470d0f7db254ec37ace1cff333 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Thu, 23 Apr 2026 11:48:08 +0200 Subject: [PATCH 1/5] Nexx360 Utils Library: add shared getGzipSetting helper Co-Authored-By: Claude Opus 4.7 (1M context) --- libraries/nexx360Utils/index.ts | 23 ++++-- modules/adgridBidAdapter.ts | 5 +- modules/insuradsBidAdapter.ts | 15 +--- modules/nexx360BidAdapter.ts | 15 +--- modules/revnewBidAdapter.ts | 5 +- .../nexx360Utils/getGzipSetting_spec.js | 70 +++++++++++++++++++ test/spec/modules/nexx360BidAdapter_spec.js | 46 ++++++++++-- 7 files changed, 143 insertions(+), 36 deletions(-) create mode 100644 test/spec/libraries/nexx360Utils/getGzipSetting_spec.js diff --git a/libraries/nexx360Utils/index.ts b/libraries/nexx360Utils/index.ts index f0bb2ce39ea..dcda56e7546 100644 --- a/libraries/nexx360Utils/index.ts +++ b/libraries/nexx360Utils/index.ts @@ -1,5 +1,6 @@ -import { deepAccess, deepSetValue, generateUUID, logInfo } from '../../src/utils.js'; +import { deepAccess, deepSetValue, generateUUID, getParameterByName, logInfo } from '../../src/utils.js'; import { Renderer } from '../../src/Renderer.js'; +import { config } from '../../src/config.js'; import { getCurrencyFromBidderRequest } from '../ortb2Utils/currency.js'; import { INSTREAM, OUTSTREAM } from '../../src/video.js'; import { BANNER, MediaType, NATIVE, VIDEO } from '../../src/mediaTypes.js'; @@ -13,10 +14,10 @@ const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstre let sessionId:string | null = null; const getSessionId = ():string => { - if (!sessionId) { - sessionId = generateUUID(); - } - return sessionId; + if (sessionId) return sessionId; + const id:string = generateUUID(); + sessionId = id; + return id; } let lastPageUrl:string = ''; @@ -244,3 +245,15 @@ export const getAmxId = ( const amxId = storage.getDataFromLocalStorage('__amuidpb'); return amxId || null; } + +export const getGzipSetting = ( + bidderCode: string, + defaultEnabled: boolean = true, +): boolean => { + if (getParameterByName('nexx360_debug') === '1') return false; + const bidderConfig = config.getBidderConfig(); + const gzipEnabled = bidderConfig[bidderCode]?.gzipEnabled; + if (gzipEnabled === true || gzipEnabled === 'true') return true; + if (gzipEnabled === false || gzipEnabled === 'false') return false; + return defaultEnabled; +}; diff --git a/modules/adgridBidAdapter.ts b/modules/adgridBidAdapter.ts index 6b5199817de..770a332191e 100644 --- a/modules/adgridBidAdapter.ts +++ b/modules/adgridBidAdapter.ts @@ -4,7 +4,7 @@ import { AdapterRequest, BidderSpec, registerBidder } from '../src/adapters/bidd import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js' import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; -import { interpretResponse, enrichImp, enrichRequest, getAmxId, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { interpretResponse, enrichImp, enrichRequest, getAmxId, getGzipSetting, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; import { ORTBRequest } from '../src/prebid.public.js'; const BIDDER_CODE = 'adgrid'; @@ -85,6 +85,9 @@ const buildRequests = ( method: 'POST', url: REQUEST_URL, data, + options: { + endpointCompression: getGzipSetting(BIDDER_CODE, true), + }, } return adapterRequest; } diff --git a/modules/insuradsBidAdapter.ts b/modules/insuradsBidAdapter.ts index e0aaf419f97..902b513b80f 100644 --- a/modules/insuradsBidAdapter.ts +++ b/modules/insuradsBidAdapter.ts @@ -4,11 +4,10 @@ import { AdapterRequest, BidderSpec, registerBidder } from '../src/adapters/bidd import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js' -import { interpretResponse as nexxInterpretResponse, enrichImp, enrichRequest, getAmxId, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { interpretResponse as nexxInterpretResponse, enrichImp, enrichRequest, getAmxId, getGzipSetting as libGetGzipSetting, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; import { ORTBImp, ORTBRequest } from '../src/prebid.public.js'; -import { config } from '../src/config.js'; const BIDDER_CODE = 'insurads'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; @@ -17,8 +16,6 @@ const BIDDER_VERSION = '7.1'; const GVLID = 596; const ALT_KEY = 'nexx360_storage'; -const DEFAULT_GZIP_ENABLED = false; - type RequireAtLeastOne = Omit & { [K in Keys]-?: Required> & @@ -56,15 +53,7 @@ export const getInsurAdsLocalStorage = getLocalStorageFunctionGenerator<{ nexx36 'nexx360Id' ); -export const getGzipSetting = (): boolean => { - const bidderConfig = config.getBidderConfig(); - const gzipEnabled = bidderConfig.insurads?.gzipEnabled; - - if (gzipEnabled === true || gzipEnabled === 'true') { - return true; - } - return DEFAULT_GZIP_ENABLED; -} +export const getGzipSetting = (): boolean => libGetGzipSetting(BIDDER_CODE, false); const converter = ortbConverter({ context: { diff --git a/modules/nexx360BidAdapter.ts b/modules/nexx360BidAdapter.ts index 9c47c2b75e6..640ff559f67 100644 --- a/modules/nexx360BidAdapter.ts +++ b/modules/nexx360BidAdapter.ts @@ -4,21 +4,18 @@ import { AdapterRequest, BidderSpec, registerBidder } from '../src/adapters/bidd import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js' -import { interpretResponse, enrichImp, enrichRequest, getAmxId, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { interpretResponse, enrichImp, enrichRequest, getAmxId, getGzipSetting as libGetGzipSetting, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; import { ORTBImp, ORTBRequest } from '../src/prebid.public.js'; -import { config } from '../src/config.js'; const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; const PAGE_VIEW_ID = generateUUID(); -const BIDDER_VERSION = '7.1'; +const BIDDER_VERSION = '8.0'; const GVLID = 965; const NEXXID_KEY = 'nexx360_storage'; -const DEFAULT_GZIP_ENABLED = false; - type RequireAtLeastOne = Omit & { [K in Keys]-?: Required> & @@ -75,13 +72,7 @@ export const getNexx360LocalStorage = getLocalStorageFunctionGenerator<{ nexx360 'nexx360Id' ); -export const getGzipSetting = (): boolean => { - const getBidderConfig = config.getBidderConfig(); - if (getBidderConfig.nexx360?.gzipEnabled === 'true') { - return getBidderConfig.nexx360?.gzipEnabled === 'true'; - } - return DEFAULT_GZIP_ENABLED; -} +export const getGzipSetting = (): boolean => libGetGzipSetting(BIDDER_CODE, true); const converter = ortbConverter({ context: { diff --git a/modules/revnewBidAdapter.ts b/modules/revnewBidAdapter.ts index c7391b3e485..0477db421e8 100644 --- a/modules/revnewBidAdapter.ts +++ b/modules/revnewBidAdapter.ts @@ -4,7 +4,7 @@ import { AdapterRequest, BidderSpec, registerBidder } from '../src/adapters/bidd import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js' -import { interpretResponse, enrichImp, enrichRequest, getAmxId, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { interpretResponse, enrichImp, enrichRequest, getAmxId, getGzipSetting, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; import { ORTBImp, ORTBRequest } from '../src/prebid.public.js'; @@ -80,6 +80,9 @@ const buildRequests = ( method: 'POST', url: REQUEST_URL, data, + options: { + endpointCompression: getGzipSetting(BIDDER_CODE, true), + }, } return adapterRequest; }; diff --git a/test/spec/libraries/nexx360Utils/getGzipSetting_spec.js b/test/spec/libraries/nexx360Utils/getGzipSetting_spec.js new file mode 100644 index 00000000000..ca7002853d3 --- /dev/null +++ b/test/spec/libraries/nexx360Utils/getGzipSetting_spec.js @@ -0,0 +1,70 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { getGzipSetting } from '../../../../libraries/nexx360Utils/index.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; + +const sandbox = sinon.createSandbox(); + +describe('nexx360Utils getGzipSetting', () => { + let getParamStub; + + beforeEach(() => { + config.resetConfig(); + getParamStub = sandbox.stub(utils, 'getParameterByName').returns(''); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('returns defaultEnabled when no config and no URL override', () => { + expect(getGzipSetting('nexx360', true)).to.equal(true); + expect(getGzipSetting('insurads', false)).to.equal(false); + }); + + it('returns false when bidder config gzipEnabled is the string "false"', () => { + config.setBidderConfig({ bidders: ['nexx360'], config: { gzipEnabled: 'false' } }); + expect(getGzipSetting('nexx360', true)).to.equal(false); + }); + + it('returns true when bidder config gzipEnabled is the string "true"', () => { + config.setBidderConfig({ bidders: ['nexx360'], config: { gzipEnabled: 'true' } }); + expect(getGzipSetting('nexx360', true)).to.equal(true); + }); + + it('returns true when bidder config gzipEnabled is the boolean true', () => { + config.setBidderConfig({ bidders: ['insurads'], config: { gzipEnabled: true } }); + expect(getGzipSetting('insurads', false)).to.equal(true); + }); + + it('returns false when bidder config gzipEnabled is the boolean false', () => { + config.setBidderConfig({ bidders: ['nexx360'], config: { gzipEnabled: false } }); + expect(getGzipSetting('nexx360', true)).to.equal(false); + }); + + it('returns false when URL nexx360_debug=1, overriding enabled config', () => { + getParamStub.withArgs('nexx360_debug').returns('1'); + config.setBidderConfig({ bidders: ['nexx360'], config: { gzipEnabled: 'true' } }); + expect(getGzipSetting('nexx360', true)).to.equal(false); + }); + + it('ignores URL nexx360_debug when its value is not "1"', () => { + getParamStub.withArgs('nexx360_debug').returns('0'); + expect(getGzipSetting('nexx360', true)).to.equal(true); + }); + + it('applies nexx360_debug=1 across all aliases regardless of defaultEnabled', () => { + getParamStub.withArgs('nexx360_debug').returns('1'); + expect(getGzipSetting('mtc', true)).to.equal(false); + expect(getGzipSetting('adgrid', true)).to.equal(false); + expect(getGzipSetting('revnew', true)).to.equal(false); + expect(getGzipSetting('insurads', false)).to.equal(false); + }); + + it('reads config keyed by the bidderCode argument', () => { + config.setBidderConfig({ bidders: ['mtc'], config: { gzipEnabled: 'false' } }); + expect(getGzipSetting('mtc', true)).to.equal(false); + expect(getGzipSetting('nexx360', true)).to.equal(true); + }); +}); diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index f6f5461d361..9c787c4a08b 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -4,6 +4,8 @@ import { } from 'modules/nexx360BidAdapter.js'; import sinon from 'sinon'; import { getAmxId } from '../../../libraries/nexx360Utils/index.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; const sandbox = sinon.createSandbox(); describe('Nexx360 bid adapter tests', () => { @@ -33,9 +35,45 @@ describe('Nexx360 bid adapter tests', () => { }, }; - it('We test getGzipSettings', () => { - const output = getGzipSetting(); - expect(output).to.be.a('boolean'); + describe('getGzipSetting', () => { + let getParamStub; + beforeEach(() => { + config.resetConfig(); + getParamStub = sandbox.stub(utils, 'getParameterByName').returns(''); + }); + afterEach(() => { + sandbox.restore(); + }); + + it('defaults to true when no config and no URL override', () => { + expect(getGzipSetting()).to.equal(true); + }); + + it('returns false when bidder config gzipEnabled is the string "false"', () => { + config.setBidderConfig({ bidders: ['nexx360'], config: { gzipEnabled: 'false' } }); + expect(getGzipSetting()).to.equal(false); + }); + + it('returns true when bidder config gzipEnabled is the string "true"', () => { + config.setBidderConfig({ bidders: ['nexx360'], config: { gzipEnabled: 'true' } }); + expect(getGzipSetting()).to.equal(true); + }); + + it('returns true when bidder config gzipEnabled is the boolean true', () => { + config.setBidderConfig({ bidders: ['nexx360'], config: { gzipEnabled: true } }); + expect(getGzipSetting()).to.equal(true); + }); + + it('returns false when URL has nexx360_debug=1, even if config would enable gzip', () => { + getParamStub.withArgs('nexx360_debug').returns('1'); + config.setBidderConfig({ bidders: ['nexx360'], config: { gzipEnabled: 'true' } }); + expect(getGzipSetting()).to.equal(false); + }); + + it('returns true when URL has nexx360_debug with a value other than 1', () => { + getParamStub.withArgs('nexx360_debug').returns('0'); + expect(getGzipSetting()).to.equal(true); + }); }); describe('isBidRequestValid()', () => { @@ -344,7 +382,7 @@ describe('Nexx360 bid adapter tests', () => { version: requestContent.ext.version, source: 'prebid.js', pageViewId: requestContent.ext.pageViewId, - bidderVersion: '7.1', + bidderVersion: '8.0', localStorage: { amxId: 'abcdef' }, sessionId: requestContent.ext.sessionId, requestCounter: 0, From 1e69124545cc615481753633a5edfe4bf96f5573 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Thu, 23 Apr 2026 11:49:34 +0200 Subject: [PATCH 2/5] Mtc Bid Adapter: initial release Co-Authored-By: Claude Opus 4.7 (1M context) --- modules/mtcBidAdapter.md | 57 ++++ modules/mtcBidAdapter.ts | 97 ++++++ test/spec/modules/mtcBidAdapter_spec.js | 386 ++++++++++++++++++++++++ 3 files changed, 540 insertions(+) create mode 100644 modules/mtcBidAdapter.md create mode 100644 modules/mtcBidAdapter.ts create mode 100644 test/spec/modules/mtcBidAdapter_spec.js diff --git a/modules/mtcBidAdapter.md b/modules/mtcBidAdapter.md new file mode 100644 index 00000000000..33c9007a976 --- /dev/null +++ b/modules/mtcBidAdapter.md @@ -0,0 +1,57 @@ +# Overview + +``` +Module Name: Mtc Bid Adapter +Module Type: Bidder Adapter +Maintainer: gabriel@nexx360.io +``` + +# Description + +Connects to Mtc network for bids. + +To use us as a bidder you must have an account and an active "tagId" or "placement" on our platform. + +# Test Parameters + +## Web + +### Display +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'mtc', + params: { + tagId: 'testnexx' + } + }] + }, +]; +``` + +### Video Instream +``` + var videoAdUnit = { + code: 'video1', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [{ + bidder: 'mtc', + params: { + placement: 'TEST_PLACEMENT' + } + }] + }; +``` diff --git a/modules/mtcBidAdapter.ts b/modules/mtcBidAdapter.ts new file mode 100644 index 00000000000..8e1d94bc889 --- /dev/null +++ b/modules/mtcBidAdapter.ts @@ -0,0 +1,97 @@ +import { deepSetValue, generateUUID, logError } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { AdapterRequest, BidderSpec, registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js' + +import { interpretResponse, enrichImp, enrichRequest, getGzipSetting, getLocalStorageFunctionGenerator, getUserSyncs } from '../libraries/nexx360Utils/index.js'; +import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; +import { ORTBImp, ORTBRequest } from '../src/prebid.public.js'; + +const BIDDER_CODE = 'mtc'; +const REQUEST_URL = 'https://fast.nexx360.io/mtc'; +const PAGE_VIEW_ID = generateUUID(); +const BIDDER_VERSION = '1.0'; +const MTC_KEY = 'mtc_storage'; + +type RequireAtLeastOne = + Omit & { + [K in Keys]-?: Required> & + Partial>> + }[Keys]; + +type MtcBidParams = RequireAtLeastOne<{ + tagId?: string; + placement?: string; + customId?: string; +}, "tagId" | "placement">; + +declare module '../src/adUnits' { + interface BidderParams { + [BIDDER_CODE]: MtcBidParams; + } +} + +export const STORAGE = getStorageManager({ + bidderCode: BIDDER_CODE, +}); + +export const getMtcLocalStorage = getLocalStorageFunctionGenerator<{ mtcId: string }>( + STORAGE, + BIDDER_CODE, + MTC_KEY, + 'mtcId' +); + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 120, + }, + imp(buildImp, bidRequest: BidRequest, context) { + let imp:ORTBImp = buildImp(bidRequest, context); + imp = enrichImp(imp, bidRequest); + deepSetValue(imp, 'ext.mtc', bidRequest.params); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + let request:ORTBRequest = buildRequest(imps, bidderRequest, context); + request = enrichRequest(request, null, PAGE_VIEW_ID, BIDDER_VERSION); + return request; + }, +}); + +const isBidRequestValid = (bid:BidRequest): boolean => { + if (!bid.params.tagId && !bid.params.placement) { + logError('bid.params.tagId or bid.params.placement must be defined'); + return false; + } + return true; +}; + +const buildRequests = ( + bidRequests: BidRequest[], + bidderRequest: ClientBidderRequest, +): AdapterRequest => { + const data:ORTBRequest = converter.toORTB({ bidRequests, bidderRequest }) + const adapterRequest:AdapterRequest = { + method: 'POST', + url: REQUEST_URL, + data, + options: { + endpointCompression: getGzipSetting(BIDDER_CODE, true), + }, + } + return adapterRequest; +}; + +export const spec:BidderSpec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/test/spec/modules/mtcBidAdapter_spec.js b/test/spec/modules/mtcBidAdapter_spec.js new file mode 100644 index 00000000000..e16881c6544 --- /dev/null +++ b/test/spec/modules/mtcBidAdapter_spec.js @@ -0,0 +1,386 @@ +import { expect } from 'chai'; +import { + spec, STORAGE, getMtcLocalStorage, +} from 'modules/mtcBidAdapter.js'; +import sinon from 'sinon'; +const sandbox = sinon.createSandbox(); + +describe('Mtc bid adapter tests', () => { + const DEFAULT_OPTIONS = { + gdprConsent: { + gdprApplies: true, + consentString: 'BOzZdA0OzZdA0AGABBENDJ-AAAAvh7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__79__3z3_9pxP78k89r7337Mw_v-_v-b7JCPN_Y3v-8Kg', + vendorData: {}, + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page', + }, + uspConsent: '111222333', + userId: { id5id: { uid: '1111' } }, + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com', + }], + }, + }; + + describe('isBidRequestValid()', () => { + let bannerBid; + beforeEach(() => { + bannerBid = { + bidder: 'mtc', + mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, + adUnitCode: 'div-1', + transactionId: '70bdc37e-9475-4b27-8c74-4634bdc2ee66', + sizes: [[300, 250], [300, 600]], + bidId: '4906582fc87d0c', + bidderRequestId: '332fda16002dbe', + auctionId: '98932591-c822-42e3-850e-4b3cf748d063', + } + }); + + it('We verify isBidRequestValid with incorrect tagid', () => { + bannerBid.params = { 'tagid': 'luvxjvgn' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('We verify isBidRequestValid with correct tagId', () => { + bannerBid.params = { 'tagId': 'luvxjvgn' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); + }); + + it('We verify isBidRequestValid with correct placement', () => { + bannerBid.params = { 'placement': 'testad' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); + }); + }); + + describe('getMtcLocalStorage disabled', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => false); + }); + it('We test if we get the mtcId', () => { + const output = getMtcLocalStorage(); + expect(output).to.be.eql(null); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getMtcLocalStorage enabled but nothing', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake(() => null); + }); + it('We test if we get the mtcId', () => { + const output = getMtcLocalStorage(); + expect(typeof output.mtcId).to.be.eql('string'); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getMtcLocalStorage enabled but wrong payload', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake(() => '{"mtcId":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4",}'); + }); + it('We test if we get the mtcId', () => { + const output = getMtcLocalStorage(); + expect(output).to.be.eql(null); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('getMtcLocalStorage enabled', () => { + before(() => { + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake(() => '{"mtcId":"5ad89a6e-7801-48e7-97bb-fe6f251f6cb4"}'); + }); + it('We test if we get the mtcId', () => { + const output = getMtcLocalStorage(); + expect(output.mtcId).to.be.eql('5ad89a6e-7801-48e7-97bb-fe6f251f6cb4'); + }); + after(() => { + sandbox.restore() + }); + }); + + describe('buildRequests()', () => { + before(() => { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs('div-1').returns({ + offsetWidth: 200, + offsetHeight: 250, + style: { + maxWidth: '400px', + maxHeight: '350px', + }, + getBoundingClientRect() { return { width: 200, height: 250 }; } + }); + sandbox.stub(STORAGE, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(STORAGE, 'setDataInLocalStorage'); + sandbox.stub(STORAGE, 'getDataFromLocalStorage').callsFake(() => 'abcdef'); + }); + describe('We test with a multiple display bids', () => { + const sampleBids = [ + { + bidder: 'mtc', + params: { + tagId: 'luvxjvgn', + divId: 'div-1', + adUnitName: 'header-ad', + adUnitPath: '/12345/mtc/Homepage/HP/Header-Ad', + }, + ortb2Imp: { + ext: { + gpid: '/12345/mtc/Homepage/HP/Header-Ad', + } + }, + adUnitCode: 'header-ad-1234', + transactionId: '469a570d-f187-488d-b1cb-48c1a2009be9', + sizes: [[300, 250], [300, 600]], + bidId: '44a2706ac3574', + bidderRequestId: '359bf8a3c06b2e', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + }, + { + bidder: 'mtc', + params: { + placement: 'testPlacement', + allBids: true, + }, + mediaTypes: { + banner: { + sizes: [[728, 90], [970, 250]] + } + }, + + adUnitCode: 'div-2-abcd', + transactionId: '6196885d-4e76-40dc-a09c-906ed232626b', + sizes: [[728, 90], [970, 250]], + bidId: '5ba94555219a03', + bidderRequestId: '359bf8a3c06b2e', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + } + ]; + const bidderRequest = { + bidderCode: 'mtc', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + bidderRequestId: '359bf8a3c06b2e', + refererInfo: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://test.nexx360.io/adapter/index.html' + ], + topmostLocation: 'https://test.nexx360.io/adapter/index.html', + location: 'https://test.nexx360.io/adapter/index.html', + canonicalUrl: null, + page: 'https://test.nexx360.io/adapter/index.html', + domain: 'test.nexx360.io', + ref: null, + legacy: { + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://test.nexx360.io/adapter/index.html' + ], + referer: 'https://test.nexx360.io/adapter/index.html', + canonicalUrl: null + }, + }, + gdprConsent: { + gdprApplies: true, + consentString: 'CPhdLUAPhdLUAAKAsAENCmCsAP_AAE7AAAqIJFNd_H__bW9r-f5_aft0eY1P9_r37uQzDhfNk-8F3L_W_LwX52E7NF36tq4KmR4ku1LBIUNlHMHUDUmwaokVryHsak2cpzNKJ7BEknMZOydYGF9vmxtj-QKY7_5_d3bx2D-t_9v239z3z81Xn3d53-_03LCdV5_9Dfn9fR_bc9KPt_58v8v8_____3_e__3_7997BIiAaADgAJYBnwEeAJXAXmAwQBj4DtgHcgPBAeKBIgAA.YAAAAAAAAAAA', + } + }; + it('We perform a test with 2 display adunits', () => { + const displayBids = structuredClone(sampleBids); + displayBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + } + }; + const request = spec.buildRequests(displayBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + expect(requestContent.imp[0].ext.mtc.tagId).to.be.eql('luvxjvgn'); + expect(requestContent.imp[0].ext.mtc.divId).to.be.eql('div-1'); + expect(requestContent.imp[1].ext.mtc.placement).to.be.eql('testPlacement'); + expect(requestContent.ext.bidderVersion).to.be.eql('1.0'); + }); + + if (FEATURES.VIDEO) { + it('We perform a test with a multiformat adunit', () => { + const multiformatBids = structuredClone(sampleBids); + multiformatBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + playback_method: ['auto_play_sound_off'] + } + }; + const request = spec.buildRequests(multiformatBids, bidderRequest); + const video = request.data.imp[0].video; + const expectedVideo = { + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + w: 640, + h: 480, + ext: { + playerSize: [640, 480], + context: 'outstream', + }, + }; + expect(video).to.eql(expectedVideo); + }); + + it('We perform a test with a instream adunit', () => { + const videoBids = structuredClone(sampleBids); + videoBids[0].mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6], + playbackmethod: [2], + skip: 1 + } + }; + const request = spec.buildRequests(videoBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + expect(requestContent.imp[0].video.ext.context).to.be.eql('instream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }); + } + }); + after(() => { + sandbox.restore() + }); + }); + + describe('We test interpretResponse', () => { + it('empty response', () => { + const response = { + body: '' + }; + const output = spec.interpretResponse(response); + expect(output.length).to.be.eql(0); + }); + it('banner responses with adm', () => { + const response = { + body: { + id: 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '4427551302944024629', + impid: '226175918ebeda', + price: 1.5, + adomain: [ + 'http://prebid.org', + ], + crid: '98493581', + ssp: 'appnexus', + h: 600, + w: 300, + adm: '
TestAd
', + cat: [ + 'IAB3-1', + ], + ext: { + adUnitCode: 'div-1', + mediaType: 'banner', + adUrl: 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', + ssp: 'appnexus', + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + id: 'de3de7c7-e1cf-4712-80a9-94eb26bfc718', + cookies: [], + }, + }, + }; + const output = spec.interpretResponse(response); + const expectedOutput = [{ + requestId: '226175918ebeda', + cpm: 1.5, + width: 300, + height: 600, + creativeId: '98493581', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'banner', + meta: { + advertiserDomains: [ + 'http://prebid.org', + ], + demandSource: 'appnexus', + }, + ad: '
TestAd
', + }]; + expect(output).to.eql(expectedOutput); + }); + }); + + describe('getUserSyncs()', () => { + const response = { body: { cookies: [] } }; + it('Verifies user sync without cookie in bid response', () => { + const syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + it('Verifies user sync with cookies in bid response', () => { + response.body.ext = { + cookies: [{ 'type': 'image', 'url': 'http://www.cookie.sync.org/' }] + }; + const syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent); + const expectedSyncs = [{ type: 'image', url: 'http://www.cookie.sync.org/' }]; + expect(syncs).to.eql(expectedSyncs); + }); + it('Verifies user sync with no bid response', () => { + var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + it('Verifies user sync with no bid body response', () => { + let syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + }); +}); From 653dae91ff99a30e906d3e2ab741ed0f53d18b67 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Fri, 24 Apr 2026 07:25:55 +0200 Subject: [PATCH 3/5] Update mtcBidAdapter.ts Bid Params guard fix --- modules/mtcBidAdapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mtcBidAdapter.ts b/modules/mtcBidAdapter.ts index 8e1d94bc889..66ff8b65cab 100644 --- a/modules/mtcBidAdapter.ts +++ b/modules/mtcBidAdapter.ts @@ -62,7 +62,7 @@ const converter = ortbConverter({ }); const isBidRequestValid = (bid:BidRequest): boolean => { - if (!bid.params.tagId && !bid.params.placement) { + if (!bid.params || (!bid.params?.tagId && !bid.params?.placement)) { logError('bid.params.tagId or bid.params.placement must be defined'); return false; } From b42fb6073564992836ecd3fc58c050d2ecf696d5 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Fri, 24 Apr 2026 10:12:31 +0200 Subject: [PATCH 4/5] Update mtcBidAdapter.md --- modules/mtcBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/mtcBidAdapter.md b/modules/mtcBidAdapter.md index 33c9007a976..bbbeefcde00 100644 --- a/modules/mtcBidAdapter.md +++ b/modules/mtcBidAdapter.md @@ -10,7 +10,7 @@ Maintainer: gabriel@nexx360.io Connects to Mtc network for bids. -To use us as a bidder you must have an account and an active "tagId" or "placement" on our platform. +To use us as a bidder you must have an account and an active "tagId" or "placement" from us. # Test Parameters From 100c060130094bbe5fa7cadea7d5932a63e12d00 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Mon, 27 Apr 2026 08:48:24 +0200 Subject: [PATCH 5/5] Nexx360 Bid Adapter: make requestCounter assertion order-independent The shared module-level requestCounter in libraries/nexx360Utils is incremented by every consuming adapter, so when mtcBidAdapter tests run in the same chunk before nexx360 tests, the counter is no longer 0. Co-Authored-By: Claude Opus 4.7 (1M context) --- test/spec/modules/nexx360BidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index 9c787c4a08b..bb298729fa7 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -385,7 +385,7 @@ describe('Nexx360 bid adapter tests', () => { bidderVersion: '8.0', localStorage: { amxId: 'abcdef' }, sessionId: requestContent.ext.sessionId, - requestCounter: 0, + requestCounter: requestContent.ext.requestCounter, }, cur: [ 'USD',