From d718516e12e0616eb0d1eb239b193d9607d052ba Mon Sep 17 00:00:00 2001 From: Anastasiia Pankiv <153929408+anastasiiapankivFS@users.noreply.github.com> Date: Thu, 30 Jan 2025 13:03:12 +0200 Subject: [PATCH] Upgrade to 9.28.0 (#212) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bidResponseFilter Module : do not run if not configured (#12362) * bidResponseFilter: do not run if not configured * fix lint * fix tests * Contxtful Bid Adapter : initial release (#12256) * feat: contxtful bid adapter * fix: ajax * fix: config, valid bid request * fix: config, valid bid request * fix: tests * refactor: construct url * fix: test * fix: test * fix: space * fix: added beacon * fix: test * fix: test * fix: pbjs version * doc: beacon for ci * doc: spec to trigger ci * doc: log trigger ci * fix: imports --------- Co-authored-by: Sébastien Rufiange * Prebid 9.17.0 release * Increment version to 9.18.0-pre * Preciso Bid adapter : Native implemented (#12278) * Bid adapter added * added the coverage code * precisonatBidAdapter.md file added * bid price macro replacement * tracking url encoding removed * fix * test * modified the adapter for native * test logs removed * updated * error fixed * Added new library bidNativeUtils.js --------- Co-authored-by: PrecisoSRL <134591565+PrecisoSRL@users.noreply.github.com> * Showheroes Bid Adapter : full rework of the adapter (#12283) * switch to openRTB endpoint * refactor and add more tests * bring back original sync * read expired from the response * remove unused sync url * read if netRevenue from response, include extra params * set displaymanager to prebid.js * update docs * test is working * don't merge pixel and iframe syncs * provide renderer * lint fix * remove banner support * switch to openRTB for response * use fromORTB to simplify bidder * don't pass entire bid to renderer * set video context in ext * endpoint should end with / * update documentation unitId * check if video features are enabled for tests * fix after review --------- Co-authored-by: Michele Nasti * QortexRtdProvider: Supports new Qortex bid enrichment process (#12173) * creates config request step * gather page data and send POST * includes player events logic * rtd MVP * change function name * saving before methodology change * satifies coverage and information specification:wq * removes adapter * remove dependencies * adds final MVP features * fixed submodules line * use cryptography * use textcontent per circleci * spelling * Prebid config options (#7) * rearrange logic, needs a few more tests * updated and unit tests written * remove logs * limits the type and amount of text collected on a page (#8) * fix lint errors * updates config param to be opt in * update markdown * resolve circle ci issue * new branch from updated pr-stage * resolves tests after code removal * spelling and CICD error * spelling * reorder md to match github io page: --------- Co-authored-by: rrochwick <65189775+rrochwick@users.noreply.github.com> * Appnexus Bid Adapter: Copy video over to custom native fields so it can be used in native creatives directly (#12345) * Add sizes... again... (#12371) * LiveIntent UserId Module : add support for sharethrough, sonobi, vidazoo IDs (#12374) * Add support for sharethrough, sonobi, vidazoo * Adjust atype * Add handling for ext * Equativ Bid Adapter: initial release, Smartadserver Bid Adapter: take lowest floor to send to endpoint (#12326) * add support of dsa * restore topics * DSA fix for UT * drafy of adapter * fixes after dev test * make world simpler * fix prev commit * return empty userSyncs array by default * adjustments * apply prettier * unit tests for Equativ adapter * add dsp user sync * add readme * body can be undef * support additional br params * remove user sync * do not send dt param * handle floors and network id * handle empty media types * get min floor * fix desc for u.t. * better name for u.t. * add u.t. for not supported media type * improve currency u.t. * fetch and pass pid * updates after pr review * add u.t. for buyeruid from config --------- Co-authored-by: Elżbieta SZPONDER Co-authored-by: eszponder <155961428+eszponder@users.noreply.github.com> Co-authored-by: janzych-smart * PubMatic bid adapter add ortb2 device (#11783) * Pubmatic Bid Adapter: Add full ORTB2 device data to request payload and Adds test to verify presence of ORTB2 device data in request --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> * NextMillenniumBidAdapter: Added OpenRTB 2.6 parameters support: `bcat`, `badv`, `wlang`, `wlangb` and `tmax` (#12368) * added support for gpp consent string * changed test for nextMillenniumBidAdapter * added some tests * added site.pagecat, site.content.cat and site.content.language to request * lint fix * formated code * formated code * formated code * pachage-lock with prebid * pachage-lock with prebid * formatted code * added device.sua, user.eids * formatted * fixed tests * fixed bug functio getSua * NextMillennium: Sending a request with several imp objects. * PB-2650 - tmax * PB-2626 - "bcat", "badv", "wlang", "wlangb" * PB-2626 - coppa * PB-2626 - save * PB-2650 - fixed bugs * PB-2650 - save * PB-2650 - save - 2 * Connatix Bid Adapter : listen for user id's (#12312) * added event listener in cnx bid adapter * deleted console logs and added cache variable * deleted test file * deleted test change * renamed response data * modified url in event listener * updated response naming * remove event listener when i get all providers * formatting * wrote data instead of response * fixed receiving id values * check if undefined before parsing * PR comments * changed naming to be the same as on BE side * PR comments * changed naming * changed to camelcase * checked for all events * unit tests * exported functions * added one more test for buildRequests --------- Co-authored-by: Octavia Suceava * Impactify Bid Adapter: fix uspConsent logic (#12332) * Updated bid adapter to log errors * Impactify Bid Adapter: fix uspConsent logic * Remove line * Add Newline --------- Co-authored-by: Filipe Neves Co-authored-by: Abderrahmen Ghadbane * Increase caching of sampling rate cookie (#12380) * Qortex Rtd Provider : implements rate limiting options for qortex enrichment and analytics (#12372) * include code from local branch * newline for linter * KRKPD-1667: replaces triggerPixel with fetch (#39) (#12387) * replaces triggerPixel with fetch * replaces triggerPixel test * Core: allow renderAd on main document for bids with a renderer (#12391) * rubicon Bid Adapter : add support for twin ad units (#12328) * Add support for twin ad units * Remove impIdMap loop in favour of ternary operator * Prebid 9.18.0 release * Increment version to 9.19.0-pre * smartxBidAdapter: add propagation of aderror event (#12388) * AdagioAnalyticsAdapter: track revelant ad-units only (#12383) * Missena Bid Adapter : send coppa and autoplay (#12352) * Missena Bid Adapter : send coppa and autoplay * switch to bidderRequest.ortb2.regs.coppa * Snigel Bid Adapter: add placement counter and adapter version field (#12358) * Added init event for yandexAnalyticsAdapter (#12386) * Rubicon bid adapter/response media type (#12401) * Migrate queryString to URLSearchParams for rubiconBidAdapter_spec * rubiconBidAdapter: add support response mediaType * rubiconBidadapter: fix semicolon * holid Bid Adapter : only iframe sync on gdpr consent (#12416) * Update holidBidAdapter.js * Update holidBidAdapter.js * Missena Bid Adapter : send all params (#12415) * HadronId System : not use localStorage for writing on it (#12378) * don't use localStorage for storing and minimize its use for reading * don't use localStorage for storing and minimize its use for reading * restoring value of hadronId in storage (doc) * making tests pass * making test pass * easybid added (#12417) Co-authored-by: Gabriel Chicoye * Removed setting the AV_WIDTH/AV_HEIGHT, bidWidth/bidHeight to `imp.ext` for video. (#12418) * Sparteo Bid Adapter: Add Prebid.JS Version (#12419) * nextMillennium Bid Adapter : added supply chain support (#12421) * PB-2782 - added support supply chain * PB-2782 - added supply chain support - 2 * Biddo invamia bid adapters: import common code (#12409) * Create index.js * Update biddoBidAdapter.js * Update invamiaBidAdapter.js * Prebid 9.19.0 release * Increment version to 9.20.0-pre * Nativo Bid Adapter: support native and video (#12134) * Initial nativoBidAdapter document creation (js, md and spec) * Fulling working prebid using nativoBidAdapter. Support for GDPR and CCPA in user syncs. * Added defult size settings based on the largest ad unit. Added response body validation. Added consent to request url qs params. * Changed bidder endpoint url * Changed double quotes to single quotes. * Reverted package-json.lock to remove modifications from PR * Added optional bidder param 'url' so the ad server can force- match an existing placement * Lint fix. Added space after if. * Added new QS param to send various adUnit data to adapter endpopint * Updated unit test for new QS param * Added qs param to keep track of ad unit refreshes * Updated bidMap key default value * Updated refresh increment logic * Refactored spread operator for IE11 support * Updated isBidRequestValid check * Refactored Object.enties to use Object.keys to fix CircleCI testing errors * Updated bid mapping key creation to prioritize ad unit code over placementId * Added filtering by ad, advertiser and campaign. * Merged master * Added more robust bidDataMap with multiple key access * Deduped filer values * Rolled back package.json * Duped upstream/master's package.lock file ... not sure how it got changed in the first place * Small refactor of filterData length check. Removed comparison with 0 since a length value of 0 is already falsy. * Added bid sizes to request * Fixed function name in spec. Added unit tests. * Added priceFloor module support * Added protection agains empty url parameter * Changed ntv_url QS param to use referrer.location instead of referrer.page * Removed testing 'only' flag * Added ntv_url QS param value validation * Added userId support * Added unit tests, refactored for bugs * Wrapped ajax in try/catch * Added more unit testing * Updated eid check for duplicate values. Removed error logging as we no longer need it. * Removed spec test .only. Fixed unit tests that were breaking. * Added Prebid version to nativo exchange request * Removed unused bidder methods * Added OpenRTB payload response. Changes requerst type to POST. * Removed debug log * Added/fixed tests * Handle video mediaType * Add built renderer files * Fix no-inner-declarations linting error * Handle native requests * Add examples in Nativo readme * Add 'mediaType' property to tests for compatibility with adapter code * Remove data URI from VAST XML * Fix 'no-unused-expressions' lint error * Fix lint error 'curcly' * Remove bidder name validation in 'isBidRequestValid' (#4) * Add GPP consent string to req (#5) --------- Co-authored-by: Josh Co-authored-by: Joshua Fledderjohn * Gamera Rtd Provider: Initial release (#12424) * - add gameraRtdProvider * - allow all site,user and imp to be enriched * - jsdoc external fn - consent handling in the gamera script * Ssp_geniee Bid Adapter : fix imuid module and spec.js (#12428) * modify adUnit infomation * fix imuid module * fix spec.js * fix import * fix deep.equal --------- Co-authored-by: Murano Takamasa Co-authored-by: daikichiteranishi <49385718+daikichiteranishi@users.noreply.github.com> * LiveIntent UserId Module: add IP and User Agent Configuration Parameters (#12402) * send ip * send header * use proper version * lint * example * Core: allow renderers without URLs (#12426) * StroeerCore Bid Adapter: add the ortb2 site extension to the request (#12433) * CadentApertureMX Bid Adapter : remove bidder code validation (#12404) * Update cadentApertureMXBidAdapter.js * Update cadentApertureMXBidAdapter_spec.js * Update cadentApertureMXBidAdapter_spec.js * Update cadentApertureMXBidAdapter_spec.js * 51Degrees RTD submodule: optimise ORTB2 enrichment speed (#12394) * 51Degrees RTD submodule: optimise ORTB2 enrichment speed by eliminating redundant call * 51Degrees RTD submodule: mock HEV --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> * Core: set string for regs.ext.gpc (#12436) * Adkernel Bid Adapter: add revbid alias (#12439) * allow outstream when placement is inStream (#12440) * WURFL RTD Module: enrich the ortb2.device object with WURFL data (#12442) This commit updates the WURFL RTD Module to enrich the ortb2.device object with WURFL data. * Smarthub: renaming Smarthub to Attekmi (#12432) * update adapter SmartHub: add aliases * Smarthub: renaming Smarthub to Attekmi * fix tests * add attekmi alias --------- Co-authored-by: Victor * ehealthcaresolutions Bid Adapter : initial release (#12384) * New Bidder:tapnative * removed duplicate * new bidder:ehealthcaresolutions * Vdo.ai Bid Adapter : update to prebid version 9 (#12284) * upgraded vdo.ai prebid adapter to prebid version 9 * linting issues fixed * refactored code * removed page visibility and timing function since they are no longer required * getting coppa from ortb2 object, then fallback to config * lint issue fixed * refactored just to rerun CI/CD build --------- Co-authored-by: rishabhsehrawat1 * Brid Bid Adapter : user sync and response changes (#12248) * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo Bid Adapter: Add GDPR/USP support * TargetVideo Bid Adapter: Add GDPR/USP support tests * TargetVideo Bid Adapter: Updating margin rule * Add Brid bid adapter * Brid adapter requested changes * BridBidAdapter: switching to plcmt * Brid Bid Adapter: getUserSyncs method and interpretResponse updates * Adding missing semicolon * AdMatic Bid Adapter : add adt alias (#12451) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js * admatic cur update * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Support for InBannerVideo (IBV) Field in Bid Response (#12453) * Revert "Support for InBannerVideo (IBV) Field in Bid Response (#12453)" (#12455) This reverts commit f44c6cf4675b529f59602dccc3a85c0449026770. * New bid adapter: Adverxo (#12376) * init adverxo adapter * Add support for user sync * Make host parameter optional Introduce new aliases and an auction url for each. * Set skipPbsAliasing=true for aliases We will also have the aliseses in pbs. * Add new alias * AdagioRtdProvider: add number of pages in session data (#12450) * gppControl: accept flat section data (#12444) * Core: add analytics option to markWinningBidAsUsed (#12437) * New User ID Submodule: Rewarded Interest (#12340) * New UserID submodule: rewardedInterestIdSystem * UserID submodule: rewardedInterestIdSystem - fixed styles --------- Co-authored-by: Konstantin Mikhalyov * Brave utils: initial commit (#12412) * Create index.js * Create nativeAssets.js * Update braveBidAdapter.js * Update videoheroesBidAdapter.js * Update videoheroesBidAdapter.js * Update braveBidAdapter.js * Update adotBidAdapter.js * Update adotBidAdapter.js * Update adotBidAdapter.js * Update adotBidAdapter.js * Update adotBidAdapter.js * Update adotBidAdapter.js * Update braveBidAdapter.js * Update videoheroesBidAdapter.js * Update videoheroesBidAdapter.js * Update braveBidAdapter.js * Update adotBidAdapter.js * Create buildAndInterpret.js * Update braveBidAdapter.js * Update videoheroesBidAdapter.js * Update buildAndInterpret.js * Update braveBidAdapter.js * Update videoheroesBidAdapter.js * Update braveBidAdapter.js * Update videoheroesBidAdapter.js * Update buildAndInterpret.js * - FIX: Replace deprecated pageXOffset and pageYOffset with scrollX and scrollY respectively (#12354) - ADD: Viewport coordinate in bidRequest.ext.vcoords * Update adkernelBidAdapter.js (#12233) * PgamSSP Bid Adapter: add gvlid (#12464) * new adapter PGAMSSP * upd * support UID 2.0 * del obj * Add id5id * add support gpp * fix spaces * add gvl_id * upd --------- Co-authored-by: Chris Huie * RichAudience Bid Adapter: add support to adomain (#12465) * Update richaudienceBidAdapter.md Update maintainer e-mail to integrations@richaudience.com * Add richaudienceBidAdapter.js file * Add richaudienceBidAdapter_spec.js * Update richaudienceBidAdapter.js * RichaudienceBidAdapter add compability with DSA * RichaudienceBidAdapter add compability with DSA * RichaudienceBidAdapter add compability with DSA * Richaudience Bid Adapter: update adomain * Richaudience Bid Adapter: update adomain test --------- Co-authored-by: Patrick McCann Co-authored-by: sergigimenez * Adkernel Bid Adapter: add spinx alias (#12460) * Media impact and adpartner bid adapters: reduce duplication (#12411) * Create index.js * Update mediaimpactBidAdapter.js * Update adpartnerBidAdapter.js * Update adpartnerBidAdapter_spec.js * Update mediaimpactBidAdapter_spec.js * Update adpartnerBidAdapter_spec.js * Update adpartnerBidAdapter.js * Update adpartnerBidAdapter.js * Update mediaimpactBidAdapter.js * Update index.js * Update mediaimpactBidAdapter.js * Update adpartnerBidAdapter.js * Update adpartnerBidAdapter.js * Update index.js * Update mediaimpactBidAdapter.js * Update mediaimpactBidAdapter_spec.js * Update mediaimpactBidAdapter.js * Update mediaimpactBidAdapter_spec.js * gppControl: check for usnat consent version (#12469) * Various adapters: setting imp secure (#12385) * Various adapters: setting imp secure * tests * Default imp.secure to pub-provided imp.secure * Update common_spec.js --------- Co-authored-by: Marcin Komorski Co-authored-by: Marcin Komorski Co-authored-by: Demetrio Girardi * Rubicon Bid Adapter: expand fastlane EID protocol and pass p_site.mobile; Support device.ip and device.ipv6. (#12471) * Rubicon Bid Adapter: Remove special source handling, add eid_ for each source, retain ppuid logic; Add ip and ipv6 from device in bidRequest * Rubicon Bid Adapter: Change check for mobile property in bidRequest * Rubicon Bid Adapter: Add tests for p_site.mobile property in bidRequest * Video Support (#12457) * FIX: Update adUnit attachment to use adUnitCode selector in rubiconBidAdapter (#12462) * contxtfulBidAdapter: revamp the sampling of events (#12466) * Update adverxoBidAdapter_spec.js (#12478) * Hadron RTD : cleaning things up (#12480) * Cleaning things up in HadronRTD module * solving lint errors * InMobi Bid Adapter : initial release (#12449) * intital commit new bid adapter inmobi * fixed linting issues * added comments on some custom functions * reverted comments on some custom functions * resolved PR review comments * PubMatic Bid Adapter : support for InBannerVideo (IBV) Field in Bid Response with meta.mediaType (#12484) Support for InBannerVideo (IBV) Field in Bid Response with meta.mediaType * PubMatic Bid Adapter: Updated default TTL and added mediaType based TTL (#12487) * setting TTL as per bid response or mediatype * added test cases for TTL * setting default ttl as failsafe * added a test case for default ttl * Using 360 as default ttl * updated test cases with defualt ttl value --------- Co-authored-by: pm-azhar-mulla * Prebid 9.20.0 release * Increment version to 9.21.0-pre * Qortex RTD Module: support messaging dispatch & receive + rate limits (#12392) * Qortex RTD module messaging dispatch & receive * Added additional JSDoc comments for readability * Rate limiting feature into QortexRTD content lookup * Updated test case to catch 429 status code * Check for bid enrichment toggle at external message received * StroeerCore Bid Adapter: Add campaignType property to the bid's meta object (#12488) * AdagioAnalyticsAdapter: stop trying to read sizes of sizeless ad-units (#12489) * allow video outstream on any placement except instream (#12491) * IntentIq ID & Analytics Modules : support domainName parameter (#12434) * update intentIqAnalyticsAdapter.js && intentIqIdSystem.js * fix lint issues * fix tests * move info * resolve issues * update storeFirstPartyData * remove unused code * update defineEmptyDataAndFireCallback * update fix lint * update reportExternalWin * small fixes * update test && add docs * AGT-347: Support domain name * AGT-347: Support domain name * AGT-374: Support domainName to vrref * AGT-374: tests in progress * AGT-374: Remove duplicate encoded in getRelevantRefferer and fix tests * AGT-374: Add test domainName, changes in documentation * AGT-374: Change js version value * AGT-374: Remove extra coma * Remove unused method --------- Co-authored-by: dlepetynskyi Co-authored-by: DimaIntentIQ Co-authored-by: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com> * Userid module: propagate ortb2.user.ext.eids to userIdsAsEids even if no UserId submodules (#12477) * userId module: temp fix empty bid.userIdAsEids * userId module: fix empty bid.userIdAsEids * userId module: fix empty bid.userIdAsEids * userId module: fix empty bid.userIdAsEids * userId module: fix tests * userId module: prevent re-adding addUserIdsHook hook * userId module: mode adding addUserIdsHook, added test --------- Co-authored-by: Konstantin Mikhalyov Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> * Bidtheatre Bidder Adapter: initial release (#12485) * TargetVideo Bid Adapter : user sync and response changes (#12461) * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo Bid Adapter: Add GDPR/USP support * TargetVideo Bid Adapter: Add GDPR/USP support tests * TargetVideo Bid Adapter: Updating margin rule * Add Brid bid adapter * Brid adapter requested changes * BridBidAdapter: switching to plcmt * Brid Bid Adapter: getUserSyncs method and interpretResponse updates * Adding missing semicolon * TargetVideo Bid Adapter : user sync and response changes * TargetVideo Bid Adapter : removing duplicate code * Adkernel: add OppaMedia alias (#12506) * Contxtful RTD Module: support ui events (#12398) * feat: ui events * doc: wording * doc: revised to re-run ci * test: removed os-specific test --------- Co-authored-by: Sébastien Rufiange * nextMillennium Bid Adapter: Fixed a bug when there were several bids with the same adUnit.code (#12505) * nextMillenniumBidAdaper: Fixed a bug when there were several bids with the same adUnit.code * nextMillenniumBidAdaper: Fixed a bug when there were several bids with the same adUnit.code * Contxtful RTD Module: added defer param (#12499) * feat: defer param * fix: added typecheck * Utiq ID module: add netID support (#12494) * Utiq ID module: Add netID support * Utiq ID module: Refactor netId test for readability --------- Co-authored-by: manuel * Copper6ssp Bid Adapter: add gvl_id (#12498) * release adapter Copper6SSP * removed code duplication * add gvl_id * vastTrackers: make request and auction info available to VAST trackers (#12468) * IntentIq ID & Analytics Modules : CMP values and browser detection bug fix (#12511) * update intentIqAnalyticsAdapter.js && intentIqIdSystem.js * fix lint issues * fix tests * move info * resolve issues * update storeFirstPartyData * remove unused code * update defineEmptyDataAndFireCallback * update fix lint * update reportExternalWin * small fixes * update test && add docs * AGT-347: Support domain name * AGT-347: Support domain name * AGT-374: Support domainName to vrref * AGT-374: tests in progress * AGT-374: Remove duplicate encoded in getRelevantRefferer and fix tests * AGT-374: Add test domainName, changes in documentation * AGT-374: Change js version value * AGT-374: Remove extra coma * Remove unused method * AGT-384: gpp string value * AGT-384: GPC value, browserDetector fix * AGT-384: Remove gpc logic * AGT-384: Reduce getGppValue method * AGT-384: Gpp tests and prevent send ids from LS in group B * AGT-384: Change empty array of eids * AGT-384: Some fixes after review --------- Co-authored-by: dlepetynskyi Co-authored-by: DimaIntentIQ Co-authored-by: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com> * Updates the Lotame User ID module to honor a publisher-supplied storage configuration object (#12403) * Prebid 9.21.0 release * Increment version to 9.22.0-pre * Adkernel: add Pixelpluses alias (#12520) * Add kuantyx alias (#12523) Co-authored-by: dev * Mobian Bid Adapter : push context data to GAM (#12389) * Push context data to GAM * Update browsi to set gpt key values * fix browsi * Revamps module to make it configurable * Revamps module and tests, adds config * Adds more config and documentation * Updates mock emotion --------- Co-authored-by: Demetrio Girardi * AGT-388: Add missed params and return old params (#12524) Co-authored-by: dmytro-po * Refactor craftBidAdapter (#12517) * Qortex RTD module : code removal & cleanup (#12515) * Qortex RTD module code removal & cleanup * Add additional information to jsdocs * JSDocs update for setters * Price floors module : accept null floors (#12295) * 10432 Accept null floors * lint fix * Adding tests for floor provider case * null-safe getFloor in adapters * Update kargoBidAdapter.js --------- Co-authored-by: Marcin Komorski Co-authored-by: Patrick McCann * Medianet Analytics Adapter: ADD bid properties in logs and small fix in bidTimeoutHandler (#12526) * Core: fix bug where adRenderSucceeded event payloads are sometimes missing adId (#12530) * Add adtarget gvlid (#12531) * Currency Module: Adding auction delay handling (#12364) * Delay auction param on currency module * hookConfig change * test improvement * review fixes * introducing timeoutQueue * fix --------- Co-authored-by: Marcin Komorski * BeOpAdapter - First Party Cookie read and set (#16) (#12486) * Core: fix bug where queue is processed before processQueue is called (#12528) * richAudience Bid Adapter : update functionality of bid param: keywords (#12537) * Update richaudienceBidAdapter.md Update maintainer e-mail to integrations@richaudience.com * Add richaudienceBidAdapter.js file * Add richaudienceBidAdapter_spec.js * Update richaudienceBidAdapter.js * RichaudienceBidAdapter add compability with DSA * RichaudienceBidAdapter add compability with DSA * RichaudienceBidAdapter add compability with DSA * Richaudience Bid Adapter: update adomain * Richaudience Bid Adapter: update adomain test * (fix)richAudienceBidAdapter change functionality of bid param: keywords --------- Co-authored-by: Patrick McCann Co-authored-by: sergigimenez * Prebid Core: Added TTL validation for suppressing expired ads (#12532) * Added TTL Validation for Suppressing Expired Ads * resolved linting issues --------- Co-authored-by: pm-azhar-mulla * Prebid 9.22.0 release * Increment version to 9.23.0-pre * Equativ Bid Adapter: add support for video media type (#12514) * add support of dsa * restore topics * DSA fix for UT * drafy of adapter * fixes after dev test * make world simpler * fix prev commit * return empty userSyncs array by default * adjustments * apply prettier * unit tests for Equativ adapter * add dsp user sync * add readme * body can be undef * support additional br params * remove user sync * do not send dt param * handle floors and network id * handle empty media types * get min floor * fix desc for u.t. * better name for u.t. * add u.t. for not supported media type * improve currency u.t. * SADR-6484: initial video setup for new PBJS adapter * SADR-6484: Adding logging requirement missed earlier * SADR-6484: handle ext.rewarded prop for video with new oRTBConverter * SADR-6484: test revision + not sending bid requests where video obj is empty * refactoring and u.t. * rename variable * revert changes rel. to test endpoint * revert changes rel. to test endpoint * split imp[0] into seperate requests and fix u.t. --------- Co-authored-by: Elżbieta SZPONDER Co-authored-by: eszponder <155961428+eszponder@users.noreply.github.com> Co-authored-by: janzych-smart Co-authored-by: Jeff Mahoney Co-authored-by: Jeff Mahoney * Core: remove individual bids from cache when minBidCacheTTL is set (#12535) * Various bid adapters: Currency config cleanup (#12102) * cleanup currency poc * update * update * update * update * fix * investigation * review fixes * Do not change ortbConverter priority * Update admaticBidAdapter.js * Update missenaBidAdapter.js * Update missenaBidAdapter.js * Update admaticBidAdapter.js * lint fix --------- Co-authored-by: Marcin Komorski Co-authored-by: Marcin Komorski Co-authored-by: Demetrio Girardi Co-authored-by: Patrick McCann * Bidmatic Bid Adapter: add syncing (#12541) * Add bidmatic syncs * Add bidmatic syncs * Missena Bid Adapter : refactor getUserSyncs, send screen size (#12497) * Missena Bid Adapter : refactor getUserSyncs * send screen * Core: support adAuctionHeaders (#12542) * Goldfish Ads RTD Adapter: Fix Description Typo (#12550) * Add goldfishAdsRtdProvider Module * Update description and change instances of GoldfishAds to Goldfish Ads * update names * Fix typo in goldfishAdsRtdProvider.md * Bump path-to-regexp and express (#12552) Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) to 0.1.12 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `path-to-regexp` from 0.1.10 to 0.1.12 - [Release notes](https://github.com/pillarjs/path-to-regexp/releases) - [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md) - [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.10...v0.1.12) Updates `express` from 4.21.1 to 4.21.2 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.1...4.21.2) --- updated-dependencies: - dependency-name: path-to-regexp dependency-type: indirect - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Core & multiple modules: replace `deepAccess` with optional chaining (#12544) * Replace `deepAccess` with optional chaining Closes #12543 * Revert changes containing variable lookups that may dynamically contain "." * Use optional chaining Adding these changes back after review discovered the missing optional chain which was causing test failure in a previous commit. * revert bidderSettings & add test case --------- Co-authored-by: Demetrio Girardi * targeting keys issue when sendAllBids is true (#12518) * Yandex Metrica Analytics: updated links to the interface (#12548) * sspBC Bid Adapter : added support for transactionID and update tests (#12547) * Update tests for sspBC adapter Update tests for sspBC adapter: - change userSync test (due to tcf param appended in v4.6) - add tests for onBidWon and onTimeout * [sspbc-adapter] 5.3 updates: content-type for notifications * [sspbc-adapter] pass CTA to native bid * [sspbc-5.3] keep pbsize for detected adunits * [maintenance] - remove old test for sspBc bid adaptor * [sspbc-5.3] increment adaptor ver * [sspbc-adapter] maintenance update to sspBCBidAdapter * remove yarn.lock * Delete package-lock.json * remove package-lock.jsonfrom pull request * [sspbc-adapter] send pageViewId in request * [sspbc-adapter] update pageViewId test * [sspbc-adapter] add viewabiility tracker to native ads * [sspbc-adapter] add support for bid.admNative property * [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request) * [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative * [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response * [sspbc-adapter] jstracker data fix * [sspbc-adapter] jstracker data fix * [sspbc-adapter] send tagid in notifications * [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync * update remote repo * cleanup of grupawp/prebid master branch * update sspBC adapter to v 5.9 * update tests for sspBC bid adapter * [sspbc-adapter] add support for topicsFPD module * [sspbc-adapter] change topic segment ids to int * sspbc adapter -> update to v6 * [sspbc-adapter] update to v6.1 - add image sync, transactionId & schain support, improve test coverage * [sspbc-adapter] fix typos --------- Co-authored-by: Wojciech Biały Co-authored-by: decemberWP <155962474+decemberWP@users.noreply.github.com> * Core: Sync between ortb2Imp and mediaTypes (#12423) * ortb2 <-> mediaTypes * native & banner params * syncOrtb2 * improvements * improvement * lint fix * type check * instance of Map check * get rid of entries * removing native from syncOrtb2 * Update test/spec/banner_spec.js Co-authored-by: Demetrio Girardi * Update src/prebid.js Co-authored-by: Demetrio Girardi * Update prebid.js * video test fix --------- Co-authored-by: Marcin Komorski Co-authored-by: Patrick McCann Co-authored-by: Demetrio Girardi * Fix deep access (#12558) * craftBidAdapter: Fix netRevenue (#12536) * Unicorn Bid Adapter : fix net revenue (#12509) * fix net revenue * fix spec * Compass Bid Adapter : add gvlid (#12561) * add Compass Adapter * fix * fix * fix * add endpointId * fix * add gpp info * AdMatic Bid Adapter: adt sync url updated (#12565) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js * admatic cur update * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Nexverse Bid Adapter : initial relese (#12297) * Add nexverseBidAdapter implementation and tests * Updated Nexverse adapter to support OpenRTB 2.5, handle 204 responses, and added device and connection type detection * Resolved conflicts and merged master into nexverse-bid-adapter * Code optimization: * created utils for all common code * reused prebid inbuild functions * created nexverse.html to text nexverse adaptor * changed test specs based on main js * Code optimization: * created utils for all common code * reused prebid inbuild functions * created nexverse.html to text nexverse adaptor * changed test specs based on main js * * Removed bidfloor from Adunit * Fixed issues of 1.Category 2.Domain 3.Attributes * Added Nexverse Demo html for testing with sample bid * Handled Height and width issue * * Added test cases for bid param checks * fixed bidFloor issue * * Added testcases for utils functions ' * * removed duplicate function and used from available library * * removed unwated logging * * added logger in try catch * fixed linter error * * fixed linter issue * fixed the getOsVersion missing function issue * fixed the getOsVersion missing function issue * removed hardcoded cpm value * Added comment for is debug param --------- Co-authored-by: yogeshverse * Ensure the correct winning bid is recorded if an adapter is returning multiple bids. (#12568) * Adagio rtd provider: fix traffic going outside the ab test (#12563) * AdagioRtdProvider: isolate AbTest logic from localstorage adagio.session object * AdagioRtdProvider: only pass AbTest data if for current session * AdagioRtdProvider: change session lastActivityTime to session to simplify logic of session users * Equativ Bid Adapter: support native bid requests (#12566) * add support of dsa * restore topics * DSA fix for UT * drafy of adapter * fixes after dev test * make world simpler * fix prev commit * return empty userSyncs array by default * adjustments * apply prettier * unit tests for Equativ adapter * add dsp user sync * add readme * body can be undef * support additional br params * remove user sync * do not send dt param * handle floors and network id * handle empty media types * get min floor * fix desc for u.t. * better name for u.t. * add u.t. for not supported media type * improve currency u.t. * updates after pr review * SADR-6484: initial video setup for new PBJS adapter * SADR-6484: Adding logging requirement missed earlier * SADR-6484: handle ext.rewarded prop for video with new oRTBConverter * SADR-6484: test revision + not sending bid requests where video obj is empty * refactoring and u.t. * rename variable * Equativ: SADR-6615: adding unit tests for and additional logging to bid adapter to support native requests * revert changes rel. to test endpoint * revert changes rel. to test endpoint * split imp[0] into seperate requests and fix u.t. * Equativ bid adapter: adding support for native media type * Equativ bid adapter: update unit test for native-support work * Equativ bid adapter: removing console.log from unit test file * Equativ bid adapter: clarifying refinements regarding native-request processing * Equativ Bid Adapter: updating unit tests for native requests * PR feedback --------- Co-authored-by: Elżbieta SZPONDER Co-authored-by: eszponder <155961428+eszponder@users.noreply.github.com> Co-authored-by: Krzysztof Sokół <88041828+krzysztofequativ@users.noreply.github.com> Co-authored-by: Krzysztof Sokół Co-authored-by: janzych-smart * feat: auctionsCounter at adUnit level (#12557) * Core: fix bug where FPD enrichments can modify bidder configuration (#12572) * Rise Bid Adapter: Add ORTB2 device data to request payload (#12017) * Rise Bid Adapter: Add ORTB2 device data to request payload * Rise Bid Adapter: Move ORTB2 device under `params` key --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> * nextMillennium Bid Adapter : added gpid support (#12567) * added gpid and pbadslot * added test cases for gpid, and pbadslot * fixed lint error --------- Co-authored-by: Yakov Klein * Reading advertiserDomains from adv response. (#12573) * Added page view ID to Kobler bid adapter. (#12556) * PBS Adapter: fix inconsistency in how bidderconfig is merged, with a special case for EIDs (#12571) * PBS adapter: pre-merge arrays in bidder config * PBS Adapter: consolidate EIDs using eidpermissions * fix lint * optimizations * Mobian RTD Module: Docs Update (#12576) * Updated overview section * Added detailed documentation for Mobian RTD module * Adding formatting for documentation * More formatting * More formatting * Updated values to be exact matches, clarified some descriptions * Prebid 9.23.0 release * Increment version to 9.24.0-pre * Lasso Bid Adapter : add npi support (#12545) * New npi functionality for Lasso prebid adapter * New npi functionality for Lasso prebid adapter * Update unit tests for lasso prebid adapter * Initial commit for ResponsiveAds bid adapter (#12554) * Bridgeupp Bidder Adapter : initial release (#12549) * Bridgeupp Bidder Adapter: initial release * update referance to bidder from spec for helo world testing e2e * update server endpoint for performance * AcuityAds Bid Adapter: add gvlid (#12581) * add prebid.js adapter * changes * changes * changes * changes * fix downolad * add gpp * Merge remote-tracking branch 'prebid/master' * add gvlid * Use credentials in requests in Aniview Bid Adapter (#12579) * Add screen and viewport to ad request (#12553) * Add first-party data handling to kueezRtbBidAdapter (#12503) * AdGeneration Bid Adapter : change endpoint and add ortb converter (#12538) * AdGeneration adapter : use POST Method * AdGeneration adapter : use response results paramster * AdGeneration adapter : fix request parameters * AdGeneration adapter : fix response parameters * AdGeneration adapter : fix imark parameter * AdGeneration adapter : fix exclude invalid novatiq HyperID * AdGeneration adapter : fix creativeId * AdGeneration adapter : add sdktype parameter * AdGeneration adapter : fix deep copy additional parameters * AdGeneration adapter : fix test spec * Rediads Bid Adapter : initial release (#12525) * feature: Rediads bid adapter * Test Cases updated | Bid Adapter improved * Bug fix * Readme updated * Test cases fixed * test cases updated * test cases reset hash fixed * Add semicolon * remove semicolon * Get site content from bidrequest instead of config --------- Co-authored-by: symplorpro Co-authored-by: Symplor Co-authored-by: symplorpro <161946060+symplorpro@users.noreply.github.com> * Greenbids Bidder Adapter (#12510) * Greenbids Bidder adapter * refacto to make the code easier and clearer * refacto to make the code easier and clearer * Alexis' review * Alex's review part 2 * export more utils * add test on news utils * remove info that could lead to finger printing * Brainx Bid Adapter : initial release (#12413) * add brainx adpater * fix adpater md * delete console * fix repeat * Modify fixed parameters * Modify endpoint to be optional * fix email * update endpoint url * remove hello_html & x-domain history changes * fix size * remove empty line * Update brainxBidAdapter.md --------- Co-authored-by: hugh.qu Co-authored-by: Chris Huie * dfpAdServerVideo: set vconp (continuous playblack on) when playbackmethod contains 7 (#12590) * PBS Adapter: only include known bidders in eidpermissions (#12594) * Bug fix: Export getProperties method (#12587) * Adf Bid Adapter: use common ortb2 data (#12582) * use common ortb2 data from bidderRequest * set iab categories on bid response * 33across ID System: Include hashed email from storage (#12529) * 33across ID System: Send hm param * fix typo thirthy to thirty * remove all the ID storage keys when response is successful but doesn't contain ID anymore. * Allow to send hashed email via config parameters * Smarthub Bid Adapter : set skipPbsAliasing to false (#12601) * update adapter SmartHub: add aliases * smarthub set skipPbsAliasing to false --------- Co-authored-by: Victor * AdMatic Bid Adapter : add yobee alias (#12588) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid * Update admaticBidAdapter.js * admatic cur update * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Integration example: Create id_lift_measurement.html (#12577) * Create id_lift_measurement.html * Update id_lift_measurement.html * Update id_lift_measurement.html * Update id_lift_measurement.html * Update id_lift_measurement.html * Update id_lift_measurement.html * AdPlayerPro Video Module : add PLCMT (#12593) * Add PLCMT to video module AdPlayer.Pro * fix tests * adtelligent bid adapter: add new allias (#12559) * addStellormedia alias * add adtellintUtils * update adtelligentUtils * fix test --------- Co-authored-by: VadymShatov * Doceree AdManager Bid Adapter : maintainence (#12578) * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Updated docereeAdManager bid adapter * Update docereeAdManagerBidAdapter.js * added test cases for payload formation in DocereeAdManager * Added support for publisherUrl * added some parameters * Added support for TCF 2.2 * Update docereeAdManagerBidAdapter.js * Update docereeAdManagerBidAdapter.js * Update docereeAdManagerBidAdapter.js * Written test cases for new method implemented. * indentation issues resolved * Update docereeAdManagerBidAdapter_spec.js * Update docereeAdManagerBidAdapter_spec.js * Update docereeAdManagerBidAdapter_spec.js * Updated DocereeAdManager Bidder Adapter * Update docereeAdManagerBidAdapter.js --------- Co-authored-by: lokesh-doceree Co-authored-by: Patrick McCann * Prebid 9.24.0 release * Increment version to 9.25.0-pre * PBS Adapter: handle edge case with duplicated EID permissions (#12595) * PBS Adapter: handle edge case with duplicated EID permissions * fix lint * Autoplay detection update: ignore NotSupportedError exceptions (#12603) * Ignore NotSupportedError exceptions in autoplay detection This error is caused by a Content Security Policy that disables data: scheme for media URLs. Before this PR, this error would cause autoplay to be disabled; now, if this error is raised it has the same effect as disabling autoplay detection. * Remove warning that has been addressed by #11822 * Taboola bid adapter add ortb2 device (#11750) * Taboola Bid Adapter: Add full ORTB2 device data to request payload * Taboola Bid Adapter: Modify FPD test to verify presence of ORTB2 device data in request * Taboola Bid Adapter: Remove device data fallback and add ortb2 device data to `commonBidderRequest` (tests) as it is always present in real-world scenarios --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> * Adagio Bid Adapter: Add full ORTB2 device data to request payload (#12006) Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> * Vidazoo Bid Adapter: Add ORTB2 device data to request payload (#12074) Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> * ImRtdProvider: add imuid param (#12475) * ImRtdProvider: add bidder function for craft * ImRtdProvider: remove bidder function for craft * ImRtdProvider: add imuid param * TNC Id Module : user input url validation and optimizations (#12527) * Bug Fixes: modules/tncIdSystem.js - Optimized User ID Recovery: Replaced the existing user ID recovery function with a faster and more efficient method, improving performance. modules/userId/userId.md - Documentation Correction: Resolved inconsistencies in the documentation, ensuring accurate information for module configuration and usage. * - Tests fixed * - TNCID module fix: "getTNCID is not a function" error * modules/tncIdSystem.js - user input URL validations added - TNCID recovered from cookie storage if available - code optimizations for faster TNCID load ________________________________________ test/spec/modules/tncIdSystem_spec.js - added tests for new functions ________________________________________ modules/tncIdSystem.md - updated documentation * - fixed lint errors * - Sales description removed * Looking forward for code approval. * Goldbach Bid Adapter: connecting to new backend, reduced bid parameters (#12540) * Squashed commit of the following: Updated Goldbach Bidder Adapter - connecting to goldlayer-api - using custom outstream video player * added slotId to bid params * changed native assets order * Yield one bid adapter: Conditionally stop sending push_sync requests (#12591) * stop sending push_sync requests in YieldOne adapter in case of Safari browser OR iOS device * stop sending push_sync requests in YieldOne adapter in case of GDPR applies * fix linter * adjust unit tests for Safari and iOS cases * adjust unit tests for GDPR applies cases --------- Co-authored-by: alukonin * Aniview: send `format` and `w`/`h` with banner request + refactoring and small fixes (#12592) * Use credentials in requests in Aniview Bid Adapter * - isBannerType / isVideoType as functions; - Now for the banner request, we’re sending format and w / h as a fallback; - Getting w, h, crid, adid, adomain from the serving (with fallbacks); - Changed the bidResponse name to prebidBid; - Updated getSize function; - Removed unnecessary conditions; - Flat map for seatbids array; - Using the proper bid in interpretResponse. * Update with testing params (#12600) * nextMillenniumBidAdapter: Added support `imp.video.pos` and `imp.banner.pos` (#12606) * PB-2866 - added position to imp * PB-2866 - added position to imp * PB-2866 - added position to imp - 2 * Retailspot bidAdapter : Endpoint update (#12602) * add retailspot GVL_ID, update retailspotads domain name * update port for local testing * update unit tests * update test parameters with new placement id * simplify getSize workflow * fix lint error on type definition --------- Co-authored-by: Guillaume Andouard * Revert "Vidazoo Bid Adapter: Add ORTB2 device data to request payload (#12074)" (#12607) This reverts commit 6e42abee5b52ec65520fdf585a93420754f0807a. * LuponMedia BidAdapter: Add adomain to bidResponse (#12604) * Fix (9.0 fixes): Remove discontinued fpd.context, remove unused onBidWon event * Update luponmediaBidAdapter.js | adomain Add adomain to bidResponse * Update luponmediaBidAdapter.js Add meta field * Update luponmediaBidAdapter.js Fix eslint trailing spaces * Update luponmediaBidAdapter_spec.js add new fields to the test spec --------- Co-authored-by: Lupon Media * Prebid 9.25.0 release * Increment version to 9.26.0-pre * Akcelo bid adapter : initial release (#12583) * Add Akcelo bidder * Use real identifiers in bid example * Analytics adapters: attach arbitrary labels to analytics events (#12597) * Mobian RTD Provider: Adds prefix to ortb data as per config (#12596) * Adds prefix to ortb data * Updates * Fixes tests * Updates * Fixes AP values not setting in GAM * Fixes tests * Updates types * Mobian RTD Module: Documentation update (#12608) * Updated overview section * Added detailed documentation for Mobian RTD module * Adding formatting for documentation * More formatting * More formatting * Updated values to be exact matches, clarified some descriptions * Updated Mobian RTD Module documentation with more detail * Attekmi add new alias artechnology (#12609) * update adapter SmartHub: add aliases * Attekmi: add new alias Artechnology --------- Co-authored-by: Victor * Escalax Bid Adapter: initial release (#12483) * init escalax adapter * region substitution based on time zone * Adkernel: add UrekaMedia alias (#12614) * Adtrgtme bid adapter changes (#12580) * adtrgtme bid adapter Bugfix, add params.zid as placement ident * #12580 add fixes add ortb2.device.ip remove deepAccess refactor site object refactor user sync * Fix linter * MadSense Bid Adapter : initial release (#12546) * Submit madSense bidder adapter * madSense Bid Adapter: Prefer banner, use optional chaining - Preferred banner over instream for non-instream contexts. - Replaced all instances of deepAccess with optional chaining. * Use mtype exclusively for media type determination - Simplified determineMediaType function to rely solely on `mtype` provided by the exchange. - Removed redundant checks for hasVideoMediaType and hasBannerMediaType to streamline logic. * Bitmedia Bidder Adapter : initial release (#12610) * init bitmedia adapter * add newline at end of file * change default CDN URL * remove deepAccess, config, rewrite OTRB request * Nexverse Bid Adapter : remove slash from the endpoint (#12617) * Add nexverseBidAdapter implementation and tests * Updated Nexverse adapter to support OpenRTB 2.5, handle 204 responses, and added device and connection type detection * Resolved conflicts and merged master into nexverse-bid-adapter * Code optimization: * created utils for all common code * reused prebid inbuild functions * created nexverse.html to text nexverse adaptor * changed test specs based on main js * Code optimization: * created utils for all common code * reused prebid inbuild functions * created nexverse.html to text nexverse adaptor * changed test specs based on main js * * Removed bidfloor from Adunit * Fixed issues of 1.Category 2.Domain 3.Attributes * Added Nexverse Demo html for testing with sample bid * Handled Height and width issue * * Added test cases for bid param checks * fixed bidFloor issue * * Added testcases for utils functions ' * * removed duplicate function and used from available library * * removed unwated logging * * added logger in try catch * fixed linter error * * fixed linter issue * fixed the getOsVersion missing function issue * fixed the getOsVersion missing function issue * removed hardcoded cpm value * Added comment for is debug param * removed extra slash from URL --------- Co-authored-by: Anand Kumar * Prebid 9.26.0 release * Increment version to 9.27.0-pre * AdagioRtdProvider: always set session.expiry (#12611) * YieldlabBidAdapter forward consent under param gdpr_consent (#12623) * Orbit soft bid Module: Change aliases (#12628) * Adding Orbitsoft module * Adding Orbitsoft module (corrected) * Adding Orbitsoft module (correction of remarks) * Adding Orbitsoft module (correction of remarks) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to alias-able) * Adding Orbitsoft module (correction to new constructor) * Adding Orbitsoft module (delete unnecessary aliases) * Adding Orbitsoft module (delete unnecessary aliases) * fixed orbitsoftAdapter * fixed orbitsoftAdapter processing undefined request referrer * fixed orbitsoftAdapter processing undefined request referrer * fix-orbitsoftAdaper: codereview fixes * added changes for new spec * added changes for new spec * added changes for new spec * added "custom" currency * fix: currency key replaced into ortb2 * rollback a change * rollback a change * added a new alias --------- Co-authored-by: Dmitriy Shimko Co-authored-by: Хатламаджиян Виталий Co-authored-by: Хатламаджиян Виталий * Equativ Bid Adapter: add warning messages for audio-related properties in bid requests (#12616) * add support of dsa * restore topics * DSA fix for UT * drafy of adapter * fixes after dev test * make world simpler * fix prev commit * return empty userSyncs array by default * adjustments * apply prettier * unit tests for Equativ adapter * add dsp user sync * add readme * body can be undef * support additional br params * remove user sync * do not send dt param * handle floors and network id * handle empty media types * get min floor * fix desc for u.t. * better name for u.t. * add u.t. for not supported media type * improve currency u.t. * updates after pr review * SADR-6484: initial video setup for new PBJS adapter * SADR-6484: Adding logging requirement missed earlier * SADR-6484: handle ext.rewarded prop for video with new oRTBConverter * SADR-6484: test revision + not sending bid requests where video obj is empty * refactoring and u.t. * rename variable * Equativ: SADR-6615: adding unit tests for and additional logging to bid adapter to support native requests * revert changes rel. to test endpoint * revert changes rel. to test endpoint * split imp[0] into seperate requests and fix u.t. * Equativ bid adapter: adding support for native media type * Equativ bid adapter: update unit test for native-support work * Equativ bid adapter: removing console.log from unit test file * Equativ bid adapter: clarifying refinements regarding native-request processing * Equativ Bid Adapter: updating unit tests for native requests * PR feedback * Equativ Bid Adapter: add audio-specific warnings for missing fields in bid requests --------- Co-authored-by: Elżbieta SZPONDER Co-authored-by: eszponder <155961428+eszponder@users.noreply.github.com> Co-authored-by: Krzysztof Sokół <88041828+krzysztofequativ@users.noreply.github.com> Co-authored-by: Krzysztof Sokół Co-authored-by: janzych-smart * Contxtful Bid Adapter: Updates the default sampling rate (#12622) * fix: default sampling rate * doc: update * fix: non inclusive comparison * doc: update * doc: ci trigger --------- Co-authored-by: rufiange * PBjs Core : fix creation of per-bidder syncOptions (#12615) * Update tests for sspBC adapter Update tests for sspBC adapter: - change userSync test (due to tcf param appended in v4.6) - add tests for onBidWon and onTimeout * [sspbc-adapter] 5.3 updates: content-type for notifications * [sspbc-adapter] pass CTA to native bid * [sspbc-5.3] keep pbsize for detected adunits * [maintenance] - remove old test for sspBc bid adaptor * [sspbc-5.3] increment adaptor ver * [sspbc-adapter] maintenance update to sspBCBidAdapter * remove yarn.lock * Delete package-lock.json * remove package-lock.jsonfrom pull request * [sspbc-adapter] send pageViewId in request * [sspbc-adapter] update pageViewId test * [sspbc-adapter] add viewabiility tracker to native ads * [sspbc-adapter] add support for bid.admNative property * [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request) * [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative * [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response * [sspbc-adapter] jstracker data fix * [sspbc-adapter] jstracker data fix * [sspbc-adapter] send tagid in notifications * [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync * update remote repo * cleanup of grupawp/prebid master branch * update sspBC adapter to v 5.9 * update tests for sspBC bid adapter * [sspbc-adapter] add support for topicsFPD module * [sspbc-adapter] change topic segment ids to int * sspbc adapter -> update to v6 * bugfix proposal - using isActivityAllowed to build per-bidder syncOptions * Fix preexisting tests * Use canBidderRegisterSync --------- Co-authored-by: Wojciech Biały Co-authored-by: decemberWP <155962474+decemberWP@users.noreply.github.com> Co-authored-by: Demetrio Girardi * Eskimi Bid Adapter: use credentials in requests (#12629) Co-authored-by: Andrius Versockas * ConceptX Bid Adapter: add gvlid (#12632) * New adapter: concepx * Syntax change * Revert syntax change * Defensive check for response from bidder server * Add better validation for the request * Merge branch 'master' of https://github.com/prebid/Prebid.js * Don't append url on every buildrequest * Add gvlId to conceptX * JW Player Video Adapter: Determine Size before player is rendered (#12624) * interpets size before render * checks for getContainer * updates test * tests size calculation * checks for NaN * uses proper vars * IX Bid Adapter: fix request options field [PB-3461] (#12637) * fix: fix requests options field [PB-3461] * feat: set credentials options field explicitly [PB-3461] * fix: fix lint issue [PB-3461] * chore: add fetch request header test [PB-3461] * chore: add fetch request header test [PB-3461] * chore: add fetch request header test [PB-3461] * chore: update fetch test [PB-3461] * chore: update fetch test [PB-3461] * chore: change fetch stub approach [PB-3461] --------- Co-authored-by: shahin.rahbariasl * Vidazoo bid adapter add ortb2 device (#12640) * Vidazoo Bid Adapter: Add ORTB2 device data to request payload * Vidazoo Bid Adapter: Update exco adapter tests to include ORTB2 device --------- Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> * Blue Bid Adapter : initial release (#12513) * add blue * uncommited stuff * uncommited stuff * uncommited stuff * less duplicated code * less duplicated code * less duplicated code * less duplicated code * less duplicated code * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * blue adapter * add floor module support * add floor module support * add floor module support * add floor module support * add floor module support * only import what we need * Prebib --------- Co-authored-by: Tulio Duarte * OpenX Bid Adapter : support native (#12625) * [EXCH-10877] Add support for native to OpenX bid adapter * [EXCH-10877] Add unit tests for interpretResponse and native * [EXCH-10877] Fix formatting * [EXCH-10877] Add support for native to OpenX bid adapter * [EXCH-10877] Add unit tests for interpretResponse and native * [EXCH-10877] Fix formatting * Revert "Merge remote-tracking branch 'origin/EXCH-10877-add-support-for-native-imps' into EXCH-10877-add-support-for-native-imps" This reverts commit c88138628dda3ac9409c1846d2baaf022521c4d6, reversing changes made to 55a23a5f526e2a4d2cebb4fe4bea4369dde22859. * [EXCH-10877] Add info about native support to the docs * [EXCH-10877] create const, use null instead of undefined * [EXCH-10877] Add support for native to OpenX bid adapter * [EXCH-10877] Add unit tests for interpretResponse and native * [EXCH-10877] Fix formatting * [EXCH-10877] Add support for native to OpenX bid adapter * [EXCH-10877] Add unit tests for interpretResponse and native * [EXCH-10877] Fix formatting * Revert "Merge remote-tracking branch 'origin/EXCH-10877-add-support-for-native-imps' into EXCH-10877-add-support-for-native-imps" This reverts commit c88138628dda3ac9409c1846d2baaf022521c4d6, reversing changes made to 55a23a5f526e2a4d2cebb4fe4bea4369dde22859. * [EXCH-10877] Add info about native support to the docs * [EXCH-10877] create const, use null instead of undefined * [EXCH-10877] Fix merge issues * [EXCH-10877] Use various params.platform in test * 33across ID System: Store hashed email when feature is enabled via config (#12630) * Allow to read hashed email from 33across global * refactoring of supplemental IDs * store hashed email as another FP supplemental ID. * split existing unit test about successful 33across ID system response. * should clear hashed email if 33x response doesn't contain ID * rename some of the internal 33x ID system variables * code review feedback. HEM sources order & var rename * encode hints in report (#12652) * UserID: allow any contents in EIDs (#12651) * symitriDapRtdProvider - Enable X2 Tokenize endpoint (#12636) * symitriDapRtdProvider - Enable X2 Tokenize endpoint * Adding test case for X2 Tokenize --------- Co-authored-by: Jeff Palladino * Missena Bid Adapter : send bid sizes (#12560) * Missena Bid Adapter : send bid sizes * Move function to utils * Move the function to the proper place * Adloox Analytics Module: apply 'js' param constraint (#12618) * Adloox Analytics: enforce only adlooxtracking.com as a subdomain may be used * Adloox Ad Server Video: remove un-necessary default parameter from test * Adloox Analytics: fix test Stop being clever for my own good with the NOOP function blatting, it is a non-idempotent operation * LiveIntent Rtd Provider: initial release (#12631) * implement liveIntentRtdProvider * fix test * trigger circleci * add typedef * fix: source and account params (#12657) * feat(docs): updates permutive GDPR docs (#12660) Ensures docs are up to date & have clear instructions * Rise Bid Adapters: native and multiformat support (#12653) * RPRD-1638: Add support for Native media type and multi-format bid requests in `index.js`, Populate the changes on `rise/minutemedia/openweb/shinez/stn/BidAdapter.js`, Update all relevant `***BidAdapter_spec.js`, Update all relevant `***BidAdapter.md`, Keep backwards compatibility, Move `mimes` and `api` determination to VIDEO media type as its only relevant to video. * RPRD-1638: Move all `spec` code duplication to `index.js` and populate across all maintained adapters via `makeBaseSpec` factory function, Move all rise related constants to `constants.js`. * RPRD-1638: Align with seller response. * RPRD-1638: fix cr comments * RPRD-1638: Align tests with native response * circle ci test * fix: error logging (#12656) Co-authored-by: rufiange * EPlanning Bid Adapter : adding support for schain (#12635) * Add schain support to eplanning bid adapter * Se modifica bid adapter para tomar Nodes de schain en lugar de un valor random --------- Co-authored-by: Maxi * AcuityAds Bid Adapter: add endpointId param (#12644) * add prebid.js adapter * changes * changes * changes * changes * fix downolad * add gpp * Merge remote-tracking branch 'prebid/master' * add gvlid * add endpointId param * SharedIdSystem: add configurable inserter (#12664) * feat: session marker (#12634) Co-authored-by: rufiange * native Rendering : fix bug where click trackers are not fired (#12655) * nativeRendering: fix bug where click trackers are not fired * Cleanup * Prebid 9.27.0 release * Increment version to 9.28.0-pre * Change expected nodes greater than 2 to less than or equal to 2 (#12670) * Bump live-connect dependency (#12677) * Liveintent Id Module: support for additional eid (#12659) * Update shared.js * Update liveIntentExternalIdSystem_spec.js * Update liveIntentIdMinimalSystem_spec.js * Update liveIntentIdSystem_spec.js * Update liveIntentIdMinimalSystem_spec.js * Update test/spec/modules/liveIntentIdSystem_spec.js Co-authored-by: Viktor Dreiling <34981284+3link@users.noreply.github.com> * Update liveIntentExternalIdSystem_spec.js * Update liveIntentIdSystem_spec.js --------- Co-authored-by: Viktor Dreiling <34981284+3link@users.noreply.github.com> * ZetaGlobalSspAnalytics Adapter: domain and page (#12674) Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko * GPP MSPA Control Module: add support for usnat version 2 (#12667) * MSPA: add support for usnat version 2 * Use 15 (array bound) for testing * PubMatic Analytics Adapter: Moving slot level parameters to root level and adding few parameters to log in tracker (#12665) * Moved floors fields at root level from slot level * Added fv value in logger and tracker * Added safecheck for null object * Reading frv,fv value from bidResponse floorData instead of auctionCache * targeting keys issue when sendAllBids is true * Removed unused function * WURFL Rtd Provide: add wurfl_id to device.ext (#12675) * OMS Adapter: add video support, test coverage and update documentation (#12671) * Bump undici from 6.19.8 to 6.21.1 (#12679) Bumps [undici](https://github.com/nodejs/undici) from 6.19.8 to 6.21.1. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v6.19.8...v6.21.1) --- updated-dependencies: - dependency-name: undici dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat: signal ext.ibv and pass it through [PB-3505] (#12666) Co-authored-by: Chris Corbo * Pubmatic analytics: fix whitespace (#12684) * Improve Digital Bid Adapter : remove razr creative logic (#12678) * 12238 - Azerion / Improve: does not properly support currency module * **Type:** Fix * **Scope:** improvedigitalBidAdapter * **Subject:** Bid floors are always converted to USD. * **Details:** * Adds `DEFAULT_CURRENCY` variable which is set to USD * Adds `convertBidFloorCurrency` function which in used to convert the bid floor when both `imp.bidfloor` and `imp.bidfloorcur` are present, and `imp.bidfloorcur` is not equal to the adapter's `DEFAULT_CURRENCY`; * **Breaks:** N/A * restored accidentally discarded change from unit test expect * * Modifies behavior to pass bid floor as is when it cannot be converted to USD; * Removes rounding of bid floor when converting its currency to USD; * remove unnecessary uses of `toUpperCase()` * * fix `convertCurrency` mock * remove redundant checks for type and NaN from `convertBidFloorCurrency` function * ImproveDigital Bid Adapter: remove RAZR specific code * Remove ImproveDigital from loadExternal whitelist --------- Co-authored-by: Lyubomir Shishkov Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> * Consumable Bid Adapter: remove EID non-objects (#12646) * consumableBidAdapter: remove eid non-objects * fix lint errors * Prebid 9.28.0 release --------- Signed-off-by: dependabot[bot] Co-authored-by: Demetrio Girardi Co-authored-by: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com> Co-authored-by: Sébastien Rufiange Co-authored-by: Prebid.js automated release Co-authored-by: Nikhil <137479857+NikhilGopalChennissery@users.noreply.github.com> Co-authored-by: PrecisoSRL <134591565+PrecisoSRL@users.noreply.github.com> Co-authored-by: Filip Stamenkovic Co-authored-by: Michele Nasti Co-authored-by: Shiloh Annese Co-authored-by: rrochwick <65189775+rrochwick@users.noreply.github.com> Co-authored-by: Jaro Vanderheijden Co-authored-by: Robert Ray Martinez III Co-authored-by: Viktor Dreiling <34981284+3link@users.noreply.github.com> Co-authored-by: Krzysztof Sokół <88041828+krzysztofequativ@users.noreply.github.com> Co-authored-by: Elżbieta SZPONDER Co-authored-by: eszponder <155961428+eszponder@users.noreply.github.com> Co-authored-by: janzych-smart Co-authored-by: James Rosewell Co-authored-by: Bohdan V <25197509+BohdanVV@users.noreply.github.com> Co-authored-by: Mikhail Malkov Co-authored-by: OctaviaS20 <151752110+OctaviaS20@users.noreply.github.com> Co-authored-by: Octavia Suceava Co-authored-by: Abdou <40374175+disparate1@users.noreply.github.com> Co-authored-by: Filipe Neves Co-authored-by: Abderrahmen Ghadbane Co-authored-by: Daniël Hoeksema <37441336+danielsao@users.noreply.github.com> Co-authored-by: Nick Llerandi Co-authored-by: ourcraig Co-authored-by: René Baudisch Co-authored-by: Olivier Co-authored-by: Petre Damoc Co-authored-by: Snigel <108489367+snigelweb@users.noreply.github.com> Co-authored-by: Stanislav Golikov Co-authored-by: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com> Co-authored-by: Patrick McCann Co-authored-by: joseluis laso Co-authored-by: Gabriel Chicoye Co-authored-by: Gabriel Chicoye Co-authored-by: Oleksandr Solodovnikov Co-authored-by: tanguylemeur-sparteo <163292075+tanguylemeur-sparteo@users.noreply.github.com> Co-authored-by: Bill Coloe Co-authored-by: Josh Co-authored-by: Joshua Fledderjohn Co-authored-by: Aleksa Trajkovic Co-authored-by: gn-daikichi <49385718+gn-daikichi@users.noreply.github.com> Co-authored-by: Murano Takamasa Co-authored-by: daikichiteranishi <49385718+daikichiteranishi@users.noreply.github.com> Co-authored-by: PeiZ <74068135+peixunzhang@users.noreply.github.com> Co-authored-by: Philip Watson Co-authored-by: Denis Logachov Co-authored-by: Yohan Boutin Co-authored-by: Luca Corbo Co-authored-by: SmartHubSolutions <87376145+SmartHubSolutions@users.noreply.github.com> Co-authored-by: Victor Co-authored-by: Pranav Sheth <57259342+pranavsheth@users.noreply.github.com> Co-authored-by: vdo-ai-tech <126867429+vdo-ai-tech@users.noreply.github.com> Co-authored-by: rishabhsehrawat1 Co-authored-by: Dejan Grbavcic Co-authored-by: Fatih Kaya Co-authored-by: pm-nitin-shirsat <107102698+pm-nitin-shirsat@users.noreply.github.com> Co-authored-by: Chris Huie Co-authored-by: dev-adverxo Co-authored-by: thede-ri <161479447+thede-ri@users.noreply.github.com> Co-authored-by: Konstantin Mikhalyov Co-authored-by: vivekyadav15 Co-authored-by: LyricWulf Co-authored-by: PGAMSSP <142323401+PGAMSSP@users.noreply.github.com> Co-authored-by: Rich Audience Co-authored-by: Patrick McCann Co-authored-by: sergigimenez Co-authored-by: mkomorski Co-authored-by: Marcin Komorski Co-authored-by: Marcin Komorski Co-authored-by: Md. Soman Mia Sarker Co-authored-by: Harry King-Riches <109534328+harrykingriches@users.noreply.github.com> Co-authored-by: Sebastien Boisvert Co-authored-by: yuva-inmobi-1 Co-authored-by: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Co-authored-by: pm-azhar-mulla Co-authored-by: Michel Chrétien Co-authored-by: dmytro-po Co-authored-by: dlepetynskyi Co-authored-by: DimaIntentIQ Co-authored-by: DimaIntentIQ <139111483+DimaIntentIQ@users.noreply.github.com> Co-authored-by: Eugene Dorfman Co-authored-by: andreasgreen Co-authored-by: Manuel Alfonso Rodríguez Co-authored-by: manuel Co-authored-by: Copper6SSP Co-authored-by: Mike Marcus Co-authored-by: Adserver.Online <61009237+adserver-online@users.noreply.github.com> Co-authored-by: dev Co-authored-by: Ariel <155993606+arielmtk@users.noreply.github.com> Co-authored-by: Hiroaki Kubota Co-authored-by: Gena Co-authored-by: SebRobert Co-authored-by: Jeff Mahoney Co-authored-by: Jeff Mahoney Co-authored-by: Keith Doggett Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Graham Higgins Co-authored-by: pm-priyanka-deshmane <107103300+pm-priyanka-deshmane@users.noreply.github.com> Co-authored-by: Evgenii Novikov Co-authored-by: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Co-authored-by: Wojciech Biały Co-authored-by: decemberWP <155962474+decemberWP@users.noreply.github.com> Co-authored-by: Kotaro Shikata Co-authored-by: CompassSSP <95415988+CompassSSP@users.noreply.github.com> Co-authored-by: anand-nexverse Co-authored-by: yogeshverse Co-authored-by: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Co-authored-by: Krzysztof Sokół Co-authored-by: pratik.ta <143182729+Pratik3307@users.noreply.github.com> Co-authored-by: JacobKlein26 <42449375+JacobKlein26@users.noreply.github.com> Co-authored-by: Yakov Klein Co-authored-by: Antonios Sarhanis Co-authored-by: Bendegúz Ács <30595431+acsbendi@users.noreply.github.com> Co-authored-by: Rich Rodriguez Co-authored-by: Victor <103455651+victorlassomarketing@users.noreply.github.com> Co-authored-by: Primož Co-authored-by: Sharon Green Co-authored-by: AcuityAdsIntegrations <72594990+AcuityAdsIntegrations@users.noreply.github.com> Co-authored-by: Saar Amrani Co-authored-by: ss-toshihide-tajima <89903823+ss-toshihide-tajima@users.noreply.github.com> Co-authored-by: rediads <123890182+rediads@users.noreply.github.com> Co-authored-by: symplorpro Co-authored-by: Symplor Co-authored-by: symplorpro <161946060+symplorpro@users.noreply.github.com> Co-authored-by: jeremy-greenbids Co-authored-by: Hugh0222 Co-authored-by: hugh.qu Co-authored-by: Jurij Sinickij Co-authored-by: Carlos Felix Co-authored-by: artemAdp <133973660+artemAdp@users.noreply.github.com> Co-authored-by: Vadym Shatov <135347097+Gunnar97@users.noreply.github.com> Co-authored-by: VadymShatov Co-authored-by: Doceree-techStack <143162581+Doceree-techStack@users.noreply.github.com> Co-authored-by: lokesh-doceree Co-authored-by: Matthieu Wipliez <89922776+github-matthieu-wipliez@users.noreply.github.com> Co-authored-by: eknis Co-authored-by: annavane <101708287+annavane@users.noreply.github.com> Co-authored-by: Ben Brachmann <49547103+bevenio@users.noreply.github.com> Co-authored-by: alukonin1 <152840501+alukonin1@users.noreply.github.com> Co-authored-by: alukonin Co-authored-by: rs-guian <119166974+rs-guian@users.noreply.github.com> Co-authored-by: Guillaume Andouard Co-authored-by: adxpremium <55161519+adxpremium@users.noreply.github.com> Co-authored-by: Lupon Media Co-authored-by: Roger <104763658+rogerDyl@users.noreply.github.com> Co-authored-by: escalax Co-authored-by: tarasmatokhniuk Co-authored-by: MadSense Ops Co-authored-by: BitmediaDevTeam Co-authored-by: yuu.t Co-authored-by: VitalyOrbit Co-authored-by: Dmitriy Shimko Co-authored-by: Хатламаджиян Виталий Co-authored-by: Хатламаджиян Виталий Co-authored-by: Andrius Versockas Co-authored-by: Andrius Versockas Co-authored-by: cpcpn-emil <115714010+cpcpn-emil@users.noreply.github.com> Co-authored-by: Karim Mourra Co-authored-by: shahinrahbariasl <56240400+shahinrahbariasl@users.noreply.github.com> Co-authored-by: shahin.rahbariasl Co-authored-by: Tulio S. Duarte Co-authored-by: Tulio Duarte Co-authored-by: Gabriela Międlar <155444733+gmiedlar-ox@users.noreply.github.com> Co-authored-by: Jeff Palladino <1226357+jpalladino84@users.noreply.github.com> Co-authored-by: Jeff Palladino Co-authored-by: Alexander Clouter Co-authored-by: Wiem Zine Elabidine Co-authored-by: Sean McGroarty Co-authored-by: michachen Co-authored-by: Maximiliano Zurita <76264143+maximilianozurita@users.noreply.github.com> Co-authored-by: Maxi Co-authored-by: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: Siminko Vlad <85431371+siminkovladyslav@users.noreply.github.com> Co-authored-by: Chris Corbo Co-authored-by: Chris Corbo Co-authored-by: Catalin Ciocov Co-authored-by: Lyubomir Shishkov Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Co-authored-by: Jason Piros --- creative/renderers/native/renderer.js | 37 +- integrationExamples/gpt/hello_world.html | 2 +- .../gpt/id_lift_measurement.html | 173 + .../gpt/liveIntentRtdProviderExample.html | 164 + integrationExamples/gpt/nexverse.html | 126 + .../gpt/x-domain/creative.html | 2 +- .../adPlayerPro/bidRequestScheduling.html | 2 +- .../adPlayerPro/eventListeners.html | 2 +- .../videoModule/jwplayer/bidMarkedAsUsed.html | 4 +- .../jwplayer/bidRequestScheduling.html | 2 + .../jwplayer/bidsBackHandlerOverride.html | 2 + .../videoModule/jwplayer/eventListeners.html | 2 + .../videoModule/jwplayer/eventsUI.html | 2 + .../jwplayer/gamAdServerMediation.html | 2 + .../videoModule/jwplayer/mediaMetadata.html | 4 +- .../videoModule/jwplayer/playlist.html | 4 +- .../adtelligentUtils/adtelligentUtils.js | 79 + .../analyticsAdapter/AnalyticsAdapter.js | 18 +- libraries/appnexusUtils/anKeywords.js | 2 +- libraries/autoplayDetection/autoplay.js | 8 +- .../creative-renderer-native/renderer.js | 2 +- libraries/fpdUtils/pageInfo.js | 4 +- .../intentIqConstants/intentIqConstants.js | 2 +- libraries/liveIntentId/shared.js | 23 +- libraries/mspa/activityControls.js | 33 +- libraries/nexverseUtils/index.js | 130 + libraries/ortb2Utils/currency.js | 3 + libraries/ortbConverter/processors/banner.js | 3 +- libraries/ortbConverter/processors/video.js | 6 +- libraries/pageInfosUtils/pageInfosUtils.js | 65 + libraries/riseUtils/constants.js | 20 + libraries/riseUtils/index.js | 185 +- libraries/sizeUtils/sizeUtils.js | 25 + .../timeToFirstBytesUtils.js | 37 + libraries/vidazooUtils/bidderUtils.js | 2 + modules/33acrossIdSystem.js | 97 +- modules/33acrossIdSystem.md | 6 + modules/acuityadsBidAdapter.js | 4 +- modules/adagioBidAdapter.js | 34 +- modules/adagioRtdProvider.js | 87 +- modules/adfBidAdapter.js | 59 +- modules/adgenerationBidAdapter.js | 277 +- modules/adkernelBidAdapter.js | 3 +- modules/adlooxAnalyticsAdapter.js | 18 +- modules/admaticBidAdapter.js | 22 +- modules/adnuntiusBidAdapter.js | 19 +- modules/adotBidAdapter.js | 17 +- modules/adplayerproVideoProvider.js | 12 + modules/adtargetBidAdapter.js | 72 +- modules/adtelligentBidAdapter.js | 78 +- modules/adtrgtmeBidAdapter.js | 494 +- modules/adtrgtmeBidAdapter.md | 19 +- modules/akceloBidAdapter.js | 148 + modules/akceloBidAdapter.md | 57 + modules/aniviewBidAdapter.js | 118 +- modules/audiencerunBidAdapter.js | 9 +- modules/beopBidAdapter.js | 14 +- modules/bidmaticBidAdapter.js | 38 +- modules/bidmaticBidAdapter.md | 6 + modules/bitmediaBidAdapter.js | 265 + modules/bitmediaBidAdapter.md | 184 + modules/blueBidAdapter.js | 122 + modules/blueBidAdapter.md | 28 + modules/brainxBidAdapter.js | 117 + modules/brainxBidAdapter.md | 56 + modules/brandmetricsRtdProvider.js | 4 - modules/bridgeuppBidAdapter.js | 285 + modules/bridgeuppBidAdapter.md | 39 + modules/carodaBidAdapter.js | 5 +- modules/cleanioRtdProvider.js | 4 - modules/cointrafficBidAdapter.js | 3 +- modules/compassBidAdapter.js | 2 + modules/conceptxBidAdapter.js | 2 + modules/consumableBidAdapter.js | 4 +- modules/contxtfulBidAdapter.js | 38 +- modules/contxtfulRtdProvider.js | 21 +- modules/craftBidAdapter.js | 2 +- modules/criteoBidAdapter.js | 34 +- modules/currency.js | 14 +- modules/deltaprojectsBidAdapter.js | 12 +- modules/dfpAdServerVideo.js | 9 +- modules/dianomiBidAdapter.js | 3 +- modules/docereeAdManagerBidAdapter.js | 23 +- modules/dsp_genieeBidAdapter.js | 3 +- modules/eplanningBidAdapter.js | 6 +- modules/equativBidAdapter.js | 99 +- modules/escalaxBidAdapter.js | 106 + modules/escalaxBidAdapter.md | 80 + modules/eskimiBidAdapter.js | 5 +- modules/finativeBidAdapter.js | 4 +- modules/gmosspBidAdapter.js | 17 +- modules/goldbachBidAdapter.js | 1401 +--- modules/goldbachBidAdapter.md | 224 +- modules/goldfishAdsRtdProvider.md | 2 +- modules/greenbidsBidAdapter.js | 238 + modules/greenbidsBidAdapter.md | 32 + modules/imRtdProvider.js | 6 +- modules/improvedigitalBidAdapter.js | 67 - modules/intentIqAnalyticsAdapter.js | 2 +- modules/ixBidAdapter.js | 8 +- modules/jwplayerVideoProvider.js | 105 +- modules/koblerBidAdapter.js | 37 +- modules/kueezRtbBidAdapter.js | 76 +- modules/lassoBidAdapter.js | 28 +- modules/liveIntentRtdProvider.js | 52 + modules/liveIntentRtdProvider.md | 45 + modules/livewrappedAnalyticsAdapter.js | 12 +- modules/livewrappedBidAdapter.js | 3 +- modules/luponmediaBidAdapter.js | 6 +- modules/madsenseBidAdapter.js | 264 + modules/madsenseBidAdapter.md | 124 + modules/medianetBidAdapter.js | 2 +- modules/mgidBidAdapter.js | 3 +- modules/minutemediaBidAdapter.js | 86 +- modules/minutemediaBidAdapter.md | 2 +- modules/missenaBidAdapter.js | 36 +- modules/mobianRtdProvider.js | 125 +- modules/mobianRtdProvider.md | 147 +- modules/nativeRendering.js | 3 +- modules/nextMillenniumBidAdapter.js | 17 +- modules/nexverseBidAdapter.js | 247 + modules/nexverseBidAdapter.md | 41 + modules/nexx360BidAdapter.js | 4 +- modules/omsBidAdapter.js | 36 +- modules/omsBidAdapter.md | 15 + modules/openwebBidAdapter.js | 87 +- modules/openwebBidAdapter.md | 2 +- modules/openxBidAdapter.js | 25 +- modules/openxBidAdapter.md | 47 +- modules/orbitsoftBidAdapter.js | 2 +- modules/permutiveRtdProvider.md | 11 +- .../prebidServerBidAdapter/bidderConfig.js | 161 + modules/prebidServerBidAdapter/index.js | 5 +- .../prebidServerBidAdapter/ortbConverter.js | 12 +- modules/pubmaticAnalyticsAdapter.js | 80 +- modules/rediadsBidAdapter.js | 102 + modules/rediadsBidAdapter.md | 38 + modules/responsiveAdsBidAdapter.js | 80 + modules/responsiveAdsBidAdapter.md | 38 + modules/retailspotBidAdapter.js | 42 +- ...BidAdapter .md => retailspotBidAdapter.md} | 2 +- modules/richaudienceBidAdapter.js | 3 +- modules/riseBidAdapter.js | 103 +- modules/riseBidAdapter.md | 2 +- modules/seedtagBidAdapter.js | 9 - modules/sharedIdSystem.js | 12 +- modules/shinezBidAdapter.js | 84 +- modules/shinezBidAdapter.md | 4 +- modules/silverpushBidAdapter.js | 11 +- modules/smartadserverBidAdapter.js | 3 +- modules/smarthubBidAdapter.js | 14 +- modules/smilewantedBidAdapter.js | 4 +- modules/sspBCBidAdapter.js | 47 +- modules/stnBidAdapter.js | 93 +- modules/stnBidAdapter.md | 2 +- modules/sublimeBidAdapter.js | 3 +- modules/symitriDapRtdProvider.js | 32 +- modules/symitriDapRtdProvider.md | 4 +- modules/taboolaBidAdapter.js | 6 +- modules/tappxBidAdapter.js | 4 - modules/teadsBidAdapter.js | 26 +- modules/tncIdSystem.js | 130 +- modules/tncIdSystem.md | 34 +- modules/unicornBidAdapter.js | 2 +- modules/userId/eids.js | 37 +- modules/userId/index.js | 3 +- modules/viewdeosDXBidAdapter.js | 53 +- modules/visxBidAdapter.js | 4 +- modules/voxBidAdapter.js | 10 +- modules/winrBidAdapter.js | 5 - modules/wurflRtdProvider.js | 8 +- modules/wurflRtdProvider.md | 2 +- modules/yandexAnalyticsAdapter.md | 4 +- modules/yandexBidAdapter.js | 4 +- modules/yieldlabBidAdapter.js | 4 +- modules/yieldoneBidAdapter.js | 24 +- modules/zeta_global_sspAnalyticsAdapter.js | 7 +- package-lock.json | 6643 ++++++++++------- package.json | 4 +- src/Renderer.js | 6 +- src/adRendering.js | 5 +- src/adUnits.js | 18 + src/adapterManager.js | 11 +- src/adapters/bidderFactory.js | 13 +- src/adloader.js | 3 - src/ajax.js | 15 +- src/auction.js | 46 +- src/auctionManager.js | 24 +- src/banner.js | 21 + src/bidTTL.js | 24 +- src/native.js | 33 +- src/prebid.js | 51 +- src/targeting.js | 14 +- src/utils/gdpr.js | 4 +- src/video.js | 8 +- test/fixtures/fixtures.js | 62 + test/spec/AnalyticsAdapter_spec.js | 89 +- test/spec/auctionmanager_spec.js | 195 +- test/spec/banner_spec.js | 182 + test/spec/creative/nativeRenderer_spec.js | 55 +- .../libraries/mspa/activityControls_spec.js | 72 +- test/spec/libraries/pageInfosUtils.js | 102 + test/spec/libraries/timeToFirstBytesUtils.js | 54 + test/spec/modules/33acrossIdSystem_spec.js | 492 +- test/spec/modules/acuityadsBidAdapter_spec.js | 49 + test/spec/modules/adagioBidAdapter_spec.js | 34 + test/spec/modules/adagioRtdProvider_spec.js | 110 +- test/spec/modules/adfBidAdapter_spec.js | 182 +- .../modules/adgenerationBidAdapter_spec.js | 1658 ++-- test/spec/modules/adlooxAdServerVideo_spec.js | 1 - .../modules/adlooxAnalyticsAdapter_spec.js | 31 + test/spec/modules/adnuntiusBidAdapter_spec.js | 58 +- .../modules/adtelligentBidAdapter_spec.js | 1 + test/spec/modules/adtrgtmeBidAdapter_spec.js | 726 +- test/spec/modules/akceloBidAdapter_spec.js | 263 + test/spec/modules/aniviewBidAdapter_spec.js | 5 +- test/spec/modules/beopBidAdapter_spec.js | 35 +- test/spec/modules/bidmaticBidAdapter_spec.js | 42 +- test/spec/modules/bitmediaBidAdapter_spec.js | 499 ++ test/spec/modules/blueBidAdapter_spec.js | 91 + test/spec/modules/brainxBidAdapter_spec.js | 132 + test/spec/modules/bridgeuppBidAdapter_spec.js | 1141 +++ test/spec/modules/carodaBidAdapter_spec.js | 26 +- .../spec/modules/consumableBidAdapter_spec.js | 42 + .../spec/modules/contxtfulRtdProvider_spec.js | 39 +- test/spec/modules/craftBidAdapter_spec.js | 2 +- test/spec/modules/currency_spec.js | 14 + test/spec/modules/dfpAdServerVideo_spec.js | 2 +- test/spec/modules/dianomiBidAdapter_spec.js | 30 +- .../docereeAdManagerBidAdapter_spec.js | 11 +- .../spec/modules/dsp_genieeBidAdapter_spec.js | 17 +- test/spec/modules/eplanningBidAdapter_spec.js | 75 +- test/spec/modules/equativBidAdapter_spec.js | 501 +- test/spec/modules/escalaxBidAdapter_spec.js | 303 + test/spec/modules/excoBidAdapter_spec.js | 51 +- .../modules/genericAnalyticsAdapter_spec.js | 2 +- test/spec/modules/goldbachBidAdapter_spec.js | 1897 ++--- .../spec/modules/greenbidsBidAdapter_specs.js | 1051 +++ test/spec/modules/illuminBidAdapter_spec.js | 51 +- .../modules/improvedigitalBidAdapter_spec.js | 6 +- test/spec/modules/ixBidAdapter_spec.js | 70 +- test/spec/modules/koblerBidAdapter_spec.js | 61 +- test/spec/modules/kueezRtbBidAdapter_spec.js | 96 +- test/spec/modules/lassoBidAdapter_spec.js | 174 +- .../liveIntentExternalIdSystem_spec.js | 22 +- .../modules/liveIntentIdMinimalSystem_spec.js | 22 +- test/spec/modules/liveIntentIdSystem_spec.js | 55 +- .../modules/liveIntentRtdProvider_spec.js | 116 + .../livewrappedAnalyticsAdapter_spec.js | 61 +- .../modules/livewrappedBidAdapter_spec.js | 49 +- .../spec/modules/luponmediaBidAdapter_spec.js | 13 +- test/spec/modules/madsenseBidAdapter_spec.js | 188 + test/spec/modules/medianetBidAdapter_spec.js | 36 +- .../modules/minutemediaBidAdapter_spec.js | 132 +- test/spec/modules/missenaBidAdapter_spec.js | 31 +- test/spec/modules/mobianRtdProvider_spec.js | 96 +- .../modules/nextMillenniumBidAdapter_spec.js | 106 +- test/spec/modules/nexverseBidAdapter_spec.js | 203 + test/spec/modules/omsBidAdapter_spec.js | 78 + test/spec/modules/openwebBidAdapter_spec.js | 132 +- test/spec/modules/openxBidAdapter_spec.js | 244 +- .../modules/prebidServerBidAdapter_spec.js | 515 +- .../modules/pubmaticAnalyticsAdapter_spec.js | 65 +- test/spec/modules/rediadsBidAdapter_spec.js | 133 + .../modules/responsiveAdsBidAdapter_spec.js | 145 + .../spec/modules/retailspotBidAdapter_spec.js | 8 +- test/spec/modules/riseBidAdapter_spec.js | 158 +- test/spec/modules/sharedIdSystem_spec.js | 26 +- test/spec/modules/shinezBidAdapter_spec.js | 142 +- test/spec/modules/shinezRtbBidAdapter_spec.js | 51 +- .../modules/smartadserverBidAdapter_spec.js | 164 +- test/spec/modules/sspBCBidAdapter_spec.js | 35 +- test/spec/modules/stnBidAdapter_spec.js | 131 +- .../modules/symitriDapRtdProvider_spec.js | 36 + test/spec/modules/taboolaBidAdapter_spec.js | 22 +- test/spec/modules/tagorasBidAdapter_spec.js | 51 +- test/spec/modules/tncIdSystem_spec.js | 70 +- .../modules/twistDigitalBidAdapter_spec.js | 52 +- test/spec/modules/unicornBidAdapter_spec.js | 6 +- test/spec/modules/userId_spec.js | 105 +- test/spec/modules/vidazooBidAdapter_spec.js | 52 +- .../adplayerproVideoProvider_spec.js | 25 +- .../submodules/jwplayerVideoProvider_spec.js | 81 + .../spec/modules/viewdeosDXBidAdapter_spec.js | 11 +- test/spec/modules/visxBidAdapter_spec.js | 73 +- test/spec/modules/voxBidAdapter_spec.js | 22 +- test/spec/modules/wurflRtdProvider_spec.js | 11 +- test/spec/modules/yandexBidAdapter_spec.js | 23 +- test/spec/modules/yieldlabBidAdapter_spec.js | 10 +- test/spec/modules/yieldoneBidAdapter_spec.js | 27 +- .../zeta_global_sspAnalyticsAdapter_spec.js | 5 +- test/spec/native_spec.js | 8 +- test/spec/unit/adUnits_spec.js | 13 + test/spec/unit/core/adapterManager_spec.js | 31 +- test/spec/unit/core/ajax_spec.js | 44 +- test/spec/unit/core/bidderFactory_spec.js | 163 +- test/spec/unit/core/bidderSettings_spec.js | 7 + test/spec/unit/core/targeting_spec.js | 45 + test/spec/unit/pbjs_api_spec.js | 23 + test/spec/video_spec.js | 204 + 300 files changed, 22613 insertions(+), 9097 deletions(-) create mode 100644 integrationExamples/gpt/id_lift_measurement.html create mode 100644 integrationExamples/gpt/liveIntentRtdProviderExample.html create mode 100644 integrationExamples/gpt/nexverse.html create mode 100644 libraries/adtelligentUtils/adtelligentUtils.js create mode 100644 libraries/nexverseUtils/index.js create mode 100644 libraries/ortb2Utils/currency.js create mode 100644 libraries/pageInfosUtils/pageInfosUtils.js create mode 100644 libraries/riseUtils/constants.js create mode 100644 libraries/timeToFirstBytesUtils/timeToFirstBytesUtils.js create mode 100644 modules/akceloBidAdapter.js create mode 100644 modules/akceloBidAdapter.md create mode 100644 modules/bitmediaBidAdapter.js create mode 100644 modules/bitmediaBidAdapter.md create mode 100644 modules/blueBidAdapter.js create mode 100644 modules/blueBidAdapter.md create mode 100644 modules/brainxBidAdapter.js create mode 100644 modules/brainxBidAdapter.md create mode 100644 modules/bridgeuppBidAdapter.js create mode 100644 modules/bridgeuppBidAdapter.md create mode 100644 modules/escalaxBidAdapter.js create mode 100644 modules/escalaxBidAdapter.md create mode 100644 modules/greenbidsBidAdapter.js create mode 100644 modules/greenbidsBidAdapter.md create mode 100644 modules/liveIntentRtdProvider.js create mode 100644 modules/liveIntentRtdProvider.md create mode 100644 modules/madsenseBidAdapter.js create mode 100644 modules/madsenseBidAdapter.md create mode 100644 modules/nexverseBidAdapter.js create mode 100644 modules/nexverseBidAdapter.md create mode 100644 modules/prebidServerBidAdapter/bidderConfig.js create mode 100644 modules/rediadsBidAdapter.js create mode 100644 modules/rediadsBidAdapter.md create mode 100644 modules/responsiveAdsBidAdapter.js create mode 100644 modules/responsiveAdsBidAdapter.md rename modules/{retailspotBidAdapter .md => retailspotBidAdapter.md} (92%) create mode 100644 src/banner.js create mode 100644 test/spec/banner_spec.js create mode 100644 test/spec/libraries/pageInfosUtils.js create mode 100644 test/spec/libraries/timeToFirstBytesUtils.js create mode 100644 test/spec/modules/akceloBidAdapter_spec.js create mode 100644 test/spec/modules/bitmediaBidAdapter_spec.js create mode 100755 test/spec/modules/blueBidAdapter_spec.js create mode 100644 test/spec/modules/brainxBidAdapter_spec.js create mode 100644 test/spec/modules/bridgeuppBidAdapter_spec.js create mode 100644 test/spec/modules/escalaxBidAdapter_spec.js create mode 100644 test/spec/modules/greenbidsBidAdapter_specs.js create mode 100644 test/spec/modules/liveIntentRtdProvider_spec.js create mode 100644 test/spec/modules/madsenseBidAdapter_spec.js create mode 100644 test/spec/modules/nexverseBidAdapter_spec.js create mode 100644 test/spec/modules/rediadsBidAdapter_spec.js create mode 100644 test/spec/modules/responsiveAdsBidAdapter_spec.js diff --git a/creative/renderers/native/renderer.js b/creative/renderers/native/renderer.js index 5cc8f100108..f7c124b41eb 100644 --- a/creative/renderers/native/renderer.js +++ b/creative/renderers/native/renderer.js @@ -45,6 +45,16 @@ function loadScript(url, doc) { }); } +function getRenderFrames(node) { + return Array.from(node.querySelectorAll('iframe[srcdoc*="render"]')) +} + +function getInnerHTML(node) { + const clone = node.cloneNode(true); + getRenderFrames(clone).forEach(node => node.parentNode.removeChild(node)); + return clone.innerHTML; +} + export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript) { const {rendererUrl, assets, ortb, adTemplate} = nativeData; const doc = win.document; @@ -58,21 +68,32 @@ export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript) return win.renderAd(payload); }); } else { - return Promise.resolve(replacer(adTemplate ?? doc.body.innerHTML)); + return Promise.resolve(replacer(adTemplate ?? getInnerHTML(doc.body))); } } export function render({adId, native}, {sendMessage}, win, getMarkup = getAdMarkup) { const {head, body} = win.document; - const resize = () => sendMessage(MESSAGE_NATIVE, { - action: ACTION_RESIZE, - height: body.offsetHeight, - width: body.offsetWidth - }); + const resize = () => { + // force redraw - for some reason this is needed to get the right dimensions + body.style.display = 'none'; + body.style.display = 'block'; + sendMessage(MESSAGE_NATIVE, { + action: ACTION_RESIZE, + height: body.offsetHeight, + width: body.offsetWidth + }); + } + function replaceMarkup(target, markup) { + // do not remove the rendering logic if it's embedded in this window; things will break otherwise + const renderFrames = getRenderFrames(target); + Array.from(target.childNodes).filter(node => !renderFrames.includes(node)).forEach(node => target.removeChild(node)); + target.insertAdjacentHTML('afterbegin', markup); + } const replacer = getReplacer(adId, native); - head && (head.innerHTML = replacer(head.innerHTML)); + replaceMarkup(head, replacer(getInnerHTML(head))); return getMarkup(adId, native, replacer, win).then(markup => { - body.innerHTML = markup; + replaceMarkup(body, markup); if (typeof win.postRenderAd === 'function') { win.postRenderAd({adId, ...native}); } diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html index 03a2356f0ef..bcf0c2fa1f2 100644 --- a/integrationExamples/gpt/hello_world.html +++ b/integrationExamples/gpt/hello_world.html @@ -92,4 +92,4 @@
Div-1
- \ No newline at end of file + diff --git a/integrationExamples/gpt/id_lift_measurement.html b/integrationExamples/gpt/id_lift_measurement.html new file mode 100644 index 00000000000..fef5fd224ac --- /dev/null +++ b/integrationExamples/gpt/id_lift_measurement.html @@ -0,0 +1,173 @@ + + + + + Measure Lift of Multiple ID Modules + + + + + + + + + + +

Measure Lift of Multiple ID Modules

+ +

Generated IDs:

+

+
+    

Generated EIDs:

+

+
+    
+    

Instructions

+
    +
  1. Ensure that the `abg` key is definied in GAM targeting with all possible keys. Each value will be a combination of the following six possible key-value pairs: +
      +
    • id1:t0
    • +
    • id1:t1
    • +
    • id2:t0
    • +
    • id2:t1
    • +
    • id3:t0
    • +
    • id3:t1
    • +
    +
  2. +
  3. In Google Ad Manager (GAM), create a report with the following setup: +
      +
    • Dimensions: Ad Unit, Key-Value Targeting (`abg`).
    • +
    • Metrics: Impressions, Revenue.
    • +
    • Filters: Include the `abg` key in the report.
    • +
    +
  4. +
  5. Analyze the report for each ID module: +
      +
    • Filter by combinations of `t1` (treatment) and `t0` (control) for each ID module (e.g., `id1:t1`, `id1:t0`).
    • +
    • Compare performance metrics (eg Impressions, Revenue) for the `t1` vs. `t0` values.
    • +
    • Calculate lift for each module using the formula: +
      Lift (%) = ((Treatment Metric / Treatment Rate - Control Metric / Control Rate) / (Control Metric / Control Rate)) * 100
      + Replace "Metric" with the relevant performance metric. +
    • +
    +
  6. +
+ + + diff --git a/integrationExamples/gpt/liveIntentRtdProviderExample.html b/integrationExamples/gpt/liveIntentRtdProviderExample.html new file mode 100644 index 00000000000..489df86f7a7 --- /dev/null +++ b/integrationExamples/gpt/liveIntentRtdProviderExample.html @@ -0,0 +1,164 @@ + + + + + + + + +

Basic Prebid.js Example

+
Div-1
+
+ +
+
+
Div-2
+
+ +
+ + diff --git a/integrationExamples/gpt/nexverse.html b/integrationExamples/gpt/nexverse.html new file mode 100644 index 00000000000..498cf2bd2f3 --- /dev/null +++ b/integrationExamples/gpt/nexverse.html @@ -0,0 +1,126 @@ + + + + + + NexVerse Prebid.Js Demo + + + + + + + + + + +

Nexverse Prebid.js Test

+
Div-1
+
+ +
+ +
Div-2
+
+ +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index b7ba6b36a79..63842b00882 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -2,7 +2,7 @@ // creative will be rendered, e.g. GAM delivering a SafeFrame // this code is autogenerated, also available in 'build/creative/creative.js' - + + + + + + + + + + + + + +

Ad Serverless Test Page

+ +
+
+
+ + +``` \ No newline at end of file diff --git a/modules/blueBidAdapter.js b/modules/blueBidAdapter.js new file mode 100644 index 00000000000..ba8d0a9e8fe --- /dev/null +++ b/modules/blueBidAdapter.js @@ -0,0 +1,122 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { deepSetValue, isFn, isPlainObject } from '../src/utils.js'; + +const BIDDER_CODE = 'blue'; +const ENDPOINT_URL = 'https://bidder-us-east-1.getblue.io/engine/?src=prebid'; +const GVLID = 620; // GVLID for your bidder +const COOKIE_NAME = 'ckid'; // Cookie name for identifying users +const CURRENCY = 'USD'; // Currency used in bid floors + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +const converter = ortbConverter({ + context: { + netRevenue: true, // Default netRevenue setting + ttl: 100, // Default time-to-live for bid responses + }, + imp, + request, +}); + +function request(buildRequest, imps, bidderRequest, context) { + let request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'site.publisher.id', context.publisherId); + return request; +} + +function imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const floor = getBidFloor(bidRequest); + if (floor) { + imp.bidfloor = floor; + imp.bidfloorcur = CURRENCY; + } + return imp; +} + +function getBidFloor(bid) { + if (isFn(bid.getFloor)) { + let floor = bid.getFloor({ + currency: CURRENCY, + mediaType: BANNER, + size: '*', + }); + if ( + isPlainObject(floor) && + !isNaN(floor.floor) && + floor.currency === CURRENCY + ) { + return floor.floor; + } + } + return null; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], // Supported ad types + + // Validate bid request + isBidRequestValid: function (bid) { + return !!bid.params.placementId && !!bid.params.publisherId; + }, + + // Build OpenRTB requests using `ortbConverter` + buildRequests: function (validBidRequests, bidderRequest) { + const context = { + publisherId: validBidRequests.find( + (bidRequest) => bidRequest.params?.publisherId + )?.params.publisherId, + }; + + const ortbRequest = converter.toORTB({ + bidRequests: validBidRequests, + bidderRequest, + context, + }); + + // Add GVLID and cookie ID to the request + ortbRequest.ext = ortbRequest.ext || {}; + deepSetValue(ortbRequest, 'ext.gvlid', GVLID); + + // Include user cookie if available + const ckid = storage.getDataFromLocalStorage('blueID') || storage.getCookie(COOKIE_NAME) || null; + if (ckid) { + deepSetValue(ortbRequest, 'user.ext.buyerid', ckid); + } + + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(ortbRequest), + options: { + contentType: 'text/plain', + }, + }; + }, + + // Interpret OpenRTB responses using `ortbConverter` + interpretResponse: function (serverResponse, request) { + const ortbResponse = serverResponse.body; + + // Parse the OpenRTB response into Prebid bid responses + const prebidResponses = converter.fromORTB({ + response: ortbResponse, + request: request.data, + }).bids; + + // Example: Modify bid responses if needed + prebidResponses.forEach((bid) => { + bid.meta = bid.meta || {}; + bid.meta.adapterVersion = '1.0.0'; + }); + + return prebidResponses; + }, +}; + +registerBidder(spec); diff --git a/modules/blueBidAdapter.md b/modules/blueBidAdapter.md new file mode 100644 index 00000000000..4f446b1ff4b --- /dev/null +++ b/modules/blueBidAdapter.md @@ -0,0 +1,28 @@ +# Overview + +Module Name: Blue Bidder Adapter +Module Type: Bidder Adapter +Maintainer: celsooliveira@getblue.io + +# Description + +Module that connects to Blue's demand sources. + +# Test Parameters +``` + var adUnits = [ + { + code: 'banner-ad-div', + sizes: [[300, 250], [728, 90]], + bids: [ + { + bidder: 'blue', + params: { + publisherId: "xpto", + placementId: "xpto", + } + } + ] + } + ]; +``` diff --git a/modules/brainxBidAdapter.js b/modules/brainxBidAdapter.js new file mode 100644 index 00000000000..69832987fb7 --- /dev/null +++ b/modules/brainxBidAdapter.js @@ -0,0 +1,117 @@ +import { deepAccess, generateUUID, isArray, logWarn } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +// import { config } from 'src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js' +// import { config } from '../src/config.js'; + +const BIDDER_CODE = 'brainx'; +const METHOD = 'POST'; +const TTL = 200; +const NET_REV = true; +let ENDPOINT = 'https://dsp.brainx.tech/bid' +// let ENDPOINT = 'http://adx-engine-gray.tec-do.cn/bid' + +const converter = ortbConverter({ + context: { + // `netRevenue` and `ttl` are required properties of bid responses - provide a default for them + netRevenue: NET_REV, // or false if your adapter should set bidResponse.netRevenue = false + ttl: TTL // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + } +}); + +export const spec = { + code: BIDDER_CODE, + // gvlid: IAB_GVL_ID, + // aliases: [ + // { code: "myalias", gvlid: IAB_GVL_ID_IF_DIFFERENT } + // ], + isBidRequestValid: function (bid) { + if (!(hasBanner(bid) || hasVideo(bid))) { + logWarn('Invalid bid request - missing required mediaTypes'); + return false; + } + if (!(bid && bid.params)) { + logWarn('Invalid bid request - missing required bid data'); + return false; + } + + if (!(bid.params.pubId)) { + logWarn('Invalid bid request - missing required field pubId'); + return false; + } + return true; + }, + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({ bidRequests, bidderRequest }) + ENDPOINT = String(deepAccess(bidRequests[0], 'params.endpoint')) ? deepAccess(bidRequests[0], 'params.endpoint') : ENDPOINT + data.user = { + buyeruid: generateUUID() + } + return { + method: METHOD, + url: `${ENDPOINT}?token=${String(deepAccess(bidRequests[0], 'params.pubId'))}`, + data + } + }, + interpretResponse(response, request) { + let bids = []; + if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { + response.body.seatbid.forEach(function (bidder) { + if (isArray(bidder.bid)) { + bidder.bid.map((bid) => { + let serverBody = response.body; + // bidRequest = request.originalBidRequest, + let mediaType = BANNER; + let currency = serverBody.cur || 'USD' + + const cpm = (parseFloat(bid.price) || 0).toFixed(2); + const categories = deepAccess(bid, 'cat', []); + + const bidRes = { + ad: bid.adm, + width: bid.w, + height: bid.h, + requestId: bid.impid, + cpm: cpm, + currency: currency, + mediaType: mediaType, + ttl: TTL, + creativeId: bid.crid || bid.id, + netRevenue: NET_REV, + nurl: bid.nurl, + lurl: bid.lurl, + meta: { + mediaType: mediaType, + primaryCatId: categories[0], + secondaryCatIds: categories.slice(1), + } + }; + if (bid.adomain && isArray(bid.adomain) && bid.adomain.length > 0) { + bidRes.meta.advertiserDomains = bid.adomain; + bidRes.meta.clickUrl = bid.adomain[0]; + } + bids.push(bidRes); + }) + } + }); + } + + return bids; + }, + // getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { }, + // onTimeout: function (timeoutData) { }, + // onBidWon: function (bid) { }, + // onSetTargeting: function (bid) { }, + // onBidderError: function ({ error, bidderRequest }) { }, + // onAdRenderSucceeded: function (bid) { }, + supportedMediaTypes: [BANNER] +} +function hasBanner(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} +function hasVideo(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.video'); +} + +registerBidder(spec); diff --git a/modules/brainxBidAdapter.md b/modules/brainxBidAdapter.md new file mode 100644 index 00000000000..a734147321b --- /dev/null +++ b/modules/brainxBidAdapter.md @@ -0,0 +1,56 @@ +# brianx Bidder Adapter + +## Overview + +``` +Module Name: brianx Bidder Adapter +Module Type: Bidder Adapter +Maintainer: brainx.official@tec-do.com +``` + +## Description + +Module that connects to brianx's demand sources + +## Bid Parameters + +| Name | Scope | Type | Description | Example | +| ---------- | ------------ | ------ | ------------------------------------ | -------------------------------------- | +| `pubId` | required | String | The Pub Id provided by Brainx Ads. | `F7B53DBC-85C1-4685-9A06-9CF4B6261FA3` | +| `endpoint` | optional | String | The endpoint provided by Brainx Url. | `https://dsp.brainx.tech/bid` | + +## Example + +### Banner Ads + +```javascript +var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [ + [320, 250], + [320, 480] + ] + } + }, + bids: [{ + bidder: 'brianx', + params: { + pubId: 'F7B53DBC-85C1-4685-9A06-9CF4B6261FA3', + endpoint: 'https://dsp.brainx.tech/bid' + } + }] +}]; +``` + +* For video ads, enable prebid cache. + +```javascript +pbjs.setConfig({ + ortb2: { + ortbVersion: '2.5' + }, + debug: false // or true +}); +``` diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js index 8ecaeb9ea4c..7502a579745 100644 --- a/modules/brandmetricsRtdProvider.js +++ b/modules/brandmetricsRtdProvider.js @@ -12,10 +12,6 @@ import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; -/** - * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule - */ - /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule */ diff --git a/modules/bridgeuppBidAdapter.js b/modules/bridgeuppBidAdapter.js new file mode 100644 index 00000000000..6adb2fea220 --- /dev/null +++ b/modules/bridgeuppBidAdapter.js @@ -0,0 +1,285 @@ +import {deepSetValue, isFn, logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid + */ + +export const ADAPTER_VERSION = 1.0; +export const BIDDER_CODE = 'sonarads'; +export const GVLID = 1300; +export const DEFAULT_CUR = 'USD'; +export const SERVER_PATH_US1_BID = 'https://prebidjs-bids-us1.sonar-ads.com/analyze_request/bids'; +export const SERVER_PATH_US1_EVENTS = 'https://prebidjs-events-us1.sonar-ads.com/events'; +export const SERVER_PATH_US1_SYNC = 'https://prebidjs-sync-us1.sonar-ads.com/sync'; + +/** + * Bridgeupp : Report events for analytics and debuging. + */ +function reportEvents(eventType, eventData) { + if (!eventData || spec?.reportEventsEnabled !== true) { + return; + } + + const payload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: eventType, + eventPayload: eventData + }); + + fetch(`${SERVER_PATH_US1_EVENTS}`, { + body: payload, + keepalive: true, + credentials: 'include', + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }).catch((_e) => { + // ignore errors for now + }); +} + +/** + * Bridgeupp : Defines the core oRTB converter inherited from converter library and all customization functions. + */ +const CONVERTER = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + currency: DEFAULT_CUR + }, + imp, + request, + bidResponse, + response +}); + +/** + * Bridgeupp : Builds an impression object for oRTB requests based on the bid request. + * + * @param {function} buildImp - Function to build the imp object. + * @param {Object} bidRequest - The request containing bid details. + * @param {Object} context - Context for the impression. + * @returns {Object} The constructed impression object. + */ +function imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + const params = bidRequest.params; + + imp.tagid = bidRequest.adUnitCode; + let floorInfo = {}; + + if (isFn(bidRequest.getFloor)) { + floorInfo = bidRequest.getFloor(); + } + + // if floor price module is not set reading from bidRequest.params or default + if (!imp.bidfloor) { + imp.bidfloor = bidRequest.params.bidfloor || 0.001; + imp.bidfloorcur = DEFAULT_CUR; + } + + imp.secure = bidRequest.ortb2Imp?.secure ?? 1; + deepSetValue(imp, 'ext', { + ...imp.ext, + params: bidRequest.params, + bidder: { + siteId: params?.siteId, + }, + floorInfo: floorInfo + }); + + return imp; +} + +/** + * Bridgeupp: Constructs the request object. + * + * @param {function} buildRequest - Function to build the request. + * @param {Array} imps - Array of impression objects. + * @param {Object} bidderRequest - Object containing bidder request information. + * @param {Object} context - Additional context. + * @returns {Object} The complete oRTB request object. + */ +function request(buildRequest, imps, bidderRequest, context) { + let request = buildRequest(imps, bidderRequest, context); + const siteId = context.bidRequests[0]?.params?.siteId; + + deepSetValue(request, 'ext.prebid.channel', { + name: 'pbjs_bridgeupp', + pbjsversion: '$prebid.version$', + adapterversion: ADAPTER_VERSION, + siteId: siteId + }); + + return request; +} + +function bidResponse(buildBidResponse, bid, context) { + return buildBidResponse(bid, context); +} + +/** + * Bridgeupp bid response + * + * @param {function} buildResponse - Function to build the response. + * @param {Array} bidResponses - List of bid responses. + * @param {Object} ortbResponse - Original oRTB response data. + * @param {Object} context - Additional context. + * @returns {Object} Prebid.js compatible bid response. + */ +function response(buildResponse, bidResponses, ortbResponse, context) { + return buildResponse(bidResponses, ortbResponse, context); +} + +export const spec = { + reportEventsEnabled: false, + code: BIDDER_CODE, + aliases: ['bridgeupp'], + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + /** + * Bridgeupp : Determines whether the given bid request is valid. + * + * @param {Object} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: (bid) => { + if (!bid || bid.bidder !== BIDDER_CODE || !bid.params.siteId) { + logWarn('Bridgeupp - bid is not valid, reach out to support@bridgeupp.com'); + return false; + } + return true; + }, + + /** + * Bridgeupp: Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests - an array of bids + * @param {Object} bidderRequest - Additional request details. + * @return {ServerRequest} Info describing the request to the server. + */ + buildRequests: (validBidRequests, bidderRequest) => { + const data = CONVERTER.toORTB({ bidderRequest: bidderRequest, bidRequests: validBidRequests, }); + + if (data) { + return { + method: 'POST', + url: SERVER_PATH_US1_BID, + data: data, + options: { + contentType: 'application/json', + crossOrigin: true, + withCredentials: true + } + }; + } + }, + + /** + * Bridgeupp: Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {ServerRequest} bidRequest - Original bid request. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, bidRequest) { + if (typeof serverResponse?.body == 'undefined') { + return []; + } + + // reportEventsEnabled is returned from the server default false + spec.reportEventsEnabled = serverResponse.headers.get('reportEventsEnabled') > 0 + + const interpretedResponse = CONVERTER.fromORTB({ response: serverResponse.body, request: bidRequest.data }); + return interpretedResponse.bids || []; + }, + + /** + * Bridgeupp : User sync options based on consent, support only iframe for now. + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + logWarn('Bridgeupp - Bidder ConnectAd: No User-Matching allowed'); + return []; + } + + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SERVER_PATH_US1_SYNC + '?'; + + syncUrl = gdprConsent ? tryAppendQueryString(syncUrl, 'gdpr', gdprConsent.gdprApplies ? 1 : 0) : syncUrl; + syncUrl = gdprConsent?.consentString ? tryAppendQueryString(syncUrl, 'gdpr_consent', gdprConsent.consentString) : syncUrl; + syncUrl = uspConsent ? tryAppendQueryString(syncUrl, 'us_privacy', uspConsent) : syncUrl; + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + syncUrl = tryAppendQueryString(syncUrl, 'gpp', gppConsent.gppString); + syncUrl = tryAppendQueryString(syncUrl, 'gpp_sid', gppConsent.applicableSections.join(',')); + } + + if ((syncUrl.slice(-1) === '&') || (syncUrl.slice(-1) === '?')) { + syncUrl = syncUrl.slice(0, -1); + } + + return [{ + type: pixelType, + url: syncUrl + }]; + }, + + /** + * Bridgeupp: Callback to report timeout event. + * + * @param {TimedOutBid[]} timeoutData - Array of timeout details. + */ + onTimeout: (timeoutData) => { + reportEvents('onTimeout', timeoutData); + }, + + /** + * Bridgeupp: Callback to report targeting event. + * + * @param {Bid} bid - The bid object + */ + onSetTargeting: (bid) => { + reportEvents('onSetTargeting', bid); + }, + + /** + * Bridgeupp: Callback to report successful ad render event. + * + * @param {Bid} bid - The bid that successfully rendered. + */ + onAdRenderSucceeded: (bid) => { + reportEvents('onAdRenderSucceeded', bid); + }, + + /** + * Bridgeupp: Callback to report bidder error event. + * + * @param {Object} errorData - Details about the error. + */ + onBidderError: (errorData) => { + reportEvents('onBidderError', errorData); + }, + + /** + * Bridgeupp: Callback to report bid won event. + * + * @param {Bid} bid - The bid that won the auction. + */ + onBidWon: (bid) => { + reportEvents('onBidWon', bid); + } + +}; + +registerBidder(spec); diff --git a/modules/bridgeuppBidAdapter.md b/modules/bridgeuppBidAdapter.md new file mode 100644 index 00000000000..3e8fa62f1d8 --- /dev/null +++ b/modules/bridgeuppBidAdapter.md @@ -0,0 +1,39 @@ +# Overview + +Module Name: Bridgeupp Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@bridgeupp.com + +# Description + +Module that connects to Bridgeupp's demand sources. + +# Bid Params + +| Name | Scope | Description | Example | Type | +|---------------|----------|----------------------|----------|----------| +| `siteId` | required | Placement ID | `'1234'` | `string` | +| `bidfloor` | optional | Minimum price in USD | `'1.50'` | `float` | + +# Test Parameters + +## Banner +``` + var adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 336]] + } + }, + + bids: [{ + bidder: 'sonarads', + params: { + siteId: 'site-id-example-132', // siteId provided by Bridgeupp + bidfloor: 0.01 + } + }] + + }]; +``` diff --git a/modules/carodaBidAdapter.js b/modules/carodaBidAdapter.js index a2370a13942..ebe4686630f 100644 --- a/modules/carodaBidAdapter.js +++ b/modules/carodaBidAdapter.js @@ -1,7 +1,9 @@ // jshint esversion: 6, es3: false, node: true 'use strict' +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { deepAccess, @@ -11,7 +13,6 @@ import { sizeTupleToRtbSize, sizesToSizeTuples } from '../src/utils.js'; -import { config } from '../src/config.js'; const { getConfig } = config; @@ -45,7 +46,7 @@ export const spec = { getFirstWithKey(validBidRequests, 'params.priceType') || 'net'; const test = getFirstWithKey(validBidRequests, 'params.test'); - const currency = getConfig('currency.adServerCurrency'); + const currency = getCurrencyFromBidderRequest(bidderRequest); const eids = getFirstWithKey(validBidRequests, 'userIdAsEids'); const schain = getFirstWithKey(validBidRequests, 'schain'); const request = { diff --git a/modules/cleanioRtdProvider.js b/modules/cleanioRtdProvider.js index 016a8d7edf9..35751210878 100644 --- a/modules/cleanioRtdProvider.js +++ b/modules/cleanioRtdProvider.js @@ -13,10 +13,6 @@ import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; -/** - * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule - */ - /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule */ diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js index 3b90529b6cc..c626d1f56aa 100644 --- a/modules/cointrafficBidAdapter.js +++ b/modules/cointrafficBidAdapter.js @@ -2,6 +2,7 @@ import { parseSizesInput, logError, isEmpty } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import { config } from '../src/config.js' +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -45,7 +46,7 @@ export const spec = { const sizes = parseSizesInput(bidRequest.params.size || bidRequest.sizes); const currency = config.getConfig(`currency.bidderCurrencyDefault.${BIDDER_CODE}`) || - config.getConfig('currency.adServerCurrency') || + getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CURRENCY; if (ALLOWED_CURRENCIES.indexOf(currency) === -1) { diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js index f5d98312a63..1a4cc3faeaf 100644 --- a/modules/compassBidAdapter.js +++ b/modules/compassBidAdapter.js @@ -3,11 +3,13 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'compass'; +const GVLID = 883; const AD_URL = 'https://sa-lb.deliverimp.com/pbjs'; const SYNC_URL = 'https://sa-cs.deliverimp.com'; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: isBidRequestValid(), diff --git a/modules/conceptxBidAdapter.js b/modules/conceptxBidAdapter.js index 87ac96f2131..47c50a4c0ad 100644 --- a/modules/conceptxBidAdapter.js +++ b/modules/conceptxBidAdapter.js @@ -5,10 +5,12 @@ import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'conceptx'; const ENDPOINT_URL = 'https://conceptx.cncpt-central.com/openrtb'; // const LOG_PREFIX = 'ConceptX: '; +const GVLID = 1340; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], + gvlid: GVLID, isBidRequestValid: function (bid) { return !!(bid.bidId && bid.params.site && bid.params.adunit); }, diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index cb802508de9..e01078890f9 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -33,7 +33,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests[]} validBidRequests An array of bids + * @param {Object} bidderRequest The bidder's request info. * @return ServerRequest Info describing the request to the server. */ @@ -300,6 +301,7 @@ function retrieveAd(decision, unitId, unitName) { function handleEids(data, validBidRequests) { let bidUserIdAsEids = deepAccess(validBidRequests, '0.userIdAsEids'); if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + bidUserIdAsEids = bidUserIdAsEids.filter(e => typeof e === 'object'); deepSetValue(data, 'user.eids', bidUserIdAsEids); } else { deepSetValue(data, 'user.eids', undefined); diff --git a/modules/contxtfulBidAdapter.js b/modules/contxtfulBidAdapter.js index acd9811b871..f7d263ae74f 100644 --- a/modules/contxtfulBidAdapter.js +++ b/modules/contxtfulBidAdapter.js @@ -8,7 +8,7 @@ import { interpretResponse, getUserSyncs as getUserSyncsLib, } from '../libraries/teqblazeUtils/bidderUtils.js'; -import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; // Constants const BIDDER_CODE = 'contxtful'; @@ -16,6 +16,7 @@ const BIDDER_ENDPOINT = 'prebid.receptivity.io'; const MONITORING_ENDPOINT = 'monitoring.receptivity.io'; const DEFAULT_NET_REVENUE = true; const DEFAULT_TTL = 300; +const DEFAULT_SAMPLING_RATE = 1.0; const PREBID_VERSION = '$prebid.version$'; // ORTB conversion @@ -74,7 +75,7 @@ const extractParameters = (config) => { // Construct the Payload towards the Bidding endpoint const buildRequests = (validBidRequests = [], bidderRequest = {}) => { - const ortb2 = converter.toORTB({bidderRequest: bidderRequest, bidRequests: validBidRequests}); + const ortb2 = converter.toORTB({ bidderRequest: bidderRequest, bidRequests: validBidRequests }); const bidRequests = []; _each(validBidRequests, bidRequest => { @@ -87,14 +88,14 @@ const buildRequests = (validBidRequests = [], bidderRequest = {}) => { }); const config = pbjsConfig.getConfig(); config.pbjsVersion = PREBID_VERSION; - const {version, customer} = extractParameters(config) + const { version, customer } = extractParameters(config) const adapterUrl = buildUrl({ protocol: 'https', host: BIDDER_ENDPOINT, pathname: `/${version}/prebid/${customer}/bid`, }); - // https://docs.prebid.org/dev-docs/bidder-adaptor.html + // See https://docs.prebid.org/dev-docs/bidder-adaptor.html let req = { url: adapterUrl, method: 'POST', @@ -147,7 +148,18 @@ const getUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent, gpp // Retrieve the sampling rate for events const getSamplingRate = (bidderConfig, eventType) => { const entry = Object.entries(bidderConfig?.contxtful?.sampling ?? {}).find(([key, value]) => key.toLowerCase() === eventType.toLowerCase()); - return entry ? entry[1] : 0.001; + return entry ? entry[1] : DEFAULT_SAMPLING_RATE; +}; + +const logBidderError = ({ error, bidderRequest }) => { + if (error) { + let jsonReason = { + message: error.reason?.message, + stack: error.reason?.stack, + }; + error.reason = jsonReason; + } + logEvent('onBidderError', { error, bidderRequest }); }; // Handles the logging of events @@ -158,13 +170,13 @@ const logEvent = (eventType, data) => { // Get Config const bidderConfig = pbjsConfig.getConfig(); - const {version, customer} = extractParameters(bidderConfig); + const { version, customer } = extractParameters(bidderConfig); // Sampled monitoring if (['onBidBillable', 'onAdRenderSucceeded'].includes(eventType)) { const randomNumber = Math.random(); const samplingRate = getSamplingRate(bidderConfig, eventType); - if (randomNumber >= samplingRate) { + if (!(randomNumber <= samplingRate)) { return; // Don't sample } } else if (!['onTimeout', 'onBidderError', 'onBidWon'].includes(eventType)) { @@ -205,12 +217,12 @@ export const spec = { buildRequests, interpretResponse, getUserSyncs, - onBidWon: function(bid) { logEvent('onBidWon', bid); }, - onBidBillable: function(bid) { logEvent('onBidBillable', bid); }, - onAdRenderSucceeded: function(bid) { logEvent('onAdRenderSucceeded', bid); }, - onSetTargeting: function(bid) { }, - onTimeout: function(timeoutData) { logEvent('onTimeout', timeoutData); }, - onBidderError: function({ error, bidderRequest }) { logEvent('onBidderError', { error, bidderRequest }); }, + onBidWon: function (bid) { logEvent('onBidWon', bid); }, + onBidBillable: function (bid) { logEvent('onBidBillable', bid); }, + onAdRenderSucceeded: function (bid) { logEvent('onAdRenderSucceeded', bid); }, + onSetTargeting: function (bid) { }, + onTimeout: function (timeoutData) { logEvent('onTimeout', timeoutData); }, + onBidderError: logBidderError, }; registerBidder(spec); diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index 55623c00591..1bd977afed1 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -15,6 +15,7 @@ import { isEmpty, buildUrl, isArray, + generateUUID, } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -26,13 +27,17 @@ const MODULE = `${MODULE_NAME}RtdProvider`; const CONTXTFUL_HOSTNAME_DEFAULT = 'api.receptivity.io'; const CONTXTFUL_DEFER_DEFAULT = 0; +let _sm; +function sm() { + return _sm ??= generateUUID(); +} + const storageManager = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME, }); let rxApi = null; -let isFirstBidRequestCall = true; /** * Return current receptivity value for the requester. @@ -150,7 +155,7 @@ function initCustomer(config) { addConnectorEventListener(customer, config); - const loadScript = () => loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME); + const loadScript = () => loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME, undefined, undefined, { 'data-sm': sm() }); // Optionally defer the loading of the script if (Number.isFinite(defer) && defer > 0) { setTimeout(loadScript, defer); @@ -228,9 +233,6 @@ function getTargetingData(adUnits, config, _userConsent) { */ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { function onReturn() { - if (isFirstBidRequestCall) { - isFirstBidRequestCall = false; - } onDone(); } @@ -245,16 +247,10 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { let fromStorage = prepareBatch(bidders, (bidder) => loadSessionReceptivity(`${config?.params?.customer}_${bidder}`)); let sources = [fromStorage, fromApi]; - if (isFirstBidRequestCall) { - sources.reverse(); - } let rxBatch = Object.assign(...sources); - let singlePointEvents; - if (isEmpty(rxBatch)) { - singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() })); - } + let singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() })); bidders .forEach(bidderCode => { @@ -266,6 +262,7 @@ function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { ext: { rx: rxBatch[bidderCode], events: singlePointEvents, + sm: sm(), params: { ev: config.params?.version, ci: config.params?.customer, diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 3161534a441..3e24a68b946 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -120,7 +120,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { ad: rtbBid.rtb.banner.content, ttl: TTL, creativeId: rtbBid.creative_id, - netRevenue: false, // ??? + netRevenue: true, dealId: rtbBid.deal_id, meta: null, _adUnitCode: bidRequest.adUnitCode, diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index d44f947dd3e..b01e7361e3f 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, deepSetValue, isArray, logError, logWarn, parseUrl, triggerPixel} from '../src/utils.js'; +import {deepSetValue, isArray, logError, logWarn, parseUrl, triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -92,7 +92,7 @@ function imp(buildImp, bidRequest, context) { } deepSetValue(imp, 'video.ext', { context: bidRequest.mediaTypes.video.context, - playersizes: parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), + playersizes: parseSizes(bidRequest?.mediaTypes?.video?.playerSize, parseSize), plcmt: bidRequest.mediaTypes.video.plcmt, poddur: bidRequest.mediaTypes.video.adPodDurationSec, rqddurs: bidRequest.mediaTypes.video.durationRangeSec, @@ -156,7 +156,7 @@ function request(buildRequest, imps, bidderRequest, context) { * @returns {*} */ function bidResponse(buildBidResponse, bid, context) { - context.mediaType = deepAccess(bid, 'ext.mediatype'); + context.mediaType = bid?.ext?.mediatype; if (context.mediaType === NATIVE && typeof bid.adm_native !== 'undefined') { bid.adm = bid.adm_native; delete bid.adm_native; @@ -165,22 +165,22 @@ function bidResponse(buildBidResponse, bid, context) { let bidResponse = buildBidResponse(bid, context); const {bidRequest} = context; - bidResponse.currency = deepAccess(bid, 'ext.cur') + bidResponse.currency = bid?.ext?.cur; - if (typeof deepAccess(bid, 'ext.meta') !== 'undefined') { + if (typeof bid?.ext?.meta !== 'undefined') { deepSetValue(bidResponse, 'meta', { ...bidResponse.meta, ...bid.ext.meta }); } - if (typeof deepAccess(bid, 'ext.paf.content_id') !== 'undefined') { + if (typeof bid?.ext?.paf?.content_id !== 'undefined') { deepSetValue(bidResponse, 'meta.paf.content_id', bid.ext.paf.content_id) } if (bidResponse.mediaType === VIDEO) { bidResponse.vastUrl = bid.ext?.displayurl; // if outstream video, add a default render for it. - if (deepAccess(bidRequest, 'mediaTypes.video.context') === OUTSTREAM) { + if (bidRequest?.mediaTypes?.video?.context === OUTSTREAM) { bidResponse.renderer = createOutstreamVideoRenderer(bid); } } @@ -200,9 +200,9 @@ function bidResponse(buildBidResponse, bid, context) { function response(buildResponse, bidResponses, ortbResponse, context) { let response = buildResponse(bidResponses, ortbResponse, context); - const pafTransmission = deepAccess(ortbResponse, 'ext.paf.transmission'); + const pafTransmission = ortbResponse?.ext?.paf?.transmission; response.bids.forEach(bid => { - if (typeof pafTransmission !== 'undefined' && typeof deepAccess(bid, 'meta.paf.content_id') !== 'undefined') { + if (typeof pafTransmission !== 'undefined' && typeof bid?.meta?.paf?.content_id !== 'undefined') { deepSetValue(bid, 'meta.paf.transmission', pafTransmission); } else { delete bid.meta.paf; @@ -362,7 +362,7 @@ export const spec = { // We support native request without assets requirements because we can fill them later on. // This is a trick to fool oRTB converter isOpenRTBBidRequestValid(ortb) fn because it needs // nativeOrtbRequest.assets to be non-empty. - if (deepAccess(bidRequest, 'nativeOrtbRequest.assets') == null) { + if (bidRequest?.nativeOrtbRequest?.assets == null) { logWarn(LOG_PREFIX + 'native asset requirements are missing'); deepSetValue(bidRequest, 'nativeOrtbRequest.assets', [{}]); } @@ -391,7 +391,7 @@ export const spec = { const interpretedResponse = CONVERTER.fromORTB({response: response.body, request: request.data}); const bids = interpretedResponse.bids || []; - const fledgeAuctionConfigs = deepAccess(response.body, 'ext.igi')?.filter(igi => isArray(igi?.igs)) + const fledgeAuctionConfigs = response.body?.ext?.igi?.filter(igi => isArray(igi?.igs)) .flatMap(igi => igi.igs); if (fledgeAuctionConfigs?.length) { return { @@ -548,11 +548,11 @@ function parseSize(size) { } function hasVideoMediaType(bidRequest) { - return deepAccess(bidRequest, 'mediaTypes.video') !== undefined; + return bidRequest?.mediaTypes?.video !== undefined; } function hasNativeMediaType(bidRequest) { - return deepAccess(bidRequest, 'mediaTypes.native') !== undefined; + return bidRequest?.mediaTypes?.native !== undefined; } function hasValidVideoMediaType(bidRequest) { @@ -562,12 +562,12 @@ function hasValidVideoMediaType(bidRequest) { requiredMediaTypesParams.forEach(function (param) { if (param === 'placement') { - if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined && deepAccess(bidRequest, 'mediaTypes.video.plcmt') === undefined && deepAccess(bidRequest, 'params.video.plcmt') === undefined) { + if (bidRequest?.mediaTypes?.video?.[param] === undefined && bidRequest?.params?.video?.[param] === undefined && bidRequest?.mediaTypes?.video?.plcmt === undefined && bidRequest?.params?.video?.plcmt === undefined) { isValid = false; logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' or mediaTypes.video.plcmt is required'); } } else { - if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) { + if (bidRequest?.mediaTypes?.video?.[param] === undefined && bidRequest?.params?.video?.[param] === undefined) { isValid = false; logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required'); } @@ -604,13 +604,13 @@ function getFloors(bidRequest) { if (getFloor) { if (bidRequest.mediaTypes?.banner) { floors.banner = {}; - const bannerSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')) + const bannerSizes = parseSizes(bidRequest?.mediaTypes?.banner?.sizes) bannerSizes.forEach(bannerSize => floors.banner[parseSize(bannerSize).toString()] = getFloor.call(bidRequest, { size: bannerSize, mediaType: BANNER })); } if (bidRequest.mediaTypes?.video) { floors.video = {}; - const videoSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) + const videoSizes = parseSizes(bidRequest?.mediaTypes?.video?.playerSize) videoSizes.forEach(videoSize => floors.video[parseSize(videoSize).toString()] = getFloor.call(bidRequest, { size: videoSize, mediaType: VIDEO })); } diff --git a/modules/currency.js b/modules/currency.js index d040dc2cf49..b149a1934c3 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -1,4 +1,4 @@ -import {logError, logInfo, logMessage, logWarn} from '../src/utils.js'; +import {deepSetValue, logError, logInfo, logMessage, logWarn} from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; import { EVENTS, REJECTION_REASON } from '../src/constants.js'; import {ajax} from '../src/ajax.js'; @@ -8,6 +8,7 @@ import {defer} from '../src/utils/promise.js'; import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js'; import {on as onEvent, off as offEvent} from '../src/events.js'; +import { enrichFPD } from '../src/fpd/enrichment.js'; import { timeoutQueue } from '../libraries/timeoutQueue/timeoutQueue.js'; const DEFAULT_CURRENCY_RATE_URL = 'https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=$$TODAY$$'; @@ -171,6 +172,7 @@ function initCurrency() { getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency); getHook('addBidResponse').before(addBidResponseHook, 100); getHook('responsesReady').before(responsesReadyHook); + enrichFPD.before(enrichFPDHook); getHook('requestBids').before(requestBidsHook, 50); onEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); onEvent(EVENTS.AUCTION_INIT, loadRates); @@ -178,10 +180,11 @@ function initCurrency() { } } -function resetCurrency() { +export function resetCurrency() { if (currencySupportEnabled) { getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); getHook('responsesReady').getHooks({hook: responsesReadyHook}).remove(); + enrichFPD.getHooks({hook: enrichFPDHook}).remove(); getHook('requestBids').getHooks({hook: requestBidsHook}).remove(); offEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); offEvent(EVENTS.AUCTION_INIT, loadRates); @@ -347,6 +350,13 @@ export function setOrtbCurrency(ortbRequest, bidderRequest, context) { registerOrtbProcessor({type: REQUEST, name: 'currency', fn: setOrtbCurrency}); +function enrichFPDHook(next, fpd) { + return next(fpd.then(ortb2 => { + deepSetValue(ortb2, 'ext.prebid.adServerCurrency', adServerCurrency); + return ortb2; + })) +} + export const requestBidsHook = timedAuctionHook('currency', function requestBidsHook(fn, reqBidsConfigObj) { const continueAuction = ((that) => () => fn.call(that, reqBidsConfigObj))(this); diff --git a/modules/deltaprojectsBidAdapter.js b/modules/deltaprojectsBidAdapter.js index bf5489322c9..2111643b344 100644 --- a/modules/deltaprojectsBidAdapter.js +++ b/modules/deltaprojectsBidAdapter.js @@ -1,5 +1,6 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; import { _each, _map, @@ -11,7 +12,6 @@ import { logWarn, setOnAny } from '../src/utils.js'; -import {config} from '../src/config.js'; export const BIDDER_CODE = 'deltaprojects'; export const BIDDER_ENDPOINT_URL = 'https://d5p.de17a.com/dogfight/prebid'; @@ -74,7 +74,7 @@ function buildRequests(validBidRequests, bidderRequest) { // build bid specific return validBidRequests.map(validBidRequest => { - const openRTBRequest = buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs); + const openRTBRequest = buildOpenRTBRequest(validBidRequest, bidderRequest, id, site, device, user, tmax, regs); return { method: 'POST', url: BIDDER_ENDPOINT_URL, @@ -85,9 +85,9 @@ function buildRequests(validBidRequests, bidderRequest) { }); } -function buildOpenRTBRequest(validBidRequest, id, site, device, user, tmax, regs) { +function buildOpenRTBRequest(validBidRequest, bidderRequest, id, site, device, user, tmax, regs) { // build cur - const currency = config.getConfig('currency.adServerCurrency') || deepAccess(validBidRequest, 'params.currency'); + const currency = getCurrencyFromBidderRequest(bidderRequest) || deepAccess(validBidRequest, 'params.currency'); const cur = currency && [currency]; // build impression diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 3e1a716d8e7..773b3896270 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -14,7 +14,6 @@ import { getRefererInfo } from '../src/refererDetection.js'; import { targeting } from '../src/targeting.js'; import { buildUrl, - deepAccess, formatQS, isEmpty, isNumber, @@ -90,7 +89,7 @@ export function buildDfpVideoUrl(options) { const derivedParams = { correlator: Date.now(), - sz: parseSizesInput(deepAccess(adUnit, 'mediaTypes.video.playerSize')).join('|'), + sz: parseSizesInput(adUnit?.mediaTypes?.video?.playerSize).join('|'), url: encodeURIComponent(location.href), }; @@ -134,7 +133,7 @@ export function buildDfpVideoUrl(options) { return 'preroll'; } }, - vconp: () => Array.isArray(video?.playbackmethod) && video.playbackmethod.every(m => m === 7) ? '2' : undefined, + vconp: () => Array.isArray(video?.playbackmethod) && video.playbackmethod.some(m => m === 7) ? '2' : undefined, vpa() { // playbackmethod = 3 is play on click; 1, 2, 4, 5, 6 are autoplay if (Array.isArray(video?.playbackmethod)) { @@ -208,7 +207,7 @@ function buildUrlFromAdserverUrlComponents(components, bid, options) { * @return {string | undefined} The encoded vast url if it exists, or undefined */ function getDescriptionUrl(bid, components, prop) { - return deepAccess(components, `${prop}.description_url`) || encodeURIComponent(dep.ri().page); + return components?.[prop]?.description_url || encodeURIComponent(dep.ri().page); } /** @@ -240,7 +239,7 @@ function getCustParams(bid, options, urlCustParams) { events.emit(EVENTS.SET_TARGETING, {[adUnit.code]: prebidTargetingSet}); // merge the prebid + publisher targeting sets - const publisherTargetingSet = deepAccess(options, 'params.cust_params'); + const publisherTargetingSet = options?.params?.cust_params; const targetingSet = Object.assign({}, prebidTargetingSet, publisherTargetingSet); let encodedParams = encodeURIComponent(formatQS(targetingSet)); if (urlCustParams) { diff --git a/modules/dianomiBidAdapter.js b/modules/dianomiBidAdapter.js index 777878b5207..3e90a76cf9e 100644 --- a/modules/dianomiBidAdapter.js +++ b/modules/dianomiBidAdapter.js @@ -15,6 +15,7 @@ import { import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; const { getConfig } = config; @@ -115,7 +116,7 @@ export const spec = { setOnAny(validBidRequests, 'params.priceType') || 'net'; const tid = bidderRequest.ortb2?.source?.tid; - const currency = getConfig('currency.adServerCurrency'); + const currency = getCurrencyFromBidderRequest(bidderRequest); const cur = currency && [currency]; const eids = setOnAny(validBidRequests, 'userIdAsEids'); const schain = setOnAny(validBidRequests, 'schain'); diff --git a/modules/docereeAdManagerBidAdapter.js b/modules/docereeAdManagerBidAdapter.js index e26045c8f1f..80f70d71a8b 100644 --- a/modules/docereeAdManagerBidAdapter.js +++ b/modules/docereeAdManagerBidAdapter.js @@ -79,6 +79,17 @@ export function getPageUrl() { return url; } +const handleConsent = (consentValue) => { + try { + if (consentValue === 0 || consentValue === '0') { + consentValue = '0'; + } + } catch (error) { + + } + return consentValue; +} + export function getPayload(bid, userData, bidderRequest) { if (!userData || !bid) { return false; @@ -98,14 +109,15 @@ export function getPayload(bid, userData, bidderRequest) { city, state, zipcode, - hashedNPI, hashedhcpid, hashedemail, hashedmobile, country, + hashedNPI, organization, platformUid, - mobile + mobile, + userconsent } = userData; const data = { @@ -119,19 +131,18 @@ export function getPayload(bid, userData, bidderRequest) { city: city || '', state: state || '', zipcode: zipcode || '', - hashedNPI: hashedNPI || '', pb: 1, adunit: placementId || '', requestId: bidId || '', - hashedhcpid: hashedhcpid || '', + hashedhcpid: hashedhcpid || hashedNPI || '', hashedemail: hashedemail || '', hashedmobile: hashedmobile || '', country: country || '', organization: organization || '', dob: dob || '', - userconsent: 1, + upref: handleConsent(userconsent) || '', mobile: mobile || '', - pageurl: publisherUrl || getPageUrl() || '' + pageurl: getPageUrl() || publisherUrl || '' }; try { diff --git a/modules/dsp_genieeBidAdapter.js b/modules/dsp_genieeBidAdapter.js index 57aafd47fc8..f97c13379f3 100644 --- a/modules/dsp_genieeBidAdapter.js +++ b/modules/dsp_genieeBidAdapter.js @@ -3,6 +3,7 @@ import { BANNER } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { deepAccess, deepSetValue } from '../src/utils.js'; import { config } from '../src/config.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -71,7 +72,7 @@ export const spec = { if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || // gdpr USPConsent(bidderRequest.uspConsent) || // usp config.getConfig('coppa') || // coppa - invalidCurrency(config.getConfig('currency.adServerCurrency')) // currency validation + invalidCurrency(getCurrencyFromBidderRequest(bidderRequest)) // currency validation ) { return { method: 'GET', diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index 1fe5cb09c10..5bd1eab8863 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -4,6 +4,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import {serializeSupplyChain} from '../libraries/schainSerializer/schainSerializer.js'; const BIDDER_CODE = 'eplanning'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -39,6 +40,7 @@ export const spec = { const method = 'GET'; const dfpClientId = '1'; const sec = 'ROS'; + const schain = bidRequests[0].schain; let url; let params; const urlConfig = getUrlConfig(bidRequests); @@ -70,7 +72,9 @@ export const spec = { if (pcrs) { params.crs = pcrs; } - + if (schain && schain.nodes.length <= 2) { + params.sch = serializeSupplyChain(schain, ['asi', 'sid', 'hp', 'rid', 'name', 'domain']); + } if (referrerUrl) { params.fr = cutUrl(referrerUrl); } diff --git a/modules/equativBidAdapter.js b/modules/equativBidAdapter.js index 7f59e46920e..39c7ee94586 100644 --- a/modules/equativBidAdapter.js +++ b/modules/equativBidAdapter.js @@ -1,9 +1,11 @@ -import { BANNER } from '../src/mediaTypes.js'; -import { getBidFloor } from '../libraries/equativUtils/equativUtils.js' + +import { getBidFloor } from '../libraries/equativUtils/equativUtils.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; -import { deepAccess, deepSetValue, mergeDeep } from '../src/utils.js'; +import { deepAccess, deepSetValue, logError, logWarn, mergeDeep } from '../src/utils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid @@ -13,14 +15,26 @@ import { deepAccess, deepSetValue, mergeDeep } from '../src/utils.js'; const BIDDER_CODE = 'equativ'; const COOKIE_SYNC_ORIGIN = 'https://apps.smartadserver.com'; const COOKIE_SYNC_URL = `${COOKIE_SYNC_ORIGIN}/diff/templates/asset/csync.html`; +const LOG_PREFIX = 'Equativ:'; const PID_COOKIE_NAME = 'eqt_pid'; +/** + * Evaluates impressions for validity. The entry evaluated is considered valid if NEITHER of these conditions are met: + * 1) it has a `video` property defined for `mediaTypes.video` which is an empty object + * 2) it has a `native` property defined for `mediaTypes.native` which is an empty object + * @param {*} bidReq A bid request object to evaluate + * @returns boolean + */ +function isValid(bidReq) { + return !(bidReq.mediaTypes.video && JSON.stringify(bidReq.mediaTypes.video) === '{}') && !(bidReq.mediaTypes.native && JSON.stringify(bidReq.mediaTypes.native) === '{}'); +} + export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const spec = { code: BIDDER_CODE, gvlid: 45, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * @param bidRequests @@ -28,11 +42,23 @@ export const spec = { * @returns {ServerRequest[]} */ buildRequests: (bidRequests, bidderRequest) => { - return { - data: converter.toORTB({ bidderRequest, bidRequests }), - method: 'POST', - url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169' - }; + if (bidRequests.filter(isValid).length === 0) { + logError(`${LOG_PREFIX} No useful bid requests to process. No requests will be sent.`, bidRequests); + return undefined; + } + + const requests = []; + + bidRequests.forEach(bid => { + const data = converter.toORTB({bidRequests: [bid], bidderRequest}); + requests.push({ + data, + method: 'POST', + url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169', + }) + }); + + return requests; }, /** @@ -89,32 +115,22 @@ export const converter = ortbConverter({ imp(buildImp, bidRequest, context) { const imp = buildImp(bidRequest, context); + const mediaType = deepAccess(bidRequest, 'mediaTypes.video') ? VIDEO : BANNER; const { siteId, pageId, formatId } = bidRequest.params; delete imp.dt; - imp.bidfloor = imp.bidfloor || getBidFloor(bidRequest); - imp.secure = bidRequest.ortb2Imp?.secure ?? 1; + imp.bidfloor = imp.bidfloor || getBidFloor(bidRequest, config.getConfig('currency.adServerCurrency'), mediaType); + imp.secure = 1; imp.tagid = bidRequest.adUnitCode; - if (siteId || pageId || formatId) { - const bidder = {}; - - if (siteId) { - bidder.siteId = siteId; - } - - if (pageId) { - bidder.pageId = pageId; - } - - if (formatId) { - bidder.formatId = formatId; - } + if (!deepAccess(bidRequest, 'ortb2Imp.rwdd') && deepAccess(bidRequest, 'mediaTypes.video.ext.rewarded')) { + mergeDeep(imp, { rwdd: bidRequest.mediaTypes.video.ext.rewarded }); + } - mergeDeep(imp, { - ext: { bidder }, - }); + const bidder = { ...(siteId && { siteId }), ...(pageId && { pageId }), ...(formatId && { formatId }) }; + if (Object.keys(bidder).length) { + mergeDeep(imp.ext, { bidder }); } return imp; @@ -124,15 +140,22 @@ export const converter = ortbConverter({ const bid = context.bidRequests[0]; const req = buildRequest(imps, bidderRequest, context); - if (deepAccess(bid, 'ortb2.site.publisher')) { - deepSetValue(req, 'site.publisher.id', bid.ortb2.site.publisher.id || bid.params.networkId); - } else if (deepAccess(bid, 'ortb2.app.publisher')) { - deepSetValue(req, 'app.publisher.id', bid.ortb2.app.publisher.id || bid.params.networkId); - } else if (deepAccess(bid, 'ortb2.dooh.publisher')) { - deepSetValue(req, 'dooh.publisher.id', bid.ortb2.dooh.publisher.id || bid.params.networkId); - } else { - deepSetValue(req, 'site.publisher.id', bid.params.networkId); - } + let env = ['ortb2.site.publisher', 'ortb2.app.publisher', 'ortb2.dooh.publisher'].find(propPath => deepAccess(bid, propPath)) || 'ortb2.site.publisher'; + deepSetValue(req, env.replace('ortb2.', '') + '.id', deepAccess(bid, env + '.id') || bid.params.networkId); + + [ + { path: 'mediaTypes.video', props: ['mimes', 'placement'] }, + { path: 'ortb2Imp.audio', props: ['mimes'] }, + { path: 'mediaTypes.native.ortb', props: ['privacy', 'plcmttype', 'eventtrackers'] }, + ].forEach(({ path, props }) => { + if (deepAccess(bid, path)) { + props.forEach(prop => { + if (!deepAccess(bid, `${path}.${prop}`)) { + logWarn(`${LOG_PREFIX} Property "${path}.${prop}" is missing from request. Request will proceed, but the use of "${prop}" is strongly encouraged.`, bid); + } + }); + } + }); const pid = storage.getCookie(PID_COOKIE_NAME); if (pid) { @@ -140,7 +163,7 @@ export const converter = ortbConverter({ } return req; - }, + } }); registerBidder(spec); diff --git a/modules/escalaxBidAdapter.js b/modules/escalaxBidAdapter.js new file mode 100644 index 00000000000..027e41d7c56 --- /dev/null +++ b/modules/escalaxBidAdapter.js @@ -0,0 +1,106 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'escalax'; +const ESCALAX_SOURCE_ID_MACRO = '[sourceId]'; +const ESCALAX_ACCOUNT_ID_MACRO = '[accountId]'; +const ESCALAX_SUBDOMAIN_MACRO = '[subdomain]'; +const ESCALAX_URL = `https://${ESCALAX_SUBDOMAIN_MACRO}.escalax.io/bid?type=pjs&partner=${ESCALAX_SOURCE_ID_MACRO}&token=${ESCALAX_ACCOUNT_ID_MACRO}`; +const ESCALAX_DEFAULT_CURRENCY = 'USD'; +const ESCALAX_DEFAULT_SUBDOMAIN = 'bidder_us'; + +function createImp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + imp.ext = { + [BIDDER_CODE]: { + sourceId: bidRequest.params.sourceId, + accountId: bidRequest.params.accountId, + } + }; + if (!imp.bidfloor) imp.bidfloor = bidRequest.params.bidfloor; + return imp; +} + +function createRequest(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const bid = context.bidRequests[0]; + request.test = config.getConfig('debug') ? 1 : 0; + if (!request.cur) request.cur = [bid.params.currency || ESCALAX_DEFAULT_CURRENCY]; + return request; +} + +function createBidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + bidResponse.cur = 'USD'; + return bidResponse; +} + +function getSubdomain() { + const regionMap = { + 'Europe': 'bidder_eu', + 'Africa': 'bidder_eu', + 'Atlantic': 'bidder_eu', + 'Arctic': 'bidder_eu', + 'Asia': 'bidder_apac', + 'Australia': 'bidder_apac', + 'Antarctica': 'bidder_apac', + 'Pacific': 'bidder_apac', + 'Indian': 'bidder_apac', + 'America': 'bidder_us' + }; + + try { + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const region = timezone.split('/')[0]; + return regionMap[region] || 'bidder_us'; + } catch (err) { + return 'bidder_us'; + } +} + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 20, + }, + imp: createImp, + request: createRequest, + bidResponse: createBidResponse +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.params.sourceId) && Boolean(bid.params.accountId); + }, + + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests && validBidRequests.length === 0) return []; + const { sourceId, accountId } = validBidRequests[0].params; + const subdomain = getSubdomain(); + const endpointURL = ESCALAX_URL + .replace(ESCALAX_SUBDOMAIN_MACRO, subdomain || ESCALAX_DEFAULT_SUBDOMAIN) + .replace(ESCALAX_SOURCE_ID_MACRO, sourceId) + .replace(ESCALAX_ACCOUNT_ID_MACRO, accountId); + const request = converter.toORTB({ bidRequests: validBidRequests, bidderRequest }); + return { + method: 'POST', + url: endpointURL, + data: request + }; + }, + + interpretResponse: (response, request) => { + if (response?.body) { + const bids = converter.fromORTB({ response: response.body, request: request.data }).bids; + return bids; + } + return []; + }, +}; + +registerBidder(spec); diff --git a/modules/escalaxBidAdapter.md b/modules/escalaxBidAdapter.md new file mode 100644 index 00000000000..7cd45cabdc6 --- /dev/null +++ b/modules/escalaxBidAdapter.md @@ -0,0 +1,80 @@ +# Overview + +``` +Module Name: Escalax SSP Bidder Adapter +Module Type: Bidder Adapter +Maintainer: connect@escalax.io +``` + +# Description + +Escalax Bidding adapter requires setup before beginning. Please contact us at + +# Test Parameters + +```js +const adUnits = [ + { + code: "banner1", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + bids: [ + { + bidder: "escalax", + params: { + accountId: "hash", + sourceId: "sourceId", + }, + }, + ], + }, + { + code: "native_example", + mediaTypes: { + native: {}, + }, + bids: [ + { + bidder: "escalax", + params: { + accountId: "hash", + sourceId: "sourceId", + }, + }, + ], + }, + { + code: "video1", + sizes: [640, 480], + mediaTypes: { + video: { + minduration: 0, + maxduration: 999, + boxingallowed: 1, + skip: 0, + mimes: ["application/javascript", "video/mp4"], + w: 1920, + h: 1080, + protocols: [2], + linearity: 1, + api: [1, 2], + }, + }, + bids: [ + { + bidder: "escalax", + params: { + accountId: "hash", + sourceId: "sourceId", + }, + }, + ], + }, +]; +``` diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js index 8b1beba09c3..5516656f467 100644 --- a/modules/eskimiBidAdapter.js +++ b/modules/eskimiBidAdapter.js @@ -232,7 +232,10 @@ function createRequest(bidRequests, bidderRequest, mediaType) { method: 'POST', url: getBidRequestUrlByRegion(), data: data, - options: {contentType: 'application/json;charset=UTF-8', withCredentials: false} + options: { + withCredentials: true, + contentType: 'application/json;charset=UTF-8', + } } } diff --git a/modules/finativeBidAdapter.js b/modules/finativeBidAdapter.js index e7613bf9cce..0cdcae15e61 100644 --- a/modules/finativeBidAdapter.js +++ b/modules/finativeBidAdapter.js @@ -4,8 +4,8 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {NATIVE} from '../src/mediaTypes.js'; import {_map, deepSetValue, isEmpty, setOnAny} from '../src/utils.js'; -import {config} from '../src/config.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; const BIDDER_CODE = 'finative'; const DEFAULT_CUR = 'EUR'; @@ -64,7 +64,7 @@ export const spec = { validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); const pt = setOnAny(validBidRequests, 'params.pt') || setOnAny(validBidRequests, 'params.priceType') || 'net'; const tid = bidderRequest.ortb2?.source?.tid; - const cur = [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR]; + const cur = [getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CUR]; let url = bidderRequest.refererInfo.referer; const imp = validBidRequests.map((bid, id) => { diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js index d7af51f7312..e0a5861f40c 100644 --- a/modules/gmosspBidAdapter.js +++ b/modules/gmosspBidAdapter.js @@ -1,3 +1,7 @@ +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; +import { tryAppendQueryString } from '../libraries/urlUtils/urlUtils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; import { createTrackPixelHtml, deepAccess, @@ -7,10 +11,6 @@ import { isEmpty, logError } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; -import {BANNER} from '../src/mediaTypes.js'; -import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -50,7 +50,7 @@ export const spec = { const bidRequests = []; const urlInfo = getUrlInfo(bidderRequest.refererInfo); - const cur = getCurrencyType(); + const cur = getCurrencyType(bidderRequest); const dnt = getDNT() ? '1' : '0'; for (let i = 0; i < validBidRequests.length; i++) { @@ -156,11 +156,8 @@ export const spec = { }; -function getCurrencyType() { - if (config.getConfig('currency.adServerCurrency')) { - return config.getConfig('currency.adServerCurrency'); - } - return 'JPY'; +function getCurrencyType(bidderRequest) { + return getCurrencyFromBidderRequest(bidderRequest) || 'JPY'; } function getUrlInfo(refererInfo) { diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index 07ad6b2fa97..e9f9604e594 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -1,1107 +1,422 @@ -import {Renderer} from '../src/Renderer.js'; -import { - createTrackPixelHtml, - deepAccess, - deepClone, - getBidRequest, - getParameterByName, - isArray, - isArrayOfNums, - isEmpty, - isFn, - isNumber, - isPlainObject, - isStr, - logError, - logInfo, - logMessage -} from '../src/utils.js'; -import {config} from '../src/config.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {find, includes} from '../src/polyfill.js'; -import {INSTREAM, OUTSTREAM} from '../src/video.js'; -import {hasPurpose1Consent} from '../src/utils/gdpr.js'; -import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; -import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping/index.js'; -import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; -import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; -import {chunk} from '../libraries/chunk/chunk.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - */ +import { ajax } from '../src/ajax.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; +/* General config */ +const IS_LOCAL_MODE = false; const BIDDER_CODE = 'goldbach'; -const URL = 'https://ib.adnxs.com/ut/v3/prebid'; -const PRICING_URL = 'https://templates.da-services.ch/01_universal/burda_prebid/1.0/json/sizeCPMMapping.json'; -const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; -const VIDEO_TARGETING = ['id', 'minduration', 'maxduration', - 'skippable', 'playback_method', 'frameworks', 'context', 'skipoffset']; -const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api']; -const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; -const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately -const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; -const DEFAULT_PRICE_MAPPING = { - '0x0': 2.5, - '300x600': 5, - '800x250': 6, - '350x600': 6 -}; -let PRICE_MAPPING; -const VIDEO_MAPPING = { - playback_method: { - 'unknown': 0, - 'auto_play_sound_on': 1, - 'auto_play_sound_off': 2, - 'click_to_play': 3, - 'mouse_over': 4, - 'auto_play_sound_unknown': 5 - }, - context: { - 'unknown': 0, - 'pre_roll': 1, - 'mid_roll': 2, - 'post_roll': 3, - 'outstream': 4, - 'in-banner': 5 - } -}; -const NATIVE_MAPPING = { - body: 'description', - body2: 'desc2', - cta: 'ctatext', - image: { - serverName: 'main_image', - requiredParams: { required: true } - }, - icon: { - serverName: 'icon', - requiredParams: { required: true } - }, - sponsoredBy: 'sponsored_by', - privacyLink: 'privacy_link', - salePrice: 'saleprice', - displayUrl: 'displayurl' -}; -const SOURCE = 'pbjs'; -const MAX_IMPS_PER_REQUEST = 15; -const SCRIPT_TAG_START = ' { - if (Array.isArray(bid.params.placementId)) { - const ids = bid.params.placementId; - for (let i = 0; i < ids.length; i++) { - const newBid = Object.assign({}, bid, {params: {placementId: ids[i]}}); - localBidRequests.push(newBid) - } - } else { - localBidRequests.push(bid); - } - }); - const tags = localBidRequests.map(bidToTag); - const userObjBid = find(bidRequests, hasUserInfo); - let userObj = {}; - if (config.getConfig('coppa') === true) { - userObj = { 'coppa': true }; - } - if (userObjBid) { - Object.keys(userObjBid.params.user) - .filter(param => includes(USER_PARAMS, param)) - .forEach((param) => { - let uparam = convertCamelToUnderscore(param); - if (param === 'segments' && isArray(userObjBid.params.user[param])) { - let segs = []; - userObjBid.params.user[param].forEach(val => { - if (isNumber(val)) { - segs.push({'id': val}); - } else if (isPlainObject(val)) { - segs.push(val); - } - }); - userObj[uparam] = segs; - } else if (param !== 'segments') { - userObj[uparam] = userObjBid.params.user[param]; - } - }); - } - - const appDeviceObjBid = find(bidRequests, hasAppDeviceInfo); - let appDeviceObj; - if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app) { - appDeviceObj = {}; - Object.keys(appDeviceObjBid.params.app) - .filter(param => includes(APP_DEVICE_PARAMS, param)) - .forEach(param => appDeviceObj[param] = appDeviceObjBid.params.app[param]); - } - - const appIdObjBid = find(bidRequests, hasAppId); - let appIdObj; - if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { - appIdObj = { - appid: appIdObjBid.params.app.id - }; - } - - let debugObj = {}; - let debugObjParams = {}; - const debugBidRequest = find(bidRequests, hasDebug); - if (debugBidRequest && debugBidRequest.debug) { - debugObj = debugBidRequest.debug; - } - - if (debugObj && debugObj.enabled) { - Object.keys(debugObj) - .filter(param => includes(DEBUG_PARAMS, param)) - .forEach(param => { - debugObjParams[param] = debugObj[param]; - }); - } - - const memberIdBid = find(bidRequests, hasMemberId); - const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; - const schain = bidRequests[0].schain; - const omidSupport = find(bidRequests, hasOmidSupport); - - const payload = { - tags: [...tags], - user: userObj, - sdk: { - source: SOURCE, - version: '$prebid.version$' - }, - schain: schain - }; - - if (omidSupport) { - payload['iab_support'] = { - omidpn: 'Appnexus', - omidpv: '$prebid.version$' - }; - } - - if (member > 0) { - payload.member_id = member; - } - - if (appDeviceObjBid) { - payload.device = appDeviceObj; - } - if (appIdObjBid) { - payload.app = appIdObj; - } - - if (config.getConfig('adpod.brandCategoryExclusion')) { - payload.brand_category_uniqueness = true; - } - - if (debugObjParams.enabled) { - payload.debug = debugObjParams; - logInfo('Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4)); - } - - if (bidderRequest && bidderRequest.gdprConsent) { - // note - objects for impbus use underscore instead of camelCase - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - - if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { - let ac = bidderRequest.gdprConsent.addtlConsent; - // pull only the ids from the string (after the ~) and convert them to an array of ints - let acStr = ac.substring(ac.indexOf('~') + 1); - payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); - } - } - - if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.refererInfo) { - let refererinfo = { - // TODO: this collects everything it finds, except for topmostLocation - rd_ref: encodeURIComponent(bidderRequest.refererInfo.topmostLocation), - rd_top: bidderRequest.refererInfo.reachedTop, - rd_ifs: bidderRequest.refererInfo.numIframes, - rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') - }; - payload.referrer_detection = refererinfo; - } - - const hasAdPodBid = find(bidRequests, hasAdPod); - if (hasAdPodBid) { - bidRequests.filter(hasAdPod).forEach(adPodBid => { - const adPodTags = createAdPodRequest(tags, adPodBid); - // don't need the original adpod placement because it's in adPodTags - const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); - payload.tags = [...nonPodTags, ...adPodTags]; - }); - } - - if (bidRequests[0].userId) { - let eids = []; - - addUserId(eids, deepAccess(bidRequests[0], `userId.criteoId`), 'criteo.com', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.netId`), 'netid.de', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.idl_env`), 'liveramp.com', null); - addUserId(eids, deepAccess(bidRequests[0], `userId.tdid`), 'adserver.org', 'TDID'); - addUserId(eids, deepAccess(bidRequests[0], `userId.uid2.id`), 'uidapi.com', 'UID2'); - - if (eids.length) { - payload.eids = eids; - } - } - - if (tags[0].publisher_id) { - payload.publisher_id = tags[0].publisher_id; - } - - const request = formatRequest(payload, bidderRequest); - // add pricing endpoint - return [{method: 'GET', url: PRICING_URL, options: {withCredentials: false}}, request]; - }, - - parseAndMapCpm: function(serverResponse) { - const responseBody = serverResponse.body; - if (Array.isArray(responseBody) && responseBody.length) { - let localData = {}; - responseBody.forEach(cpmPerSize => { - Object.keys(cpmPerSize).forEach(size => { - let obj = {}; - obj[size] = cpmPerSize[size]; - localData = Object.assign({}, localData, obj) - }) - }) - PRICE_MAPPING = localData; - return null; - } - - if (responseBody.version) { - const localPriceMapping = PRICE_MAPPING || DEFAULT_PRICE_MAPPING; - if (responseBody.tags && Array.isArray(responseBody.tags) && responseBody.tags.length) { - responseBody.tags.forEach((tag) => { - if (tag.ads && Array.isArray(tag.ads) && tag.ads.length) { - tag.ads.forEach(ad => { - if (ad.ad_type === 'banner') { - const size = `${ad.rtb.banner.width}x${ad.rtb.banner.height}`; - if (localPriceMapping[size]) { - ad.cpm = localPriceMapping[size]; - } else { - ad.cpm = localPriceMapping['0x0']; - } - } - }) - } - }); - } - } - return responseBody; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, { bidderRequest }) { - serverResponse = this.parseAndMapCpm(serverResponse); - if (!serverResponse) return []; - const bids = []; - if (serverResponse.error) { - let errorMessage = `in response for ${bidderRequest.bidderCode} adapter : ${serverResponse.error}`; - logError(errorMessage); - return bids; - } - - if (serverResponse.tags) { - serverResponse.tags.forEach(serverBid => { - const rtbBid = getRtbBid(serverBid); - if (rtbBid) { - if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { - const bid = newBid(serverBid, rtbBid, bidderRequest); - bid.mediaType = parseMediaType(rtbBid); - bids.push(bid); - } - } - }); - } - - if (serverResponse.debug && serverResponse.debug.debug_info) { - let debugHeader = 'AppNexus Debug Auction for Prebid\n\n' - let debugText = debugHeader + serverResponse.debug.debug_info - debugText = debugText - .replace(/(|)/gm, '\t') // Tables - .replace(/(<\/td>|<\/th>)/gm, '\n') // Tables - .replace(/^
/gm, '') // Remove leading
- .replace(/(
\n|
)/gm, '\n') //
- .replace(/

(.*)<\/h1>/gm, '\n\n===== $1 =====\n\n') // Header H1 - .replace(/(.*)<\/h[2-6]>/gm, '\n\n*** $1 ***\n\n') // Headers - .replace(/(<([^>]+)>)/igm, ''); // Remove any other tags - logMessage(debugText); - } - - return bids; - }, - - getUserSyncs: function (syncOptions, responses, gdprConsent) { - if (syncOptions.iframeEnabled && hasPurpose1Consent({gdprConsent})) { - return [{ - type: 'iframe', - url: 'https://acdn.adnxs.com/dmp/async_usersync.html' - }]; - } - }, - - /** - * Add element selector to javascript tracker to improve native viewability - * @param {Bid} bid - */ - onBidWon: function (bid) { - if (bid.native) { - reloadViewabilityScriptWithCorrectParameters(bid); - } +const URL = 'https://goldlayer-api.prod.gbads.net/bid/pbjs'; +const URL_LOCAL = 'http://localhost:3000/bid/pbjs'; +const LOGGING_PERCENTAGE_REGULAR = 0.0001; +const LOGGING_PERCENTAGE_ERROR = 0.001; +const LOGGING_URL = 'https://l.da-services.ch/pb'; + +/* Renderer settings */ +const RENDERER_OPTIONS = { + OUTSTREAM_GP: { + MIN_HEIGHT: 300, + MIN_WIDTH: 300, + URL: 'https://goldplayer.prod.gbads.net/scripts/goldplayer.js' } }; -function reloadViewabilityScriptWithCorrectParameters(bid) { - let viewJsPayload = getAppnexusViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); - - if (viewJsPayload) { - let prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode; - - let jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload); - - let newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); - - // find iframe containing script tag - let frameArray = document.getElementsByTagName('iframe'); - - // boolean var to modify only one script. That way if there are muliple scripts, - // they won't all point to the same creative. - let modifiedAScript = false; - - // first, loop on all ifames - for (let i = 0; i < frameArray.length && !modifiedAScript; i++) { - let currentFrame = frameArray[i]; - try { - // IE-compatible, see https://stackoverflow.com/a/3999191/2112089 - let nestedDoc = currentFrame.contentDocument || currentFrame.contentWindow.document; - - if (nestedDoc) { - // if the doc is present, we look for our jstracker - let scriptArray = nestedDoc.getElementsByTagName('script'); - for (let j = 0; j < scriptArray.length && !modifiedAScript; j++) { - let currentScript = scriptArray[j]; - if (currentScript.getAttribute('data-src') == jsTrackerSrc) { - currentScript.setAttribute('src', newJsTrackerSrc); - currentScript.setAttribute('data-src', ''); - if (currentScript.removeAttribute) { - currentScript.removeAttribute('data-src'); - } - modifiedAScript = true; - } - } - } - } catch (exception) { - // trying to access a cross-domain iframe raises a SecurityError - // this is expected and ignored - if (!(exception instanceof DOMException && exception.name === 'SecurityError')) { - // all other cases are raised again to be treated by the calling function - throw exception; - } - } - } - } -} - -function strIsAppnexusViewabilityScript(str) { - let regexMatchUrlStart = str.match(VIEWABILITY_URL_START); - let viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1; - - let regexMatchFileName = str.match(VIEWABILITY_FILE_NAME); - let fileNameInStr = regexMatchFileName != null && regexMatchFileName.length >= 1; +/* Event types */ +const EVENTS = { + BID_WON: 'bid_won', + TARGETING: 'targeting_set', + RENDER: 'creative_render', + TIMEOUT: 'timeout', + ERROR: 'error' +}; - return str.startsWith(SCRIPT_TAG_START) && fileNameInStr && viewUrlStartInStr; -} +/* Targeting mapping */ +const TARGETING_KEYS = { + // request level + GEO_LAT: 'lat', + GEO_LON: 'long', + GEO_ZIP: 'zip', + CONNECTION_TYPE: 'connection', + // slot level + VIDEO_DURATION: 'duration', +}; -function getAppnexusViewabilityScriptFromJsTrackers(jsTrackerArray) { - let viewJsPayload; - if (isStr(jsTrackerArray) && strIsAppnexusViewabilityScript(jsTrackerArray)) { - viewJsPayload = jsTrackerArray; - } else if (isArray(jsTrackerArray)) { - for (let i = 0; i < jsTrackerArray.length; i++) { - let currentJsTracker = jsTrackerArray[i]; - if (strIsAppnexusViewabilityScript(currentJsTracker)) { - viewJsPayload = currentJsTracker; - } +/* Native mapping */ +export const OPENRTB = { + NATIVE: { + IMAGE_TYPE: { + ICON: 1, + MAIN: 3, + }, + ASSET_ID: { + TITLE: 1, + IMAGE: 2, + ICON: 3, + BODY: 4, + CTA: 5, + SPONSORED: 6, } } - return viewJsPayload; -} - -function getViewabilityScriptUrlFromPayload(viewJsPayload) { - // extracting the content of the src attribute - // -> substring between src=" and " - let indexOfFirstQuote = viewJsPayload.indexOf('src="') + 5; // offset of 5: the length of 'src=' + 1 - let indexOfSecondQuote = viewJsPayload.indexOf('"', indexOfFirstQuote); - let jsTrackerSrc = viewJsPayload.substring(indexOfFirstQuote, indexOfSecondQuote); - return jsTrackerSrc; -} - -function formatRequest(payload, bidderRequest) { - let request = []; - let options = { - withCredentials: true - }; - - let endpointUrl = URL; - - if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { - endpointUrl = URL_SIMPLE; - } - - if (getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { - options.customHeaders = { - 'X-Is-Test': 1 - }; - } - - if (payload.tags.length > MAX_IMPS_PER_REQUEST) { - const clonedPayload = deepClone(payload); - - chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { - clonedPayload.tags = tags; - const payloadString = JSON.stringify(clonedPayload); - request.push({ - method: 'POST', - url: endpointUrl, - data: payloadString, - bidderRequest, - options - }); - }); - } else { - const payloadString = JSON.stringify(payload); - request = { - method: 'POST', - url: endpointUrl, - data: payloadString, - bidderRequest, - options - }; - } +}; - return request; -} +/* Mapping */ +const convertToCustomTargeting = (bidderRequest) => { + const customTargeting = {}; -function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { - const renderer = Renderer.install({ - id: rtbBid.renderer_id, - url: rtbBid.renderer_url, - config: rendererOptions, - loaded: false, - adUnitCode - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - logError('Prebid Error calling setRender on renderer', err); - } - - renderer.setEventHandlers({ - impression: () => logMessage('Outstream video impression event'), - loaded: () => logMessage('Outstream video loaded event'), - ended: () => { - logMessage('Outstream renderer video event'); - document.querySelector(`#${adUnitCode}`).style.display = 'none'; + // geo - lat/long + if (bidderRequest?.ortb2?.device?.geo) { + if (bidderRequest?.ortb2?.device?.geo?.lon) { + customTargeting[TARGETING_KEYS.GEO_LON] = bidderRequest.ortb2.device.geo.lon; } - }); - return renderer; -} - -/** - * Unpack the Server's Bid into a Prebid-compatible one. - * @param serverBid - * @param rtbBid - * @param bidderRequest - * @return Bid - */ -function newBid(serverBid, rtbBid, bidderRequest) { - const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); - const bid = { - requestId: serverBid.uuid, - cpm: rtbBid.cpm, - creativeId: rtbBid.creative_id, - dealId: rtbBid.deal_id, - currency: 'USD', - netRevenue: true, - ttl: 300, - adUnitCode: bidRequest.adUnitCode, - appnexus: { - buyerMemberId: rtbBid.buyer_member_id, - dealPriority: rtbBid.deal_priority, - dealCode: rtbBid.deal_code + if (bidderRequest?.ortb2?.device?.geo?.lat) { + customTargeting[TARGETING_KEYS.GEO_LAT] = bidderRequest.ortb2.device.geo.lat; } - }; - - // WE DON'T FULLY SUPPORT THIS ATM - future spot for adomain code; creating a stub for 5.0 compliance - if (rtbBid.adomain) { - bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [] }); } - if (rtbBid.advertiser_id) { - bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); - } - - if (rtbBid.rtb.video) { - // shared video properties used for all 3 contexts - Object.assign(bid, { - width: rtbBid.rtb.video.player_width, - height: rtbBid.rtb.video.player_height, - vastImpUrl: rtbBid.notify_url, - ttl: 3600 - }); - - const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); - switch (videoContext) { - case ADPOD: - const primaryCatId = (APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id]) ? APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id] : null; - bid.meta = Object.assign({}, bid.meta, { primaryCatId }); - const dealTier = rtbBid.deal_priority; - bid.video = { - context: ADPOD, - durationSeconds: Math.floor(rtbBid.rtb.video.duration_ms / 1000), - dealTier - }; - bid.vastUrl = rtbBid.rtb.video.asset_url; + // connection + if (bidderRequest?.ortb2?.device?.connectiontype) { + switch (bidderRequest.ortb2.device.connectiontype) { + case 1: + customTargeting[TARGETING_KEYS.CONNECTION_TYPE] = 'ethernet'; break; - case OUTSTREAM: - bid.adResponse = serverBid; - bid.adResponse.ad = bid.adResponse.ads[0]; - bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; - bid.vastXml = rtbBid.rtb.video.content; - - if (rtbBid.renderer_url) { - const videoBid = find(bidderRequest.bids, bid => bid.bidId === serverBid.uuid); - const rendererOptions = deepAccess(videoBid, 'renderer.options'); - bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions); - } + case 2: + customTargeting[TARGETING_KEYS.CONNECTION_TYPE] = 'wifi'; break; - case INSTREAM: - bid.vastUrl = rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url); + case 4: + customTargeting[TARGETING_KEYS.CONNECTION_TYPE] = '2G'; + break; + case 5: + customTargeting[TARGETING_KEYS.CONNECTION_TYPE] = '3G'; + break; + case 6: + customTargeting[TARGETING_KEYS.CONNECTION_TYPE] = '4G'; + break; + case 0: + case 3: + default: break; } - } else if (rtbBid.rtb[NATIVE]) { - const nativeAd = rtbBid.rtb[NATIVE]; - - // setting up the jsTracker: - // we put it as a data-src attribute so that the tracker isn't called - // until we have the adId (see onBidWon) - let jsTrackerDisarmed = rtbBid.viewability.config.replace('src=', 'data-src='); - - let jsTrackers = nativeAd.javascript_trackers; - - if (jsTrackers == undefined) { - jsTrackers = jsTrackerDisarmed; - } else if (isStr(jsTrackers)) { - jsTrackers = [jsTrackers, jsTrackerDisarmed]; - } else { - jsTrackers.push(jsTrackerDisarmed); - } - - bid[NATIVE] = { - title: nativeAd.title, - body: nativeAd.desc, - body2: nativeAd.desc2, - cta: nativeAd.ctatext, - rating: nativeAd.rating, - sponsoredBy: nativeAd.sponsored, - privacyLink: nativeAd.privacy_link, - address: nativeAd.address, - downloads: nativeAd.downloads, - likes: nativeAd.likes, - phone: nativeAd.phone, - price: nativeAd.price, - salePrice: nativeAd.saleprice, - clickUrl: nativeAd.link.url, - displayUrl: nativeAd.displayurl, - clickTrackers: nativeAd.link.click_trackers, - impressionTrackers: nativeAd.impression_trackers, - javascriptTrackers: jsTrackers - }; - if (nativeAd.main_img) { - bid['native'].image = { - url: nativeAd.main_img.url, - height: nativeAd.main_img.height, - width: nativeAd.main_img.width, - }; - } - if (nativeAd.icon) { - bid['native'].icon = { - url: nativeAd.icon.url, - height: nativeAd.icon.height, - width: nativeAd.icon.width, - }; - } - } else { - Object.assign(bid, { - width: rtbBid.rtb.banner.width, - height: rtbBid.rtb.banner.height, - ad: rtbBid.rtb.banner.content - }); - try { - if (rtbBid.rtb.trackers) { - const url = rtbBid.rtb.trackers[0].impression_urls[0]; - const tracker = createTrackPixelHtml(url); - bid.ad += tracker; - } - } catch (error) { - logError('Error appending tracking pixel', error); - } - } - - return bid; -} - -function bidToTag(bid) { - const tag = {}; - tag.sizes = transformSizes(bid.sizes); - tag.primary_size = tag.sizes[0]; - tag.ad_types = []; - tag.uuid = bid.bidId; - if (bid.params.placementId) { - tag.id = parseInt(bid.params.placementId, 10); - } else { - tag.code = bid.params.invCode; - } - tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false; - tag.prebid = true; - tag.disable_psa = true; - let bidFloor = getBidFloor(bid); - if (bidFloor) { - tag.reserve = bidFloor; - } - if (bid.params.position) { - tag.position = { 'above': 1, 'below': 2 }[bid.params.position] || 0; - } - if (bid.params.trafficSourceCode) { - tag.traffic_source_code = bid.params.trafficSourceCode; - } - if (bid.params.privateSizes) { - tag.private_sizes = transformSizes(bid.params.privateSizes); - } - if (bid.params.supplyType) { - tag.supply_type = bid.params.supplyType; - } - if (bid.params.pubClick) { - tag.pubclick = bid.params.pubClick; - } - if (bid.params.extInvCode) { - tag.ext_inv_code = bid.params.extInvCode; - } - if (bid.params.publisherId) { - tag.publisher_id = parseInt(bid.params.publisherId, 10); - } - if (bid.params.externalImpId) { - tag.external_imp_id = bid.params.externalImpId; - } - tag.keywords = getANKeywordParam(bid.ortb2, bid.params.keywords); - - let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - if (gpid) { - tag.gpid = gpid; - } - - if (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`)) { - tag.ad_types.push(NATIVE); - if (tag.sizes.length === 0) { - tag.sizes = transformSizes([1, 1]); - } - - if (bid.nativeParams) { - const nativeRequest = buildNativeRequest(bid.nativeParams); - tag[NATIVE] = { layouts: [nativeRequest] }; - } - } - - const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = deepAccess(bid, 'mediaTypes.video.context'); - - if (videoMediaType && context === 'adpod') { - tag.hb_source = 7; - } else { - tag.hb_source = 1; - } - if (bid.mediaType === VIDEO || videoMediaType) { - tag.ad_types.push(VIDEO); - } - - // instream gets vastUrl, outstream gets vastXml - if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { - tag.require_asset_url = true; - } - - if (bid.params.video) { - tag.video = {}; - // place any valid video params on the tag - Object.keys(bid.params.video) - .filter(param => includes(VIDEO_TARGETING, param)) - .forEach(param => { - switch (param) { - case 'context': - case 'playback_method': - let type = bid.params.video[param]; - type = (isArray(type)) ? type[0] : type; - tag.video[param] = VIDEO_MAPPING[param][type]; - break; - // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks - case 'frameworks': - break; - default: - tag.video[param] = bid.params.video[param]; - } - }); - - if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { - tag['video_frameworks'] = bid.params.video.frameworks; - } - } - - // use IAB ORTB values if the corresponding values weren't already set by bid.params.video - if (videoMediaType) { - tag.video = tag.video || {}; - Object.keys(videoMediaType) - .filter(param => includes(VIDEO_RTB_TARGETING, param)) - .forEach(param => { - switch (param) { - case 'minduration': - case 'maxduration': - if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; - break; - case 'skip': - if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); - break; - case 'skipafter': - if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; - break; - case 'playbackmethod': - if (typeof tag.video['playback_method'] !== 'number') { - let type = videoMediaType[param]; - type = (isArray(type)) ? type[0] : type; - - // we only support iab's options 1-4 at this time. - if (type >= 1 && type <= 4) { - tag.video['playback_method'] = type; - } - } - break; - case 'api': - if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { - // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) - let apiTmp = videoMediaType[param].map(val => { - let v = (val === 4) ? 5 : (val === 5) ? 4 : val; - - if (v >= 1 && v <= 5) { - return v; - } - }).filter(v => v); - tag['video_frameworks'] = apiTmp; - } - break; - } - }); } - if (bid.renderer) { - tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); + // zip + if (bidderRequest?.ortb2?.device?.geo?.zip) { + customTargeting[TARGETING_KEYS.GEO_ZIP] = bidderRequest.ortb2.device.geo.zip; } - if (bid.params.frameworks && isArray(bid.params.frameworks)) { - tag['banner_frameworks'] = bid.params.frameworks; - } - - if (bid.mediaTypes?.banner) { - tag.ad_types.push(BANNER); - } - - if (tag.ad_types.length === 0) { - delete tag.ad_types; - } - - return tag; + return customTargeting; } -/* Turn bid request sizes into ut-compatible format */ -function transformSizes(requestSizes) { - let sizes = []; - let sizeObj = {}; +const convertToCustomSlotTargeting = (validBidRequest) => { + const customTargeting = {}; - if (isArray(requestSizes) && requestSizes.length === 2 && - !isArray(requestSizes[0])) { - sizeObj.width = parseInt(requestSizes[0], 10); - sizeObj.height = parseInt(requestSizes[1], 10); - sizes.push(sizeObj); - } else if (typeof requestSizes === 'object') { - for (let i = 0; i < requestSizes.length; i++) { - let size = requestSizes[i]; - sizeObj = {}; - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - sizes.push(sizeObj); + // Video duration + if (validBidRequest.mediaTypes?.[VIDEO]) { + if (validBidRequest.params?.video?.maxduration) { + const duration = validBidRequest.params?.video?.maxduration; + if (duration <= 15) customTargeting[TARGETING_KEYS.VIDEO_DURATION] = 'M'; + if (duration > 15 && duration <= 30) customTargeting[TARGETING_KEYS.VIDEO_DURATION] = 'XL'; + if (duration > 30) customTargeting[TARGETING_KEYS.VIDEO_DURATION] = 'XXL'; } } - return sizes; + return customTargeting } -function hasUserInfo(bid) { - return !!bid.params.user; -} +const convertToProprietaryData = (validBidRequests, bidderRequest) => { + const requestData = { + mock: false, + debug: false, + timestampStart: undefined, + timestampEnd: undefined, + config: { + publisher: { + id: undefined, + } + }, + gdpr: { + consent: undefined, + consentString: undefined, + }, + contextInfo: { + contentUrl: undefined, + bidderResources: undefined, + }, + appInfo: { + id: undefined, + }, + userInfo: { + ip: undefined, + ua: undefined, + ifa: undefined, + ppid: [], + }, + slots: [], + targetings: {}, + }; -function hasMemberId(bid) { - return !!parseInt(bid.params.member, 10); -} + // Set timestamps + requestData.timestampStart = Date.now(); + requestData.timestampEnd = Date.now() + (!isNaN(bidderRequest.timeout) ? Number(bidderRequest.timeout) : 0); -function hasAppDeviceInfo(bid) { - if (bid.params) { - return !!bid.params.app + // Set config + if (validBidRequests[0]?.params?.publisherId) { + requestData.config.publisher.id = validBidRequests[0].params.publisherId; } -} -function hasAppId(bid) { - if (bid.params && bid.params.app) { - return !!bid.params.app.id + // Set GDPR + if (bidderRequest?.gdprConsent) { + requestData.gdpr.consent = bidderRequest.gdprConsent.gdprApplies; + requestData.gdpr.consentString = bidderRequest.gdprConsent.consentString; } - return !!bid.params.app -} -function hasDebug(bid) { - return !!bid.debug -} + // Set contextInfo + requestData.contextInfo.contentUrl = bidderRequest.refererInfo?.canonicalUrl || bidderRequest.refererInfo?.topmostLocation || bidderRequest?.ortb2?.site?.page; -function hasAdPod(bid) { - return ( - bid.mediaTypes && - bid.mediaTypes.video && - bid.mediaTypes.video.context === ADPOD - ); -} - -function hasOmidSupport(bid) { - let hasOmid = false; - const bidderParams = bid.params; - const videoParams = bid.params.video; - if (bidderParams.frameworks && isArray(bidderParams.frameworks)) { - hasOmid = includes(bid.params.frameworks, 6); - } - if (!hasOmid && videoParams && videoParams.frameworks && isArray(videoParams.frameworks)) { - hasOmid = includes(bid.params.video.frameworks, 6); - } - return hasOmid; -} + // Set appInfo + requestData.appInfo.id = bidderRequest?.ortb2?.site?.domain || bidderRequest.refererInfo?.page; -/** - * Expand an adpod placement into a set of request objects according to the - * total adpod duration and the range of duration seconds. Sets minduration/ - * maxduration video property according to requireExactDuration configuration - */ -function createAdPodRequest(tags, adPodBid) { - const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; + // Set userInfo + requestData.userInfo.ip = bidderRequest?.ortb2?.device?.ip || navigator.ip; + requestData.userInfo.ua = bidderRequest?.ortb2?.device?.ua || navigator.userAgent; - const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); - const maxDuration = Math.max(...durationRangeSec); - - const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); - let request = fill(...tagToDuplicate, numberOfPlacements); - - if (requireExactDuration) { - const divider = Math.ceil(numberOfPlacements / durationRangeSec.length); - const chunked = chunk(request, divider); - - // each configured duration is set as min/maxduration for a subset of requests - durationRangeSec.forEach((duration, index) => { - chunked[index].map(tag => { - setVideoProperty(tag, 'minduration', duration); - setVideoProperty(tag, 'maxduration', duration); + // Set userInfo.ppid + requestData.userInfo.ppid = (validBidRequests || []).reduce((ppids, validBidRequest) => { + const extractedPpids = []; + (validBidRequest.userIdAsEids || []).forEach((eid) => { + (eid?.uids || []).forEach(uid => { + if (uid?.ext?.stype === 'ppuid') { + const isExistingInExtracted = !!extractedPpids.find(id => id.source === eid.source); + const isExistingInPpids = !!ppids.find(id => id.source === eid.source); + if (!isExistingInExtracted && !isExistingInPpids) extractedPpids.push({source: eid.source, id: uid.id}); + } }); - }); + }) + return [...ppids, ...extractedPpids]; + }, []); + + // Set userInfo.ifa + if (bidderRequest.ortb2?.device?.ifa) { + requestData.userInfo.ifa = bidderRequest.ortb2.device.ifa; } else { - // all maxdurations should be the same - request.map(tag => setVideoProperty(tag, 'maxduration', maxDuration)); + requestData.userInfo.ifa = validBidRequests.find(validBidRequest => { + return !!validBidRequest.ortb2?.device?.ifa; + }); } - return request; -} - -function getAdPodPlacementNumber(videoParams) { - const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; - const minAllowedDuration = Math.min(...durationRangeSec); - const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); - - return requireExactDuration - ? Math.max(numberOfPlacements, durationRangeSec.length) - : numberOfPlacements; -} - -function setVideoProperty(tag, key, value) { - if (isEmpty(tag.video)) { tag.video = {}; } - tag.video[key] = value; -} - -function getRtbBid(tag) { - return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); -} - -function buildNativeRequest(params) { - const request = {}; - - // map standard prebid native asset identifier to /ut parameters - // e.g., tag specifies `body` but /ut only knows `description`. - // mapping may be in form {tag: ''} or - // {tag: {serverName: '', requiredParams: {...}}} - Object.keys(params).forEach(key => { - // check if one of the forms is used, otherwise - // a mapping wasn't specified so pass the key straight through - const requestKey = - (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) || - NATIVE_MAPPING[key] || - key; - - // required params are always passed on request - const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; - request[requestKey] = Object.assign({}, requiredParams, params[key]); - - // convert the sizes of image/icon assets to proper format (if needed) - const isImageAsset = !!(requestKey === NATIVE_MAPPING.image.serverName || requestKey === NATIVE_MAPPING.icon.serverName); - if (isImageAsset && request[requestKey].sizes) { - let sizes = request[requestKey].sizes; - if (isArrayOfNums(sizes) || (isArray(sizes) && sizes.length > 0 && sizes.every(sz => isArrayOfNums(sz)))) { - request[requestKey].sizes = transformSizes(request[requestKey].sizes); + // Set slots + requestData.slots = validBidRequests.map((validBidRequest) => { + const slot = { + id: validBidRequest.params?.slotId, + sizes: [ + ...(validBidRequest.sizes || []), + ...(validBidRequest.mediaTypes?.[VIDEO]?.sizes ? validBidRequest.mediaTypes[VIDEO].sizes : []) + ], + targetings: { + ...validBidRequest?.params?.customTargeting, + ...convertToCustomSlotTargeting(validBidRequest) } - } - - if (requestKey === NATIVE_MAPPING.privacyLink) { - request.privacy_supported = true; - } + }; + return slot; }); - return request; -} + // Set targetings + requestData.targetings = convertToCustomTargeting(bidderRequest); -/** - * This function hides google div container for outstream bids to remove unwanted space on page. Appnexus renderer creates a new iframe outside of google iframe to render the outstream creative. - * @param {string} elementId element id - */ -function hidedfpContainer(elementId) { - var el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); - if (el[0]) { - el[0].style.setProperty('display', 'none'); - } + return requestData; } -function hideSASIframe(elementId) { - try { - // find script tag with id 'sas_script'. This ensures it only works if you're using Smart Ad Server. - const el = document.getElementById(elementId).querySelectorAll("script[id^='sas_script']"); - if (el[0].nextSibling && el[0].nextSibling.localName === 'iframe') { - el[0].nextSibling.style.setProperty('display', 'none'); - } - } catch (e) { - // element not found! +const getRendererForBid = (bidRequest, creative) => { + if (!bidRequest.renderer && creative.contextType === 'video_outstream') { + if (!creative.vastUrl && !creative.vastXml) return undefined; + + const config = { documentResolver: (_, sourceDocument, renderDocument) => renderDocument ?? sourceDocument }; + const renderer = Renderer.install({id: bidRequest.bidId, url: RENDERER_OPTIONS.OUTSTREAM_GP.URL, adUnitCode: bidRequest.adUnitCode, config}); + + renderer.setRender((bid, doc) => { + bid.renderer.push(() => { + if (doc.defaultView?.GoldPlayer) { + const options = { + vastUrl: creative.vastUrl, + vastXML: creative.vastXml, + autoplay: false, + muted: false, + controls: true, + styling: { progressbarColor: '#000' }, + videoHeight: Math.min(doc.defaultView?.innerWidth / 16 * 9, RENDERER_OPTIONS.OUTSTREAM_GP.MIN_HEIGHT), + videoVerticalHeight: Math.min(doc.defaultView?.innerWidth / 9 * 16, RENDERER_OPTIONS.OUTSTREAM_GP.MIN_WIDTH), + }; + const GP = doc.defaultView.GoldPlayer; + const player = new GP(options); + player.play(); + } + }); + }); + + return renderer; } + return undefined; } -function outstreamRender(bid) { - hidedfpContainer(bid.adUnitCode); - hideSASIframe(bid.adUnitCode); - // push to render queue because ANOutstreamVideo may not be loaded yet - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - tagId: bid.adResponse.tag_id, - sizes: [bid.getSize().split('x')], - targetId: bid.adUnitCode, // target div id to render video - uuid: bid.adResponse.uuid, - adResponse: bid.adResponse, - rendererOptions: bid.renderer.getConfig() - }, handleOutstreamRendererEvents.bind(null, bid)); - }); +const getNativeAssetsForBid = (bidRequest, creative) => { + if (creative.contextType === 'native' && creative.ad) { + const nativeAssets = JSON.parse(creative.ad); + const result = { + clickUrl: encodeURI(nativeAssets?.link?.url), + impressionTrackers: nativeAssets?.imptrackers, + clickTrackers: nativeAssets?.clicktrackers, + javascriptTrackers: nativeAssets?.jstracker && [nativeAssets.jstracker], + }; + (nativeAssets?.assets || []).forEach(asset => { + switch (asset.id) { + case OPENRTB.NATIVE.ASSET_ID.TITLE: + result.title = asset.title?.text; + break; + case OPENRTB.NATIVE.ASSET_ID.IMAGE: + result.image = { + url: encodeURI(asset.img?.url), + width: asset.img?.w, + height: asset.img?.h + }; + break; + case OPENRTB.NATIVE.ASSET_ID.ICON: + result.icon = { + url: encodeURI(asset.img.url), + width: asset.img?.w, + height: asset.img?.h + }; + break; + case OPENRTB.NATIVE.ASSET_ID.BODY: + result.body = asset.data?.value; + break; + case OPENRTB.NATIVE.ASSET_ID.SPONSORED: + result.sponsoredBy = asset.data?.value; + break; + case OPENRTB.NATIVE.ASSET_ID.CTA: + result.cta = asset.data?.value; + break; + } + }); + return result; + } + return undefined; } -function handleOutstreamRendererEvents(bid, id, eventName) { - bid.renderer.handleVideoEvent({ id, eventName }); -} +const convertProprietaryResponseToBidResponses = (serverResponse, bidRequest) => { + const bidRequests = bidRequest?.bidderRequest?.bids || []; + const creativeGroups = serverResponse?.body?.creatives || {}; -function parseMediaType(rtbBid) { - const adType = rtbBid.ad_type; - if (adType === VIDEO) { - return VIDEO; - } else if (adType === NATIVE) { - return NATIVE; - } else { - return BANNER; - } + return bidRequests.reduce((bidResponses, bidRequest) => { + const matchingCreativeGroup = (creativeGroups[bidRequest.params?.slotId] || []).filter((creative) => { + if (bidRequest.mediaTypes?.[BANNER] && creative.mediaType === BANNER) return true; + if (bidRequest.mediaTypes?.[VIDEO] && creative.mediaType === VIDEO) return true; + if (bidRequest.mediaTypes?.[NATIVE] && creative.mediaType === NATIVE) return true; + return false; + }); + const matchingBidResponses = matchingCreativeGroup.map((creative) => { + return { + requestId: bidRequest.bidId, + cpm: creative.cpm, + currency: creative.currency, + width: creative.width, + height: creative.height, + creativeId: creative.creativeId, + dealId: creative.dealId, + netRevenue: creative.netRevenue, + ttl: creative.ttl, + ad: creative.ad, + vastUrl: creative.vastUrl, + vastXml: creative.vastXml, + mediaType: creative.mediaType, + meta: creative.meta, + native: getNativeAssetsForBid(bidRequest, creative), + renderer: getRendererForBid(bidRequest, creative), + }; + }); + return [...bidResponses, ...matchingBidResponses]; + }, []); } -function addUserId(eids, id, source, rti) { - if (id) { - if (rti) { - eids.push({ source, id, rti_partner: rti }); - } else { - eids.push({ source, id }); - } - } - return eids; +/* Logging */ +const sendLog = (data, percentage = 0.0001) => { + if (Math.random() > percentage) return; + const encodedData = `data=${window.btoa(JSON.stringify({...data, source: 'goldbach_pbjs', projectedAmount: (1 / percentage)}))}`; + ajax(LOGGING_URL, null, encodedData, { + withCredentials: false, + method: 'POST', + crossOrigin: true, + contentType: 'application/x-www-form-urlencoded', + }); } -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return (bid.params.reserve) ? bid.params.reserve : null; - } - - let floor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid: function (bid) { + return typeof bid.params.publisherId === 'string' && Array.isArray(bid.sizes); + }, + buildRequests: function (validBidRequests, bidderRequest) { + const url = IS_LOCAL_MODE ? URL_LOCAL : URL; + const data = convertToProprietaryData(validBidRequests, bidderRequest); + return [{ + method: 'POST', + url: url, + data: data, + bidderRequest: bidderRequest, + options: { + withCredentials: false, + contentType: 'application/json', + } + }]; + }, + interpretResponse: function (serverResponse, request) { + return convertProprietaryResponseToBidResponses(serverResponse, request); + }, + onTimeout: function(timeoutData) { + const payload = { + event: EVENTS.TIMEOUT, + error: timeoutData, + }; + sendLog(payload, LOGGING_PERCENTAGE_ERROR); + }, + onBidWon: function(bid) { + const payload = { + event: EVENTS.BID_WON, + adUnitCode: bid.adUnitCode, + adId: bid.adId, + mediaType: bid.mediaType, + size: bid.size, + }; + sendLog(payload, LOGGING_PERCENTAGE_REGULAR); + }, + onSetTargeting: function(bid) { + const payload = { + event: EVENTS.TARGETING, + adUnitCode: bid.adUnitCode, + adId: bid.adId, + mediaType: bid.mediaType, + size: bid.size, + }; + sendLog(payload, LOGGING_PERCENTAGE_REGULAR); + }, + onBidderError: function({ error }) { + const payload = { + event: EVENTS.ERROR, + error: error, + }; + sendLog(payload, LOGGING_PERCENTAGE_ERROR); + }, + onAdRenderSucceeded: function(bid) { + const payload = { + event: EVENTS.RENDER, + adUnitCode: bid.adUnitCode, + adId: bid.adId, + mediaType: bid.mediaType, + size: bid.size, + }; + sendLog(payload, LOGGING_PERCENTAGE_REGULAR); + }, } registerBidder(spec); diff --git a/modules/goldbachBidAdapter.md b/modules/goldbachBidAdapter.md index f7c9479439b..7335c95f77f 100644 --- a/modules/goldbachBidAdapter.md +++ b/modules/goldbachBidAdapter.md @@ -1,151 +1,89 @@ -#Overview +# Goldbach Bidder Adapter -``` -Module Name: Goldbach Bid Adapter -Module Type: Bidder Adapter -Maintainer: dusan.veljovic@goldbach.com -``` +## Overview -# Description +```text +Module Name: Goldbach Bidder Adapter +Module Type: Bidder Adapter +Maintainer: benjamin.brachmann@goldbach.com +``` -Connects to Xandr exchange for bids. +## Description -Goldbach bid adapter supports Banner, Video (instream and outstream) and Native. +Module that connects to Goldbach SSP demand sources. -# Test Parameters +```shell +gulp build --modules=goldbachBidAdapter,userId,pubProvidedIdSystem ``` -var adUnits = [ - // Banner adUnit - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [{ - bidder: 'goldbach', - params: { - placementId: 13144370 - } - }] - }, - // Native adUnit - { - code: 'native-div', - sizes: [[1, 1]], - mediaTypes: { - native: { - title: { - required: true - }, - body: { - required: true - }, - image: { - required: true - }, - sponsoredBy: { - required: true - }, - icon: { - required: false - } - } - }, - bids: [{ - bidder: 'goldbach', - params: { - placementId: 13232354, - allowSmallerSizes: true - } - }] - }, - // Video instream adUnit - { - code: 'video-instream', - sizes: [[640, 480]], - mediaTypes: { - video: { - playerSize: [[640, 480]], - context: 'instream' + +## Test Parameters + +```javascript + var adUnits = [ + { + code: 'au-1', + mediaTypes: { + video: { + sizes: [[640, 480]], + maxduration: 30, + } + }, + bids: [ + { + bidder: 'goldbach', + params: { + publisherId: 'goldbach_debug', + slotId: '/1235/example.com/video/video/example' + } + } + ] }, - }, - bids: [{ - goldbach: 'goldbach', - params: { - placementId: 13232361, - video: { - skippable: true, - playback_methods: ['auto_play_sound_off'] - } - } - }] - }, - // Video outstream adUnit - { - code: 'video-outstream', - sizes: [[300, 250]], - mediaTypes: { - video: { - playerSize: [[300, 250]], - context: 'outstream', - // Certain ORTB 2.5 video values can be read from the mediatypes object; below are examples of supported params. - // To note - goldbach supports additional values for our system that are not part of the ORTB spec. If you want - // to use these values, they will have to be declared in the bids[].params.video object instead using the goldbach syntax. - // Between the corresponding values of the mediaTypes.video and params.video objects, the properties in params.video will - // take precedence if declared; eg in the example below, the `skippable: true` setting will be used instead of the `skip: 0`. - minduration: 1, - maxduration: 60, - skip: 0, // 1 - true, 0 - false - skipafter: 5, - playbackmethod: [2], // note - we only support options 1-4 at this time - api: [1,2,3] // note - option 6 is not supported at this time - } - }, - bids: [ - { - bidder: 'goldbach', - params: { - placementId: 13232385, - video: { - skippable: true, - playback_method: 'auto_play_sound_off' - } - } - } - ] - }, - // Banner adUnit in a App Webview - // Only use this for situations where prebid.js is in a webview of an App - // See Prebid Mobile for displaying ads via an SDK - { - code: 'banner-div', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - } - bids: [{ - bidder: 'goldbach', - params: { - placementId: 13144370, - app: { - id: "B1O2W3M4AN.com.prebid.webview", - geo: { - lat: 40.0964439, - lng: -75.3009142 - }, - device_id: { - idfa: "4D12078D-3246-4DA4-AD5E-7610481E7AE", // Apple advertising identifier - aaid: "38400000-8cf0-11bd-b23e-10b96e40000d", // Android advertising identifier - md5udid: "5756ae9022b2ea1e47d84fead75220c8", // MD5 hash of the ANDROID_ID - sha1udid: "4DFAA92388699AC6539885AEF1719293879985BF", // SHA1 hash of the ANDROID_ID - windowsadid: "750c6be243f1c4b5c9912b95a5742fc5" // Windows advertising identifier - } - } - } - }] - } -]; + { + code: 'au-2', + sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true, + len: 50 + }, + image: { + required: true, + sizes: [300, 157] + }, + icon: { + required: true, + sizes: [30, 30] + } + } + }, + bids: [ + { + bidder: 'goldbach', + params: { + publisherId: 'goldbach_debug', + slotId: '/1235/example.com/inside-full-test-native/example' + } + } + ] + }, + { + code: 'au-3', + sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'goldbach', + params: { + publisherId: 'goldbach_debug', + slotId: '/1235/example.com/inside-full-test-banner/example' + } + } + ] + }, + ]; ``` diff --git a/modules/goldfishAdsRtdProvider.md b/modules/goldfishAdsRtdProvider.md index 4625c9a7988..9a6fd939bd1 100755 --- a/modules/goldfishAdsRtdProvider.md +++ b/modules/goldfishAdsRtdProvider.md @@ -8,7 +8,7 @@ ## Description -This RTD module provides access to the Goldfish Ads Geograph, which leverages geographic and temporal data on a privcay-first platform. This module works without using cookies, PII, emails, or device IDs across all website traffic, including unauthenticated users, and adds audience data into bid requests to increase scale and yields. +This RTD module provides access to the Goldfish Ads Geograph, which leverages geographic and temporal data on a privacy-first platform. This module works without using cookies, PII, emails, or device IDs across all website traffic, including unauthenticated users, and adds audience data into bid requests to increase scale and yields. ## Usage diff --git a/modules/greenbidsBidAdapter.js b/modules/greenbidsBidAdapter.js new file mode 100644 index 00000000000..418cb850527 --- /dev/null +++ b/modules/greenbidsBidAdapter.js @@ -0,0 +1,238 @@ +import { getValue, logError, deepAccess, parseSizesInput, getBidIdParameter, logInfo } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { getHLen } from '../libraries/navigatorData/navigatorData.js'; +import { getTimeToFirstByte } from '../libraries/timeToFirstBytesUtils/timeToFirstBytesUtils.js'; +import { getReferrerInfo, getPageTitle, getPageDescription, getConnectionDownLink } from '../libraries/pageInfosUtils/pageInfosUtils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + +const BIDDER_CODE = 'greenbids'; +const GVL_ID = 1232; +const ENDPOINT_URL = 'https://hb.greenbids.ai'; +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVL_ID, + supportedMediaTypes: ['banner', 'video'], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + if (typeof bid.params !== 'undefined' && parseInt(getValue(bid.params, 'placementId')) > 0) { + logInfo('Greenbids bidder adapter valid bid request'); + return true; + } else { + logError('Greenbids bidder adapter requires placementId to be defined and a positive number'); + return false; + } + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} validBidRequests array of bids + * @param bidderRequest bidder request object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const bids = validBidRequests.map(bids => { + const reqObj = {}; + let placementId = getValue(bids.params, 'placementId'); + const gpid = deepAccess(bids, 'ortb2Imp.ext.gpid'); + reqObj.sizes = getSizes(bids); + reqObj.bidId = getBidIdParameter('bidId', bids); + reqObj.bidderRequestId = getBidIdParameter('bidderRequestId', bids); + reqObj.placementId = parseInt(placementId, 10); + reqObj.adUnitCode = getBidIdParameter('adUnitCode', bids); + reqObj.transactionId = bids.ortb2Imp?.ext?.tid || ''; + if (gpid) { reqObj.gpid = gpid; } + }); + const topWindow = window.top; + + const payload = { + referrer: getReferrerInfo(bidderRequest), + pageReferrer: document.referrer, + pageTitle: getPageTitle().slice(0, 300), + pageDescription: getPageDescription().slice(0, 300), + networkBandwidth: getConnectionDownLink(window.navigator), + timeToFirstByte: getTimeToFirstByte(window), + data: bids, + device: bidderRequest?.ortb2?.device || {}, + deviceWidth: screen.width, + deviceHeight: screen.height, + devicePixelRatio: topWindow.devicePixelRatio, + screenOrientation: screen.orientation?.type, + historyLength: getHLen(), + viewportHeight: topWindow.visualViewport?.height, + viewportWidth: topWindow.visualViewport?.width, + prebid_version: '$prebid.version$', + }; + + const firstBidRequest = validBidRequests[0]; + + if (firstBidRequest.schain) { + payload.schain = firstBidRequest.schain; + } + + hydratePayloadWithGppConsentData(payload, bidderRequest.gppConsent); + hydratePayloadWithGdprConsentData(payload, bidderRequest.gdprConsent); + hydratePayloadWithUspConsentData(payload, bidderRequest.uspConsent); + + const userAgentClientHints = deepAccess(firstBidRequest, 'ortb2.device.sua'); + if (userAgentClientHints) { + payload.userAgentClientHints = userAgentClientHints; + } + + const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); + if (dsa) { + payload.dsa = dsa; + } + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL, + data: payloadString, + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server response. + */ + interpretResponse: function (serverResponse) { + serverResponse = serverResponse.body; + if (!serverResponse.responses) { + return []; + } + return serverResponse.responses.map((bid) => { + const bidResponse = { + cpm: bid.cpm, + width: bid.width, + height: bid.height, + currency: bid.currency, + netRevenue: true, + size: bid.size, + ttl: bid.ttl, + meta: { + advertiserDomains: bid && bid.adomain ? bid.adomain : [], + }, + ad: bid.ad, + requestId: bid.bidId, + creativeId: bid.creativeId, + placementId: bid.placementId, + }; + if (bid.dealId) { + bidResponse.dealId = bid.dealId + } + if (bid?.ext?.dsa) { + bidResponse.meta.dsa = bid.ext.dsa; + } + return bidResponse; + }); + } +}; + +registerBidder(spec); + +/** + * Converts the sizes from the bid object to the required format. + * + * @param {Object} bid - The bid object containing size information. + * @param {Array} bid.sizes - The sizes array from the bid object. + * @returns {Array} - The parsed sizes in the required format. + */ +function getSizes(bid) { + return parseSizesInput(bid.sizes); +} + +// Privacy handling + +/** + * Hydrates the given payload with GPP consent data if available. + * + * @param {Object} payload - The payload object to be hydrated. + * @param {Object} gppData - The GPP consent data object. + * @param {string} gppData.gppString - The GPP consent string. + * @param {number[]} gppData.applicableSections - An array of applicable section IDs. + */ +function hydratePayloadWithGppConsentData(payload, gppData) { + if (!gppData) { return; } + let isValidConsentString = typeof gppData.gppString === 'string'; + let validateApplicableSections = + Array.isArray(gppData.applicableSections) && + gppData.applicableSections.every((section) => typeof (section) === 'number') + payload.gpp = { + consentString: isValidConsentString ? gppData.gppString : '', + applicableSectionIds: validateApplicableSections ? gppData.applicableSections : [], + }; +} + +/** + * Hydrates the given payload with GDPR consent data if available. + * + * @param {Object} payload - The payload object to be hydrated with GDPR consent data. + * @param {Object} gdprData - The GDPR data object containing consent information. + * @param {boolean} gdprData.gdprApplies - Indicates if GDPR applies. + * @param {string} gdprData.consentString - The GDPR consent string. + * @param {number} gdprData.apiVersion - The version of the GDPR API being used. + * @param {Object} gdprData.vendorData - Additional vendor data related to GDPR. + */ +function hydratePayloadWithGdprConsentData(payload, gdprData) { + if (!gdprData) { return; } + let isCmp = typeof gdprData.gdprApplies === 'boolean'; + let isConsentString = typeof gdprData.consentString === 'string'; + let status = isCmp + ? findGdprStatus(gdprData.gdprApplies, gdprData.vendorData) + : gdprStatus.CMP_NOT_FOUND_OR_ERROR; + payload.gdpr_iab = { + consent: isConsentString ? gdprData.consentString : '', + status: status, + apiVersion: gdprData.apiVersion + }; +} + +/** + * Adds USP (CCPA) consent data to the payload if available. + * + * @param {Object} payload - The payload object to be hydrated with USP consent data. + * @param {string} uspConsentData - The USP consent string to be added to the payload. + */ +function hydratePayloadWithUspConsentData(payload, uspConsentData) { + if (!uspConsentData) { return; } + payload.us_privacy = uspConsentData; +} + +const gdprStatus = { + GDPR_APPLIES_PUBLISHER: 12, + GDPR_APPLIES_GLOBAL: 11, + GDPR_DOESNT_APPLY: 0, + CMP_NOT_FOUND_OR_ERROR: 22 +}; + +/** + * Determines the GDPR status based on whether GDPR applies and the provided GDPR data. + * + * @param {boolean} gdprApplies - Indicates if GDPR applies. + * @param {Object} gdprData - The GDPR data object. + * @param {boolean} gdprData.isServiceSpecific - Indicates if the GDPR data is service-specific. + * @returns {string} The GDPR status. + */ +function findGdprStatus(gdprApplies, gdprData) { + let status = gdprStatus.GDPR_APPLIES_PUBLISHER; + if (gdprApplies) { + if (gdprData && !gdprData.isServiceSpecific) { + status = gdprStatus.GDPR_APPLIES_GLOBAL; + } + } else { + status = gdprStatus.GDPR_DOESNT_APPLY; + } + return status; +} diff --git a/modules/greenbidsBidAdapter.md b/modules/greenbidsBidAdapter.md new file mode 100644 index 00000000000..df536294f47 --- /dev/null +++ b/modules/greenbidsBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +**Module Name**: Greenbids Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: tech@greenbids.ai + +# Description + +Use `greenbids` as bidder. + +## AdUnits configuration example +``` + var adUnits = [{ + code: 'your-slot_1-div', //use exactly the same code as your slot div id. + sizes: [[300, 250]], + bids: [{ + bidder: 'greenbids', + params: { + placementId: 12345, + } + }] + },{ + code: 'your-slot_2-div', //use exactly the same code as your slot div id. + sizes: [[600, 800]], + bids: [{ + bidder: 'greenbids', + params: { + placementId: 12345, + } + }] + }]; +``` diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js index 78681c2beda..46573a81c15 100644 --- a/modules/imRtdProvider.js +++ b/modules/imRtdProvider.js @@ -109,6 +109,7 @@ export function setRealTimeData(bidConfig, moduleConfig, data) { const segments = getSegments(data.im_segments, moduleConfig); const ortb2 = bidConfig.ortb2Fragments?.global || {}; deepSetValue(ortb2, 'user.ext.data.im_segments', segments); + deepSetValue(ortb2, 'user.ext.data.im_uid', data.im_uid); if (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues')) { window.googletag = window.googletag || {cmd: []}; @@ -145,6 +146,7 @@ export function getRealTimeData(reqBidsConfigObj, onDone, moduleConfig) { onDone(); return; } + const uid = storage.getDataFromLocalStorage(imUidLocalName); const sids = storage.getDataFromLocalStorage(imRtdLocalName); const parsedSids = sids ? sids.split(',') : []; const mt = storage.getDataFromLocalStorage(`${imRtdLocalName}_mt`); @@ -163,7 +165,7 @@ export function getRealTimeData(reqBidsConfigObj, onDone, moduleConfig) { } if (sids !== null) { - setRealTimeData(reqBidsConfigObj, moduleConfig, {im_segments: parsedSids}); + setRealTimeData(reqBidsConfigObj, moduleConfig, {im_uid: uid, im_segments: parsedSids}); onDone(); alreadyDone = true; } @@ -210,7 +212,7 @@ export function getApiCallback(reqBidsConfigObj, onDone, moduleConfig) { } if (parsedResponse.segments) { - setRealTimeData(reqBidsConfigObj, moduleConfig, {im_segments: parsedResponse.segments}); + setRealTimeData(reqBidsConfigObj, moduleConfig, {im_uid: parsedResponse.uid, im_segments: parsedResponse.segments}); storage.setDataInLocalStorage(imRtdLocalName, parsedResponse.segments); storage.setDataInLocalStorage(`${imRtdLocalName}_mt`, new Date(timestamp()).toUTCString()); } diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 33ba08e8fd2..a0347382dd1 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -6,14 +6,6 @@ import {Renderer} from '../src/Renderer.js'; import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {convertCurrency} from '../libraries/currencyUtils/currency.js'; -/** - * See https://github.com/prebid/Prebid.js/pull/8827 for details on linting exception - * ImproveDigital only imports after winning a bid and only if the creative cannot reach top - * Also see https://github.com/prebid/Prebid.js/issues/11656 - */ -// eslint-disable-next-line no-restricted-imports -import {loadExternalScript} from '../src/adloader.js'; -import { MODULE_TYPE_BIDDER } from '../src/activities/modules.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -225,7 +217,6 @@ export const CONVERTER = ortbConverter({ renderer: ID_OUTSTREAM.createRenderer(bidRequest) }) } - ID_RAZR.forwardBid({bidRequest, bid: bidResponse}); return bidResponse; }, overrides: { @@ -367,61 +358,3 @@ const ID_OUTSTREAM = { bid.renderer.handleVideoEvent({ id, eventName }); }, }; - -const ID_RAZR = { - RENDERER_URL: 'https://cdn.360yield.com/razr/tag.js', - - forwardBid({bidRequest, bid}) { - if (bid.mediaType !== BANNER) { - return; - } - - const cfg = { - prebid: { - bidRequest, - bid - } - }; - - const cfgStr = JSON.stringify(cfg).replace(/<\/script>/ig, '\\x3C/script>'); - const s = ``; - // prepend RAZR config to ad markup: - bid.ad = s + bid.ad; - - this.installListener(); - }, - - installListener() { - if (this._listenerInstalled) { - return; - } - - window.addEventListener('message', function(e) { - const data = e.data?.razr?.load; - if (!data) { - return; - } - - if (e.source) { - data.source = e.source; - if (data.id) { - e.source.postMessage({ - razr: { - id: data.id - } - }, '*'); - } - } - - const ns = window.razr = window.razr || {}; - ns.q = ns.q || []; - ns.q.push(data); - - if (!ns.loaded) { - loadExternalScript(ID_RAZR.RENDERER_URL, MODULE_TYPE_BIDDER, BIDDER_CODE); - } - }); - - this._listenerInstalled = true; - } -}; diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js index 1cf270117b7..e3b5625dc7b 100644 --- a/modules/intentIqAnalyticsAdapter.js +++ b/modules/intentIqAnalyticsAdapter.js @@ -304,7 +304,7 @@ function constructFullUrl(data) { '&jsver=' + VERSION + '&source=pbjs' + '&payload=' + JSON.stringify(report) + - '&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints + + '&uh=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints) + (gppData.gppString ? '&gpp=' + encodeURIComponent(gppData.gppString) : ''); url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName); diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 0ec60b51ca2..a96b0da132b 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -496,6 +496,11 @@ function parseBid(rawBid, currency, bidRequest) { if (rawBid.ext?.dsa) { bid.meta.dsa = rawBid.ext.dsa } + + if (rawBid.ext?.ibv) { + bid.ext = bid.ext || {} + bid.ext.ibv = rawBid.ext.ibv + } return bid; } @@ -751,8 +756,9 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { method: 'POST', url: exchangeUrl, data: deepClone(r), - option: { + options: { contentType: 'text/plain', + withCredentials: true }, validBidRequests }); diff --git a/modules/jwplayerVideoProvider.js b/modules/jwplayerVideoProvider.js index 54de1949e6f..e2b194057fd 100644 --- a/modules/jwplayerVideoProvider.js +++ b/modules/jwplayerVideoProvider.js @@ -50,6 +50,8 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba VIDEO_MIME_TYPE.AAC, VIDEO_MIME_TYPE.HLS ]; + let height = null; + let width = null; function init() { if (!jwplayer) { @@ -92,6 +94,20 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba const adConfig = config.advertising || {}; supportedMediaTypes = supportedMediaTypes || utils.getSupportedMediaTypes(MEDIA_TYPES); + if (height === null) { + height = utils.getPlayerHeight(player, config); + } + + if (width === null) { + width = utils.getPlayerWidth(player, config); + } + + if (config.aspectratio && !height && !width) { + const size = utils.getPlayerSizeFromAspectRatio(player, config); + height = size.height; + width = size.width; + } + const video = { mimes: supportedMediaTypes, protocols: [ @@ -102,8 +118,8 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba PROTOCOLS.VAST_3_0_WRAPPER, PROTOCOLS.VAST_4_0_WRAPPER ], - h: player.getHeight(), // TODO does player call need optimization ? - w: player.getWidth(), // TODO does player call need optimization ? + h: height, + w: width, startdelay: utils.getStartDelay(), placement: utils.getPlacement(adConfig, player), // linearity is omitted because both forms are supported. @@ -414,10 +430,14 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba break; case PLAYER_RESIZE: - getEventPayload = e => ({ - height: e.height, - width: e.width, - }); + getEventPayload = e => { + height = e.height; + width = e.width; + return { + height, + width + }; + }; break; case VIEWABLE: @@ -585,6 +605,79 @@ export const utils = { return jwConfig; }, + getPlayerHeight: function(player, config) { + let height; + + if (player.getHeight) { + height = player.getHeight(); + } + + // Height is undefined when player has not yet rendered + if (height !== undefined) { + return height; + } + + return config.height; + }, + + getPlayerWidth: function(player, config) { + let width; + + if (player.getWidth) { + width = player.getWidth(); + } + + // Width is undefined when player has not yet rendered + if (width !== undefined) { + return width; + } + + // Width can be a string when aspectratio is set + if (typeof config.width === 'number') { + return config.width; + } + }, + + getPlayerSizeFromAspectRatio: function(player, config) { + const aspectRatio = config.aspectratio; + let percentageWidth = config.width; + + if (typeof aspectRatio !== 'string' || typeof percentageWidth !== 'string') { + return {}; + } + + const ratios = aspectRatio.split(':'); + + if (ratios.length !== 2) { + return {}; + } + + const containerElement = player.getContainer && player.getContainer(); + if (!containerElement) { + return {}; + } + + const containerWidth = containerElement.clientWidth; + const containerHeight = containerElement.clientHeight; + + const xRatio = parseInt(ratios[0], 10); + const yRatio = parseInt(ratios[1], 10); + + if (isNaN(xRatio) || isNaN(yRatio)) { + return {}; + } + + const numericWidthPercentage = parseInt(percentageWidth, 10); + + const desiredWidth = containerWidth * numericWidthPercentage / 100; + const desiredHeight = Math.min(desiredWidth * yRatio / xRatio, containerHeight); + + return { + height: desiredHeight, + width: desiredWidth + }; + }, + getJwEvent: function(eventName) { switch (eventName) { case SETUP_COMPLETE: diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 54e70686fcc..a5c14e7bce1 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -1,5 +1,6 @@ import { deepAccess, + generateUUID, getWindowSelf, isArray, isStr, @@ -7,10 +8,24 @@ import { replaceAuctionPrice, triggerPixel } from '../src/utils.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {getRefererInfo} from '../src/refererDetection.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; + +const additionalData = new WeakMap(); + +export const pageViewId = generateUUID(); + +export function setAdditionalData(obj, key, value) { + const prevValue = additionalData.get(obj) || {}; + additionalData.set(obj, { ...prevValue, [key]: value }); +} + +export function getAdditionalData(obj, key) { + const data = additionalData.get(obj) || {}; + return data[key]; +} const BIDDER_CODE = 'kobler'; const BIDDER_ENDPOINT = 'https://bid.essrtb.com/bid/prebid_rtb_call'; @@ -36,17 +51,18 @@ export const buildRequests = function (validBidRequests, bidderRequest) { data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest), options: { contentType: 'application/json' - } + }, + bidderRequest }; }; -export const interpretResponse = function (serverResponse) { +export const interpretResponse = function (serverResponse, request) { const res = serverResponse.body; const bids = [] if (res) { res.seatbid.forEach(sb => { sb.bid.forEach(b => { - bids.push({ + const bid = { requestId: b.impid, cpm: b.price, currency: res.cur, @@ -61,20 +77,24 @@ export const interpretResponse = function (serverResponse) { meta: { advertiserDomains: b.adomain } - }) + } + setAdditionalData(bid, 'adServerCurrency', getCurrencyFromBidderRequest(request.bidderRequest)); + bids.push(bid); }) }); } + return bids; }; export const onBidWon = function (bid) { + const adServerCurrency = getAdditionalData(bid, 'adServerCurrency'); // We intentionally use the price set by the publisher to replace the ${AUCTION_PRICE} macro // instead of the `originalCpm` here. This notification is not used for billing, only for extra logging. const publisherPrice = bid.cpm || 0; - const publisherCurrency = bid.currency || config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; + const publisherCurrency = bid.currency || adServerCurrency || SUPPORTED_CURRENCY; const adServerPrice = deepAccess(bid, 'adserverTargeting.hb_pb', 0); - const adServerPriceCurrency = config.getConfig('currency.adServerCurrency') || SUPPORTED_CURRENCY; + const adServerPriceCurrency = adServerCurrency || SUPPORTED_CURRENCY; if (isStr(bid.nurl) && bid.nurl !== '') { const winNotificationUrl = replaceAuctionPrice(bid.nurl, publisherPrice) .replace(/\${AUCTION_PRICE_CURRENCY}/g, publisherCurrency) @@ -152,7 +172,8 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { ext: { kobler: { tcf_purpose_2_given: purpose2Given, - tcf_purpose_3_given: purpose3Given + tcf_purpose_3_given: purpose3Given, + page_view_id: pageViewId } } }; diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index c0fb17672af..60eced6a490 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -5,7 +5,8 @@ import { createBuildRequestsFn, createInterpretResponseFn, createUserSyncGetter, - isBidRequestValid + isBidRequestValid, + tryParseJSON } from '../libraries/vidazooUtils/bidderUtils.js'; const GVLID = 1165; @@ -14,40 +15,63 @@ const BIDDER_CODE = 'kueezrtb'; const BIDDER_VERSION = '1.0.0'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests: createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, false), + interpretResponse: createInterpretResponseFn(BIDDER_CODE, false), + getUserSyncs: createUserSyncGetter({ + iframeSyncUrl: 'https://sync.kueezrtb.com/api/sync/iframe', + imageSyncUrl: 'https://sync.kueezrtb.com/api/sync/image' + }), + createFirstPartyData, +}; + export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.kueezrtb.com`; } -function createUniqueRequestData(hashUrl, bid) { - const { - auctionId, - transactionId, - } = bid; +export function getAndSetFirstPartyData() { + if (!storage.hasLocalStorage()) { + return; + } + let fdata = tryParseJSON(storage.getDataFromLocalStorage('_iiq_fdata')); + if (!fdata) { + fdata = spec.createFirstPartyData(); + storage.setDataInLocalStorage('_iiq_fdata', JSON.stringify(fdata)); + } + return fdata; +} +export function createFirstPartyData() { return { - auctionId, - transactionId, + pcid: getFirstPartyUUID(), pcidDate: Date.now(), }; } -const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, false); - -const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); - -const getUserSyncs = createUserSyncGetter({ - iframeSyncUrl: 'https://sync.kueezrtb.com/api/sync/iframe', - imageSyncUrl: 'https://sync.kueezrtb.com/api/sync/image' -}); - -export const spec = { - code: BIDDER_CODE, - version: BIDDER_VERSION, - gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs +function getFirstPartyUUID() { + let d = new Date().getTime(); + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); + }); }; +function createUniqueRequestData(hashUrl, bid) { + const {auctionId, transactionId} = bid; + const fdata = getAndSetFirstPartyData(); + return { + auctionId, + transactionId, + ...(fdata && { + iiqpcid: fdata.pcid, + iiqpcidDate: fdata.pcidDate + }) + }; +} + registerBidder(spec); diff --git a/modules/lassoBidAdapter.js b/modules/lassoBidAdapter.js index 6215af03a97..ccee9616044 100644 --- a/modules/lassoBidAdapter.js +++ b/modules/lassoBidAdapter.js @@ -32,6 +32,22 @@ export const spec = { sizes = bidRequest.mediaTypes[BANNER].sizes; } + const { params } = bidRequest; + + let npi = params.npi || ''; + let dgid = params.dgid || ''; + let test = false; + + if (params.testNPI) { + npi = params.testNPI; + test = true; + } + + if (params.testDGID) { + dgid = params.testDGID; + test = true; + } + const payload = { auctionStart: bidderRequest.auctionStart, url: encodeURIComponent(window.location.href), @@ -44,12 +60,16 @@ export const spec = { sizes, aimXR, uid: '$UID', + npi, + dgid, + npi_hash: params.npiHash || '', params: JSON.stringify(bidRequest.params), crumbs: JSON.stringify(bidRequest.crumbs), prebidVersion: '$prebid.version$', version: 4, coppa: config.getConfig('coppa') == true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined + ccpa: bidderRequest.uspConsent || undefined, + test } if ( @@ -128,10 +148,10 @@ function getBidRequestUrl(aimXR, params) { if (params && params.dtc) { path = '/dtc-request'; } - if (!aimXR) { - return GET_IUD_URL + ENDPOINT_URL + path; + if (aimXR || params.npi || params.dgid || params.npiHash || params.testNPI || params.testDGID) { + return ENDPOINT_URL + path; } - return ENDPOINT_URL + path; + return GET_IUD_URL + ENDPOINT_URL + path; } function getDeviceData() { diff --git a/modules/liveIntentRtdProvider.js b/modules/liveIntentRtdProvider.js new file mode 100644 index 00000000000..92cd09ae346 --- /dev/null +++ b/modules/liveIntentRtdProvider.js @@ -0,0 +1,52 @@ +/** + * This module adds the LiveIntent provider to the Real Time Data module (rtdModule). + */ +import { submodule } from '../src/hook.js'; +import {deepAccess, deepSetValue} from '../src/utils.js' + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/rtdModule/index.js').UserConsentData} UserConsentData + */ + +const SUBMODULE_NAME = 'liveintent'; +const GVLID = 148; + +/** + * Init + * @param {Object} config Module configuration + * @param {UserConsentData} userConsent User consent + * @returns true + */ +const init = (config, userConsent) => { + return true; +} + +/** + * onBidRequest is called for each bidder during an auction and contains the bids for that bidder. + * + * @param {Object} bidRequest + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ + +function onBidRequest(bidRequest, config, userConsent) { + bidRequest.bids.forEach(bid => { + const providedSegmentsFromUserId = deepAccess(bid, 'userId.lipb.segments', []) + if (providedSegmentsFromUserId.length > 0) { + const providedSegments = { name: 'liveintent.com', segment: providedSegmentsFromUserId.map(id => ({ id })) } + const existingData = deepAccess(bid, 'ortb2.user.data', []) + deepSetValue(bid, 'ortb2.user.data', existingData.concat(providedSegments)) + } + }) +} + +export const liveIntentRtdSubmodule = { + name: SUBMODULE_NAME, + gvlid: GVLID, + init: init, + onBidRequestEvent: onBidRequest +}; + +submodule('realTimeData', liveIntentRtdSubmodule); diff --git a/modules/liveIntentRtdProvider.md b/modules/liveIntentRtdProvider.md new file mode 100644 index 00000000000..e742fb74bee --- /dev/null +++ b/modules/liveIntentRtdProvider.md @@ -0,0 +1,45 @@ +# Overview + +Module Name: LiveIntent Provider +Module Type: Rtd Provider +Maintainer: product@liveIntent.com + +# Description + +This module extracts segments from `bidRequest.userId.lipb.segments` enriched by the userID module and +injects them in `ortb2.user.data` array entry. + +Please visit [LiveIntent](https://www.liveIntent.com/) for more information. + +# Testing + +To run the example and test the Rtd provider: + +```sh +gulp serve --modules=appnexusBidAdapter,rtdModule,liveIntentRtdProvider,userId,liveIntentIdSystem +``` + +Open chrome with this URL: +`http://localhost:9999/integrationExamples/gpt/liveIntentRtdProviderExample.html` + +To run the unit test: +```sh +gulp test --file "test/spec/modules/liveIntentRtdProvider_spec.js" +``` + +# Integration + +```bash +gulp build --modules=userId,liveIntentIdSystem,rtdModule,liveIntentRtdProvider +``` + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders:[{ + name: 'liveintent', + waitForIt: true + }] + } +}); +``` diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index 413147b4fa4..ec8fea42bac 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -77,6 +77,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE logInfo('LIVEWRAPPED_BID_RESPONSE:', args); let bidResponse = cache.auctions[args.auctionId].bids[args.requestId]; + if (bidResponse.cpm > args.cpm) break; // For now we only store the highest bid bidResponse.isBid = args.getStatusCode() === STATUS.GOOD; bidResponse.width = args.width; bidResponse.height = args.height; @@ -84,7 +85,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE bidResponse.originalCpm = prebidGlobal.convertCurrency(args.originalCpm, args.originalCurrency, args.currency); bidResponse.ttr = args.timeToRespond; bidResponse.readyToSend = 1; - bidResponse.mediaType = args.mediaType == 'native' ? 2 : (args.mediaType == 'video' ? 4 : 1); + bidResponse.mediaType = getMediaTypeEnum(args.mediaType); bidResponse.floorData = args.floorData; bidResponse.meta = args.meta; @@ -115,6 +116,11 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE logInfo('LIVEWRAPPED_BID_WON:', args); let wonBid = cache.auctions[args.auctionId].bids[args.requestId]; wonBid.won = true; + wonBid.width = args.width; + wonBid.height = args.height; + wonBid.cpm = args.cpm; + wonBid.originalCpm = prebidGlobal.convertCurrency(args.originalCpm, args.originalCurrency, args.currency); + wonBid.mediaType = getMediaTypeEnum(args.mediaType); wonBid.floorData = args.floorData; wonBid.rUp = args.rUp; wonBid.meta = args.meta; @@ -185,6 +191,10 @@ livewrappedAnalyticsAdapter.sendEvents = function() { ajax(initOptions.endpoint || URL, undefined, JSON.stringify(events), {method: 'POST'}); }; +function getMediaTypeEnum(mediaType) { + return mediaType == 'native' ? 2 : (mediaType == 'video' ? 4 : 1); +} + function getSentRequests() { var sentRequests = []; var gdpr = []; diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index cfbd2b5b3b5..203996c9fb5 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -4,6 +4,7 @@ import {config} from '../src/config.js'; import {find} from '../src/polyfill.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -69,7 +70,7 @@ export const spec = { bidUrl = bidUrl ? bidUrl.params.bidUrl : URL; url = url ? url.params.url : (getAppDomain() || getTopWindowLocation(bidderRequest)); test = test ? test.params.test : undefined; - const currency = config.getConfig('currency.adServerCurrency') || 'USD'; + const currency = getCurrencyFromBidderRequest(bidderRequest) || 'USD'; var adRequests = bidRequests.map(b => bidToAdRequest(b, currency)); const adRequestsContainFloors = adRequests.some(r => r.flr !== undefined); diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 63435437967..3dab4524db1 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -172,7 +172,11 @@ export const spec = { netRevenue: false, ttl: 300, referrer: parsedReferrer, - ad: bid.adm + ad: bid.adm, + adomain: bid.adomain || [], + meta: { + advertiserDomains: bid && bid.adomain ? bid.adomain : [] + } }; bidResponses.push(newBid); diff --git a/modules/madsenseBidAdapter.js b/modules/madsenseBidAdapter.js new file mode 100644 index 00000000000..ba866a9314e --- /dev/null +++ b/modules/madsenseBidAdapter.js @@ -0,0 +1,264 @@ +import { + logError, + logWarn, + logMessage, + deepSetValue, + mergeDeep, +} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'madsense'; +const DEFAULT_BID_TTL = 55; +const DEFAULT_NET_REVENUE = true; +const DEFAULT_CURRENCY = 'USD'; +const MS_EXCHANGE_BASE_URL = 'https://ads.madsense.io/pbjs'; + +const buildImpWithDefaults = (buildImp, bidRequest, context) => { + const imp = buildImp(bidRequest, context); + + imp.bidfloor = imp.bidfloor || bidRequest.params.bidfloor || 0; + imp.bidfloorcur = imp.bidfloorcur || bidRequest.params.currency || DEFAULT_CURRENCY; + + return imp; +}; + +const enrichRequestWithConsent = (req, bidderRequest) => { + if (bidderRequest.gdprConsent) { + deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(req, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + } + + if (bidderRequest.uspConsent) { + deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } +}; + +const determineMediaType = (bid) => { + if (bid.mtype === 2) { + return VIDEO; + } + + if (bid.mtype === 1) { + return BANNER; + } + + logWarn('Unrecognized media type, defaulting to BANNER (madSense)'); + return BANNER; +}; + +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_BID_TTL, + }, + imp: (buildImp, bidRequest, context) => buildImpWithDefaults(buildImp, bidRequest, context), + request: (buildRequest, imps, bidderRequest, context) => { + const req = buildRequest(imps, bidderRequest, context); + + mergeDeep(req, { + ext: { + hb: 1, + prebidver: '$prebid.version$', + adapterver: '1.0.0', + }, + }); + + enrichRequestWithConsent(req, bidderRequest); + return req; + }, + bidResponse: (buildBidResponse, bid, context) => { + const resMediaType = determineMediaType(bid); + + Object.assign(context, { + mediaType: resMediaType, + currency: DEFAULT_CURRENCY, + ...(resMediaType === VIDEO && { vastXml: bid.adm }), + }); + + return buildBidResponse(bid, context); + }, +}); + +export const spec = { + code: BIDDER_CODE, + VERSION: '1.0.0', + supportedMediaTypes: [BANNER, VIDEO], + ENDPOINT: MS_EXCHANGE_BASE_URL, + + isBidRequestValid: function (bid) { + return validateBidRequest(bid); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const contextMediaType = determineMediaType(validBidRequests[0]); + const data = converter.toORTB({ + bidRequests: validBidRequests, + bidderRequest, + context: { contextMediaType }, + }); + + const companyId = getCompanyId(validBidRequests); + const madsenseExchangeEndpointUrl = buildEndpointUrl(companyId); + + return { + method: 'POST', + url: madsenseExchangeEndpointUrl, + data: data, + }; + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bids = parseServerResponse(serverResponse, bidRequest); + return filterValidBids(bids); + }, +}; + +function validateBidRequest(bid) { + return ( + _validateParams(bid) && + _validateBanner(bid) && + _validateVideo(bid) + ); +} + +function getCompanyId(validBidRequests) { + let companyId = validBidRequests[0].params.company_id; + + if (validBidRequests[0].params.test) { + logMessage('madsense: test mode'); + companyId = 'test'; + } + + return companyId; +} + +function buildEndpointUrl(companyId) { + return `${spec.ENDPOINT}?company_id=${companyId}`; +} + +function parseServerResponse(serverResponse, bidRequest) { + return converter.fromORTB({ + response: serverResponse.body, + request: bidRequest.data, + }).bids; +} + +function filterValidBids(bids) { + return bids + .map((bid) => { + if (bid.mtype === 2 && bid.adm) { + if (!config.getConfig('cache.url')) { + bid.vastXml = bid.adm; + delete bid.adm; + } else { + logError('Prebid Cache is not configured (madSense)'); + return null; + } + } + return bid; + }) + .filter((bid) => bid !== null); +} + +function hasBannerMediaType(bidRequest) { + return !!bidRequest.mediaTypes?.banner; +} + +function hasVideoMediaType(bidRequest) { + return !!bidRequest.mediaTypes?.video; +} + +function _validateParams(bidRequest) { + if (!bidRequest.params) { + return false; + } + + if (bidRequest.params.test) { + return true; + } + + if (!bidRequest.params.company_id) { + logError('company_id not declared (madSense)'); + return false; + } + + const mediaTypesExists = hasVideoMediaType(bidRequest) || hasBannerMediaType(bidRequest); + if (!mediaTypesExists) { + return false; + } + + return true; +} + +function _validateBanner(bidRequest) { + if (!hasBannerMediaType(bidRequest)) { + return true; + } + + const bannerSizes = bidRequest.mediaTypes?.banner?.sizes; + + if (!Array.isArray(bannerSizes)) { + return false; + } + + return true; +} + +function _validateVideo(bidRequest) { + if (!hasVideoMediaType(bidRequest)) { + return true; + } + + const videoPlacement = bidRequest.mediaTypes?.video || {}; + const videoBidderParams = bidRequest.params?.video || {}; + const params = bidRequest.params || {}; + + if (params && params.test) { + return true; + } + + const videoParams = { + ...videoPlacement, + ...videoBidderParams, + }; + + if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) { + logWarn('Invalid MIME types (madSense)'); + return false; + } + + if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) { + logWarn('Invalid protocols (madSense)'); + return false; + } + + if (!videoParams.context) { + logWarn('Context not declared (madSense)'); + return false; + } + + if (videoParams.context !== 'instream') { + if (hasBannerMediaType(bidRequest)) { + logWarn('Context is not instream, preferring banner (madSense)'); + return true; + } else { + logWarn('Only instream context is supported (madSense)'); + } + } + + if ( + typeof videoParams.playerSize === 'undefined' || + !Array.isArray(videoParams.playerSize) || + !Array.isArray(videoParams.playerSize[0]) + ) { + logWarn('Player size not declared or not in [[w,h]] format'); + return false; + } + + return true; +} + +registerBidder(spec); diff --git a/modules/madsenseBidAdapter.md b/modules/madsenseBidAdapter.md new file mode 100644 index 00000000000..aacfbbafacb --- /dev/null +++ b/modules/madsenseBidAdapter.md @@ -0,0 +1,124 @@ +# Overview +``` +Module Name: madSense Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@madsense.io +``` + +# Description + +- A module that integrates with madSense's demand sources. +- The madSense bid adapter supports both Banner and Video formats. + + +### Test Parameters + +#### Banner + +``` +var adUnits = [ + { + code: 'adUnitBanner_div_1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'madsense', + params: { + company_id: '1234567', + bidfloor: 2.7, + } + }] + } +]; +``` + +#### Video + +We support the following OpenRTB parameters, which can be defined in `mediaTypes.video` or `bids[].params.video`. +- `mimes`, `minduration`, `maxduration`, `plcmt`, `protocols`, `startdelay`, `skip`, `skipafter`, `minbitrate`, `maxbitrate`, `delivery`, `playbackmethod`, `api`, `linearity` + + +##### Instream Video Ad Unit with mediaTypes.video +- Note: The adapter, by default, will retrieve the required parameters from mediaTypes.video. +- Note: The Video SSP ad server will return a VAST XML, which can be loaded into your specified player. +``` + var adUnits = [ + { + code: 'adUnitVideo_1', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4', 'application/javascript'], + protocols: [2,5], + api: [2], + position: 1, + delivery: [2], + minduration: 10, + maxduration: 30, + plcmt: 1, + playbackmethod: [1,5], + } + }, + bids: [ + { + bidder: 'madsense', + params: { + company_id: '1234567' + } + } + ] + } + ] +``` + +## End To End Testing Mode +By setting `bid.params.test = true`, you can receive a test creative. + +#### Banner +``` +var adUnits = [ + { + code: 'adUnitBanner_div_1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'madsense', + params: { + test: true + } + }] + } +]; +``` + +#### Video +``` +var adUnits = [ + { + code: 'adUnitVideo_1', + mediaTypes: { + video: { + context: "instream", + playerSize: [[640, 480]], + mimes: ['video/mp4'], + protocols: [2,5], + } + }, + bids: [ + { + bidder: 'madsense', + params: { + test: true + } + } + ] + } +] +``` diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 5adf3f743a7..bd6d684c9a0 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -210,7 +210,7 @@ function slotParams(bidRequest, bidderRequests) { transactionId: bidRequest.ortb2Imp?.ext?.tid, ext: { dfp_id: bidRequest.adUnitCode, - display_count: bidRequest.bidRequestsCount + display_count: bidRequest.auctionsCount }, all: bidRequest.params }; diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index a384b9d676c..9e2ea06df69 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -22,6 +22,7 @@ import {config} from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { getUserSyncs } from '../libraries/mgidUtils/mgidUtils.js' +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -158,7 +159,7 @@ export const spec = { if (isStr(muid) && muid.length > 0) { url += '?muid=' + muid; } - const cur = setOnAny(validBidRequests, 'params.currency') || setOnAny(validBidRequests, 'params.cur') || config.getConfig('currency.adServerCurrency') || DEFAULT_CUR; + const cur = setOnAny(validBidRequests, 'params.currency') || setOnAny(validBidRequests, 'params.cur') || getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CUR; const secure = window.location.protocol === 'https:' ? 1 : 0; let imp = []; validBidRequests.forEach(bid => { diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index 4e83c5c6db4..d5aae8a84d0 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -1,35 +1,19 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'minutemedia'; -const ADAPTER_VERSION = '6.0.0'; -const TTL = 360; -const DEFAULT_CURRENCY = 'USD'; const BASE_URL = 'https://hb.minutemedia-prebid.com/'; +const GVLID = 918; const MODES = { PRODUCTION: 'hb-mm-multi', TEST: 'hb-multi-mm-test' }; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - gvlid: 918, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, + gvlid: GVLID, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to MinuteMedia adapter'); @@ -42,64 +26,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest, ADAPTER_VERSION); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, BASE_URL, MODES), - data: combinedRequestsObject - }; - }, - interpretResponse: function ({ body }) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - }; - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/minutemediaBidAdapter.md b/modules/minutemediaBidAdapter.md index 66b54adaf0e..b22cf856364 100644 --- a/modules/minutemediaBidAdapter.md +++ b/modules/minutemediaBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to MinuteMedia's demand sources. The MinuteMedia adapter requires setup and approval from the MinuteMedia. Please reach out to hb@minutemedia.com to create an MinuteMedia account. -The adapter supports Video(instream) & Banner. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index e621e478ce7..c5c8678e0b1 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -7,11 +7,12 @@ import { safeJSONParse, triggerPixel, } from '../src/utils.js'; -import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; import { isAutoplayEnabled } from '../libraries/autoplayDetection/autoplay.js'; +import { normalizeBannerSizes } from '../libraries/sizeUtils/sizeUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -82,10 +83,12 @@ function toPayload(bidRequest, bidderRequest) { const bidFloor = getFloor(bidRequest); payload.floor = bidFloor?.floor; payload.floor_currency = bidFloor?.currency; - payload.currency = config.getConfig('currency.adServerCurrency'); + payload.currency = getCurrencyFromBidderRequest(bidderRequest); payload.schain = bidRequest.schain; payload.coppa = bidderRequest?.ortb2?.regs?.coppa ? 1 : 0; payload.autoplay = isAutoplayEnabled() === true ? 1 : 0; + payload.screen = { height: screen.height, width: screen.width }; + payload.sizes = normalizeBannerSizes(bidRequest.mediaTypes.banner.sizes); return { method: 'POST', @@ -130,6 +133,8 @@ export const spec = { return []; } + this.msnaApiKey = validBidRequests[0]?.params.apiKey; + return validBidRequests.map((bidRequest) => toPayload(bidRequest, bidderRequest), ); @@ -154,26 +159,25 @@ export const spec = { getUserSyncs: function ( syncOptions, serverResponses, - gdprConsent, + gdprConsent = {}, uspConsent, ) { - if (!syncOptions.iframeEnabled) { + if (!syncOptions.iframeEnabled || !this.msnaApiKey) { return []; } - let gdprParams = ''; - if ( - gdprConsent && - 'gdprApplies' in gdprConsent && - typeof gdprConsent.gdprApplies === 'boolean' - ) { - gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${ - gdprConsent.consentString - }`; + const url = new URL('https://sync.missena.io/iframe'); + url.searchParams.append('t', this.msnaApiKey); + + if (typeof gdprConsent.gdprApplies === 'boolean') { + url.searchParams.append('gdpr', Number(gdprConsent.gdprApplies)); + url.searchParams.append('gdpr_consent', gdprConsent.consentString); + } + if (uspConsent) { + url.searchParams.append('us_privacy', uspConsent); } - return [ - { type: 'iframe', url: 'https://sync.missena.io/iframe' + gdprParams }, - ]; + + return [{ type: 'iframe', url: url.href }]; }, /** * Register bidder specific code, which will execute if bidder timed out after an auction diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js index 02f2d1b83cf..01a0a5d93d1 100644 --- a/modules/mobianRtdProvider.js +++ b/modules/mobianRtdProvider.js @@ -37,15 +37,24 @@ import { setKeyValue } from '../libraries/gptUtils/gptUtils.js'; export const MOBIAN_URL = 'https://prebid.outcomes.net/api/prebid/v1/assessment/async'; +export const AP_VALUES = 'apValues'; +export const CATEGORIES = 'categories'; +export const EMOTIONS = 'emotions'; +export const GENRES = 'genres'; +export const RISK = 'risk'; +export const SENTIMENT = 'sentiment'; +export const THEMES = 'themes'; +export const TONES = 'tones'; + export const CONTEXT_KEYS = [ - 'apValues', - 'categories', - 'emotions', - 'genres', - 'risk', - 'sentiment', - 'themes', - 'tones' + AP_VALUES, + CATEGORIES, + EMOTIONS, + GENRES, + RISK, + SENTIMENT, + THEMES, + TONES ]; const AP_KEYS = ['a0', 'a1', 'p0', 'p1']; @@ -73,6 +82,24 @@ function makeMemoizedFetch() { export const getContextData = makeMemoizedFetch(); +const entriesToObjectReducer = (acc, [key, value]) => ({ ...acc, [key]: value }); + +export function makeContextDataToKeyValuesReducer(config) { + const { prefix } = config; + return function contextDataToKeyValuesReducer(keyValues, [key, value]) { + if (key === AP_VALUES) { + AP_KEYS.forEach((apKey) => { + if (!value?.[apKey]?.length) return; + keyValues.push([`${prefix}_ap_${apKey}`, value[apKey].map((v) => String(v))]); + }); + } + if (value?.length) { + keyValues.push([`${prefix}_${key}`, value]); + } + return keyValues; + } +} + export async function fetchContextData() { const pageUrl = encodeURIComponent(window.location.href); const requestUrl = `${MOBIAN_URL}?url=${pageUrl}`; @@ -101,33 +128,22 @@ export function getConfig(config) { } /** - * @param {MobianConfigParams} parsedConfig + * @param {MobianConfig} config * @param {MobianContextData} contextData - * @returns {function} */ -export function setTargeting(parsedConfig, contextData) { - const { publisherTargeting, prefix } = parsedConfig; +export function setTargeting(config, contextData) { logMessage('context', contextData); + const keyValues = Object.entries(contextData) + .filter(([key]) => config.publisherTargeting.includes(key)) + .reduce(makeContextDataToKeyValuesReducer(config), []) - CONTEXT_KEYS.forEach((key) => { - if (!publisherTargeting.includes(key)) return; - - if (key === 'apValues') { - AP_KEYS.forEach((apKey) => { - if (!contextData[key]?.[apKey]?.length) return; - logMessage(`${prefix}_ap_${apKey}`, contextData[key][apKey]); - setKeyValue(`${prefix}_ap_${apKey}`, contextData[key][apKey]); - }); - return; - } - - if (contextData[key]?.length) { - logMessage(`${prefix}_${key}`, contextData[key]); - setKeyValue(`${prefix}_${key}`, contextData[key]); - } - }); + keyValues.forEach(([key, value]) => setKeyValue(key, value)); } +/** + * @param {Object|string} contextData + * @returns {MobianContextData} + */ export function makeDataFromResponse(contextData) { const data = typeof contextData === 'string' ? safeJSONParse(contextData) : contextData; const results = data.results; @@ -135,50 +151,57 @@ export function makeDataFromResponse(contextData) { return {}; } return { - apValues: results.ap || {}, - categories: results.mobianContentCategories, - emotions: results.mobianEmotions, - genres: results.mobianGenres, - risk: results.mobianRisk || 'unknown', - sentiment: results.mobianSentiment || 'unknown', - themes: results.mobianThemes, - tones: results.mobianTones, + [AP_VALUES]: results.ap || {}, + [CATEGORIES]: results.mobianContentCategories, + [EMOTIONS]: results.mobianEmotions, + [GENRES]: results.mobianGenres, + [RISK]: results.mobianRisk || 'unknown', + [SENTIMENT]: results.mobianSentiment || 'unknown', + [THEMES]: results.mobianThemes, + [TONES]: results.mobianTones, }; } -export function extendBidRequestConfig(bidReqConfig, contextData) { +/** + * @param {Object} bidReqConfig + * @param {MobianContextData} contextData + * @param {MobianConfig} config + */ +export function extendBidRequestConfig(bidReqConfig, contextData, config) { logMessage('extendBidRequestConfig', bidReqConfig, contextData); const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; + const keyValues = Object.entries(contextData) + .filter(([key]) => config.advertiserTargeting.includes(key)) + .reduce(makeContextDataToKeyValuesReducer(config), []) + .reduce(entriesToObjectReducer, {}); ortb2Site.ext = ortb2Site.ext || {}; ortb2Site.ext.data = { ...(ortb2Site.ext.data || {}), - ...contextData + ...keyValues }; return bidReqConfig; } /** - * @param {MobianConfig} config + * @param {MobianConfig} rawConfig * @returns {boolean} */ -function init(config) { - logMessage('init', config); - - const parsedConfig = getConfig(config); - - if (parsedConfig.publisherTargeting.length) { - getContextData().then((contextData) => setTargeting(parsedConfig, contextData)); +function init(rawConfig) { + logMessage('init', rawConfig); + const config = getConfig(rawConfig); + if (config.publisherTargeting.length) { + getContextData().then((contextData) => setTargeting(config, contextData)); } - return true; } -function getBidRequestData(bidReqConfig, callback, config) { +function getBidRequestData(bidReqConfig, callback, rawConfig) { logMessage('getBidRequestData', bidReqConfig); - const { advertiserTargeting } = getConfig(config); + const config = getConfig(rawConfig); + const { advertiserTargeting } = config; if (!advertiserTargeting.length) { callback(); @@ -187,7 +210,7 @@ function getBidRequestData(bidReqConfig, callback, config) { getContextData() .then((contextData) => { - extendBidRequestConfig(bidReqConfig, contextData); + extendBidRequestConfig(bidReqConfig, contextData, config); }) .catch(() => {}) .finally(() => callback()); diff --git a/modules/mobianRtdProvider.md b/modules/mobianRtdProvider.md index e974e56dcdd..48e7a1c6cec 100644 --- a/modules/mobianRtdProvider.md +++ b/modules/mobianRtdProvider.md @@ -3,16 +3,24 @@ ## Overview Module Name: Mobian Rtd Provider + Module Type: Rtd Provider + Maintainer: rich.rodriguez@themobian.com -## Description +The Mobian Real-Time Data (RTD) Module is a plug-and-play Prebid.js adapter that is designed to provide Mobian Contextual results on the publisher’s page. + +## Downloading and Configuring the Mobian RTD module + +Navigate to https://docs.prebid.org/download.html and check the box labeled Mobian Prebid Contextual Evaluation. If you have installed Prebid.js on your site previously, please be sure to select any other modules and adaptors to suit your needs. When clicking the "Get Prebid.js" button at the bottom of the page, the site will build a version of Prebid.js with all of your selections. + +Direct link to the Mobian module in the Prebid.js repository: https://github.com/prebid/Prebid.js/blob/master/modules/mobianRtdProvider.js -RTD provider for themobian Brand Safety determinations. Publishers -should use this to get Mobian's GARM Risk evaluations for -a URL. +The client will need to provide Mobian with all the domains that would be using the prebid module so that Mobian can whitelist those domains. Failure to whitelist the domains will yield a 404 when making a request to the Mobian Contextual API at https://prebid.outcomes.net/. -## Configuration +## Configuration Highlight + +Below is Mobian's suggested default for configuration: ```js pbjs.setConfig({ @@ -22,7 +30,7 @@ pbjs.setConfig({ params: { // Prefix for the targeting keys (default: 'mobian') prefix: 'mobian', - + // Enable targeting keys for advertiser data advertiserTargeting: true, // Or set it as an array to pick specific targeting keys: @@ -39,3 +47,130 @@ pbjs.setConfig({ } }); ``` + +## Functionality + +At a high level, the Mobian RTD Module is designed to call the Mobian Contextal API on page load, requesting the Mobian classifications and results for the URL. The classifications and results are designed to be picked up by any SSP or DSP in the Prebid.js ecosystem. The module also supports placing the Mobian classifications on each ad slot on the page, thus allowing for targeting within GAM. + +## Available Classifications + +NOTE: The examples below for targetable keys for GAM or otherwise in the ortb2 object assume that your prefix is the default of "mobian". The prefix in the targetable key will change based on your settings. + +Risk: + +Prebid.outcomes.net endpoint key: mobianRisk + +Targetable Key: mobian_risk + +Possible values: "none", "low", "medium" or "high" + +Description: This category assesses whether content contains any potential risks or concerns to advertisers and returns a determination of Low Risk, Medium Risk, or High Risk based on the inclusion of sensitive or high-risk topics. Content that might be categorized as unsafe may include violence, hate speech, misinformation, or sensitive topics that most advertisers would like to avoid. Content that is explicit or overly graphic in nature will be more likely to fall into the High Risk tier compared to content that describes similar subjects in a more informative or educational manner. + +------------------ + +Content Categories: + +Prebid.outcomes.net endpoint key: mobianContentCategories + +Targetable Key: mobian_categories + +Possible values: "adult_content", "arms", "crime", "death_injury", "debated_issue", "hate_speech", "drugs_alcohol", "obscenity", "piracy", "spam", "terrorism" + +Description: Brand Safety Categories contain categorical results for brand safety when relevant (e.g. Low Risk Adult Content). Note there can be Medium and High Risk content that is not associated to a specific brand safety category. + +------------------ + +Sentiment: + +Prebid.outcomes.net endpoint key: mobianSentiment + +Targetable Key: mobian_sentiment + +Possible values: "negative", "neutral" or "positive" + +Description: This category analyzes the overall positivity, negativity, or neutrality of a piece of content. This is a broad categorization of the content’s tone; every piece of content receives one of three possible sentiment ratings: Positive, Negative, or Neutral. + +------------------ + +Emotion: + +Prebid.outcomes.net endpoint key: mobianEmotions + +Targetable Key: mobian_emotions + +Possible values: Various but some examples include "love", "joy", "surprise", "anger", "sadness", "fear" + +Description: This category represents the specific feelings expressed or evoked through the content. Emotions are the reactions tied to the content’s presentation. Multiple emotions may be evoked by a single piece of content as this category reflects the way humans engage with the content. + +------------------ + +Tone: + +Prebid.outcomes.net endpoint key: mobianTones + +Targetable Key: mobian_tones + +Possible values: Various, but some examples include "comedic", "serious" or "emotional" + +Description: This category represents the content’s stylistic attitude or perspective that is being conveyed. If the Genre classification above represents the more objective structure, the Tone classification represents the subjective form. This categorization influences the way audiences may receive the piece of content and how they could be impacted by it. + +------------------ + +Theme: + +Prebid.outcomes.net endpoint key: mobianThemes + +Targetable Key: mobian_themes + +Possible values: Various, but some examples include "skincare", "food" and "nightlife" + +Description: This category includes broad conceptual ideas or underlying topics that form the foundation of the content. Themes represent the central message or idea conveyed throughout, rather than the specific details of the subject. Themes are intended to be broad and high-level, describing the overall purpose and intent of the content, and can connect multiple pieces of content, even if they are not from the same property. + +------------------ + +Genre: + +Prebid.outcomes.net endpoint key: mobianGenres + +Targetable Key: mobian_genres + +Possible values: Various, but some examples include "journalism", "gaming" or "how-to" + +Description: This category represents the type or style of the content, focusing on the purpose, format, or presentation of the content. Genres group pieces of content into recognizable categories based on style and provide a framework for understanding the structure of the content. + +------------------ + +AP Values + +Prebid.outcomes.net endpoint key: ap (an array, containing values of a0, a1, p0, p1) + +Targetable Keys: mobian_ap_a0, mobian_ap_a1, mobian_ap_p0, mobian_ap_p1 + +Possible values: Various, numerically id-based and customizable based on Mobian Context Settings. + +Description: Mobian AI Personas are custom created based on prompts to find a specific audience. Please contact your Mobian contact directly for more information on this tool. The difference between the keys is below: + +a0 = Advertisers (via Campaign IDs) in this list should NOT want to advertise on this page + +a1 = Advertisers (via Campaign IDs) should want to advertise on this page + +p0 = Advertisers (via Campaign IDs) should AVOID targeting these personas + +p1 = Advertisers (via Campaign IDs) should target these personas + +*AP Values is in the early stages of testing and is subject to change. + +## GAM Targeting: + +On each page load, the Mobian RTD module finds each ad slot on the page and performs the following function: + +```js +window.googletag.cmd.push(() => { + window.googletag.pubads().setTargeting(key, value); +``` + +"key" and "value" will be replaced with the various classifications as described in the previous section. Notably, this function runs before ad calls are made to GAM, which enables the keys and value to be used for targeting or blocking in GAM. + +For more details on how to set up key-value pairs in GAM, please see this documentation from Google: https://support.google.com/admanager/answer/9796369 + +For example, if you wanted to target articles where mobianRisk is "low", the key to set in GAM would be "mobian_risk" and the value would be "low". Once these keys and values are set within the Inventory section in GAM as listed by their documentation, you can then reference the key value pair in Custom Targeting for any line item you create. diff --git a/modules/nativeRendering.js b/modules/nativeRendering.js index 8e6b6baab55..a6a404a0253 100644 --- a/modules/nativeRendering.js +++ b/modules/nativeRendering.js @@ -7,7 +7,8 @@ import {getCreativeRendererSource} from '../src/creativeRenderers.js'; function getRenderingDataHook(next, bidResponse, options) { if (isNativeResponse(bidResponse)) { next.bail({ - native: getNativeRenderingData(bidResponse, auctionManager.index.getAdUnit(bidResponse)) + native: getNativeRenderingData(bidResponse, auctionManager.index.getAdUnit(bidResponse)), + rendererVersion: 2 // 9.28 fixed a rendering bug; this signals to PUC that the native renderer is safe to use }) } else { next(bidResponse, options) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 112f0d68504..6e1443b85e5 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -21,7 +21,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; -const NM_VERSION = '4.2.1'; +const NM_VERSION = '4.3.0'; const PBJS_VERSION = 'v$prebid.version$'; const GVLID = 1060; const BIDDER_CODE = 'nextMillennium'; @@ -291,6 +291,11 @@ export function getImp(bid, id, mediaTypes) { }, }; + const gpid = bid?.ortb2Imp?.ext?.gpid; + const pbadslot = bid?.ortb2Imp?.ext?.data?.pbadslot; + if (gpid) imp.ext.gpid = gpid; + if (pbadslot) imp.ext.data = { pbadslot }; + getImpBanner(imp, banner); getImpVideo(imp, video); @@ -303,13 +308,15 @@ export function getImpBanner(imp, banner) { if (banner.bidfloorcur) imp.bidfloorcur = banner.bidfloorcur; if (banner.bidfloor) imp.bidfloor = banner.bidfloor; - const format = (banner.data?.sizes || []).map(s => { return {w: s[0], h: s[1]} }) + const format = (banner.data?.sizes || []).map(s => { return {w: s[0], h: s[1]} }); const {w, h} = (format[0] || {}) imp.banner = { w, h, format, }; + + setImpPos(imp.banner, banner?.pos); }; export function getImpVideo(imp, video) { @@ -331,6 +338,12 @@ export function getImpVideo(imp, video) { imp.video.w = video.data.w; imp.video.h = video.data.h; }; + + setImpPos(imp.video, video?.pos); +}; + +export function setImpPos(obj, pos) { + if (typeof pos === 'number' && pos >= 0 && pos <= 7) obj.pos = pos; }; export function setConsentStrings(postBody = {}, bidderRequest) { diff --git a/modules/nexverseBidAdapter.js b/modules/nexverseBidAdapter.js new file mode 100644 index 00000000000..6243ef575ed --- /dev/null +++ b/modules/nexverseBidAdapter.js @@ -0,0 +1,247 @@ +/* eslint-disable camelcase */ + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +import { isArray } from '../src/utils.js'; +import {getConnectionType} from '../libraries/connectionInfo/connectionUtils.js' +import { getDeviceType, getOS } from '../libraries/userAgentUtils/index.js'; +import { getDeviceModel, buildEndpointUrl, isBidRequestValid, parseNativeResponse, printLog, getUid } from '../libraries/nexverseUtils/index.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import { getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; +import { getOsVersion } from '../libraries/advangUtils/index.js'; + +const BIDDER_CODE = 'nexverse'; +const BIDDER_ENDPOINT = 'https://rtb.nexverse.ai'; +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; +const DEFAULT_CURRENCY = 'USD'; +const BID_TTL = 300; +const DEFAULT_LANG = 'en'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: BIDDER_CODE}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + isBidRequestValid, + /** + * Builds the OpenRTB server request from the list of valid bid requests. + * + * @param {Array} validBidRequests - Array of valid bid requests. + * @param {Object} bidderRequest - The bidder request object containing additional data. + * @returns {Array} Array of server requests to be sent to the endpoint. + */ + buildRequests(validBidRequests, bidderRequest) { + const requests = validBidRequests.map((bid) => { + // Build the endpoint URL with query parameters + const endpointUrl = buildEndpointUrl(BIDDER_ENDPOINT, bid); + + // Build the OpenRTB payload + const payload = buildOpenRtbRequest(bid, bidderRequest); + + if (!payload) { + printLog('error', 'Payload could not be built.'); + return null; // Skip this bid + } + + // Return the server request + return { + method: 'POST', + url: endpointUrl, + data: JSON.stringify(payload), + bidRequest: bid, + }; + }); + + return requests.filter((request) => request !== null); // Remove null entries + }, + + /** + * Interprets the server's response and extracts bid information. + * + * @param {Object} serverResponse - The response from the server. + * @param {Object} request - The original server request. + * @returns {Array} Array of bids to be passed to the auction. + */ + interpretResponse(serverResponse, request) { + if (serverResponse && serverResponse.status === 204) { + printLog('info', 'No ad available (204 response).'); + return []; + } + + const bidResponses = []; + const response = serverResponse.body; + + if (!response || !response.seatbid || !isArray(response.seatbid)) { + printLog('warning', 'No valid bids in the response.'); + return bidResponses; + } + + response.seatbid.forEach((seatbid) => { + seatbid.bid.forEach((bid) => { + const bidResponse = { + requestId: bid.impid, + cpm: bid.price, + currency: response.cur || DEFAULT_CURRENCY, + width: bid.width || 0, + height: bid.height || 0, + creativeId: bid.crid || bid.id, + ttl: BID_TTL, + netRevenue: true, + meta: {}, + }; + // Determine media type and assign the ad content + if (bid.ext && bid.ext.mediaType) { + bidResponse.mediaType = bid.ext.mediaType; + } else if (bid.adm && bid.adm.indexOf(' ({ w: size[0], h: size[1] })), // List of size objects + w: bid.sizes[0][0], + h: bid.sizes[0][1], + }, + secure: window.location.protocol === 'https:' ? 1 : 0, // Indicates whether the request is secure (HTTPS) + }); + } + if (bid.mediaTypes.video) { + imp.push({ + id: bid.bidId, + video: { + w: bid.sizes[0][0], + h: bid.sizes[0][1], + mimes: bid.mediaTypes.video.mimes || ['video/mp4'], // Default to video/mp4 if not specified + protocols: bid.mediaTypes.video.protocols || [2, 3, 5, 6], // RTB video ad serving protocols + maxduration: bid.mediaTypes.video.maxduration || 30, + linearity: bid.mediaTypes.video.linearity || 1, + playbackmethod: bid.mediaTypes.video.playbackmethod || [2], + }, + secure: window.location.protocol === 'https:' ? 1 : 0, // Indicates whether the request is secure (HTTPS) + }); + } + if (bid.mediaTypes.native) { + imp.push({ + id: bid.bidId, + native: { + request: JSON.stringify(bid.mediaTypes.native), // Convert native request to JSON string + }, + secure: window.location.protocol === 'https:' ? 1 : 0, // Indicates whether the request is secure (HTTPS) + }); + } + + // Construct the OpenRTB request object + const openRtbRequest = { + id: bidderRequest.auctionId, + imp: imp, + site: { + page: bidderRequest.refererInfo.page, + domain: bidderRequest.refererInfo.domain, + ref: bidderRequest.refererInfo.ref || '', // Referrer URL + }, + device: { + ua: navigator.userAgent, + devicetype: getDeviceType(), // 1 = Mobile/Tablet, 2 = Desktop + os: getOS(), + osv: getOsVersion(), + make: navigator.vendor || '', + model: getDeviceModel(), + connectiontype: getConnectionType(), // Include connection type + geo: { + lat: bid.params.geoLat || 0, + lon: bid.params.geoLon || 0, + }, + language: navigator.language || DEFAULT_LANG, + dnt: navigator.doNotTrack === '1' ? 1 : 0, // Do Not Track flag + }, + user: { + id: getUid(storage), + buyeruid: bidderRequest.userId || '', // User ID or Buyer ID + ext: { + consent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : null, // GDPR consent string + }, + }, + regs: { + ext: { + gdpr: bidderRequest.gdprConsent ? (bidderRequest.gdprConsent.gdprApplies ? 1 : 0) : 0, + }, + }, + ext: { + prebid: { + auctiontimestamp: bidderRequest.auctionStart, + }, + }, + }; + + // Add app object if the request comes from a mobile app + if (bidderRequest.app) { + openRtbRequest.app = { + id: bidderRequest.app.id, + name: bidderRequest.app.name, + bundle: bidderRequest.app.bundle, + domain: bidderRequest.app.domain, + storeurl: bidderRequest.app.storeUrl, + cat: bidderRequest.app.cat || [], + }; + } + // Add additional fields related to GDPR, US Privacy, CCPA + if (bidderRequest.uspConsent) { + openRtbRequest.regs.ext.us_privacy = bidderRequest.uspConsent; + } + return openRtbRequest; +} + +registerBidder(spec); diff --git a/modules/nexverseBidAdapter.md b/modules/nexverseBidAdapter.md new file mode 100644 index 00000000000..1de5dda01e9 --- /dev/null +++ b/modules/nexverseBidAdapter.md @@ -0,0 +1,41 @@ +# Nexverse Bid Adapter + +## Overview +The Nexverse Bid Adapter enables publishers to connect with the Nexverse Real-Time Bidding (RTB) platform. This adapter supports multiple ad formats, including Banner, Video, and Native ads. By integrating this adapter, publishers can send bid requests to Nexverse’s marketplace and receive high-quality ads in response. + +- **Module name**: Nexverse +- **Module type**: Bidder Adapter +- **Supported Media Types**: Banner, Video, Native +- **Maintainer**: anand.kumar@nexverse.ai + +## Bidder Parameters +To correctly configure the Nexverse Bid Adapter, the following parameters are required: + +| Param Name | Scope | Type | Description | +|--------------|----------|--------|-----------------------------------------------------| +| `uid` | required | string | Unique User ID assigned by Nexverse for the publisher | +| `pubId` | required | string | The unique ID for the publisher | +| `pubEpid` | required | string | The unique endpoint ID for the publisher | + +### Example Configuration +The following is an example configuration for a Nexverse bid request using Prebid.js: + +```javascript +var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [{ + bidder: 'nexverse', + params: { + uid: '12345', + pubId: '54321', + pubEpid: 'abcde' + }, + isDebug: false // Optional, i.e True for debug mode + }] +}]; +``` diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 9a76b24f223..933c0378f03 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -1,4 +1,3 @@ -import {config} from '../src/config.js'; import { deepAccess, deepSetValue, generateUUID, logError, logInfo } from '../src/utils.js'; import {Renderer} from '../src/Renderer.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -7,6 +6,7 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js' import { INSTREAM, OUTSTREAM } from '../src/video.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -132,7 +132,7 @@ const converter = ortbConverter({ deepSetValue(request, 'ext.source', 'prebid.js'); deepSetValue(request, 'ext.pageViewId', PAGE_VIEW_ID); deepSetValue(request, 'ext.bidderVersion', BIDDER_VERSION); - deepSetValue(request, 'cur', [config.getConfig('currency.adServerCurrency') || 'USD']); + deepSetValue(request, 'cur', [getCurrencyFromBidderRequest(bidderRequest) || 'USD']); if (!request.user) request.user = {}; if (getAmxId()) { if (!request.user.ext) request.user.ext = {}; diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index 901b9a138d5..de03f65781e 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -13,7 +13,7 @@ import { formatQS, } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; import {percentInView} from '../libraries/percentInView/percentInView.js'; import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; @@ -27,7 +27,7 @@ export const spec = { code: BIDDER_CODE, aliases: ['brightcom', 'bcmssp'], gvlid: 883, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, buildRequests, interpretResponse, @@ -39,7 +39,7 @@ export const spec = { function buildRequests(bidReqs, bidderRequest) { try { const impressions = bidReqs.map(bid => { - let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes; + let bidSizes = bid?.mediaTypes?.banner?.sizes || bid?.mediaTypes?.video?.playerSize || bid.sizes; bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); bidSizes = bidSizes.filter(size => isArray(size)); const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); @@ -52,18 +52,25 @@ function buildRequests(bidReqs, bidderRequest) { const imp = { id: bid.bidId, - banner: { - format: processedSizes, - ext: { - viewability: viewabilityAmountRounded, - } - }, ext: { ...gpidData }, tagid: String(bid.adUnitCode) }; + if (bid?.mediaTypes?.video) { + imp.video = { + ...bid.mediaTypes.video, + } + } else { + imp.banner = { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded, + } + } + } + const bidFloor = _getBidFloor(bid); if (bidFloor) { @@ -158,7 +165,7 @@ function interpretResponse(serverResponse) { try { if (id && seatbid && seatbid.length > 0 && seatbid[0].bid && seatbid[0].bid.length > 0) { response = seatbid[0].bid.map(bid => { - return { + const bidResponse = { requestId: bid.impid, cpm: parseFloat(bid.price), width: parseInt(bid.w), @@ -166,13 +173,20 @@ function interpretResponse(serverResponse) { creativeId: bid.crid || bid.id, currency: 'USD', netRevenue: true, - mediaType: BANNER, ad: _getAdMarkup(bid), ttl: 300, meta: { advertiserDomains: bid?.adomain || [] } }; + + if (bid.mtype === 2) { + bidResponse.mediaType = VIDEO; + } else { + bidResponse.mediaType = BANNER; + } + + return bidResponse; }); } } catch (e) { diff --git a/modules/omsBidAdapter.md b/modules/omsBidAdapter.md index 0a6b9cac82c..506ba5fdbd5 100644 --- a/modules/omsBidAdapter.md +++ b/modules/omsBidAdapter.md @@ -41,6 +41,21 @@ var adUnits = [ publisherId: 2141020 } }] + }, + { + code: 'video-instream', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + bids: [{ + bidder: 'oms', + params: { + publisherId: 2141020 + } + }] } ] ``` diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index 60364f41d3c..0dff314ce17 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -1,35 +1,19 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openweb'; -const ADAPTER_VERSION = '6.0.0'; -const TTL = 360; -const DEFAULT_CURRENCY = 'USD'; const BASE_URL = 'https://hb.openwebmp.com/'; +const GVLID = 280; const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' }; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - gvlid: 280, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, + gvlid: GVLID, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to OpenWeb adapter'); @@ -47,65 +31,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - // use data from the first bid, to create the general params for all bids - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, BASE_URL, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({body}) { - const bidResponses = []; - - if (body && body.bids && body.bids.length) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/openwebBidAdapter.md b/modules/openwebBidAdapter.md index 5450182265c..c5bc10c3c12 100644 --- a/modules/openwebBidAdapter.md +++ b/modules/openwebBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to OpenWeb's demand sources. The OpenWeb adapter requires setup and approval from OpenWeb. Please reach out to monetization@openweb.com to create an OpenWeb account. -The adapter supports Video and Display demand. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 19da19e661f..cf8a54242de 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -2,7 +2,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; import {mergeDeep} from '../src/utils.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const bidderConfig = 'hb_pb_ortb'; @@ -13,7 +13,7 @@ export const DEFAULT_PH = '2d1251ae-7f3a-47cf-bd2a-2f288854a0ba'; export const spec = { code: 'openx', gvlid: 69, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid, buildRequests, interpretResponse, @@ -25,7 +25,12 @@ registerBidder(spec); const converter = ortbConverter({ context: { netRevenue: true, - ttl: 300 + ttl: 300, + nativeRequest: { + eventtrackers: [ + {event: 1, methods: [1, 2]}, + ] + } }, imp(buildImp, bidRequest, context) { const imp = buildImp(bidRequest, context); @@ -158,8 +163,11 @@ function isBidRequestValid(bidRequest) { function buildRequests(bids, bidderRequest) { let videoBids = bids.filter(bid => isVideoBid(bid)); - let bannerBids = bids.filter(bid => isBannerBid(bid)); - let requests = bannerBids.length ? [createRequest(bannerBids, bidderRequest, BANNER)] : []; + let bannerAndNativeBids = bids.filter(bid => isBannerBid(bid) || isNativeBid(bid)) + // In case of multi-format bids remove `video` from mediaTypes as for video a separate bid request is built + .map(bid => ({...bid, mediaTypes: {...bid.mediaTypes, video: undefined}})); + + let requests = bannerAndNativeBids.length ? [createRequest(bannerAndNativeBids, bidderRequest, null)] : []; videoBids.forEach(bid => { requests.push(createRequest([bid], bidderRequest, VIDEO)); }); @@ -178,8 +186,13 @@ function isVideoBid(bid) { return utils.deepAccess(bid, 'mediaTypes.video'); } +function isNativeBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.native'); +} + function isBannerBid(bid) { - return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); + const isNotVideoOrNativeBid = !isVideoBid(bid) && !isNativeBid(bid) + return utils.deepAccess(bid, 'mediaTypes.banner') || isNotVideoOrNativeBid; } function interpretResponse(resp, req) { diff --git a/modules/openxBidAdapter.md b/modules/openxBidAdapter.md index a39aa1580cd..61b6426d113 100644 --- a/modules/openxBidAdapter.md +++ b/modules/openxBidAdapter.md @@ -34,6 +34,16 @@ Please note you should only include either openxBidAdapter or openxOrtbBidAdapte | `delDomain` | required | String | OpenX delivery domain provided by your OpenX representative. | "PUBLISHER-d.openx.net" | `video` | optional | OpenRTB video subtypes | Use of adUnit.mediaTypes.video is now preferred. | `{ video: {mimes: ['video/mp4']}` +## Native + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `delDomain` or `platform` | required | String | OpenX delivery domain or platform id provided by your OpenX representative. | "PUBLISHER-d.openx.net" or "555not5a-real-plat-form-id0123456789" +| `unit` | required | String | OpenX ad unit ID provided by your OpenX representative. | "1611023122" +| `customParams` | optional | Object | User-defined targeting key-value pairs. customParams applies to a specific unit. | `{key1: "v1", key2: ["v2","v3"]}` +| `customFloor` | optional | Number | Minimum price in USD. customFloor applies to a specific unit. For example, use the following value to set a $1.50 floor: 1.50

**WARNING:**
Misuse of this parameter can impact revenue | 1.50 +| `doNotTrack` | optional | Boolean | Prevents advertiser from using data for this user.

**WARNING:**
Request-level setting. May impact revenue. | true +| `coppa` | optional | Boolean | Enables Child's Online Privacy Protection Act (COPPA) regulations. Use of `pbjs.setConfig({coppa: true});` is now preferred. | true # Example ```javascript @@ -84,7 +94,42 @@ var adUnits = [ mimes: ['video/x-ms-wmv, video/mp4'] // mediaTypes.video preferred } } - }]p + }] + }, + { + code: 'native1', + mediaTypes: { + native: { + ortb: { + ver: '1.2', + assets: [ + { + required: 1, + img: { + type: 1, + hmin: 50 + }, + }, { + required: 1, + title: { + len: 80 + } + } + ] + } + } + }, + bids: [{ + bidder: 'openx', + params: { + unit: '1611023124', + delDomain: 'PUBLISHER-d.openx.net', + customParams: { + key1: 'v1', + key2: ['v2', 'v3'] + } + } + }] } ]; ``` diff --git a/modules/orbitsoftBidAdapter.js b/modules/orbitsoftBidAdapter.js index f55c7ff9917..a22afd16a49 100644 --- a/modules/orbitsoftBidAdapter.js +++ b/modules/orbitsoftBidAdapter.js @@ -25,7 +25,7 @@ let styleParamsMap = { }; export const spec = { code: BIDDER_CODE, - aliases: ['oas', '152media'], // short code and customer aliases + aliases: ['oas', '152media', 'paradocs'], // short code and customer aliases isBidRequestValid: function (bid) { switch (true) { case !('params' in bid): diff --git a/modules/permutiveRtdProvider.md b/modules/permutiveRtdProvider.md index 9399dffab93..3cf3ed2b367 100644 --- a/modules/permutiveRtdProvider.md +++ b/modules/permutiveRtdProvider.md @@ -50,14 +50,15 @@ as well as enabling settings for specific use cases mentioned above (e.g. acbidd #### Context -Permutive is not listed as a TCF vendor as all data collection is on behalf of the publisher and based on consent the publisher has received from the user. -Rather than through the TCF framework, this consent is provided to Permutive when the user gives the relevant permissions on the publisher website which allow the Permutive SDK to run. -This means that if GDPR enforcement is configured _and_ the user consent isn’t given for Permutive to fire, no cohorts will populate. -As Prebid utilizes TCF vendor consent, for the Permutive RTD module to load, Permutive needs to be labeled within the Vendor Exceptions +While Permutive is listed as a TCF vendor (ID: 361), Permutive does not obtain consent directly from the TCF. As we act as a processor on behalf of our publishers consent is given to the Permutive SDK by the publisher, not by the [GDPR Consent Management Module](https://prebid-docs.atre.net/dev-docs/modules/consentManagement.html). + +This means that if GDPR enforcement is configured within the Permutive SDK _and_ the user consent isn’t given for Permutive to fire, no cohorts will populate. + +If you are also using the [TCF Control Module](https://docs.prebid.org/dev-docs/modules/tcfControl.html), in order to prevent Permutive from being blocked, it needs to be labeled within the Vendor Exceptions. #### Instructions -1. Publisher enables rules within Prebid GDPR module +1. Publisher enables rules within Prebid.js configuration. 2. Label Permutive as an exception, as shown below. ```javascript [ diff --git a/modules/prebidServerBidAdapter/bidderConfig.js b/modules/prebidServerBidAdapter/bidderConfig.js new file mode 100644 index 00000000000..f6f4fb91389 --- /dev/null +++ b/modules/prebidServerBidAdapter/bidderConfig.js @@ -0,0 +1,161 @@ +import {mergeDeep, deepEqual, deepAccess, deepSetValue, deepClone} from '../../src/utils.js'; +import {ORTB_EIDS_PATHS} from '../../src/activities/redactor.js'; + +/** + * Perform a partial pre-merge of bidder config for PBS. + * + * Prebid.js and Prebid Server use different strategies for merging global and bidder-specific config; JS attemps to + * merge arrays (concatenating them, with some deduping, cfr. mergeDeep), while PBS only merges objects - + * a bidder-specific array will replace a global array. + * + * This returns bidder config (from `bidder`) where arrays are replaced with what you get from merging them with `global`, + * so that the result of merging in PBS is the same as in JS. + */ +export function getPBSBidderConfig({global, bidder}) { + return Object.fromEntries( + Object.entries(bidder).map(([bidderCode, bidderConfig]) => { + return [bidderCode, replaceArrays(bidderConfig, mergeDeep({}, global, bidderConfig))] + }) + ) +} + +function replaceArrays(config, mergedConfig) { + return Object.fromEntries( + Object.entries(config).map(([key, value]) => { + const mergedValue = mergedConfig[key]; + if (Array.isArray(value)) { + if (!deepEqual(value, mergedValue) && Array.isArray(mergedValue)) { + value = mergedValue; + } + } else if (value != null && typeof value === 'object') { + value = replaceArrays(value, mergedValue); + } + return [key, value]; + }) + ) +} + +/** + * Extract all EIDs from FPD. + * + * Returns {eids, conflicts}, where: + * + * - `eids` contains an object of the form `{eid, bidders}` for each unique EID object found anywhere in FPD; + * `bidders` is a list of all the bidders that refer to that specific EID object, or false if that EID object is defined globally. + * - `conflicts` is a set containing all EID sources that appear in multiple, otherwise different, EID objects. + */ +export function extractEids({global, bidder}) { + const entries = []; + const bySource = {}; + const conflicts = new Set() + + function getEntry(eid) { + let entry = entries.find((candidate) => deepEqual(candidate.eid, eid)); + if (entry == null) { + entry = {eid, bidders: new Set()} + entries.push(entry); + } + if (bySource[eid.source] == null) { + bySource[eid.source] = entry.eid; + } else if (entry.eid === eid) { + // if this is the first time we see this eid, but not the first time we see its source, we have a conflict + conflicts.add(eid.source); + } + return entry; + } + + ORTB_EIDS_PATHS.forEach(path => { + (deepAccess(global, path) || []).forEach(eid => { + getEntry(eid).bidders = false; + }); + }) + Object.entries(bidder).forEach(([bidderCode, bidderConfig]) => { + ORTB_EIDS_PATHS.forEach(path => { + (deepAccess(bidderConfig, path) || []).forEach(eid => { + const entry = getEntry(eid); + if (entry.bidders !== false) { + entry.bidders.add(bidderCode); + } + }) + }) + }) + return {eids: entries.map(({eid, bidders}) => ({eid, bidders: bidders && Array.from(bidders)})), conflicts}; +} + +/** + * Consolidate extracted EIDs to take advantage of PBS's eidpermissions feature: + * https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#eid-permissions + * + * If different bidders have different EID configurations, in most cases we can avoid repeating it in each bidder's + * specific config. As long as there are no conflicts (different EID objects that refer to the same source constitute a conflict), + * the EID can be set as global, and eidpermissions can restrict its access only to specific bidders. + * + * Returns {global, bidder, permissions}, where: + * - `global` is a list of global EID objects (some of which may be restricted through `permissions` + * - `bidder` is a map from bidder code to EID objects that are specific to that bidder, and cannot be restricted through `permissions` + * - `permissions` is a list of EID permissions as expected by PBS. + */ +export function consolidateEids({eids, conflicts = new Set()}) { + const globalEntries = []; + const bidderEntries = []; + const byBidder = {}; + eids.forEach(eid => { + (eid.bidders === false ? globalEntries : bidderEntries).push(eid); + }); + bidderEntries.forEach(({eid, bidders}) => { + if (!conflicts.has(eid.source)) { + globalEntries.push({eid, bidders}) + } else { + bidders.forEach(bidderCode => { + (byBidder[bidderCode] = byBidder[bidderCode] || []).push(eid) + }) + } + }); + return { + global: globalEntries.map(({eid}) => eid), + permissions: globalEntries.filter(({bidders}) => bidders !== false).map(({eid, bidders}) => ({ + source: eid.source, + bidders + })), + bidder: byBidder + } +} + +function replaceEids({global, bidder}, requestedBidders) { + const consolidated = consolidateEids(extractEids({global, bidder})); + global = deepClone(global); + bidder = deepClone(bidder); + function removeEids(target) { + delete target?.user?.eids; + delete target?.user?.ext?.eids; + } + removeEids(global); + Object.values(bidder).forEach(removeEids); + if (consolidated.global.length) { + deepSetValue(global, 'user.ext.eids', consolidated.global); + } + if (requestedBidders?.length) { + consolidated.permissions.forEach((permission) => permission.bidders = permission.bidders.filter(bidder => requestedBidders.includes(bidder))); + } + if (consolidated.permissions.length) { + deepSetValue(global, 'ext.prebid.data.eidpermissions', consolidated.permissions); + } + Object.entries(consolidated.bidder).forEach(([bidderCode, bidderEids]) => { + if (bidderEids.length) { + deepSetValue(bidder[bidderCode], 'user.ext.eids', bidderEids); + } + }) + return {global, bidder} +} + +export function premergeFpd(ortb2Fragments, requestedBidders) { + if (ortb2Fragments == null || Object.keys(ortb2Fragments.bidder || {}).length === 0) { + return ortb2Fragments; + } else { + ortb2Fragments = replaceEids(ortb2Fragments, requestedBidders); + return { + ...ortb2Fragments, + bidder: getPBSBidderConfig(ortb2Fragments) + }; + } +} diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 33ddb9847fa..673e92f709b 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -1,6 +1,5 @@ import Adapter from '../../src/adapter.js'; import { - deepAccess, deepClone, flatten, generateUUID, @@ -450,7 +449,7 @@ export function PrebidServer() { /* Prebid executes this function when the page asks to send out bid requests */ baseAdapter.callBids = function(s2sBidRequest, bidRequests, addBidResponse, done, ajax) { - const adapterMetrics = s2sBidRequest.metrics = useMetrics(deepAccess(bidRequests, '0.metrics')) + const adapterMetrics = s2sBidRequest.metrics = useMetrics(bidRequests?.[0]?.metrics) .newMetrics() .renameWith((n) => [`adapter.s2s.${n}`, `adapters.s2s.${s2sBidRequest.s2sConfig.defaultVendor}.${n}`]) done = adapterMetrics.startTiming('total').stopBefore(done); @@ -568,7 +567,7 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques const requestJson = request && JSON.stringify(request); logInfo('BidRequest: ' + requestJson); const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent); - const customHeaders = deepAccess(s2sBidRequest, 's2sConfig.customHeaders', {}); + const customHeaders = s2sBidRequest?.s2sConfig?.customHeaders ?? {}; if (request && requestJson && endpointUrl) { const networkDone = s2sBidRequest.metrics.startTiming('net'); ajax( diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 67cc5a372cf..51a01e04fdd 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -1,5 +1,5 @@ import {ortbConverter} from '../../libraries/ortbConverter/converter.js'; -import {deepAccess, deepSetValue, getBidRequest, logError, logWarn, mergeDeep, timestamp} from '../../src/utils.js'; +import {deepSetValue, getBidRequest, logError, logWarn, mergeDeep, timestamp} from '../../src/utils.js'; import {config} from '../../src/config.js'; import {S2S, STATUS} from '../../src/constants.js'; import {createBid} from '../../src/bidfactory.js'; @@ -16,6 +16,7 @@ import {ACTIVITY_TRANSMIT_TID} from '../../src/activities/activities.js'; import {currencyCompare} from '../../libraries/currencyUtils/currency.js'; import {minimum} from '../../src/utils/reducers.js'; import {s2sDefaultConfig} from './index.js'; +import {premergeFpd} from './bidderConfig.js'; const DEFAULT_S2S_TTL = 60; const DEFAULT_S2S_CURRENCY = 'USD'; @@ -183,7 +184,7 @@ const PBS_CONVERTER = ortbConverter({ }, sourceExtSchain(orig, ortbRequest, proxyBidderRequest, context) { // pass schains in ext.prebid.schains - let chains = (deepAccess(ortbRequest, 'ext.prebid.schains') || []); + let chains = ortbRequest?.ext?.prebid?.schains || []; const chainBidders = new Set(chains.flatMap((item) => item.bidders)); chains = Object.values( @@ -192,7 +193,7 @@ const PBS_CONVERTER = ortbConverter({ .filter((req) => !chainBidders.has(req.bidderCode)) // schain defined in s2sConfig.extPrebid takes precedence .map((req) => ({ bidders: [req.bidderCode], - schain: deepAccess(req, 'bids.0.schain') + schain: req?.bids?.[0]?.schain }))) .filter(({bidders, schain}) => bidders?.length > 0 && schain) .reduce((chains, {bidders, schain}) => { @@ -296,7 +297,10 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste currency: config.getConfig('currency.adServerCurrency') || DEFAULT_S2S_CURRENCY, ttl: s2sBidRequest.s2sConfig.defaultTtl || DEFAULT_S2S_TTL, requestTimestamp, - s2sBidRequest, + s2sBidRequest: { + ...s2sBidRequest, + ortb2Fragments: premergeFpd(s2sBidRequest.ortb2Fragments, requestedBidders) + }, requestedBidders, actualBidderRequests: bidderRequests, nativeRequest: s2sBidRequest.s2sConfig.ortbNative, diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 2fec213a612..8f9dead2cba 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -281,6 +281,31 @@ function isOWPubmaticBid(adapterName) { }) } +function getFloorsCommonField (floorData) { + if (!floorData) return; + const { location, fetchStatus, floorProvider, modelVersion } = floorData; + return { + ffs: { + [FLOOR_VALUES.SUCCESS]: 1, + [FLOOR_VALUES.ERROR]: 2, + [FLOOR_VALUES.TIMEOUT]: 4, + undefined: 0 + }[fetchStatus], + fsrc: { + [FLOOR_VALUES.FETCH]: 2, + [FLOOR_VALUES.NO_DATA]: 0, + [FLOOR_VALUES.AD_UNIT]: 1, + [FLOOR_VALUES.SET_CONFIG]: 1 + }[location], + fp: floorProvider, + mv: modelVersion + } +} + +function getFloorType(floorResponseData) { + return floorResponseData ? (floorResponseData.enforcements.enforceJS == false ? 0 : 1) : undefined; +} + function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid, e) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { @@ -325,6 +350,7 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid, e) { 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, 'ocry': bid.bidResponse ? (bid.bidResponse.originalCurrency || CURRENCY_USD) : CURRENCY_USD, 'frv': bid.bidResponse ? bid.bidResponse.floorData?.floorRuleValue : undefined, + 'fv': bid.bidResponse ? bid.bidResponse.floorData?.floorValue : undefined, 'md': bid.bidResponse ? getMetadata(bid.bidResponse.meta) : undefined, 'pb': pg || undefined }); @@ -403,9 +429,22 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['tgid'] = getTgId(); outputObj['pbv'] = '$prebid.version$' || '-1'; - if (floorData && floorFetchStatus) { - outputObj['fmv'] = floorData.floorRequestData ? floorData.floorRequestData.modelVersion || undefined : undefined; - outputObj['ft'] = floorData.floorResponseData ? (floorData.floorResponseData.enforcements.enforceJS == false ? 0 : 1) : undefined; + if (floorData) { + const floorRootValues = getFloorsCommonField(floorData?.floorRequestData); + if (floorRootValues) { + const { ffs, fsrc, fp, mv } = floorRootValues; + if (floorData?.floorRequestData) { + outputObj['ffs'] = ffs; + outputObj['fsrc'] = fsrc; + outputObj['fp'] = fp; + } + if (floorFetchStatus) { + outputObj['fmv'] = mv || undefined; + } + } + if (floorFetchStatus) { + outputObj['ft'] = getFloorType(floorData?.floorResponseData); + } } outputObj.s = Object.keys(auctionCache.adUnitCodes).reduce(function(slotsArray, adUnitId) { @@ -421,22 +460,6 @@ function executeBidsLoggerCall(e, highestCpmBids) { 'fskp': floorData && floorFetchStatus ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, 'sid': generateUUID() }; - if (floorData?.floorRequestData) { - const { location, fetchStatus, floorProvider } = floorData?.floorRequestData; - slotObject.ffs = { - [FLOOR_VALUES.SUCCESS]: 1, - [FLOOR_VALUES.ERROR]: 2, - [FLOOR_VALUES.TIMEOUT]: 4, - undefined: 0 - }[fetchStatus]; - slotObject.fsrc = { - [FLOOR_VALUES.FETCH]: 2, - [FLOOR_VALUES.NO_DATA]: 2, - [FLOOR_VALUES.AD_UNIT]: 1, - [FLOOR_VALUES.SET_CONFIG]: 1 - }[location]; - slotObject.fp = floorProvider; - } slotsArray.push(slotObject); return slotsArray; }, []); @@ -509,6 +532,25 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&orig=' + enc(getDomainFromUrl(referrer)); pixelURL += '&ss=' + enc(isS2SBidder(winningBid.bidder)); (fskp != undefined) && (pixelURL += '&fskp=' + enc(fskp)); + if (floorData) { + const floorRootValues = getFloorsCommonField(floorData.floorRequestData); + const { fsrc, fp, mv } = floorRootValues || {}; + const params = { fsrc, fp, fmv: mv }; + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined) { + pixelURL += `&${key}=${enc(value)}`; + } + }); + const floorType = getFloorType(floorData.floorResponseData); + if (floorType !== undefined) { + pixelURL += '&ft=' + enc(floorType); + } + const floorRuleValue = winningBid?.bidResponse?.floorData?.floorRuleValue; + (floorRuleValue !== undefined) && (pixelURL += '&frv=' + enc(floorRuleValue)); + + const floorValue = winningBid?.bidResponse?.floorData?.floorValue; + (floorValue !== undefined) && (pixelURL += '&fv=' + enc(floorValue)); + } pixelURL += '&af=' + enc(winningBid.bidResponse ? (winningBid.bidResponse.mediaType || undefined) : undefined); ajax( diff --git a/modules/rediadsBidAdapter.js b/modules/rediadsBidAdapter.js new file mode 100644 index 00000000000..ec4b12f6e39 --- /dev/null +++ b/modules/rediadsBidAdapter.js @@ -0,0 +1,102 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepSetValue, logWarn, logError } from '../src/utils.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'rediads'; +const ENDPOINT_URL = 'https://bidding.rediads.com/openrtb2/auction'; +const STAGING_ENDPOINT_URL = 'https://stagingbidding.rediads.com/openrtb2/auction'; +const DEFAULT_CURRENCY = 'USD'; +const LOG_PREFIX = 'Rediads: '; + +const MEDIA_TYPES = { + [BANNER]: 1, + [VIDEO]: 2, + [NATIVE]: 4, +}; + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + currency: DEFAULT_CURRENCY, + }, + bidResponse(buildBidResponse, bid, context) { + let mediaType = 'banner'; // Default media type + + if (bid.vastXml || bid.vastUrl || (bid.adm && bid.adm.startsWith(' { + logMessage('onBidWon', bid); + } + +}; + +registerBidder(spec); diff --git a/modules/responsiveAdsBidAdapter.md b/modules/responsiveAdsBidAdapter.md new file mode 100644 index 00000000000..af3b59e80e3 --- /dev/null +++ b/modules/responsiveAdsBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +```markdown +Module Name: responsiveAdsBidAdapter +Module Type: Bidder Adapter +Maintainer: support@responsiveads.com +``` + + +# Description +Module that connects to ResponsiveAds Programmatic Fluid demand. + + +## Running the code +To view an example of the on page setup required: + +```bash +gulp serve-and-test --file test/spec/modules/responsiveAdsBidAdapter_spec.js +``` + +# Test Parameters +``` +var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'responsiveads', + params: {} + }] + +}]; +``` diff --git a/modules/retailspotBidAdapter.js b/modules/retailspotBidAdapter.js index 557dd617274..da8e46bec81 100644 --- a/modules/retailspotBidAdapter.js +++ b/modules/retailspotBidAdapter.js @@ -5,17 +5,21 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest */ const BIDDER_CODE = 'retailspot'; -const DEFAULT_SUBDOMAIN = 'ssp'; -const PREPROD_SUBDOMAIN = 'ssp-preprod'; -const HOST = 'retail-spot.io'; -const ENDPOINT = '/prebid'; -const DEV_URL = 'http://localhost:8090/prebid'; +const GVL_ID = 1319; + +const DEFAULT_SUBDOMAIN = 'hbapi'; +const PREPROD_SUBDOMAIN = 'hbapi-preprod'; +const HOST = 'retailspotads.com'; +const ENDPOINT = '/'; +const DEV_URL = 'http://localhost:3030/'; export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, supportedMediaTypes: [BANNER, VIDEO], aliases: ['rs'], // short code /** @@ -25,7 +29,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - const sizes = getSize(getSizeArray(bid)); + const sizes = getSize(bid); const sizeValid = sizes.width > 0 && sizes.height > 0; return deepAccess(bid, 'params.placement') && sizeValid; @@ -33,7 +37,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {BidRequests} - bidRequests.bids[] is an array of AdUnits and bids + * @param {BidRequest} bidRequests is an array of AdUnits and bids + * @param {BidderRequest} bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { @@ -96,40 +101,39 @@ export const spec = { } } -function getSizeArray(bid) { +/* Get parsed size from request size */ +function getSize(bid) { let inputSize = bid.sizes || []; - if (bid.mediaTypes && bid.mediaTypes.banner) { + if (bid.mediaTypes?.banner) { inputSize = bid.mediaTypes.banner.sizes || []; } - // handle size in bid.params in formats: [w, h] and [[w,h]]. - if (bid.params && Array.isArray(bid.params.size)) { + // Size can be [w, h] or array of sizes : [[w,h]]. + if (Array.isArray(bid.params?.size)) { inputSize = bid.params.size; if (!Array.isArray(inputSize[0])) { inputSize = [inputSize] } } - return parseSizesInput(inputSize); -} - -/* Get parsed size from request size */ -function getSize(sizesArray) { + const sizesArray = parseSizesInput(inputSize); const parsed = {}; - // the main requested size is the first one + + // Use the first size as the main requested one const size = sizesArray[0]; + // size is ready if (typeof size !== 'string') { return parsed; } - const parsedSize = size.toUpperCase().split('X'); + // size is given as string "wwwxhhh" or "www*hhh" + const parsedSize = size.includes('*') ? size.split('*') : size.toUpperCase().split('X'); const width = parseInt(parsedSize[0], 10); if (width) { parsed.width = width; } - const height = parseInt(parsedSize[1], 10); if (height) { parsed.height = height; diff --git a/modules/retailspotBidAdapter .md b/modules/retailspotBidAdapter.md similarity index 92% rename from modules/retailspotBidAdapter .md rename to modules/retailspotBidAdapter.md index a9b4cb4bec3..1f56f66fbc8 100644 --- a/modules/retailspotBidAdapter .md +++ b/modules/retailspotBidAdapter.md @@ -25,7 +25,7 @@ Banner and Video ad formats are supported. bids: [{ bidder: "retailspot", params: { - placement: "test-12345" + placement: "eq-609785-1856964-125234" } }] }; diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index cfb0f97fafd..135d2e94b65 100644 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; const BIDDER_CODE = 'richaudience'; let REFERER = ''; @@ -35,7 +36,7 @@ export const spec = { ifa: bid.params.ifa, pid: bid.params.pid, supplyType: bid.params.supplyType, - currencyCode: config.getConfig('currency.adServerCurrency'), + currencyCode: getCurrencyFromBidderRequest(bidderRequest), auctionId: bid.auctionId, bidId: bid.bidId, BidRequestsCount: bid.bidRequestsCount, diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 236c048982a..a6970e959ce 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -1,40 +1,19 @@ +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; - -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -const BIDDER_CODE = 'rise'; -const ADAPTER_VERSION = '6.0.0'; -const TTL = 360; -const DEFAULT_CURRENCY = 'USD'; -const DEFAULT_GVLID = 1043; -const BASE_URL = 'https://hb.yellowblue.io/'; -const MODES = { - PRODUCTION: 'hb-multi', - TEST: 'hb-multi-test' -}; + ALIASES, + BASE_URL, + BIDDER_CODE, + DEFAULT_GVLID, + MODES, +} from '../libraries/riseUtils/constants.js'; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - aliases: [ - { code: 'risexchange', gvlid: DEFAULT_GVLID }, - { code: 'openwebxchange', gvlid: 280 } - ], + aliases: ALIASES, gvlid: DEFAULT_GVLID, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to Rise adapter'); @@ -47,66 +26,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - // use data from the first bid, to create the general params for all bids - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - const rtbDomain = generalObject.params.rtbDomain || BASE_URL; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, rtbDomain, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({ body }) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index 94d36a08510..2d355cd0cb6 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to Rise's demand sources. The Rise adapter requires setup and approval from the Rise. Please reach out to prebid-rise-engage@risecodes.com to create an Rise account. -The adapter supports Video(instream). +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 98316d68800..ad579f1cdbf 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -14,15 +14,6 @@ import { _map, isArray, triggerPixel } from '../src/utils.js'; * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid */ -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests - */ - const BIDDER_CODE = 'seedtag'; const SEEDTAG_ALIAS = 'st'; const SEEDTAG_SSP_ENDPOINT = 'https://s.seedtag.com/c/hb/bid'; diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index fa8b5e3bfdb..a96681f749c 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -183,9 +183,15 @@ export const sharedIdSystemSubmodule = { domainOverride: domainOverrideToRootDomain(storage, 'sharedId'), eids: { - 'pubcid': { - source: 'pubcid.org', - atype: 1 + 'pubcid'(values, config) { + const eid = { + source: 'pubcid.org', + uids: values.map(id => ({id, atype: 1})) + } + if (config?.params?.inserter != null) { + eid.inserter = config.params.inserter; + } + return eid; }, } }; diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js index 89f39284bed..f88360c4c38 100644 --- a/modules/shinezBidAdapter.js +++ b/modules/shinezBidAdapter.js @@ -1,24 +1,8 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'shinez'; -const ADAPTER_VERSION = '1.0.0'; -const TTL = 360; -const CURRENCY = 'USD'; const BASE_URL = 'https://hb.sweetgum.io/'; const MODES = { PRODUCTION: 'hb-sz-multi', @@ -26,9 +10,8 @@ const MODES = { }; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to Shinez adapter'); @@ -41,65 +24,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - // use data from the first bid, to create the general params for all bids - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, BASE_URL, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({body}) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }); - syncs.push(...pixels); - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/shinezBidAdapter.md b/modules/shinezBidAdapter.md index f0ef7a6c218..203b6ece4ff 100644 --- a/modules/shinezBidAdapter.md +++ b/modules/shinezBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to Shinez's demand sources. The Shinez adapter requires setup and approval from the Shinez. Please reach out to tech-team@shinez.io to create an Shinez account. -The adapter supports Video(instream) & Banner. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video @@ -73,4 +73,4 @@ var adUnits = [{ }] } ]; -``` \ No newline at end of file +``` diff --git a/modules/silverpushBidAdapter.js b/modules/silverpushBidAdapter.js index 593e613d603..70c0e475cc4 100644 --- a/modules/silverpushBidAdapter.js +++ b/modules/silverpushBidAdapter.js @@ -1,4 +1,3 @@ -import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import * as utils from '../src/utils.js'; import { mergeDeep } from '../src/utils.js'; @@ -6,6 +5,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { Renderer } from '../src/Renderer.js'; import { ajax } from '../src/ajax.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; const BIDDER_CODE = 'silverpush'; const bidderConfig = 'sp_pb_ortb'; @@ -65,7 +65,7 @@ export const CONVERTER = ortbConverter({ imp = buildBannerImp(bidRequest, imp); } - const bidFloor = getBidFloor(bidRequest); + const bidFloor = getBidFloor(bidRequest, bidRequest.bidderRequest); utils.deepSetValue(imp, 'bidfloor', bidFloor); @@ -216,7 +216,8 @@ function createRequest(bidRequests, bidderRequest, mediaType) { return { method: 'POST', url: REQUEST_URL, - data: CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } }) + data: CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } }), + bidderRequest } } @@ -247,8 +248,8 @@ function buildVideoOutstreamResponse(bidResponse, context) { return {...bidResponse}; } -function getBidFloor(bid) { - const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; +function getBidFloor(bid, bidderRequest) { + const currency = getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CURRENCY; if (typeof bid.getFloor !== 'function') { return utils.deepAccess(bid, 'params.bidFloor', 0.05); diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index e0ddf0a66e6..e3cdb3dc5bf 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -11,6 +11,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { getBidFloor } from '../libraries/equativUtils/equativUtils.js' import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -180,7 +181,7 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { // use bidderRequest.bids[] to get bidder-dependent request info - const adServerCurrency = config.getConfig('currency.adServerCurrency'); + const adServerCurrency = getCurrencyFromBidderRequest(bidderRequest); const sellerDefinedAudience = deepAccess(bidderRequest, 'ortb2.user.data', config.getAnyConfig('ortb2.user.data')); const sellerDefinedContext = deepAccess(bidderRequest, 'ortb2.site.content.data', config.getAnyConfig('ortb2.site.content.data')); diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index c9cc737fac7..d6600b4afd5 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -9,12 +9,13 @@ import { const BIDDER_CODE = 'smarthub'; const ALIASES = [ - {code: 'attekmi', skipPbsAliasing: true}, - {code: 'markapp', skipPbsAliasing: true}, - {code: 'jdpmedia', skipPbsAliasing: true}, - {code: 'tredio', skipPbsAliasing: true}, - {code: 'felixads', skipPbsAliasing: true}, - {code: 'vimayx', skipPbsAliasing: true}, + {code: 'attekmi'}, + {code: 'markapp'}, + {code: 'jdpmedia'}, + {code: 'tredio'}, + {code: 'felixads'}, + {code: 'vimayx'}, + {code: 'artechnology'}, ]; const BASE_URLS = { attekmi: 'https://prebid.attekmi.com/pbjs', @@ -24,6 +25,7 @@ const BASE_URLS = { tredio: 'https://tredio-prebid.attekmi.com/pbjs', felixads: 'https://felixads-prebid.attekmi.com/pbjs', vimayx: 'https://vimayx-prebid.attekmi.com/pbjs', + artechnology: 'https://artechnology-prebid.attekmi.com/pbjs', }; const _getUrl = (partnerName) => { diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index 7f83dc025cb..a78c60f8308 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -1,11 +1,11 @@ import {deepAccess, deepClone, isArray, isFn, isPlainObject, logError, logWarn} from '../src/utils.js'; import {Renderer} from '../src/Renderer.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {serializeSupplyChain} from '../libraries/schainSerializer/schainSerializer.js' import {convertOrtbRequestToProprietaryNative, toOrtbNativeRequest, toLegacyResponse} from '../src/native.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; const BIDDER_CODE = 'smilewanted'; @@ -66,7 +66,7 @@ export const spec = { return validBidRequests.map(bid => { const payload = { zoneId: bid.params.zoneId, - currencyCode: config.getConfig('currency.adServerCurrency') || 'EUR', + currencyCode: getCurrencyFromBidderRequest(bidderRequest) || 'EUR', tagId: bid.adUnitCode, sizes: bid.sizes.map(size => ({ w: size[0], diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index ba9af3314b0..03a9f5a1da9 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -1,18 +1,19 @@ import { deepAccess, getWindowTop, isArray, logInfo, logWarn } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { includes as strIncludes } from '../src/polyfill.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; const BIDDER_CODE = 'sspBC'; const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; -const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync'; +const SYNC_URL_IFRAME = 'https://ssp.wp.pl/bidder/usersync'; +const SYNC_URL_IMAGE = 'https://ssp.wp.pl/v1/sync/pixel'; const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify'; const GVLID = 676; const TMAX = 450; -const BIDDER_VERSION = '6.00'; +const BIDDER_VERSION = '6.10'; const DEFAULT_CURRENCY = 'PLN'; const W = window; const { navigator } = W; @@ -70,7 +71,20 @@ const getContentLanguage = () => { const topWindow = getWindowTop(); return topWindow.document.body.parentNode.lang; } catch (err) { - logWarn('Could not read language form top-level html', err); + logWarn('Could not read language from top-level html', err); + } +}; + +/** + * Get host name of the top level html object + * @returns {string} host name + */ +const getTopHost = () => { + try { + const topWindow = getWindowTop(); + return topWindow.location.host; + } catch (err) { + logWarn('Could not read host from top-level window', err); } }; @@ -284,7 +298,7 @@ const getHighestFloor = (slot) => { * Get currency (either default or adserver) * @returns {string} currency name */ -const getCurrency = () => config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; +const getCurrency = (bidderRequest) => getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CURRENCY; /** * Get value for first occurence of key within the collection @@ -604,7 +618,9 @@ const spec = { const pbver = '$prebid.version$'; const testMode = setOnAny(validBidRequests, 'params.test') ? 1 : undefined; const ref = bidderRequest.refererInfo.ref; - const { regs = {} } = ortb2 || {}; + const { source = {}, regs = {} } = ortb2 || {}; + + source.schain = setOnAny(validBidRequests, 'schain'); const payload = { id: bidderRequest.bidderRequestId, @@ -617,10 +633,11 @@ const spec = { content: { language: getContentLanguage() }, }, imp: validBidRequests.map(slot => mapImpression(slot)), - cur: [getCurrency()], + cur: [getCurrency(bidderRequest)], tmax, user: {}, regs, + source, device: { language: getBrowserLanguage(), w: screen.width, @@ -764,14 +781,22 @@ const spec = { return fledgeAuctionConfigs.length ? { bids, fledgeAuctionConfigs } : bids; }, - getUserSyncs(syncOptions) { + + getUserSyncs(syncOptions, _, gdprConsent = {}) { + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; let mySyncs = []; - if (syncOptions.iframeEnabled) { + if (iframeEnabled) { mySyncs.push({ type: 'iframe', - url: `${SYNC_URL}?tcf=2&pvid=${pageView.id}&sn=${pageView.sn}`, + url: `${SYNC_URL_IFRAME}?tcf=2&pvid=${pageView.id}&sn=${pageView.sn}`, }); - }; + } else if (pixelEnabled) { + mySyncs.push({ + type: 'image', + url: `${SYNC_URL_IMAGE}?inver=0&platform=wpartner&host=${getTopHost() || ''}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}`, + }); + } return mySyncs; }, diff --git a/modules/stnBidAdapter.js b/modules/stnBidAdapter.js index ba922c0fd57..62d82b8d4b2 100644 --- a/modules/stnBidAdapter.js +++ b/modules/stnBidAdapter.js @@ -1,38 +1,17 @@ -import { - logWarn, - logInfo, - isArray, - deepAccess, - triggerPixel, -} from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { - getEndpoint, - generateBidsParams, - generateGeneralParams, - buildBidResponse, -} from '../libraries/riseUtils/index.js'; +import {logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {makeBaseSpec} from '../libraries/riseUtils/index.js'; -export const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -export const BIDDER_CODE = 'stn'; -export const ADAPTER_VERSION = '6.1.0'; -export const TTL = 360; -export const DEFAULT_CURRENCY = 'USD'; -export const SELLER_ENDPOINT = 'https://hb.stngo.com/'; -export const MODES = { +const BIDDER_CODE = 'stn'; +const BASE_URL = 'https://hb.stngo.com/'; +const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' }; -export const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -}; export const spec = { + ...makeBaseSpec(BASE_URL, MODES), code: BIDDER_CODE, - version: ADAPTER_VERSION, - supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { if (!bidRequest.params) { logWarn('no params have been set to STN adapter'); @@ -45,64 +24,6 @@ export const spec = { } return true; - }, - buildRequests: function (validBidRequests, bidderRequest) { - const combinedRequestsObject = {}; - - const generalObject = validBidRequests[0]; - const testMode = generalObject.params.testMode; - - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); - combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); - - return { - method: 'POST', - url: getEndpoint(testMode, SELLER_ENDPOINT, MODES), - data: combinedRequestsObject - } - }, - interpretResponse: function ({ body }) { - const bidResponses = []; - - if (body.bids) { - body.bids.forEach(adUnit => { - const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); - bidResponses.push(bidResponse); - }); - } - - return bidResponses; - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; - for (const response of serverResponses) { - if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { - syncs.push({ - type: 'iframe', - url: deepAccess(response, 'body.params.userSyncURL') - }); - } - if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { - const pixels = response.body.params.userSyncPixels.map(pixel => { - return { - type: 'image', - url: pixel - } - }) - syncs.push(...pixels) - } - } - return syncs; - }, - onBidWon: function (bid) { - if (bid == null) { - return; - } - - logInfo('onBidWon:', bid); - if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { - triggerPixel(bid.nurl); - } } }; diff --git a/modules/stnBidAdapter.md b/modules/stnBidAdapter.md index 90b0b58e34b..d5f5f4f3e1d 100644 --- a/modules/stnBidAdapter.md +++ b/modules/stnBidAdapter.md @@ -13,7 +13,7 @@ Module that connects to STN's demand sources. The STN adapter requires setup and approval from the STN. Please reach out to hb@stnvideo.com to create an STN account. -The adapter supports Video(instream) & Banner. +The adapter supports Video(instream), Banner, Native and multi-format bid requests. # Bid Parameters ## Video diff --git a/modules/sublimeBidAdapter.js b/modules/sublimeBidAdapter.js index a29265ce9cd..eaae877ba0e 100644 --- a/modules/sublimeBidAdapter.js +++ b/modules/sublimeBidAdapter.js @@ -1,6 +1,7 @@ import { logInfo, generateUUID, formatQS, triggerPixel, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; +import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -152,7 +153,7 @@ function buildRequests(validBidRequests, bidderRequest) { pbav: SUBLIME_VERSION, // Current Prebid params prebidVersion: '$prebid.version$', - currencyCode: config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY, + currencyCode: getCurrencyFromBidderRequest(bidderRequest) || DEFAULT_CURRENCY, timeout: (typeof bidderRequest === 'object' && !!bidderRequest) ? bidderRequest.timeout : config.getConfig('bidderTimeout'), }; diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index c9c68c25e79..7befe826382 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -159,29 +159,9 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { let deals = membership.deals; // Get list of Deals the user is mapped to deals.forEach((deal) => { deal = JSON.parse(deal); - if (bidResponse.dealId == deal.id) { - // Check if the bid response deal Id matches to the deals mapped to the user + if (bidResponse.dealId == deal.id) { // Check if the bid response deal Id matches to the deals mapped to the user let token = dapUtils.dapGetTokenFromLocalStorage(); - let url = - config.params.pixelUrl + - '?token=' + - token + - '&ad_id=' + - bidResponse.adId + - '&bidder=' + - bidResponse.bidder + - '&bidder_code=' + - bidResponse.bidderCode + - '&cpm=' + - bidResponse.cpm + - '&creative_id=' + - bidResponse.creativeId + - '&deal_id=' + - bidResponse.dealId + - '&media_type=' + - bidResponse.mediaType + - '&response_timestamp=' + - bidResponse.responseTimestamp; + let url = config.params.pixelUrl + '?token=' + token + '&ad_id=' + bidResponse.adId + '&bidder=' + bidResponse.bidder + '&bidder_code=' + bidResponse.bidderCode + '&cpm=' + bidResponse.cpm + '&creative_id=' + bidResponse.creativeId + '&deal_id=' + bidResponse.dealId + '&media_type=' + bidResponse.mediaType + '&response_timestamp=' + bidResponse.responseTimestamp; bidResponse.ad = `${bidResponse.ad}
'}, + { + ad: '
', + beacon: '', + cpm: 36.0008, + ids: {}, + w: 320, + h: 100, + locationid: '58279', + rotation: '0', + scheduleid: '512603', + sdktype: '0', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000, + adomain: ['advertiserdomain.com'] + }, ], - adomain: ['advertiserdomain.com'] }, native: { - ad: '<\!DOCTYPE html>↵ ↵ ↵ ↵ ↵
↵ ', - beacon: '', - cpm: 36.0008, displaytype: '1', - ids: {}, location_params: null, locationid: '58279', - adomain: ['advertiserdomain.com'], - native_ad: { - assets: [ - { - data: { - label: 'accompanying_text', - value: 'AD' - }, - id: 501 - }, - { - data: { - label: 'optout_url', - value: 'https://supership.jp/optout/#' - }, - id: 502 - }, - { - data: { - ext: { - black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', + results: [ + { + ad: '
', + beacon: '', + cpm: 36.0008, + ids: {}, + adomain: ['advertiserdomain.com'], + scheduleid: '512603', + creativeid: '1k2kv35vsa5r', + dealid: 'fd5sa5fa7f', + ttl: 1000, + native: { + assets: [ + { + data: { + label: 'accompanying_text', + value: 'AD' + }, + id: 501 }, - label: 'information_icon_url', - value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', - id: 503 - } - }, - { - id: 1, - required: 1, - title: {text: 'Title'} - }, - { - id: 2, - img: { - h: 250, - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - w: 300 - }, - required: 1 - }, - { - id: 3, - img: { - h: 300, - url: 'https://placehold.jp/300x300.png', - w: 300 + { + data: { + label: 'optout_url', + value: 'https://example.com/optout/#' + }, + id: 502 + }, + { + data: { + ext: { + black_back: 'https://example.com/icon_adg_optout_26x26_white.png', + }, + label: 'information_icon_url', + value: 'https://example.com/icon_adg_optout_26x26_gray.png', + id: 503 + } + }, + { + id: 1, + required: 1, + title: {text: 'Title'} + }, + { + id: 2, + img: { + h: 250, + url: 'https://example.com/adg-sample-ad/img/300x250.png', + w: 300 + }, + required: 1 + }, + { + id: 3, + img: { + h: 300, + url: 'https://example.com/300x300.png', + w: 300 + }, + required: 1 + }, + { + data: {value: 'Description'}, + id: 5, + required: 0 + }, + { + data: {value: 'CTA'}, + id: 6, + required: 0 + }, + { + data: {value: 'Sponsored'}, + id: 4, + required: 0 + } + ], + imptrackers: ['https://example.com/1x1.gif'], + link: { + clicktrackers: [ + 'https://example.com/1x1_clicktracker_access.gif' + ], + url: 'https://example.com' }, - required: 1 - }, - { - data: {value: 'Description'}, - id: 5, - required: 0 }, - { - data: {value: 'CTA'}, - id: 6, - required: 0 - }, - { - data: {value: 'Sponsored'}, - id: 4, - required: 0 - } - ], - imptrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'], - link: { - clicktrackers: [ - 'https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif' - ], - url: 'https://supership.jp' - }, - }, - results: [ - {ad: 'Creative<\/body>'} + } ], rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000 }, upperBillboard: { - 'ad': '<\!DOCTYPE html>\n \n \n \n \n \n \n \n
\n \n
\n \n', - 'beacon': '', - 'beaconurl': 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif', - 'cpm': 80, - 'creative_params': {}, - 'creativeid': 'ScaleOut_2146187', - 'dealid': '2134-132864_newformat_test', 'displaytype': '1', 'h': 180, 'ids': { 'anid': '', 'diid': '', 'idfa': '', - 'soc': 'Xm8Q8cCo5r8AAHCCMg0AAAAA' + 'soc': 'yyyyyyyy' }, 'location_params': { 'option': { @@ -470,284 +1112,50 @@ describe('AdgenerationAdapter', function () { 'locationid': '143038', 'results': [ { - 'ad': '<\!DOCTYPE html>\n \n \n \n \n \n \n \n
\n \n
\n \n', - 'beacon': '', - 'beaconurl': 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif', + 'ad': '
', + 'beacon': '', + 'beaconurl': 'http://example.com', 'cpm': 80, 'creative_params': {}, 'creativeid': 'ScaleOut_2146187', 'dealid': '2134-132864_newformat_test', 'h': 180, - 'landing_url': 'https://supership.jp/', + 'landing_url': 'https://example.com/', 'rparams': {}, 'scheduleid': '1233323', 'trackers': { 'imp': [ - 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif' + 'https://example.com' ], 'viewable_imp': [ - 'https://tg.socdm.com/aux/inview?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' + 'https://example.com' ], 'viewable_measured': [ - 'https://tg.socdm.com/aux/measured?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' + 'https://example.com' ] }, 'ttl': 1000, - 'vastxml': '\n \n \n SOADS\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n https://i.socdm.com/a/2/2095/2091787/20210810043037-de3e74aec30f36.mp4\n https://i.socdm.com/a/2/2095/2091788/20210810043037-6dd368dc91d507.mp4\n https://i.socdm.com/a/2/2095/2091789/20210810043037-c8eb814ddd85c4.webm\n https://i.socdm.com/a/2/2095/2091790/20210810043037-0a7f74c40268ab.webm\n \n \n \n \n \n \n', + 'vastxml': '', 'vcpm': 0, 'w': 320, 'weight': 1 } ], 'rotation': '0', - 'scheduleid': '1233323', - 'sdktype': '0', - 'trackers': { - 'imp': [ - 'https://tg.socdm.com/bc/v3?b=Y2hzbT0yOTQsNGZiM2NkNWVpZD0xNDMwMzgmcG9zPVNTUExPQyZhZD0xMjMzMzIzLzI2MTA2MS4yNjU3OTkuMTIzMzMyMy8yMTY2NDY2LzE1NDQxMC8xNDMwMzg6U1NQTE9DOiovaWR4PTA7ZHNwaWQ9MTtkaTI9MjEzNC0xMzI4NjRfbmV3Zm9ybWF0X3Rlc3Q7ZnR5cGU9Mztwcj15TXB3O3ByYj15UTtwcm89eVE7cHJvYz1KUFk7Y3JkMnk9MTExLjkyO2NyeTJkPTAuMDA4OTM0OTUzNTM4MjQxNjAxMztwcnY9aWp6QVZtWW9wbmJUV1B0cWhtZEN1ZWRXNDd0MjU1MEtmYjFWYmI3SzsmZXg9MTYzMzMyNzU4MyZjdD0xNjMzMzI3NTgzODAzJnNyPWh0dHA-&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA&ctsv=m-ad240&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&seqctx=gat3by5hZy5mdHlwZaEz&t=.gif' - ], - 'viewable_imp': [ - 'https://tg.socdm.com/aux/inview?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' - ], - 'viewable_measured': [ - 'https://tg.socdm.com/aux/measured?creative_id=2166466&ctsv=m-ad240&extra_field=idx%3D0%3Bdspid%3D1%3Bdi2%3D2134-132864_newformat_test%3Bftype%3D3%3Bprb%3D0%3Bpro%3D0%3Bproc%3DJPY%3Bcrd2y%3D111.92%3Bcry2d%3D0.0089349535382416013%3Bsspm%3D0%3Bsom%3D0.2%3Borgm%3D0%3Btechm%3D0%3Bssp_margin%3D0%3Bso_margin%3D0.2%3Borg_margin%3D0%3Btech_margin%3D0%3Bbs%3Dclassic%3B&family_id=1233323&id=143038&loglocation_id=154410&lookupname=143038%3ASSPLOC%3A*&pos=SSPLOC&schedule_id=261061.265799.1233323&seqid=be38bdb4-74a7-14a7-3439-b7f1ea8b4f33&seqtime=1633327583803&xuid=Xm8Q8cCo5r8AAHCCMg0AAAAA' - ] - }, - 'ttl': 1000, - 'vastxml': '\n \n \n SOADS\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n 00:00:15\n \n \n \n \n \n \n \n \n \n \n \n \n https://i.socdm.com/a/2/2095/2091787/20210810043037-de3e74aec30f36.mp4\n https://i.socdm.com/a/2/2095/2091788/20210810043037-6dd368dc91d507.mp4\n https://i.socdm.com/a/2/2095/2091789/20210810043037-c8eb814ddd85c4.webm\n https://i.socdm.com/a/2/2095/2091790/20210810043037-0a7f74c40268ab.webm\n \n \n \n \n \n \n', - 'vcpm': 0, - 'w': 320, } }, - emptyAdomain: { - banner: { - ad: '
', - beacon: '', - cpm: 36.0008, - displaytype: '1', - ids: {}, - w: 320, - h: 100, - location_params: null, - locationid: '58279', - rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000, - results: [ - {ad: '<\!DOCTYPE html>
'}, - ], - adomain: [] - }, - native: { - ad: '<\!DOCTYPE html>↵ ↵ ↵ ↵ ↵
↵ ', - beacon: '', - cpm: 36.0008, - displaytype: '1', - ids: {}, - location_params: null, - locationid: '58279', - adomain: [], - native_ad: { - assets: [ - { - data: { - label: 'accompanying_text', - value: 'AD' - }, - id: 501 - }, - { - data: { - label: 'optout_url', - value: 'https://supership.jp/optout/#' - }, - id: 502 - }, - { - data: { - ext: { - black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', - }, - label: 'information_icon_url', - value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', - id: 503 - } - }, - { - id: 1, - required: 1, - title: {text: 'Title'} - }, - { - id: 2, - img: { - h: 250, - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - w: 300 - }, - required: 1 - }, - { - id: 3, - img: { - h: 300, - url: 'https://placehold.jp/300x300.png', - w: 300 - }, - required: 1 - }, - { - data: {value: 'Description'}, - id: 5, - required: 0 - }, - { - data: {value: 'CTA'}, - id: 6, - required: 0 - }, - { - data: {value: 'Sponsored'}, - id: 4, - required: 0 - } - ], - imptrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'], - link: { - clicktrackers: [ - 'https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif' - ], - url: 'https://supership.jp' - }, - }, - results: [ - {ad: 'Creative<\/body>'} - ], - rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000 - } - }, - noAdomain: { - banner: { - ad: '
', - beacon: '', - cpm: 36.0008, - displaytype: '1', - ids: {}, - w: 320, - h: 100, - location_params: null, - locationid: '58279', - rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000, - results: [ - {ad: '<\!DOCTYPE html>
'}, - ], - }, - native: { - ad: '<\!DOCTYPE html>↵ ↵ ↵ ↵ ↵
↵ ', - beacon: '', - cpm: 36.0008, - displaytype: '1', - ids: {}, - location_params: null, - locationid: '58279', - native_ad: { - assets: [ - { - data: { - label: 'accompanying_text', - value: 'AD' - }, - id: 501 - }, - { - data: { - label: 'optout_url', - value: 'https://supership.jp/optout/#' - }, - id: 502 - }, - { - data: { - ext: { - black_back: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_white.png', - }, - label: 'information_icon_url', - value: 'https://i.socdm.com/sdk/img/icon_adg_optout_26x26_gray.png', - id: 503 - } - }, - { - id: 1, - required: 1, - title: {text: 'Title'} - }, - { - id: 2, - img: { - h: 250, - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - w: 300 - }, - required: 1 - }, - { - id: 3, - img: { - h: 300, - url: 'https://placehold.jp/300x300.png', - w: 300 - }, - required: 1 - }, - { - data: {value: 'Description'}, - id: 5, - required: 0 - }, - { - data: {value: 'CTA'}, - id: 6, - required: 0 - }, - { - data: {value: 'Sponsored'}, - id: 4, - required: 0 - } - ], - imptrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'], - link: { - clicktrackers: [ - 'https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif' - ], - url: 'https://supership.jp' - }, - }, - results: [ - {ad: 'Creative<\/body>'} - ], - rotation: '0', - scheduleid: '512603', - sdktype: '0', - creativeid: '1k2kv35vsa5r', - dealid: 'fd5sa5fa7f', - ttl: 1000 - } - } - }; + } + serverResponse.emptyAdomain = {}; + serverResponse.emptyAdomain.banner = JSON.parse(JSON.stringify(serverResponse.normal.banner)); + serverResponse.emptyAdomain.banner.results[0].adomain = []; + serverResponse.emptyAdomain.native = JSON.parse(JSON.stringify(serverResponse.normal.native)); + serverResponse.emptyAdomain.native.results[0].adomain = []; + + serverResponse.noAdomain = {}; + serverResponse.noAdomain.banner = JSON.parse(JSON.stringify(serverResponse.normal.banner)); + delete serverResponse.noAdomain.banner.results[0].adomain; + serverResponse.noAdomain.native = JSON.parse(JSON.stringify(serverResponse.normal.native)); + delete serverResponse.noAdomain.native.results[0].adomain; const bidResponses = { normal: { @@ -761,7 +1169,7 @@ describe('AdgenerationAdapter', function () { currency: 'JPY', netRevenue: true, ttl: 1000, - ad: '
', + ad: '
', adomain: ['advertiserdomain.com'] }, native: { @@ -775,26 +1183,26 @@ describe('AdgenerationAdapter', function () { netRevenue: true, ttl: 1000, adomain: ['advertiserdomain.com'], - ad: '↵
', + ad: '
', native: { title: 'Title', image: { - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', + url: 'https://example.com/adg-sample-ad/img/300x250.png', height: 250, width: 300 }, icon: { - url: 'https://placehold.jp/300x300.png', + url: 'https://example.com/300x300.png', height: 300, width: 300 }, sponsoredBy: 'Sponsored', body: 'Description', cta: 'CTA', - privacyLink: 'https://supership.jp/optout/#', - clickUrl: 'https://supership.jp', - clickTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif'], - impressionTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'] + privacyLink: 'https://example.com/optout/#', + clickUrl: 'https://example.com', + clickTrackers: ['https://example.com/1x1_clicktracker_access.gif'], + impressionTrackers: ['https://example.com/1x1.gif'] }, mediaType: NATIVE }, @@ -805,109 +1213,13 @@ describe('AdgenerationAdapter', function () { height: 180, creativeId: 'ScaleOut_2146187', dealId: '2134-132864_newformat_test', - currency: 'JPY', + currency: 'USD', netRevenue: true, ttl: 1000, - ad: ``, + ad: ``, adomain: ['advertiserdomain.com'] }, - }, - emptyAdomain: { - banner: { - requestId: '2f6ac468a9c15e', - cpm: 36.0008, - width: 320, - height: 100, - creativeId: '1k2kv35vsa5r', - dealId: 'fd5sa5fa7f', - currency: 'JPY', - netRevenue: true, - ttl: 1000, - ad: '
', - adomain: [] - }, - native: { - requestId: '2f6ac468a9c15e', - cpm: 36.0008, - width: 1, - height: 1, - creativeId: '1k2kv35vsa5r', - dealId: 'fd5sa5fa7f', - currency: 'JPY', - netRevenue: true, - ttl: 1000, - adomain: [], - ad: '↵
', - native: { - title: 'Title', - image: { - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - height: 250, - width: 300 - }, - icon: { - url: 'https://placehold.jp/300x300.png', - height: 300, - width: 300 - }, - sponsoredBy: 'Sponsored', - body: 'Description', - cta: 'CTA', - privacyLink: 'https://supership.jp/optout/#', - clickUrl: 'https://supership.jp', - clickTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif'], - impressionTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'] - }, - mediaType: NATIVE - }, - }, - noAdomain: { - banner: { - requestId: '2f6ac468a9c15e', - cpm: 36.0008, - width: 320, - height: 100, - creativeId: '1k2kv35vsa5r', - dealId: 'fd5sa5fa7f', - currency: 'JPY', - netRevenue: true, - ttl: 1000, - ad: '
', - }, - native: { - requestId: '2f6ac468a9c15e', - cpm: 36.0008, - width: 1, - height: 1, - creativeId: '1k2kv35vsa5r', - dealId: 'fd5sa5fa7f', - currency: 'JPY', - netRevenue: true, - ttl: 1000, - ad: '↵
', - native: { - title: 'Title', - image: { - url: 'https://sdk-temp.s3-ap-northeast-1.amazonaws.com/adg-sample-ad/img/300x250.png', - height: 250, - width: 300 - }, - icon: { - url: 'https://placehold.jp/300x300.png', - height: 300, - width: 300 - }, - sponsoredBy: 'Sponsored', - body: 'Description', - cta: 'CTA', - privacyLink: 'https://supership.jp/optout/#', - clickUrl: 'https://supership.jp', - clickTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1_clicktracker_access.gif'], - impressionTrackers: ['https://adg-dummy-dsp.s3-ap-northeast-1.amazonaws.com/1x1.gif'] - }, - mediaType: NATIVE - } - }, + } }; it('no bid responses', function () { @@ -916,107 +1228,115 @@ describe('AdgenerationAdapter', function () { }); it('handles ADGBrowserM responses', function () { - config.setConfig({ - currency: { - adServerCurrency: 'JPY' + setCurrencyConfig({ adServerCurrency: 'USD' }); + const bidderRequest = { + refererInfo: { + page: 'https://example.com' } + }; + return addFPDToBidderRequest(bidderRequest).then(res => { + const result = spec.interpretResponse({body: serverResponse.normal.upperBillboard}, { ...bidRequests.upperBillboard, bidderRequest: res })[0]; + expect(result.requestId).to.equal(bidResponses.normal.upperBillboard.requestId); + expect(result.width).to.equal(bidResponses.normal.upperBillboard.width); + expect(result.height).to.equal(bidResponses.normal.upperBillboard.height); + expect(result.creativeId).to.equal(bidResponses.normal.upperBillboard.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.upperBillboard.dealId); + expect(result.currency).to.equal(bidResponses.normal.upperBillboard.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.upperBillboard.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.upperBillboard.ttl); + expect(result.ad).to.equal(bidResponses.normal.upperBillboard.ad); + setCurrencyConfig({}); }); - const result = spec.interpretResponse({body: serverResponse.normal.upperBillboard}, bidRequests.upperBillboard)[0]; - expect(result.requestId).to.equal(bidResponses.normal.upperBillboard.requestId); - expect(result.width).to.equal(bidResponses.normal.upperBillboard.width); - expect(result.height).to.equal(bidResponses.normal.upperBillboard.height); - expect(result.creativeId).to.equal(bidResponses.normal.upperBillboard.creativeId); - expect(result.dealId).to.equal(bidResponses.normal.upperBillboard.dealId); - expect(result.currency).to.equal(bidResponses.normal.upperBillboard.currency); - expect(result.netRevenue).to.equal(bidResponses.normal.upperBillboard.netRevenue); - expect(result.ttl).to.equal(bidResponses.normal.upperBillboard.ttl); - expect(result.ad).to.equal(bidResponses.normal.upperBillboard.ad); }); it('handles banner responses for empty adomain', function () { const result = spec.interpretResponse({body: serverResponse.emptyAdomain.banner}, bidRequests.banner)[0]; - expect(result.requestId).to.equal(bidResponses.emptyAdomain.banner.requestId); - expect(result.width).to.equal(bidResponses.emptyAdomain.banner.width); - expect(result.height).to.equal(bidResponses.emptyAdomain.banner.height); - expect(result.creativeId).to.equal(bidResponses.emptyAdomain.banner.creativeId); - expect(result.dealId).to.equal(bidResponses.emptyAdomain.banner.dealId); - expect(result.currency).to.equal(bidResponses.emptyAdomain.banner.currency); - expect(result.netRevenue).to.equal(bidResponses.emptyAdomain.banner.netRevenue); - expect(result.ttl).to.equal(bidResponses.emptyAdomain.banner.ttl); - expect(result.ad).to.equal(bidResponses.emptyAdomain.banner.ad); + expect(result.requestId).to.equal(bidResponses.normal.banner.requestId); + expect(result.width).to.equal(bidResponses.normal.banner.width); + expect(result.height).to.equal(bidResponses.normal.banner.height); + expect(result.creativeId).to.equal(bidResponses.normal.banner.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.banner.dealId); + expect(result.currency).to.equal(bidResponses.normal.banner.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.banner.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.banner.ttl); + expect(result.ad).to.equal(bidResponses.normal.banner.ad); + // no adomian expect(result).to.not.have.any.keys('meta'); expect(result).to.not.have.any.keys('advertiserDomains'); }); it('handles native responses for empty adomain', function () { const result = spec.interpretResponse({body: serverResponse.emptyAdomain.native}, bidRequests.native)[0]; - expect(result.requestId).to.equal(bidResponses.emptyAdomain.native.requestId); - expect(result.width).to.equal(bidResponses.emptyAdomain.native.width); - expect(result.height).to.equal(bidResponses.emptyAdomain.native.height); - expect(result.creativeId).to.equal(bidResponses.emptyAdomain.native.creativeId); - expect(result.dealId).to.equal(bidResponses.emptyAdomain.native.dealId); - expect(result.currency).to.equal(bidResponses.emptyAdomain.native.currency); - expect(result.netRevenue).to.equal(bidResponses.emptyAdomain.native.netRevenue); - expect(result.ttl).to.equal(bidResponses.emptyAdomain.native.ttl); - expect(result.native.title).to.equal(bidResponses.emptyAdomain.native.native.title); - expect(result.native.image.url).to.equal(bidResponses.emptyAdomain.native.native.image.url); - expect(result.native.image.height).to.equal(bidResponses.emptyAdomain.native.native.image.height); - expect(result.native.image.width).to.equal(bidResponses.emptyAdomain.native.native.image.width); - expect(result.native.icon.url).to.equal(bidResponses.emptyAdomain.native.native.icon.url); - expect(result.native.icon.width).to.equal(bidResponses.emptyAdomain.native.native.icon.width); - expect(result.native.icon.height).to.equal(bidResponses.emptyAdomain.native.native.icon.height); - expect(result.native.sponsoredBy).to.equal(bidResponses.emptyAdomain.native.native.sponsoredBy); - expect(result.native.body).to.equal(bidResponses.emptyAdomain.native.native.body); - expect(result.native.cta).to.equal(bidResponses.emptyAdomain.native.native.cta); - expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.emptyAdomain.native.native.privacyLink); - expect(result.native.clickUrl).to.equal(bidResponses.emptyAdomain.native.native.clickUrl); - expect(result.native.impressionTrackers[0]).to.equal(bidResponses.emptyAdomain.native.native.impressionTrackers[0]); - expect(result.native.clickTrackers[0]).to.equal(bidResponses.emptyAdomain.native.native.clickTrackers[0]); - expect(result.mediaType).to.equal(bidResponses.emptyAdomain.native.mediaType); + expect(result.requestId).to.equal(bidResponses.normal.native.requestId); + expect(result.width).to.equal(bidResponses.normal.native.width); + expect(result.height).to.equal(bidResponses.normal.native.height); + expect(result.creativeId).to.equal(bidResponses.normal.native.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.native.dealId); + expect(result.currency).to.equal(bidResponses.normal.native.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.native.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.native.ttl); + expect(result.native.title).to.equal(bidResponses.normal.native.native.title); + expect(result.native.image.url).to.equal(bidResponses.normal.native.native.image.url); + expect(result.native.image.height).to.equal(bidResponses.normal.native.native.image.height); + expect(result.native.image.width).to.equal(bidResponses.normal.native.native.image.width); + expect(result.native.icon.url).to.equal(bidResponses.normal.native.native.icon.url); + expect(result.native.icon.width).to.equal(bidResponses.normal.native.native.icon.width); + expect(result.native.icon.height).to.equal(bidResponses.normal.native.native.icon.height); + expect(result.native.sponsoredBy).to.equal(bidResponses.normal.native.native.sponsoredBy); + expect(result.native.body).to.equal(bidResponses.normal.native.native.body); + expect(result.native.cta).to.equal(bidResponses.normal.native.native.cta); + expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.normal.native.native.privacyLink); + expect(result.native.clickUrl).to.equal(bidResponses.normal.native.native.clickUrl); + expect(result.native.impressionTrackers[0]).to.equal(bidResponses.normal.native.native.impressionTrackers[0]); + expect(result.native.clickTrackers[0]).to.equal(bidResponses.normal.native.native.clickTrackers[0]); + expect(result.mediaType).to.equal(bidResponses.normal.native.mediaType); + // no adomain expect(result).to.not.have.any.keys('meta'); expect(result).to.not.have.any.keys('advertiserDomains'); }); it('handles banner responses for no adomain', function () { const result = spec.interpretResponse({body: serverResponse.noAdomain.banner}, bidRequests.banner)[0]; - expect(result.requestId).to.equal(bidResponses.noAdomain.banner.requestId); - expect(result.width).to.equal(bidResponses.noAdomain.banner.width); - expect(result.height).to.equal(bidResponses.noAdomain.banner.height); - expect(result.creativeId).to.equal(bidResponses.noAdomain.banner.creativeId); - expect(result.dealId).to.equal(bidResponses.noAdomain.banner.dealId); - expect(result.currency).to.equal(bidResponses.noAdomain.banner.currency); - expect(result.netRevenue).to.equal(bidResponses.noAdomain.banner.netRevenue); - expect(result.ttl).to.equal(bidResponses.noAdomain.banner.ttl); - expect(result.ad).to.equal(bidResponses.noAdomain.banner.ad); + expect(result.requestId).to.equal(bidResponses.normal.banner.requestId); + expect(result.width).to.equal(bidResponses.normal.banner.width); + expect(result.height).to.equal(bidResponses.normal.banner.height); + expect(result.creativeId).to.equal(bidResponses.normal.banner.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.banner.dealId); + expect(result.currency).to.equal(bidResponses.normal.banner.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.banner.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.banner.ttl); + expect(result.ad).to.equal(bidResponses.normal.banner.ad); + // no adomain expect(result).to.not.have.any.keys('meta'); expect(result).to.not.have.any.keys('advertiserDomains'); }); it('handles native responses for no adomain', function () { const result = spec.interpretResponse({body: serverResponse.noAdomain.native}, bidRequests.native)[0]; - expect(result.requestId).to.equal(bidResponses.noAdomain.native.requestId); - expect(result.width).to.equal(bidResponses.noAdomain.native.width); - expect(result.height).to.equal(bidResponses.noAdomain.native.height); - expect(result.creativeId).to.equal(bidResponses.noAdomain.native.creativeId); - expect(result.dealId).to.equal(bidResponses.noAdomain.native.dealId); - expect(result.currency).to.equal(bidResponses.noAdomain.native.currency); - expect(result.netRevenue).to.equal(bidResponses.noAdomain.native.netRevenue); - expect(result.ttl).to.equal(bidResponses.noAdomain.native.ttl); - expect(result.native.title).to.equal(bidResponses.noAdomain.native.native.title); - expect(result.native.image.url).to.equal(bidResponses.noAdomain.native.native.image.url); - expect(result.native.image.height).to.equal(bidResponses.noAdomain.native.native.image.height); - expect(result.native.image.width).to.equal(bidResponses.noAdomain.native.native.image.width); - expect(result.native.icon.url).to.equal(bidResponses.noAdomain.native.native.icon.url); - expect(result.native.icon.width).to.equal(bidResponses.noAdomain.native.native.icon.width); - expect(result.native.icon.height).to.equal(bidResponses.noAdomain.native.native.icon.height); - expect(result.native.sponsoredBy).to.equal(bidResponses.noAdomain.native.native.sponsoredBy); - expect(result.native.body).to.equal(bidResponses.noAdomain.native.native.body); - expect(result.native.cta).to.equal(bidResponses.noAdomain.native.native.cta); - expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.noAdomain.native.native.privacyLink); - expect(result.native.clickUrl).to.equal(bidResponses.noAdomain.native.native.clickUrl); - expect(result.native.impressionTrackers[0]).to.equal(bidResponses.noAdomain.native.native.impressionTrackers[0]); - expect(result.native.clickTrackers[0]).to.equal(bidResponses.noAdomain.native.native.clickTrackers[0]); - expect(result.mediaType).to.equal(bidResponses.noAdomain.native.mediaType); + expect(result.requestId).to.equal(bidResponses.normal.native.requestId); + expect(result.width).to.equal(bidResponses.normal.native.width); + expect(result.height).to.equal(bidResponses.normal.native.height); + expect(result.creativeId).to.equal(bidResponses.normal.native.creativeId); + expect(result.dealId).to.equal(bidResponses.normal.native.dealId); + expect(result.currency).to.equal(bidResponses.normal.native.currency); + expect(result.netRevenue).to.equal(bidResponses.normal.native.netRevenue); + expect(result.ttl).to.equal(bidResponses.normal.native.ttl); + expect(result.native.title).to.equal(bidResponses.normal.native.native.title); + expect(result.native.image.url).to.equal(bidResponses.normal.native.native.image.url); + expect(result.native.image.height).to.equal(bidResponses.normal.native.native.image.height); + expect(result.native.image.width).to.equal(bidResponses.normal.native.native.image.width); + expect(result.native.icon.url).to.equal(bidResponses.normal.native.native.icon.url); + expect(result.native.icon.width).to.equal(bidResponses.normal.native.native.icon.width); + expect(result.native.icon.height).to.equal(bidResponses.normal.native.native.icon.height); + expect(result.native.sponsoredBy).to.equal(bidResponses.normal.native.native.sponsoredBy); + expect(result.native.body).to.equal(bidResponses.normal.native.native.body); + expect(result.native.cta).to.equal(bidResponses.normal.native.native.cta); + expect(decodeURIComponent(result.native.privacyLink)).to.equal(bidResponses.normal.native.native.privacyLink); + expect(result.native.clickUrl).to.equal(bidResponses.normal.native.native.clickUrl); + expect(result.native.impressionTrackers[0]).to.equal(bidResponses.normal.native.native.impressionTrackers[0]); + expect(result.native.clickTrackers[0]).to.equal(bidResponses.normal.native.native.clickTrackers[0]); + expect(result.mediaType).to.equal(bidResponses.normal.native.mediaType); + // no adomain expect(result).to.not.have.any.keys('meta'); expect(result).to.not.have.any.keys('advertiserDomains'); }); diff --git a/test/spec/modules/adlooxAdServerVideo_spec.js b/test/spec/modules/adlooxAdServerVideo_spec.js index 58277bc830d..c941b9dc710 100644 --- a/test/spec/modules/adlooxAdServerVideo_spec.js +++ b/test/spec/modules/adlooxAdServerVideo_spec.js @@ -34,7 +34,6 @@ describe('Adloox Ad Server Video', function () { }; const analyticsOptions = { - js: 'https://j.adlooxtracking.com/ads/js/tfav_adl_%%clientid%%.js', client: 'adlooxtest', clientid: 127, platformid: 0, diff --git a/test/spec/modules/adlooxAnalyticsAdapter_spec.js b/test/spec/modules/adlooxAnalyticsAdapter_spec.js index fa8204a9dc5..964eb6650e9 100644 --- a/test/spec/modules/adlooxAnalyticsAdapter_spec.js +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -45,6 +45,11 @@ describe('Adloox Analytics Adapter', function () { adapter: analyticsAdapter }); describe('enableAnalytics', function () { + afterEach(function () { + analyticsAdapter.disableAnalytics(); + expect(analyticsAdapter.context).is.null; + }); + describe('invalid options', function () { it('should require options', function (done) { adapterManager.enableAnalytics({ @@ -68,6 +73,32 @@ describe('Adloox Analytics Adapter', function () { done(); }); + it('should accept subdomains of adlooxtracking.com for options.js', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.js = 'https://test.adlooxtracking.com/test.js'; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.not.null; + + done(); + }); + + it('should reject non-subdomains of adlooxtracking.com for options.js', function (done) { + const analyticsOptionsLocal = utils.deepClone(analyticsOptions); + analyticsOptionsLocal.js = 'https://example.com/test.js'; + + adapterManager.enableAnalytics({ + provider: analyticsAdapterName, + options: analyticsOptionsLocal + }); + expect(analyticsAdapter.context).is.null; + + done(); + }); + it('should reject non-function options.toselector', function (done) { const analyticsOptionsLocal = utils.deepClone(analyticsOptions); analyticsOptionsLocal.toselector = esplode; diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 3c5bc713100..c82671040d0 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -6,7 +6,7 @@ import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; import { getStorageManager } from 'src/storageManager.js'; import { getGlobal } from '../../../src/prebidGlobal'; -import {getUnixTimestampFromNow} from 'src/utils.js'; +import {getUnixTimestampFromNow, getWindowTop} from 'src/utils.js'; describe('adnuntiusBidAdapter', function () { const URL = 'https://ads.adnuntius.delivery/i?tzo='; @@ -48,11 +48,14 @@ describe('adnuntiusBidAdapter', function () { }); const tzo = new Date().getTimezoneOffset(); - const ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid`; + const win = getWindowTop() || window; + const screen = win.screen.availWidth + 'x' + win.screen.availHeight; + const viewport = win.innerWidth + 'x' + win.innerHeight; + const ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid&screen=${screen}&viewport=${viewport}`; const ENDPOINT_URL = `${ENDPOINT_URL_BASE}&userId=${usi}`; const ENDPOINT_URL_NOCOOKIE = `${ENDPOINT_URL_BASE}&userId=${usi}&noCookies=true`; const ENDPOINT_URL_SEGMENTS = `${ENDPOINT_URL_BASE}&segments=segment1,segment2,segment3&userId=${usi}`; - const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&consentString=consentString&gdpr=1&userId=${usi}`; + const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&consentString=consentString&gdpr=1&screen=${screen}&viewport=${viewport}&userId=${usi}`; const adapter = newBidder(spec); const bidderRequests = [ @@ -198,6 +201,7 @@ describe('adnuntiusBidAdapter', function () { 'urlsEsc': { 'destination': 'https%3A%2F%2Fads.adnuntius.delivery%2Fc%2FyQtMUwYBn5P4v72WJMqLW4z7uJOBFXJTfjoRyz0z_wsAAAAQCtjQz9kbGWD4nuZy3q6HaCYxq6Lckz2kThplNb227EJdQ5032jcIGkf-UrPmXCU2EbXVaQ3Ok6_FNLuIDTONJyx6ZZCB10wGqA3OaSe1EqwQp84u1_5iQZAWDk73UYf7_vcIypn7ev-SICZ3qaevb2jYSRqTVZx6AiBZQQGlzlOOrbZU9AU1F-JwTds-YV3qtJHGlxI2peWFIuxFlOYyeX9Kzg%3Fct%3D673%26r%3Dhttp%253A%252F%252Fadnuntius.com' }, + 'advertiserDomains': ['fred.com', 'george.com'], 'destinationUrls': { 'destination': 'https://adnuntius.com' }, @@ -669,7 +673,7 @@ describe('adnuntiusBidAdapter', function () { const bid = request[0].bid[0] expect(bid).to.have.property('bidId'); expect(request[0]).to.have.property('url'); - expect(request[0].url).to.equal(ENDPOINT_URL.replace('format=prebid', 'format=prebid&so=overridden-value')); + expect(request[0].url).to.equal(ENDPOINT_URL.replace('&userId', '&so=overridden-value&userId')); expect(request[0]).to.have.property('data'); expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}],"context":"https://canonical.com/something-else.html","canonical":"https://canonical.com/page.html"}'); }); @@ -1064,47 +1068,6 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); }); - - it('should user in user', function () { - config.setBidderConfig({ - bidders: ['adnuntius'], - }); - const req = [ - { - bidId: 'adn-000000000008b6bc', - bidder: 'adnuntius', - params: { - auId: '000000000008b6bc', - network: 'adnuntius', - userId: 'different_user_id' - } - } - ] - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(req, { bids: req })); - expect(request.length).to.equal(1); - expect(request[0]).to.have.property('url') - expect(request[0].url).to.equal(`${ENDPOINT_URL_BASE}&userId=different_user_id`); - }); - - it('should handle no user specified', function () { - config.setBidderConfig({ - bidders: ['adnuntius'], - }); - const req = [ - { - bidId: 'adn-000000000008b6bc', - bidder: 'adnuntius', - params: { - auId: '000000000008b6bc', - network: 'adnuntius' - } - } - ] - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(req, { bids: req })); - expect(request.length).to.equal(1); - expect(request[0]).to.have.property('url') - expect(request[0].url).to.equal(ENDPOINT_URL); - }); }); describe('user privacy', function () { @@ -1296,8 +1259,9 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[0].currency).to.equal(deal.bid.currency); expect(interpretedResponse[0].netRevenue).to.equal(false); expect(interpretedResponse[0].meta).to.have.property('advertiserDomains'); - expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(1); - expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('adnuntius.com'); + expect(interpretedResponse[0].meta.advertiserDomains).to.have.lengthOf(2); + expect(interpretedResponse[0].meta.advertiserDomains[0]).to.equal('fred.com'); + expect(interpretedResponse[0].meta.advertiserDomains[1]).to.equal('george.com'); expect(interpretedResponse[0].ad).to.equal(serverResponse.body.adUnits[0].deals[0].html); expect(interpretedResponse[0].ttl).to.equal(360); expect(interpretedResponse[0].dealId).to.equal('abc123xyz'); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index b03bba95357..b12744d3a28 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -17,6 +17,7 @@ const aliasEP = { 'ocm': 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', '9dotsmedia': 'https://ghb.platform.audiodots.com/v2/auction/', 'indicue': 'https://ghb.console.indicue.com/v2/auction/', + 'stellormedia': 'https://ghb.ads.stellormedia.com/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; diff --git a/test/spec/modules/adtrgtmeBidAdapter_spec.js b/test/spec/modules/adtrgtmeBidAdapter_spec.js index fce270b4ea7..a74844857ce 100644 --- a/test/spec/modules/adtrgtmeBidAdapter_spec.js +++ b/test/spec/modules/adtrgtmeBidAdapter_spec.js @@ -1,249 +1,238 @@ import { expect } from 'chai'; import { config } from 'src/config.js'; -import { BANNER } from 'src/mediaTypes.js'; import { spec } from 'modules/adtrgtmeBidAdapter.js'; -const DEFAULT_SID = 1220291391; -const DEFAULT_ZID = 1836455615; -const DEFAULT_BID_ID = '84ab500420319d'; +const DEFAULT_SID = '1220291391'; +const DEFAULT_ZID = '1836455615'; +const DEFAULT_PIXEL_URL = 'https://cdn.adtarget.me/libs/1x1.gif'; +const DEFAULT_BANNER_URL = 'https://cdn.adtarget.me/libs/banner/300x250.jpg'; +const BIDDER_VERSION = '1.0.5'; +const PREBIDJS_VERSION = '$prebid.version$'; -const DEFAULT_AD_UNIT_CODE = '/1220291391/header-banner'; -const DEFAULT_AD_UNIT_TYPE = BANNER; -const DEFAULT_PARAMS_BID_OVERRIDE = {}; - -const ADAPTER_VERSION = '1.0.0'; -const PREBID_VERSION = '$prebid.version$'; -const INTEGRATION_METHOD = 'prebid.js'; - -// Utility functions -const generateBidRequest = ({bidId, adUnitCode, bidOverrideObject, zid, ortb2}) => { - const bidRequest = { +const createBidRequest = ({bidId, adUnitCode, bidOverride, zid, ortb2}) => { + const bR = { + auctionId: 'f3c594t-3o0ch1b0rm-ayn93c3o0ch1b0rm', adUnitCode, - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', bidId, - bidderRequestsCount: 1, bidder: 'adtrgtme', - bidderRequestId: '7101db09af0db2', - bidderWinsCount: 0, mediaTypes: {}, params: { - bidOverride: bidOverrideObject + sid: DEFAULT_SID, + bidOverride }, - src: 'client', transactionId: '5b17b67d-7704-4732-8cc9-5b1723e9bcf9', ortb2 }; - const bannerObj = { + bR.mediaTypes.banner = { sizes: [[300, 250]] }; + bR.sizes = [[300, 250]]; - bidRequest.mediaTypes.banner = bannerObj; - bidRequest.sizes = [[300, 250]]; - - bidRequest.params.sid = DEFAULT_SID; - if (typeof zid == 'number') { - bidRequest.params.zid = zid; + if (typeof zid == 'string') { + bR.params.zid = zid; } - - return bidRequest; + return bR; } -let generateBidderRequest = (bidRequestArray, adUnitCode, ortb2 = {}) => { - const bidderRequest = { - adUnitCode: adUnitCode || 'default-adUnitCode', +let createBidderRequest = (arr, code = 'default-code', ortb2 = {}) => { + return { + adUnitCode: code, auctionId: 'd4c83a3b-18e4-4208-b98b-63848449c7aa', - auctionStart: new Date().getTime(), bidderCode: 'adtrgtme', - bidderRequestId: '112f1c7c5d399a', - bids: bidRequestArray, + bids: arr, refererInfo: { - page: 'https://publisher-test.com', - reachedTop: true, - isAmp: false, - numIframes: 0, - stack: ['https://publisher-test.com'], + page: 'https://partner-site.com', + stack: ['https://partner-site.com'], }, gdprConsent: { consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', vendorData: {}, gdprApplies: true }, - start: new Date().getTime(), timeout: 1000, ortb2 }; - - return bidderRequest; }; -const generateBuildRequestMock = ({bidId, adUnitCode, adUnitType, zid, bidOverrideObject, pubIdMode, ortb2}) => { - const bidRequestConfig = { - bidId: bidId || DEFAULT_BID_ID, - adUnitCode: adUnitCode || DEFAULT_AD_UNIT_CODE, - adUnitType: adUnitType || DEFAULT_AD_UNIT_TYPE, +const createRequestMock = ({bidId, adUnitCode, type, zid, bidOverride, pubIdMode, ortb2}) => { + const bR = createBidRequest({ + bidId: bidId || '84ab500420319d', + adUnitCode: adUnitCode || '/1220291391/banner', + type: type || 'banner', zid: zid || DEFAULT_ZID, - bidOverrideObject: bidOverrideObject || DEFAULT_PARAMS_BID_OVERRIDE, - + bidOverride: bidOverride || {}, pubIdMode: pubIdMode || false, ortb2: ortb2 || {} - }; - const bidRequest = generateBidRequest(bidRequestConfig); - const validBidRequests = [bidRequest]; - const bidderRequest = generateBidderRequest(validBidRequests, adUnitCode, ortb2); - - return { bidRequest, validBidRequests, bidderRequest } + }); + return { bidRequest: bR, validBR: [bR], bidderRequest: createBidderRequest([bR], adUnitCode, ortb2) } }; -const generateAdmPayload = (admPayloadType) => { - let ADM_PAYLOAD; - switch (admPayloadType) { +const createAdm = (type) => { + let ADM; + switch (type) { case 'banner': - ADM_PAYLOAD = ''; // banner + ADM = ` + `; break; - default: ''; break; + default: 'Ad is here'; break; }; - - return ADM_PAYLOAD; + return ADM; }; -const generateResponseMock = (admPayloadType) => { - const bidResponse = { - id: 'fc0c35df-21fb-4f93-9ebd-88759dbe31f9', - impid: '274395c06a24e5', - adm: generateAdmPayload(admPayloadType), - price: 1, - w: 300, - h: 250, - crid: 'ssp-placement-name', - adomain: ['advertiser-domain.com'] - }; - - const serverResponse = { +const createResponseMock = (type) => { + const sR = { body: { - id: 'fc0c35df-21fb-4f93-9ebd-88759dbe31f9', - seatbid: [{ bid: [ bidResponse ], seat: 13107 }] + id: '5qtvluj7bk6jhzmqwu4zzulv', + seatbid: [{ + bid: [{ + id: '5qtvluj7bk6jhzmqwu4zzulv', + impid: 'y7v7iu0uljj94rbjcw9', + adm: createAdm(type), + price: 1, + w: 300, + h: 250, + crid: 'creativeid', + adomain: ['some-advertiser-domain.com'] + }], + seat: 12345 + }] } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({adUnitType: admPayloadType}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const { validBR, bidderRequest } = createRequestMock({type}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; - return {serverResponse, data, bidderRequest}; + return {sR, data, bidderRequest}; } -// Unit tests -describe('adtrgtme Bid Adapter:', () => { +describe('Adtrgtme Bid Adapter:', () => { it('PLACEHOLDER TO PASS GULP', () => { - const obj = {}; - expect(obj).to.be.an('object'); + expect({}).to.be.an('object'); }); - describe('Validate basic properties', () => { - it('should define the correct bidder code', () => { + describe('check basic properties', () => { + it('should define bidder code', () => { expect(spec.code).to.equal('adtrgtme') }); }); - describe('getUserSyncs()', () => { - const IMAGE_PIXEL_URL = 'http://image-pixel.com/foo/bar?1234&baz=true'; - const IFRAME_ONE_URL = 'http://image-iframe.com/foo/bar?1234&baz=true'; - const IFRAME_TWO_URL = 'http://image-iframe-two.com/foo/bar?1234&baz=true'; + describe('getUserSyncs', () => { + const BAD_SYNC_URL = 'cdn.adtarget.me/libs/1x1.gif?image&rnd=5fr55r'; + const IMAGE_SYNC_URL = `${DEFAULT_PIXEL_URL}?image&rnd=5fr55r`; + const IFRAME_SYNC_ONE_URL = `${DEFAULT_PIXEL_URL}?iframe1&rnd=5fr55r`; + const IFRAME_SYNC_TWO_URL = `${DEFAULT_PIXEL_URL}?iframe2&rnd=5fr55r`; - let serverResponses = []; + let sRs = []; beforeEach(() => { - serverResponses[0] = { + sRs[0] = { body: { ext: { - pixels: `` + pixels: [ + ['image', BAD_SYNC_URL], + ['invalid', IMAGE_SYNC_URL], + ['image', IMAGE_SYNC_URL], + ['iframe', IFRAME_SYNC_ONE_URL], + ['iframe', IFRAME_SYNC_TWO_URL] + ] } } } }); after(() => { - serverResponses = undefined; + sRs = undefined; }); - it('for only iframe enabled syncs', () => { - let syncOptions = { + it('sync check bad url and type in pixels', () => { + let opt = { + iframeEnabled: true, + pixelEnabled: true + }; + let pixels = spec.getUserSyncs(opt, sRs); + expect(pixels.length).to.equal(3); + }); + + it('sync check for iframe only', () => { + let opt = { iframeEnabled: true, pixelEnabled: false }; - let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); - expect(pixelsObjects.length).to.equal(2); - expect(pixelsObjects).to.deep.equal( + let pixels = spec.getUserSyncs(opt, sRs); + expect(pixels.length).to.equal(2); + expect(pixels).to.deep.equal( [ - {type: 'iframe', 'url': IFRAME_ONE_URL}, - {type: 'iframe', 'url': IFRAME_TWO_URL} + {type: 'iframe', 'url': IFRAME_SYNC_ONE_URL}, + {type: 'iframe', 'url': IFRAME_SYNC_TWO_URL} ] ) }); - it('for only pixel enabled syncs', () => { - let syncOptions = { + it('sync check for image only', () => { + let opt = { iframeEnabled: false, pixelEnabled: true }; - let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); - expect(pixelsObjects.length).to.equal(1); - expect(pixelsObjects).to.deep.equal( + let pixels = spec.getUserSyncs(opt, sRs); + expect(pixels.length).to.equal(1); + expect(pixels).to.deep.equal( [ - {type: 'image', 'url': IMAGE_PIXEL_URL} + {type: 'image', 'url': IMAGE_SYNC_URL} ] ) }); - it('for both pixel and iframe enabled syncs', () => { - let syncOptions = { + it('Sync for iframe and image', () => { + let opt = { iframeEnabled: true, pixelEnabled: true }; - let pixelsObjects = spec.getUserSyncs(syncOptions, serverResponses); - expect(pixelsObjects.length).to.equal(3); - expect(pixelsObjects).to.deep.equal( + let pixels = spec.getUserSyncs(opt, sRs); + expect(pixels.length).to.equal(3); + expect(pixels).to.deep.equal( [ - {type: 'iframe', 'url': IFRAME_ONE_URL}, - {type: 'image', 'url': IMAGE_PIXEL_URL}, - {type: 'iframe', 'url': IFRAME_TWO_URL} + {type: 'image', 'url': IMAGE_SYNC_URL}, + {type: 'iframe', 'url': IFRAME_SYNC_ONE_URL}, + {type: 'iframe', 'url': IFRAME_SYNC_TWO_URL} ] ) }); }); - // Validate Bid Requests - describe('isBidRequestValid()', () => { - const INVALID_INPUT = [ + describe('Check if bid request is OK', () => { + const BAD_VALUE = [ {}, {params: {}}, - {params: {sid: '1234', zid: '4321'}}, - {params: {sid: '1220291391', zid: 4321}}, + {params: {sid: 1220291391, zid: '1836455615'}}, + {params: {sid: '1220291391', zid: 'A'}}, + {params: {sid: '', zid: '1836455615'}}, + {params: {sid: '', zid: 'A'}}, {params: {zid: ''}}, - {params: {sid: '', zid: ''}}, ]; - INVALID_INPUT.forEach(input => { - it(`should determine that the bid is INVALID for the input ${JSON.stringify(input)}`, () => { - expect(spec.isBidRequestValid(input)).to.be.false; + BAD_VALUE.forEach(value => { + it(`should determine bad bid for ${JSON.stringify(value)}`, () => { + expect(spec.isOK(value)).to.be.false; }); }); - it('should determine that the bid is VALID if sid and zid are present on the params object', () => { - const validBid = { - params: { - sid: 1220291391, - zid: 1836455615 - } - }; - expect(spec.isBidRequestValid(validBid)).to.be.true; + const OK_VALUE = [ + {params: {sid: '1220291391'}}, + {params: {sid: '1220291391', zid: 1836455615}}, + {params: {sid: '1220291391', zid: '1836455615'}}, + {params: {sid: '1220291391', zid: '1836455615A'}}, + ]; + + OK_VALUE.forEach(value => { + it(`should determine OK bid for ${JSON.stringify(value)}`, () => { + expect(spec.isOK(value)).to.be.true; + }); }); }); - describe('Price Floor module support:', () => { + describe('Bidfloor support:', () => { it('should get bidfloor from getFloor method', () => { - const { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); - bidRequest.params.bidOverride = {cur: 'EUR'}; + const { bidRequest, validBR, bidderRequest } = createRequestMock({}); + bidRequest.params.bidOverride = {cur: 'AUD'}; bidRequest.getFloor = (floorObj) => { return { floor: bidRequest.floors.values[floorObj.mediaType + '|300x250'], @@ -252,203 +241,167 @@ describe('adtrgtme Bid Adapter:', () => { } }; bidRequest.floors = { - currency: 'EUR', + currency: 'AUD', values: { - 'banner|300x250': 5.55 + 'banner|300x250': 1.111 } }; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.cur).to.deep.equal(['EUR']); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.cur).to.deep.equal(['AUD']); expect(data.imp[0].bidfloor).is.a('number'); - expect(data.imp[0].bidfloor).to.equal(5.55); + expect(data.imp[0].bidfloor).to.equal(1.111); }); }); - describe('Schain module support:', () => { - it('should send Global or Bidder specific schain', function () { - const { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); + describe('Schain support:', () => { + it('should send schains', function () { + const { bidRequest, validBR, bidderRequest } = createRequestMock({}); const globalSchain = { ver: '1.0', complete: 1, nodes: [{ - asi: 'some-platform.com', - sid: '111111', + asi: 'adtarget-partner.com', + sid: '1234567890', rid: bidRequest.bidId, hp: 1 }] }; bidRequest.schain = globalSchain; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const data = spec.buildRequests(validBR, bidderRequest)[0].data; const schain = data.source.ext.schain; expect(schain.nodes.length).to.equal(1); expect(schain).to.equal(globalSchain); }); }); - describe('First party data module - "Site" support (ortb2):', () => { - // Should not allow invalid "site" data types - const INVALID_ORTB2_TYPES = [ null, [], 123, 'unsupportedKeyName', true, false, undefined ]; - INVALID_ORTB2_TYPES.forEach(param => { - it(`should not allow invalid site types to be added to bid-request: ${JSON.stringify(param)}`, () => { - const ortb2 = { site: param } - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site[param]).to.be.undefined; + describe('Check Site obj support (ortb2):', () => { + const BAD_ORTB2_TYPES = [ null, [], 123, 'invalidID', true, false, undefined ]; + BAD_ORTB2_TYPES.forEach(key => { + it(`should remove bad site data: ${JSON.stringify(key)}`, () => { + const ortb2 = { site: key } + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.site[key]).to.be.undefined; }); }); - // Should add valid "site" params - const VALID_SITE_STRINGS = ['name', 'domain', 'page', 'ref', 'keywords']; - const VALID_SITE_ARRAYS = ['cat', 'sectioncat', 'pagecat']; + const OK_SITE_STR = ['id', 'name', 'domain', 'page', 'ref', 'keywords']; + const OK_SITE_ARR = ['cat', 'sectioncat', 'pagecat']; - VALID_SITE_STRINGS.forEach(param => { - it(`should allow supported site keys to be added bid-request: ${JSON.stringify(param)}`, () => { + OK_SITE_STR.forEach(key => { + it(`should allow supported site keys to be added bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { site: { - [param]: 'something' + [key]: 'some value here' } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site[param]).to.exist; - expect(data.site[param]).to.be.a('string'); - expect(data.site[param]).to.be.equal(ortb2.site[param]); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.site[key]).to.exist; + expect(data.site[key]).to.be.a('string'); + expect(data.site[key]).to.be.equal(ortb2.site[key]); }); }); - VALID_SITE_ARRAYS.forEach(param => { - it(`should determine that the ortb2.site Array key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + OK_SITE_ARR.forEach(key => { + it(`should determine valid keys of the ortb2 site and append to the bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { site: { - [param]: ['something'] + [key]: ['some value here'] } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site[param]).to.exist; - expect(data.site[param]).to.be.a('array'); - expect(data.site[param]).to.be.equal(ortb2.site[param]); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.site[key]).to.exist; + expect(data.site[key]).to.be.a('array'); + expect(data.site[key]).to.be.equal(ortb2.site[key]); }); }); - // Should not allow invalid "site.content" data types - INVALID_ORTB2_TYPES.forEach(param => { - it(`should determine that the ortb2.site.content key is invalid and should not be added to bid-request: ${JSON.stringify(param)}`, () => { - const ortb2 = { - site: { - content: param - } - }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site.content).to.be.undefined; - }); - }); - - // Should not allow invalid "site.content" keys - it(`should not allow invalid ortb2.site.content object keys to be added to bid-request: {custom object}`, () => { - const ortb2 = { - site: { - content: { - fake: 'news', - unreal: 'param', - counterfit: 'data' - } - } - }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site.content).to.be.a('object'); - }); - - // Should append valid "site.content" keys - const VALID_CONTENT_STRINGS = ['id', 'title', 'language']; - VALID_CONTENT_STRINGS.forEach(param => { - it(`should determine that the ortb2.site String key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const OK_CONTENT_STR = ['id', 'title', 'language']; + OK_CONTENT_STR.forEach(key => { + it(`should determine that the ortb2.site String key is ok and append to the bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { site: { content: { - [param]: 'something' + [key]: 'some value here' } } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site.content[param]).to.exist; - expect(data.site.content[param]).to.be.a('string'); - expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.site.content[key]).to.exist; + expect(data.site.content[key]).to.be.a('string'); + expect(data.site.content[key]).to.be.equal(ortb2.site.content[key]); }); }); - const VALID_CONTENT_ARRAYS = ['cat']; - VALID_CONTENT_ARRAYS.forEach(param => { - it(`should determine that the ortb2.site Array key is valid and append to the bid-request: ${JSON.stringify(param)}`, () => { + const OK_CONTENT_ARR = ['cat']; + OK_CONTENT_ARR.forEach(key => { + it(`should determine that the ortb2.site key is ok and append to the bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { site: { content: { - [param]: ['something', 'something-else'] + [key]: ['some value here', 'something-else'] } } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.site.content[param]).to.be.a('array'); - expect(data.site.content[param]).to.be.equal(ortb2.site.content[param]); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.site.content[key]).to.be.a('array'); + expect(data.site.content[key]).to.be.equal(ortb2.site.content[key]); }); }); }); - describe('First party data module - "User" support (ortb2):', () => { - // Global ortb2.user validations - // Should not allow invalid "user" data types - const INVALID_ORTB2_TYPES = [ null, [], 'unsupportedKeyName', true, false, undefined ]; - INVALID_ORTB2_TYPES.forEach(param => { - it(`should not allow invalid site types to be added to bid-request: ${JSON.stringify(param)}`, () => { - const ortb2 = { user: param } - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user[param]).to.be.undefined; + describe('Check ortb2 user support:', () => { + const BAD_ORTB2_TYPES = [ null, [], 'unsupportedKeyName', true, false, undefined ]; + BAD_ORTB2_TYPES.forEach(key => { + it(`should not allow bad site types to be added to bid request: ${JSON.stringify(key)}`, () => { + const ortb2 = { user: key } + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.user[key]).to.be.undefined; }); }); - // Should add valid "user" params - const VALID_USER_STRINGS = ['id', 'buyeruid', 'gender', 'keywords', 'customdata']; - VALID_USER_STRINGS.forEach(param => { - it(`should allow supported user string keys to be added bid-request: ${JSON.stringify(param)}`, () => { + const OK_USER_STR = ['id', 'buyeruid', 'gender', 'keywords', 'customdata']; + OK_USER_STR.forEach(key => { + it(`should allow valid keys of the user to be added to bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { user: { - [param]: 'something' + [key]: 'some value here' } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user[param]).to.exist; - expect(data.user[param]).to.be.a('string'); - expect(data.user[param]).to.be.equal(ortb2.user[param]); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.user[key]).to.exist; + expect(data.user[key]).to.be.a('string'); + expect(data.user[key]).to.be.equal(ortb2.user[key]); }); }); - const VALID_USER_OBJECTS = ['ext']; - VALID_USER_OBJECTS.forEach(param => { - it(`should allow supported user extObject keys to be added to the bid-request: ${JSON.stringify(param)}`, () => { + const OK_USER_OBJECTS = ['ext']; + OK_USER_OBJECTS.forEach(key => { + it(`should allow user ext to be added to the bid request: ${JSON.stringify(key)}`, () => { const ortb2 = { user: { - [param]: {a: '123', b: '456'} + [key]: {a: '123', b: '456'} } }; - const { validBidRequests, bidderRequest } = generateBuildRequestMock({ortb2}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.user[param]).to.exist; - expect(data.user[param]).to.be.a('object'); - expect(data.user[param]).to.be.deep.include({[param]: {a: '123', b: '456'}}); + const { validBR, bidderRequest } = createRequestMock({ortb2}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.user[key]).to.exist; + expect(data.user[key]).to.be.a('object'); + expect(data.user[key].a).to.be.equal('123'); + expect(data.user[key].b).to.be.equal('456'); config.setConfig({ortb2: {}}); }); }); - // adUnit.ortb2Imp.ext.data - it(`should allow adUnit.ortb2Imp.ext.data object to be added to the bid-request`, () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) - validBidRequests[0].ortb2Imp = { + it(`should allow adUnit.ortb2Imp.ext.data object to be added to the bid request`, () => { + let { validBR, bidderRequest } = createRequestMock({}) + validBR[0].ortb2Imp = { ext: { data: { pbadslot: 'homepage-top-rect', @@ -456,43 +409,42 @@ describe('adtrgtme Bid Adapter:', () => { } } }; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.imp[0].ext.data).to.deep.equal(validBidRequests[0].ortb2Imp.ext.data); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.imp[0].ext.data).to.deep.equal(validBR[0].ortb2Imp.ext.data); }); - // adUnit.ortb2Imp.instl - it(`should allow adUnit.ortb2Imp.instl numeric boolean "1" to be added to the bid-request`, () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) - validBidRequests[0].ortb2Imp = { + it(`should allow adUnit.ortb2Imp.instl numeric boolean "1" to be added to the bid request`, () => { + let { validBR, bidderRequest } = createRequestMock({}) + validBR[0].ortb2Imp = { instl: 1 }; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; - expect(data.imp[0].instl).to.deep.equal(validBidRequests[0].ortb2Imp.instl); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; + expect(data.imp[0].instl).to.deep.equal(validBR[0].ortb2Imp.instl); }); - it(`should prevent adUnit.ortb2Imp.instl boolean "true" to be added to the bid-request`, () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) - validBidRequests[0].ortb2Imp = { + it(`should prevent adUnit.ortb2Imp.instl boolean "true" to be added to the bid request`, () => { + let { validBR, bidderRequest } = createRequestMock({}) + validBR[0].ortb2Imp = { instl: true }; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const data = spec.buildRequests(validBR, bidderRequest)[0].data; expect(data.imp[0].instl).to.not.exist; }); - it(`should prevent adUnit.ortb2Imp.instl boolean "false" to be added to the bid-request`, () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({}) - validBidRequests[0].ortb2Imp = { + it(`should prevent adUnit.ortb2Imp.instl boolean false to be added to the bid request`, () => { + let { validBR, bidderRequest } = createRequestMock({}) + validBR[0].ortb2Imp = { instl: false }; - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const data = spec.buildRequests(validBR, bidderRequest)[0].data; expect(data.imp[0].instl).to.not.exist; }); }); - describe('GDPR & Consent:', () => { + describe('GDPR:', () => { it('should return request objects that do not send cookies if purpose 1 consent is not provided', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + const { validBR, bidderRequest } = createRequestMock({}); bidderRequest.gdprConsent = { - consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + consentString: 'BOtmiBKO234234tmiBKABABAEN234AFAAAAACeAAA', apiVersion: 2, vendorData: { purpose: { @@ -503,22 +455,22 @@ describe('adtrgtme Bid Adapter:', () => { }, gdprApplies: true }; - const options = spec.buildRequests(validBidRequests, bidderRequest)[0].options; - expect(options.withCredentials).to.be.false; + const opt = spec.buildRequests(validBR, bidderRequest)[0].options; + expect(opt.withCredentials).to.be.false; }); }); - describe('Endpoint & Impression Request Mode:', () => { + describe('Endpoint & Impression request mode:', () => { it('should route request to config override endpoint', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const sid = validBidRequests[0].params.sid; - const testOverrideEndpoint = 'http://new_bidder_host.com/ssp?s='; + const { validBR, bidderRequest } = createRequestMock({}); + const sid = validBR[0].params.sid; + const testOverrideEndpoint = 'http://partner-adserv-domain.com/ssp?s='; config.setConfig({ adtrgtme: { endpoint: testOverrideEndpoint } }); - const response = spec.buildRequests(validBidRequests, bidderRequest)[0]; + const response = spec.buildRequests(validBR, bidderRequest)[0]; expect(response).to.deep.include( { method: 'POST', @@ -530,9 +482,9 @@ describe('adtrgtme Bid Adapter:', () => { config.setConfig({ adtrgtme: {} }); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const sid = validBidRequests[0].params.sid; - const response = spec.buildRequests(validBidRequests, bidderRequest); + const { validBR, bidderRequest } = createRequestMock({}); + const sid = validBR[0].params.sid; + const response = spec.buildRequests(validBR, bidderRequest); expect(response[0]).to.deep.include({ method: 'POST', url: 'https://z.cdn.adtarget.market/ssp?prebid&s=' + sid @@ -540,13 +492,10 @@ describe('adtrgtme Bid Adapter:', () => { }); it('should return a single request object for single request mode', () => { - let { bidRequest, validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const BID_ID_2 = '84ab50xxxxx'; - const BID_ZID_2 = 98876543210; - const AD_UNIT_CODE_2 = 'test-ad-unit-code-123'; - const { bidRequest: bidRequest2 } = generateBuildRequestMock({bidId: BID_ID_2, zid: BID_ZID_2, adUnitCode: AD_UNIT_CODE_2}); - validBidRequests = [bidRequest, bidRequest2]; - bidderRequest.bids = validBidRequests; + let { bidRequest, validBR, bidderRequest } = createRequestMock({}); + const { bidRequest: mock } = createRequestMock({bidId: '6heos7ks8z0j', zid: '98876543210', adUnitCode: 'bidder-code'}); + validBR = [bidRequest, mock]; + bidderRequest.bids = validBR; config.setConfig({ adtrgtme: { @@ -554,60 +503,57 @@ describe('adtrgtme Bid Adapter:', () => { } }); - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + const data = spec.buildRequests(validBR, bidderRequest).data; expect(data.imp).to.be.an('array').with.lengthOf(2); expect(data.imp[0]).to.deep.include({ - id: DEFAULT_BID_ID, + id: '84ab500420319d', ext: { - dfp_ad_unit_code: DEFAULT_AD_UNIT_CODE + dfp_ad_unit_code: '/1220291391/banner' } }); expect(data.imp[1]).to.deep.include({ - id: BID_ID_2, - tagid: BID_ZID_2, + id: '6heos7ks8z0j', + tagid: '98876543210', ext: { - dfp_ad_unit_code: AD_UNIT_CODE_2 + dfp_ad_unit_code: 'bidder-code' } }); }); }); - describe('Validate request filtering:', () => { - it('should not return request when no bids are present', function () { + describe('validate request filtering:', () => { + it('should return undefined when no bids', function () { let request = spec.buildRequests([]); expect(request).to.be.undefined; }); - it('buildRequests(): should return an array with the correct amount of request objects', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const response = spec.buildRequests(validBidRequests, bidderRequest).bidderRequest; + it('buildRequests should return correct amount of objects', () => { + const { validBR, bidderRequest } = createRequestMock({}); + const response = spec.buildRequests(validBR, bidderRequest).bidderRequest; expect(response.bids).to.be.an('array').to.have.lengthOf(1); }); }); - describe('Request Headers validation:', () => { - it('should return request objects with the relevant custom headers and content type declaration', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); + describe('Validate request headers:', () => { + it('should return request objects with the custom headers and content type', () => { + const { validBR, bidderRequest } = createRequestMock({}); bidderRequest.gdprConsent.gdprApplies = false; - const options = spec.buildRequests(validBidRequests, bidderRequest).options; - expect(options).to.deep.equal( + const opt = spec.buildRequests(validBR, bidderRequest).options; + expect(opt).to.deep.equal( { contentType: 'application/json', - customHeaders: { - 'x-openrtb-version': '2.5' - }, withCredentials: true }); }); }); - describe('Request Payload oRTB bid validation:', () => { - it('should generate a valid openRTB bid-request object in the data field', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + describe('Request data oRTB bid validation:', () => { + it('should create valid oRTB bid request object in the data field', () => { + const { validBR, bidderRequest } = createRequestMock({}); + const data = spec.buildRequests(validBR, bidderRequest).data; expect(data.site).to.deep.include({ id: bidderRequest.bids[0].params.sid, page: bidderRequest.refererInfo.page @@ -629,12 +575,8 @@ describe('adtrgtme Bid Adapter:', () => { expect(data.source).to.deep.equal({ ext: { hb: 1, - adapterver: ADAPTER_VERSION, - prebidver: PREBID_VERSION, - integration: { - name: INTEGRATION_METHOD, - ver: PREBID_VERSION - } + bidderver: BIDDER_VERSION, + prebidjsver: PREBIDJS_VERSION }, fd: 1 }); @@ -642,52 +584,49 @@ describe('adtrgtme Bid Adapter:', () => { expect(data.cur).to.deep.equal(['USD']); }); - it('should generate a valid openRTB imp.ext object in the bid-request', () => { - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const bid = validBidRequests[0]; - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + it('should create valid oRTB imp.ext in the bid request', () => { + const { validBR, bidderRequest } = createRequestMock({}); + const data = spec.buildRequests(validBR, bidderRequest).data; expect(data.imp[0].ext).to.deep.equal({ - dfp_ad_unit_code: DEFAULT_AD_UNIT_CODE + dfp_ad_unit_code: '/1220291391/banner' }); }); - it('should use siteId value as site.id in the outbound bid-request when using "pubId" integration mode', () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({pubIdMode: true}); - validBidRequests[0].params.sid = 9876543210; - const data = spec.buildRequests(validBidRequests, bidderRequest).data; - expect(data.site.id).to.equal(9876543210); + it('should use siteId value as site.id', () => { + let { validBR, bidderRequest } = createRequestMock({pubIdMode: true}); + validBR[0].params.sid = '9876543210'; + const data = spec.buildRequests(validBR, bidderRequest).data; + expect(data.site.id).to.equal('9876543210'); }); - it('should use placementId value as imp.tagid in the outbound bid-request when using "zid"', () => { - let { validBidRequests, bidderRequest } = generateBuildRequestMock({}), - TEST_ZID = 54321; - validBidRequests[0].params.zid = TEST_ZID; - const data = spec.buildRequests(validBidRequests, bidderRequest).data; + it('should use placementId value as imp.tagid when using "zid"', () => { + let { validBR, bidderRequest } = createRequestMock({}), + TEST_ZID = '54321'; + validBR[0].params.zid = TEST_ZID; + const data = spec.buildRequests(validBR, bidderRequest).data; expect(data.imp[0].tagid).to.deep.equal(TEST_ZID); }); }); - describe('Request Payload oRTB bid.imp validation:', () => { - // Validate Banner imp imp when adtrgtme.mode=undefined - it('should generate a valid "Banner" imp object', () => { + describe('Request oRTB bid.imp validation:', () => { + it('should create valid default Banner imp', () => { config.setConfig({ adtrgtme: {} }); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const { validBR, bidderRequest } = createRequestMock({}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; expect(data.imp[0].banner).to.deep.equal({ mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], format: [{w: 300, h: 250}] }); }); - // Validate Banner imp - it('should generate a valid "Banner" imp object', () => { + it('should create valid Banner imp', () => { config.setConfig({ adtrgtme: { mode: 'banner' } }); - const { validBidRequests, bidderRequest } = generateBuildRequestMock({}); - const data = spec.buildRequests(validBidRequests, bidderRequest)[0].data; + const { validBR, bidderRequest } = createRequestMock({}); + const data = spec.buildRequests(validBR, bidderRequest)[0].data; expect(data.imp[0].banner).to.deep.equal({ mimes: ['text/html', 'text/javascript', 'application/javascript', 'image/jpg'], format: [{w: 300, h: 250}] @@ -697,104 +636,105 @@ describe('adtrgtme Bid Adapter:', () => { describe('interpretResponse()', () => { describe('for mediaTypes: "banner"', () => { - it('should insert banner payload into response[0].ad', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(response[0].ad).to.equal(''); + it('should insert banner object into response[0].ad', () => { + const { sR, bidderRequest } = createResponseMock('banner'); + const response = spec.interpretResponse(sR, {bidderRequest}); + expect(response[0].ad).to.equal(` + `); expect(response[0].mediaType).to.equal('banner'); }) }); - describe('Support Advertiser domains', () => { + describe('Support adomains', () => { it('should append bid-response adomain to meta.advertiserDomains', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const { sR, bidderRequest } = createResponseMock('banner'); + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].meta.advertiserDomains).to.be.a('array'); - expect(response[0].meta.advertiserDomains[0]).to.equal('advertiser-domain.com'); + expect(response[0].meta.advertiserDomains[0]).to.equal('some-advertiser-domain.com'); }) }); - describe('bid response Ad ID / Creative ID', () => { + describe('Check response Ad ID / Creative ID', () => { it('should use adId if it exists in the bid-response', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const { sR, bidderRequest } = createResponseMock('banner'); const adId = 'bid-response-adId'; - serverResponse.body.seatbid[0].bid[0].adId = adId; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + sR.body.seatbid[0].bid[0].adId = adId; + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].adId).to.equal(adId); }); it('should use impid if adId does not exist in the bid-response', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - const impid = '25b6c429c1f52f'; - serverResponse.body.seatbid[0].bid[0].impid = impid; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const { sR, bidderRequest } = createResponseMock('banner'); + const impid = 'y7v7iu0uljj94rbjcw9'; + sR.body.seatbid[0].bid[0].impid = impid; + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].adId).to.equal(impid); }); it('should use crid if adId & impid do not exist in the bid-response', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const { sR, bidderRequest } = createResponseMock('banner'); const crid = 'passback-12579'; - serverResponse.body.seatbid[0].bid[0].impid = undefined; - serverResponse.body.seatbid[0].bid[0].crid = crid; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + sR.body.seatbid[0].bid[0].impid = undefined; + sR.body.seatbid[0].bid[0].crid = crid; + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].adId).to.equal(crid); }); }); describe('Time To Live (ttl)', () => { - const UNSUPPORTED_TTL_FORMATS = ['string', [1, 2, 3], true, false, null, undefined]; - UNSUPPORTED_TTL_FORMATS.forEach(param => { + const BAD_TTL_FORMATS = ['string', [1, 2, 3], true, false, null, undefined]; + BAD_TTL_FORMATS.forEach(key => { it('should not allow unsupported global adtrgtme.ttl formats and default to 300', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const { sR, bidderRequest } = createResponseMock('banner'); config.setConfig({ - adtrgtme: { ttl: param } + adtrgtme: { ttl: key } }); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].ttl).to.equal(300); }); - it('should not allow unsupported params.ttl formats and default to 300', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - bidderRequest.bids[0].params.ttl = param; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + it('should not set unsupported ttl formats and check default to 300', () => { + const { sR, bidderRequest } = createResponseMock('banner'); + bidderRequest.bids[0].params.ttl = key; + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].ttl).to.equal(300); }); }); - const UNSUPPORTED_TTL_VALUES = [-1, 3601]; - UNSUPPORTED_TTL_VALUES.forEach(param => { - it('should not allow invalid global adtrgtme.ttl values 3600 < ttl < 0 and default to 300', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); + const BAD_TTL_VALUES = [-1, 12345]; + BAD_TTL_VALUES.forEach(key => { + it('should not set bad global adtrgtme.ttl and check default to 300', () => { + const { sR, bidderRequest } = createResponseMock('banner'); config.setConfig({ - adtrgtme: { ttl: param } + adtrgtme: { ttl: key } }); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].ttl).to.equal(300); }); - it('should not allow invalid params.ttl values 3600 < ttl < 0 and default to 300', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - bidderRequest.bids[0].params.ttl = param; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + it('should not set bad keys.ttl values', () => { + const { sR, bidderRequest } = createResponseMock('banner'); + bidderRequest.bids[0].params.ttl = key; + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].ttl).to.equal(300); }); }); - it('should give presedence to Gloabl ttl over params.ttl ', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); + it('should set gloabl ttl over params.ttl if it presents', () => { + const { sR, bidderRequest } = createResponseMock('banner'); config.setConfig({ adtrgtme: { ttl: 500 } }); bidderRequest.bids[0].params.ttl = 400; - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].ttl).to.equal(500); }); }); - describe('Aliasing support', () => { + describe('Alias support', () => { it('should return undefined as the bidder code value', () => { - const { serverResponse, bidderRequest } = generateResponseMock('banner'); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); + const { sR, bidderRequest } = createResponseMock('banner'); + const response = spec.interpretResponse(sR, {bidderRequest}); expect(response[0].bidderCode).to.be.undefined; }); }); diff --git a/test/spec/modules/akceloBidAdapter_spec.js b/test/spec/modules/akceloBidAdapter_spec.js new file mode 100644 index 00000000000..5c519ea9834 --- /dev/null +++ b/test/spec/modules/akceloBidAdapter_spec.js @@ -0,0 +1,263 @@ +import { converter, spec } from 'modules/akceloBidAdapter.js'; +import * as utils from '../../../src/utils.js'; +import { deepClone } from '../../../src/utils.js'; +import sinon from 'sinon'; + +describe('Akcelo bid adapter tests', () => { + let sandBox; + + beforeEach(() => { + sandBox = sinon.createSandbox(); + sandBox.stub(utils, 'logError'); + }); + + afterEach(() => sandBox.restore()); + + const DEFAULT_BANNER_BID_REQUESTS = [ + { + adUnitCode: 'div-banner-id', + bidId: 'bid-123', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + bidder: 'akcelo', + params: { + siteId: 123, + adUnitId: 456, + }, + requestId: 'request-123', + } + ]; + + const DEFAULT_BANNER_BIDDER_REQUEST = { + bidderCode: 'akcelo', + bids: DEFAULT_BANNER_BID_REQUESTS, + }; + + const SAMPLE_RESPONSE = { + body: { + id: '12h712u7-k22g-8124-ab7a-h268s22dy271', + seatbid: [ + { + bid: [ + { + id: '1bh7jku7-ko2g-8654-ab72-h268abcde271', + impid: 'bid-123', + price: 0.6565, + adm: '

AD

', + adomain: ['abc.com'], + cid: '1242512', + crid: '535231', + w: 300, + h: 600, + mtype: 1, + ext: { + prebid: { + type: 'banner', + } + } + }, + ], + seat: '4212', + }, + ], + cur: 'EUR', + }, + }; + + describe('isBidRequestValid', () => { + it('should return true if params.siteId and params.adUnitId are set', () => { + const bidRequest = { + params: { + siteId: 123, + adUnitId: 456, + }, + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false if params.siteId is missing', () => { + const bidRequest = { + params: { + adUnitId: 456, + }, + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + + it('should return false if params.adUnitId is missing', () => { + const bidRequest = { + params: { + siteId: 123, + }, + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); + + describe('buildRequests', () => { + it('should build correct requests using ORTB converter', () => { + const request = spec.buildRequests( + DEFAULT_BANNER_BID_REQUESTS, + DEFAULT_BANNER_BIDDER_REQUEST + ); + const dataFromConverter = converter.toORTB({ + bidderRequest: DEFAULT_BANNER_BIDDER_REQUEST, + bidRequests: DEFAULT_BANNER_BID_REQUESTS, + }); + expect(request[0]).to.deep.equal({ + data: { ...dataFromConverter, id: request[0].data.id }, + method: 'POST', + url: 'https://s2s.sportslocalmedia.com/openrtb2/auction', + }); + }); + + it('should add site.publisher.ext.prebid.parentAccount to request object when siteId is defined', () => { + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + expect(request.data.site.publisher.ext.prebid.parentAccount).to.equal(123); + }); + + it('should not add site.publisher.ext.prebid.parentAccount to request object when siteId is not defined', () => { + const bidRequests = [ + { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { adUnitId: 456 } }, + ]; + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.site.publisher.ext.prebid.parentAccount).to.be.undefined; + }); + + it('should add ext.akcelo to imp object when siteId and adUnitId are defined', () => { + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + expect(request.data.imp[0].ext.akcelo).to.deep.equal({ + siteId: 123, + adUnitId: 456, + }); + }); + + it('should not add ext.akcelo.siteId to imp object when siteId is not defined', () => { + const bidRequests = [ + { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { adUnitId: 456 } }, + ]; + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.imp[0].ext.akcelo.siteId).to.be.undefined; + expect(utils.logError.calledOnce).to.equal(true); + expect(utils.logError.args[0][0]).to.equal('Missing parameter : siteId') + }); + + it('should not add ext.akcelo.adUnitId to imp object when adUnitId is not defined', () => { + const bidRequests = [ + { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { siteId: 123 } }, + ]; + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + expect(request.data.imp[0].ext.akcelo.adUnitId).to.be.undefined; + expect(utils.logError.calledOnce).to.equal(true); + expect(utils.logError.args[0][0]).to.equal('Missing parameter : adUnitId') + }); + + it('should add ext.akcelo.test=1 to imp object when param akcelo_demo is true', () => { + sandBox.stub(utils, 'getParameterByName').callsFake(() => 'true'); + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + expect(request.data.imp[0].ext.akcelo.test).to.equal(1); + }); + + it('should not add ext.akcelo.test to imp object when param akcelo_demo is not true', () => { + sandBox.stub(utils, 'getParameterByName').callsFake(() => 'something_else'); + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + expect(request.data.imp[0].ext.akcelo.test).to.be.undefined; + }); + + it('should not add ext.akcelo.test to imp object when param akcelo_demo is not defined', () => { + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + expect(request.data.imp[0].ext.akcelo.test).to.be.undefined; + }); + }); + + describe('interpretResponse', () => { + it('should return data returned by ORTB converter', () => { + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + const bids = spec.interpretResponse(SAMPLE_RESPONSE, request); + const responseFromConverter = converter.fromORTB({ + request: request.data, + response: SAMPLE_RESPONSE.body, + }); + expect(bids).to.deep.equal(responseFromConverter.bids); + }); + + it('should find the media type from bid.mtype if possible', () => { + const serverResponse = deepClone(SAMPLE_RESPONSE); + serverResponse.body.seatbid[0].bid[0].mtype = 2; + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + const bids = spec.interpretResponse(serverResponse, request); + expect(bids[0].mediaType).to.equal('video'); + }); + + it('should find the media type from bid.ext.prebid.type if mtype is not defined', () => { + const serverResponse = deepClone(SAMPLE_RESPONSE); + delete serverResponse.body.seatbid[0].bid[0].mtype; + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + const bids = spec.interpretResponse(serverResponse, request); + expect(bids[0].mediaType).to.equal('banner'); + }); + + it('should skip the bid if bid.mtype and bid.ext.prebid.type are not defined', () => { + const serverResponse = deepClone(SAMPLE_RESPONSE); + delete serverResponse.body.seatbid[0].bid[0].mtype; + delete serverResponse.body.seatbid[0].bid[0].ext.prebid.type; + const request = spec.buildRequests(DEFAULT_BANNER_BID_REQUESTS, DEFAULT_BANNER_BIDDER_REQUEST)[0]; + const bids = spec.interpretResponse(serverResponse, request); + expect(bids).to.be.empty; + }); + }); + + describe('getUserSyncs', () => { + it('should return an empty array if iframe sync is not enabled', () => { + const syncs = spec.getUserSyncs({}, [SAMPLE_RESPONSE], null, null); + expect(syncs).to.deep.equal([]); + }); + + it('should return an array with iframe url', () => { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], null, null); + expect(syncs).to.deep.equal([{ + type: 'iframe', + url: 'https://ads.sportslocalmedia.com/load-cookie.html?endpoint=akcelo' + }]); + }); + + it('should return an array with iframe URL and GDPR parameters', () => { + const gdprConsent = { gdprApplies: true, consentString: 'the_gdpr_consent' }; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], gdprConsent, null); + expect(syncs[0].url).to.contain('?endpoint=akcelo&gdpr=1&gdpr_consent=the_gdpr_consent'); + }); + + it('should return an array with iframe URL containing empty GDPR parameters when GDPR does not apply', () => { + const gdprConsent = { gdprApplies: false }; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], gdprConsent, null); + expect(syncs[0].url).to.contain('?endpoint=akcelo&gdpr=0&gdpr_consent='); + }); + + it('should URI encode the GDPR consent string', () => { + const gdprConsent = { gdprApplies: true, consentString: 'the_gdpr_consent==' }; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], gdprConsent, null); + expect(syncs[0].url).to.contain('?endpoint=akcelo&gdpr=1&gdpr_consent=the_gdpr_consent%3D%3D'); + }); + + it('should return an array with iframe URL containing USP parameters when USP is defined', () => { + const uspConsent = 'the_usp_consent'; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], null, uspConsent); + expect(syncs[0].url).to.contain('?endpoint=akcelo&us_privacy=the_usp_consent'); + }); + + it('should URI encode the USP consent string', () => { + const uspConsent = 'the_usp_consent=='; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [SAMPLE_RESPONSE], null, uspConsent); + expect(syncs[0].url).to.contain('?endpoint=akcelo&us_privacy=the_usp_consent%3D%3D'); + }); + }); +}); diff --git a/test/spec/modules/aniviewBidAdapter_spec.js b/test/spec/modules/aniviewBidAdapter_spec.js index 726bccaa027..a4ccdce1117 100644 --- a/test/spec/modules/aniviewBidAdapter_spec.js +++ b/test/spec/modules/aniviewBidAdapter_spec.js @@ -265,9 +265,8 @@ describe('Aniview Bid Adapter', function () { const bids = spec.interpretResponse(bidderResponse, bidRequests[0]); const bid = bids[0]; - expect(bid.width).to.not.exist; - expect(bid.height).to.not.exist; - expect(bid.creativeId).to.not.exist; + expect(bid.renderer).to.not.exist; + expect(bid.ad).to.not.exist; }); it('should return empty bids array if no bids in response', function () { diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index 0a752761531..3f06cd04910 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -2,6 +2,8 @@ import { expect } from 'chai'; import { spec } from 'modules/beopBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; +import { setConfig as setCurrencyConfig } from '../../../modules/currency'; +import { addFPDToBidderRequest } from '../../helpers/fpd'; const utils = require('src/utils'); const ENDPOINT = 'https://hb.beop.io/bid'; @@ -92,18 +94,27 @@ describe('BeOp Bid Adapter tests', () => { bidRequests.push(validBid); it('should build the request', function () { - config.setConfig({'currency': {'adServerCurrency': 'USD'}}); - const request = spec.buildRequests(bidRequests, {}); - const payload = JSON.parse(request.data); - const url = request.url; - expect(url).to.equal(ENDPOINT); - expect(payload.pid).to.exist; - expect(payload.pid).to.equal('5a8af500c9e77c00017e4cad'); - expect(payload.gdpr_applies).to.exist; - expect(payload.gdpr_applies).to.equals(false); - expect(payload.slts[0].name).to.exist; - expect(payload.slts[0].name).to.equal('bellow-article'); - expect(payload.slts[0].flr).to.equal(10); + const bidderRequest = { + refererInfo: { + page: 'https://example.com' + } + }; + setCurrencyConfig({ adServerCurrency: 'USD' }) + + return addFPDToBidderRequest(bidderRequest).then(res => { + const request = spec.buildRequests(bidRequests, res); + const payload = JSON.parse(request.data); + const url = request.url; + expect(url).to.equal(ENDPOINT); + expect(payload.pid).to.exist; + expect(payload.pid).to.equal('5a8af500c9e77c00017e4cad'); + expect(payload.gdpr_applies).to.exist; + expect(payload.gdpr_applies).to.equals(false); + expect(payload.slts[0].name).to.exist; + expect(payload.slts[0].name).to.equal('bellow-article'); + expect(payload.slts[0].flr).to.equal(10); + setCurrencyConfig({}); + }); }); it('should call the endpoint with GDPR consent and pageURL info if found', function () { diff --git a/test/spec/modules/bidmaticBidAdapter_spec.js b/test/spec/modules/bidmaticBidAdapter_spec.js index 4b213c6dcc4..225c2a6dce6 100644 --- a/test/spec/modules/bidmaticBidAdapter_spec.js +++ b/test/spec/modules/bidmaticBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { END_POINT, spec } from 'modules/bidmaticBidAdapter.js'; +import { END_POINT, SYNC_URL, spec, createUserSyncs } from 'modules/bidmaticBidAdapter.js'; import { deepClone, deepSetValue, mergeDeep } from '../../../src/utils'; const expectedImp = { @@ -165,6 +165,46 @@ describe('Bidmatic Bid Adapter', () => { }); }) + describe('syncs creation', () => { + const syncOptions = { iframeEnabled: true }; + + it('should not operate without syncs enabled', () => { + const syncs = createUserSyncs({}); + expect(syncs).to.eq(undefined); + }); + + it('should call uniq and unused sources only', () => { + const sources = { 111: 0, 222: 0, 333: 1 } + const syncs = createUserSyncs(sources, syncOptions); + + expect(syncs.length).to.eq(2); + + expect(syncs[0].type).to.eq('iframe'); + expect(syncs[0].url).to.eq(`${SYNC_URL}?aid=111`); + expect(syncs[1].type).to.eq('iframe'); + expect(syncs[1].url).to.eq(`${SYNC_URL}?aid=222`); + + expect(sources[111]).to.eq(1); + expect(sources[222]).to.eq(1); + + const syncs2 = createUserSyncs(sources, syncOptions); + expect(syncs2.length).to.eq(0); + }); + + it('should add consent info', () => { + const [{ url: syncUrl }] = createUserSyncs( + { 111: 0 }, + syncOptions, + { gdprApplies: true, consentString: '111' }, + 'yyy', + { gppString: '222', applicableSections: [1, 2] }); + + expect(syncUrl.includes('gdpr=1&gdpr_consent=111')).to.eq(true); + expect(syncUrl.includes('usp=yyy')).to.eq(true); + expect(syncUrl.includes('gpp=222&gpp_sid=1,2')).to.eq(true); + }); + }) + describe('response interpreter', () => { const SERVER_RESPONSE = { 'body': { diff --git a/test/spec/modules/bitmediaBidAdapter_spec.js b/test/spec/modules/bitmediaBidAdapter_spec.js new file mode 100644 index 00000000000..be454690235 --- /dev/null +++ b/test/spec/modules/bitmediaBidAdapter_spec.js @@ -0,0 +1,499 @@ +import {expect} from 'chai'; +import {spec, STORAGE, ENDPOINT_URL} from 'modules/bitmediaBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {config} from 'src/config.js'; +import {BANNER} from '../../../src/mediaTypes'; + +describe('Bitmedia Bid Adapter', function () { + const createBidRequest = (sandbox, overrides = {}) => { + return Object.assign({ + bidder: 'bitmedia', + params: { + adUnitID: 'test-ad-unit-id', + currency: 'USD' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'bid123', + auctionId: 'auction123', + transactionId: 'transaction123', + adUnitCode: 'adunit123', + sizes: [[300, 250], [300, 600]], + getFloor: sandbox.stub().returns({ + currency: 'USD', + floor: 0.4 + }) + }, overrides); + } + + const createBidderRequest = (overrides = {}) => { + return Object.assign({ + refererInfo: { + page: 'https://example.com/page.html', + domain: 'example.com', + referer: 'https://google.com' + }, + timeout: 2000, + bidderCode: 'bitmedia', + auctionId: 'auction123', + ortb2: { + site: { + domain: 'publisher.com', + page: 'https://publisher.com/homepage.html', + publisher: { + domain: 'publisher.com' + } + }, + device: { + ua: 'custom-user-agent', + language: 'fr' + } + } + }, overrides); + } + // Helper function to stub storage for user ID + const stubStorage = (sandbox, userIdInLocalStorage = null, userIdInCookies = null) => { + sandbox.stub(STORAGE, 'hasLocalStorage').returns(true); + sandbox.stub(STORAGE, 'getDataFromLocalStorage') + .withArgs('bitmedia_fid') + .returns(userIdInLocalStorage); + + sandbox.stub(STORAGE, 'cookiesAreEnabled').returns(true); + sandbox.stub(STORAGE, 'getCookie') + .withArgs('bitmedia_fid') + .returns(userIdInCookies); + + if (userIdInLocalStorage || userIdInCookies) { + const encodedFid = userIdInLocalStorage || userIdInCookies; + sandbox.stub(window, 'atob') + .withArgs(encodedFid) + .returns(`{"fid":"user123"}`); + } + } + + describe('isBidRequestValid', function () { + let bid; + beforeEach(function () { + bid = { + bidder: 'bitmedia', + params: { + adUnitID: 'test-ad-unit-id', + currency: 'USD' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + } + }; + }); + + it('should return true when required params found and sizes are valid', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false if adUnitID is missing', function () { + delete bid.params.adUnitID; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if currency is invalid', function () { + bid.params.currency = 'EUR'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if no valid sizes provided', function () { + bid.mediaTypes.banner.sizes = [[123, 456], [789, 101]]; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if mediaTypes.banner is missing', function () { + delete bid.mediaTypes.banner; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if banner sizes are not an array', function () { + bid.mediaTypes.banner.sizes = '300x250'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false if bid params are missing', function () { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + + // Stub generateUUID to return a fixed value for testing + sandbox.stub(utils, 'generateUUID').returns('test-generated-uuid'); + + // Stub config.getConfig for bidderTimeout + sandbox.stub(config, 'getConfig').withArgs('bidderTimeout').returns(30); + }); + + afterEach(function () { + sandbox.restore(); + }); + describe('when building the request', function () { + let bidRequests; + let bidderRequest; + let requests; + let request; + let data; + + beforeEach(function () { + bidRequests = [createBidRequest(sandbox,)]; + bidderRequest = createBidderRequest(); + stubStorage(sandbox, null, 'encodedFidString'); // User ID in cookies + + requests = spec.buildRequests(bidRequests, bidderRequest); + request = requests[0]; + data = request.data; + }); + + it('should return an array with one request', function () { + expect(requests).to.be.an('array').with.lengthOf(1); + }); + + it('should have method POST and the correct URL', function () { + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(`${ENDPOINT_URL}${bidRequests[0].params.adUnitID}`); + }); + + it('should have the correct request options', function () { + expect(request.options).to.deep.equal({ + withCredentials: false, + crossOrigin: true + }); + }); + + it('should have the correct request data structure', function () { + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('id', 'imp', 'site', 'device', 'cur', 'tmax', 'ext', 'user'); + }); + + it('should include the generated UUID in the request data', function () { + expect(data.id).to.equal('test-generated-uuid'); + }); + + it('should include the correct impressions in the request data', function () { + expect(data.imp).to.be.an('array').with.lengthOf(2); + + const expectedImp1 = { + id: 'bid123', + tagid: 'test-ad-unit-id', + banner: { + w: 300, + h: 250 + }, + bidfloor: 0.4, + bidfloorcur: 'USD' + }; + + const expectedImp2 = { + id: 'bid123', + tagid: 'test-ad-unit-id', + banner: { + w: 300, + h: 600 + }, + bidfloor: 0.4, + bidfloorcur: 'USD' + }; + + expect(data.imp[0]).to.deep.equal(expectedImp1); + expect(data.imp[1]).to.deep.equal(expectedImp2); + }); + + it('should include the correct site information', function () { + expect(data.site).to.deep.equal({ + domain: 'publisher.com', + page: 'https://publisher.com/homepage.html', + publisher: { + domain: 'publisher.com' + } + }); + }); + + it('should include the correct device information', function () { + expect(data.device).to.deep.equal({ + ua: 'custom-user-agent', + language: 'fr' + }); + }); + + it('should include the default currency', function () { + expect(data.cur).to.deep.equal(['USD']); + }); + + it('should include the correct timeout (tmax)', function () { + expect(data.tmax).to.equal(2000); + }); + + it('should include the ext field with adapter_version and prebid_version as strings', function () { + expect(data.ext.adapter_version).to.be.a('string'); + expect(data.ext.prebid_version).to.be.a('string'); + }); + + it('should include the user ID when present in cookies', function () { + expect(data.user).to.deep.equal({ + id: 'user123' + }); + }); + }); + + describe('when some invalid sizes are provided', function () { + let bidRequests; + let bidderRequest; + let requests; + let request; + let data; + + beforeEach(function () { + bidRequests = [createBidRequest(sandbox, { + mediaTypes: { + banner: { + sizes: [[300, 600], [888, 888]], + }, + }, + sizes: [[300, 600], [888, 888]], + })]; + bidderRequest = createBidderRequest(); + stubStorage(sandbox, null, null); + + requests = spec.buildRequests(bidRequests, bidderRequest); + request = requests[0]; + data = request.data; + }); + + it('should not build imp with invalid size', function () { + expect(data.imp).to.be.an('array').with.lengthOf(1); + }); + }); + + describe('when user ID is absent', function () { + let bidRequests; + let bidderRequest; + let requests; + let data; + + beforeEach(function () { + bidRequests = [createBidRequest(sandbox,)]; + bidderRequest = createBidderRequest(); + stubStorage(sandbox, null, null); + + requests = spec.buildRequests(bidRequests, bidderRequest); + data = requests[0].data; + }); + + it('should not include user ID in the request data', function () { + expect(data.user).to.be.undefined; + }); + }); + + describe('when getFloor is not available', function () { + let bidRequests; + let bidderRequest; + let requests; + let imp; + + beforeEach(function () { + bidRequests = [createBidRequest(sandbox, { + getFloor: undefined + })]; + bidderRequest = createBidderRequest(); + + requests = spec.buildRequests(bidRequests, bidderRequest); + imp = requests[0].data.imp; + }); + + it('should not include bidfloor in imp objects', function () { + imp.forEach(impression => { + expect(impression).to.not.have.property('bidfloor'); + expect(impression).to.not.have.property('bidfloorcur'); + }); + }); + }); + + describe('when different bid floors are provided per size', function () { + let bidRequests; + let bidderRequest; + let requests; + let imp; + + beforeEach(function () { + const getFloorStub = sinon.stub(); + getFloorStub.withArgs({currency: 'USD', mediaType: 'banner', size: [300, 250]}).returns({ + currency: 'USD', + floor: 0.5 + }); + getFloorStub.withArgs({currency: 'USD', mediaType: 'banner', size: [300, 600]}).returns({ + currency: 'USD', + floor: 0.7 + }); + + bidRequests = [createBidRequest(sandbox, { + getFloor: getFloorStub + })]; + bidderRequest = createBidderRequest(); + + requests = spec.buildRequests(bidRequests, bidderRequest); + imp = requests[0].data.imp; + }); + + it('should include the correct bidfloor per impression', function () { + expect(imp[0].bidfloor).to.equal(0.5); + expect(imp[0].banner).to.deep.equal({w: 300, h: 250}); + expect(imp[1].bidfloor).to.equal(0.7); + expect(imp[1].banner).to.deep.equal({w: 300, h: 600}); + }); + }); + + describe('when bid floor data is invalid', function () { + let bidRequests; + let bidderRequest; + let requests; + let imp; + + beforeEach(function () { + const invalidGetFloor = sinon.stub().returns({ + currency: 'USD', + floor: 'invalid' + }); + bidRequests = [createBidRequest(sandbox, { + getFloor: invalidGetFloor + })]; + bidderRequest = createBidderRequest(); + + requests = spec.buildRequests(bidRequests, bidderRequest); + imp = requests[0].data.imp; + }); + + it('should not include bidfloor when floor value is invalid', function () { + imp.forEach(impression => { + expect(impression).to.not.have.property('bidfloor'); + expect(impression).to.not.have.property('bidfloorcur'); + }); + }); + }); + }); + + describe('interpretResponse', function () { + let sandbox; + let bidRequests; + let bidderRequest; + let requests; + beforeEach(function () { + sandbox = sinon.createSandbox(); + sandbox.stub(utils, 'generateUUID').returns('test-generated-uuid'); + bidRequests = [createBidRequest(sandbox,)]; + bidderRequest = createBidderRequest(); + stubStorage(sandbox, null, null); // No user ID for simplicity + + requests = spec.buildRequests(bidRequests, bidderRequest); + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('should return bids with all required keys when server response has valid bids', function () { + const request = requests[0]; + + const serverResponse = { + body: { + id: request.data.id, + seatbid: [ + { + bid: [ + { + impid: request.data.imp[0].id, + price: 1.5, + w: request.data.imp[0].banner.w, + h: request.data.imp[0].banner.h, + adm: '
Ad Content
', + crid: 'creative123', + adomain: ['example.com'], + nurl: 'https://example.com/win', + exp: 360, + }, + ], + }, + ], + cur: 'USD', + }, + }; + + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.an('array').with.lengthOf(1); + const bid = bids[0]; + expect(bid).to.include.all.keys( + 'requestId', + 'cpm', + 'currency', + 'width', + 'height', + 'ad', + 'ttl', + 'creativeId', + 'netRevenue', + 'meta', + 'nurl', + 'mediaType' + ); + expect(bid.mediaType).to.equal(BANNER); + expect(bid.ttl).to.equal(360); + expect(bid.nurl).to.equal('https://example.com/win'); + }); + + it('should return an empty array when server response is empty', function () { + const serverResponse = {body: {}}; + const bidRequest = {}; + + const bids = spec.interpretResponse(serverResponse, bidRequest); + + expect(bids).to.be.an('array').that.is.empty; + }); + }); + + describe('onBidWon', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('exists and is a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + + it('should return nothing and trigger a pixel with nurl', function () { + const bid = { + nurl: 'https://example.com/win', + }; + + const triggerPixelSpy = sandbox.spy(utils, 'triggerPixel'); + + const response = spec.onBidWon(bid); + + expect(response).to.be.undefined; + + expect(triggerPixelSpy.calledOnce).to.equal(true); + + expect(triggerPixelSpy.calledWith(bid.nurl)).to.equal(true); + }); + }); +}); diff --git a/test/spec/modules/blueBidAdapter_spec.js b/test/spec/modules/blueBidAdapter_spec.js new file mode 100755 index 00000000000..f3f6f435f20 --- /dev/null +++ b/test/spec/modules/blueBidAdapter_spec.js @@ -0,0 +1,91 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { spec, storage } from 'modules/blueBidAdapter.js'; + +const BIDDER_CODE = 'blue'; +const ENDPOINT_URL = 'https://bidder-us-east-1.getblue.io/engine/?src=prebid'; +const GVLID = 620; +const COOKIE_NAME = 'ckid'; +const CURRENCY = 'USD'; + +describe('blueBidAdapter:', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('isBidRequestValid:', function () { + it('should return true for valid bid requests', function () { + const validBid = { + params: { + placementId: '12345', + publisherId: '67890', + }, + }; + expect(spec.isBidRequestValid(validBid)).to.be.true; + }); + + it('should return false for invalid bid requests', function () { + const invalidBid = { + params: { + placementId: '12345', + }, + }; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests:', function () { + let validBidRequests; + let bidderRequest; + + beforeEach(function () { + validBidRequests = [ + { + bidId: 'bid1', + params: { + placementId: '12345', + publisherId: '67890', + }, + getFloor: () => ({ currency: CURRENCY, floor: 1.5 }), + }, + ]; + + bidderRequest = { + refererInfo: { + page: 'https://example.com', + }, + }; + + sandbox.stub(storage, 'getDataFromLocalStorage').returns('testBuyerId'); + }); + + it('should build a valid OpenRTB request', function () { + const request = spec.buildRequests(validBidRequests, bidderRequest); + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT_URL); + expect(request.options.contentType).to.equal('text/plain'); + + const ortbRequest = JSON.parse(request.data); + expect(ortbRequest.ext.gvlid).to.equal(GVLID); + expect(ortbRequest.user.ext.buyerid).to.equal('testBuyerId'); + expect(ortbRequest.imp[0].bidfloor).to.equal(1.5); + expect(ortbRequest.imp[0].bidfloorcur).to.equal(CURRENCY); + }); + + it('should omit bidfloor if getFloor is not implemented', function () { + validBidRequests[0].getFloor = undefined; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const ortbRequest = JSON.parse(request.data); + + expect(ortbRequest.imp[0].bidfloor).to.be.undefined; + }); + }); +}); diff --git a/test/spec/modules/brainxBidAdapter_spec.js b/test/spec/modules/brainxBidAdapter_spec.js new file mode 100644 index 00000000000..1d01e2cc642 --- /dev/null +++ b/test/spec/modules/brainxBidAdapter_spec.js @@ -0,0 +1,132 @@ +// import or require modules necessary for the test, e.g.: +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { spec } from 'modules/brainxBidAdapter.js'; +import utils, { deepClone } from '../../../src/utils'; +// import adapter from 'src/adapters/'; +import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes.js'; + +describe('Brain-X Aapater', function () { + describe('isBidRequestValid', function () { + it('undefined bid should return false', function () { + expect(spec.isBidRequestValid()).to.be.false; + }); + + it('null bid should return false', function () { + expect(spec.isBidRequestValid(null)).to.be.false; + }); + + it('bid.params should be set', function () { + expect(spec.isBidRequestValid({})).to.be.false; + }); + + it('bid.params.pubId should be set', function () { + expect(spec.isBidRequestValid({ + params: { pubId: 'F7B53DBC-85C1-4685-9A06-9CF4B6261FA3', endpoint: 'http://adx-engine-gray.tec-do.cn/bid' } + })).to.be.false; + }); + }) + + // describe('isBidRequestValid', function () { + // it('Test the banner request processing function', function () { + // const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + // expect(request).to.not.be.empty; + // const payload = request.data; + // expect(payload).to.not.be.empty; + // }); + // it('Test the video request processing function', function () { + // const request = spec.buildRequests(videoRequest, videoRequest[0]); + // expect(request).to.not.be.empty; + // const payload = request.data; + // expect(payload).to.not.be.empty; + // }); + // it('Test the param', function () { + // const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + // const payload = JSON.parse(request.data); + // expect(payload.imp[0].tagid).to.eql(videoRequest[0].params.tagid); + // expect(payload.imp[0].bidfloor).to.eql(videoRequest[0].params.bidfloor); + // }); + // }) + + describe('interpretResponse', function () { + it('Test banner interpretResponse', function () { + const serverResponse = { + body: { + 'bidid': 'a82042c055b04e539ec6876112c10ced1729663902983', + 'cur': 'USD', + 'id': '28f8f1f525372a', + 'seatbid': [ + { + 'bid': [ + { + 'adid': '76797', + 'adm': "
\n \n
\n
\n \n \n
\n
\n\n", + 'adomain': [ + 'taobao.com' + ], + 'bundle': 'com.taobao', + 'burl': 'https://adx-event-server.bidtrail.top/billing?s=Eid0ZWMxNDk4NDQzODk3MjcwOTU1MzAwNzM3YjczMTQwMTA4ODk5ODQaDjI4ZjhmMWY1MjUzNzJhIKkCKgbmtYvor5U6DmxvY2FsaG9zdDo5OTk5Qgl1bmRlZmluZWRKA0hLR1ADYAFoAXADeAOAAQKKAQpjb20udGFvYmFvlQEX2U49nQEX2U49pQEX2U49ugEqCO4HEiUI7gcSCFRlY2RvRFNQGA01F9lOPTgBQBVKC1RlY2RvRFNQX1NHwAHuB8gBjcqCwKsy0AEC-gGbAmh0dHBzOi8vbm90aWNlLXNnLmJpZHRyYWlsLnRvcC93aW4_YmlkX2lkPWE4MjA0MmMwNTViMDRlNTM5ZWM2ODc2MTEyYzEwY2VkMTcyOTY2MzkwMjk4MyZjYW1wYWlnbl9pZD00MjgmYWRfZ3JvdXBfaWQ9OTc2MSZhZF9pZD03Njc5NyZjcmVhdGl2ZV9pZD02NTI2JmFmZmlsaWF0ZV9pZD0yOTcmYnVuZGxlPSZmaXJzdF9zc3A9YnJhaW54LnRlY2gmcHVibGlzaF9pZD0mb3M9QW5kcm9pZCZ0YWdfaWQ9dW5kZWZpbmVkJmJpZF9zdWNjZXNzX3ByaWNlPTAuMDUwNSZzaWduPWUzODQyNjhiYzAzYzhmYmOSAgp0YW9iYW8uY29togILYnJhaW54LnRlY2ioAgGyAgdhbmRyb2lkwAICygICc2fQAgHYAo3hlcWrMuACjeGVxasy&v=P-1b7JJWs-uUQ68A37V4xDLplU0&auction_price=${AUCTION_PRICE}', + 'cat': [ + 'IAB18-5' + ], + 'cid': '428', + 'crid': 'D06g1RVGMEnC+9Le4SZMJw==', + 'h': 480, + 'id': 'a82042c055b04e539ec6876112c10ced1729663902983', + 'impid': '3c1dd9e1700358', + 'iurl': 'https://creative.bidtrail.top/png/2/20f24c10f21e/091b422e3014033e57acffcf2a5c71dbb17383ec15ac9421', + 'lurl': 'https://notice-sg.bidtrail.top/loss?bid_id=a82042c055b04e539ec6876112c10ced1729663902983&sign=e384268bc03c8fbc&campaign_id=428&ad_group_id=9761&ad_id=76797&creative_id=6526&affiliate_id=297&loss_code=${AUCTION_LOSS}', + 'nurl': 'https://adx-event-server.bidtrail.top/winnotice?s=Eid0ZWMxNDk4NDQzODk3MjcwOTU1MzAwNzM3YjczMTQwMTA4ODk5ODQaDjI4ZjhmMWY1MjUzNzJhIKkCKgbmtYvor5U6DmxvY2FsaG9zdDo5OTk5Qgl1bmRlZmluZWRKA0hLR1ADYAFoAXADeAOAAQKKAQpjb20udGFvYmFvlQEX2U49nQEX2U49pQEX2U49ugEqCO4HEiUI7gcSCFRlY2RvRFNQGA01F9lOPTgBQBVKC1RlY2RvRFNQX1NHwAHuB8gBjcqCwKsy0AEB-gGbAmh0dHBzOi8vbm90aWNlLXNnLmJpZHRyYWlsLnRvcC93aW4_YmlkX2lkPWE4MjA0MmMwNTViMDRlNTM5ZWM2ODc2MTEyYzEwY2VkMTcyOTY2MzkwMjk4MyZjYW1wYWlnbl9pZD00MjgmYWRfZ3JvdXBfaWQ9OTc2MSZhZF9pZD03Njc5NyZjcmVhdGl2ZV9pZD02NTI2JmFmZmlsaWF0ZV9pZD0yOTcmYnVuZGxlPSZmaXJzdF9zc3A9YnJhaW54LnRlY2gmcHVibGlzaF9pZD0mb3M9QW5kcm9pZCZ0YWdfaWQ9dW5kZWZpbmVkJmJpZF9zdWNjZXNzX3ByaWNlPTAuMDUwNSZzaWduPWUzODQyNjhiYzAzYzhmYmOSAgp0YW9iYW8uY29togILYnJhaW54LnRlY2ioAgGyAgdhbmRyb2lkwAICygICc2fQAgHYAo3hlcWrMuACjeGVxasy&v=jFUg_b6F14d50JL-M6UE5Jc8VuA&auction_price=${AUCTION_PRICE}', + 'price': 0.0505, + 'w': 320 + } + ], + 'group': 0, + 'seat': 'agency' + } + ] + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + originalBidRequest: { + auctionId: '3eedbf83-7d1d-423c-be27-39e4af687040', + auctionStart: 1729663900819, + adUnitCode: 'dev-1', + bidId: '28f8f1f525372a', + bidder: 'brainx', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + params: { + pubId: 'F7B53DBC-85C1-4685-9A06-9CF4B6261FA3', + endpoint: 'http://adx-engine-gray.tec-do.cn/bid' + }, + src: 'client' + } + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bid = serverResponse.body.seatbid[0].bid[0]; + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(BANNER); + expect(bidResponse.requestId).to.equal(bid.impid); + expect(bidResponse.cpm).to.equal(parseFloat(bid.price).toFixed(2)) + expect(bidResponse.currency).to.equal(serverResponse.body.cur); + expect(bidResponse.creativeId).to.equal(bid.crid || bid.id); + expect(bidResponse.netRevenue).to.be.true; + expect(bidResponse.nurl).to.equal(bid.nurl); + expect(bidResponse.lurl).to.equal(bid.lurl); + + expect(bidResponse.meta).to.be.an('object'); + expect(bidResponse.meta.mediaType).to.equal(BANNER); + expect(bidResponse.meta.primaryCatId).to.equal('IAB18-5'); + // expect(bidResponse.meta.secondaryCatIds).to.deep.equal(['IAB8']); + expect(bidResponse.meta.advertiserDomains).to.deep.equal(bid.adomain); + expect(bidResponse.meta.clickUrl).to.equal(bid.adomain[0]); + + expect(bidResponse.ad).to.equal(bid.adm); + expect(bidResponse.width).to.equal(bid.w); + expect(bidResponse.height).to.equal(bid.h); + }); + }); +}); diff --git a/test/spec/modules/bridgeuppBidAdapter_spec.js b/test/spec/modules/bridgeuppBidAdapter_spec.js new file mode 100644 index 00000000000..0a786a7e9ec --- /dev/null +++ b/test/spec/modules/bridgeuppBidAdapter_spec.js @@ -0,0 +1,1141 @@ +import { expect } from 'chai'; +import { + spec, BIDDER_CODE, SERVER_PATH_US1_SYNC, SERVER_PATH_US1_EVENTS +} from '../../../modules/bridgeuppBidAdapter.js'; +import * as utils from 'src/utils.js'; +import * as ajax from 'src/ajax.js'; +import { hook } from '../../../src/hook'; +import { config } from '../../../src/config.js'; +import { syncAddFPDToBidderRequest } from '../../helpers/fpd'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/consentManagementGpp.js'; +import 'modules/priceFloors.js'; +import sinon from 'sinon'; + +// constants +const SITE_DOMAIN_NAME = 'sonargames.com'; +const SITE_PAGE = 'https://sonargames.com'; +describe('bridgeuppBidAdapter_spec', function () { + let utilsMock, sandbox, ajaxStub, fetchStub; + + afterEach(function () { + sandbox.restore(); + utilsMock.restore(); + ajaxStub.restore(); + fetchStub.restore(); + }); + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + utilsMock = sinon.mock(utils); + ajaxStub = sandbox.stub(ajax, 'ajax'); + fetchStub = sinon.stub(global, 'fetch').resolves(new Response('OK')); + }); + + describe('isBidRequestValid', function () { + let bid; + beforeEach(function () { + bid = { + 'bidder': BIDDER_CODE, + 'params': { + 'siteId': 'site-id-12', + } + }; + }); + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when missing bidder', function () { + delete bid.bidder; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when bidder is not valid', function () { + bid.bidder = 'invalid-bidder'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when missing siteId', function () { + delete bid.params.siteId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + before(() => { + hook.ready(); + }) + afterEach(function () { + config.resetConfig(); + }); + const bidderRequest = { + refererInfo: { + page: 'https://sonarads.com/home', + ref: 'https://referrer' + }, + timeout: 1000, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '1300': 1 + }, + }, + apiVersion: 1, + }, + }; + + it('request should build with correct siteId', function () { + const bidRequests = [ + { + bidId: 'bidId', + bidder: 'sonarads', + adUnitCode: 'bid-12', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + 'siteId': 'site-id-12' + } + }, + ]; + + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].ext.bidder.siteId).to.deep.equal('site-id-12'); + }); + + it('request should build with correct imp', function () { + const expectedMetric = { + url: 'https://sonarads.com' + } + const bidRequests = [{ + bidId: 'bidId', + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + ortb2Imp: { + instl: 1, + metric: expectedMetric, + ext: { + gpid: 'gpid_sonarads' + }, + rwdd: 1 + }, + params: { + siteId: 'site-id-12', + bidfloor: 2.12, + bidfloorcur: 'USD' + } + }]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].id).to.deep.equal('bidId'); + expect(ortbRequest.imp[0].tagid).to.deep.equal('impId'); + expect(ortbRequest.imp[0].instl).to.equal(1); + expect(ortbRequest.imp[0].bidfloor).to.equal(2.12); + expect(ortbRequest.imp[0].bidfloorcur).to.equal('USD'); + expect(ortbRequest.imp[0].metric).to.deep.equal(expectedMetric); + expect(ortbRequest.imp[0].ext.gpid).to.equal('gpid_sonarads'); + expect(ortbRequest.imp[0].secure).to.equal(1); + expect(ortbRequest.imp[0].rwdd).to.equal(1); + }); + + it('request should build with proper site data', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + }, + }, + ]; + const ortb2 = { + site: { + name: SITE_DOMAIN_NAME, + domain: SITE_DOMAIN_NAME, + keywords: 'keyword1, keyword2', + cat: ['IAB2'], + pagecat: ['IAB3'], + sectioncat: ['IAB4'], + page: SITE_PAGE, + ref: 'google.com', + privacypolicy: 1, + content: { + url: SITE_PAGE + '/games1' + } + } + }; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.site.domain).to.equal(SITE_DOMAIN_NAME); + expect(ortbRequest.site.publisher.domain).to.equal('sonarads.com'); + expect(ortbRequest.site.page).to.equal(SITE_PAGE); + expect(ortbRequest.site.name).to.equal(SITE_DOMAIN_NAME); + expect(ortbRequest.site.keywords).to.equal('keyword1, keyword2'); + expect(ortbRequest.site.cat).to.deep.equal(['IAB2']); + expect(ortbRequest.site.pagecat).to.deep.equal(['IAB3']); + expect(ortbRequest.site.sectioncat).to.deep.equal(['IAB4']); + expect(ortbRequest.site.ref).to.equal('google.com'); + expect(ortbRequest.site.privacypolicy).to.equal(1); + expect(ortbRequest.site.content.url).to.equal(SITE_PAGE + '/games1') + }); + + it('request should build with proper device data', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + }, + }, + ]; + const ortb2 = { + device: { + dnt: 1, + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36', + ip: '203.0.113.42', + h: 800, + w: 1280, + language: 'fr', + lmt: 0, + js: 0, + connectiontype: 2, + hwv: 'iPad', + model: 'Pro', + mccmnc: '234-030', + geo: { + lat: 48.8566, + lon: 2.3522 + } + } + }; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.device.dnt).to.equal(1); + expect(ortbRequest.device.lmt).to.equal(0); + expect(ortbRequest.device.js).to.equal(0); + expect(ortbRequest.device.connectiontype).to.equal(2); + expect(ortbRequest.device.ua).to.equal('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36'); + expect(ortbRequest.device.ip).to.equal('203.0.113.42'); + expect(ortbRequest.device.h).to.equal(800); + expect(ortbRequest.device.w).to.equal(1280); + expect(ortbRequest.device.language).to.deep.equal('fr'); + expect(ortbRequest.device.hwv).to.deep.equal('iPad'); + expect(ortbRequest.device.model).to.deep.equal('Pro'); + expect(ortbRequest.device.mccmnc).to.deep.equal('234-030'); + expect(ortbRequest.device.geo.lat).to.deep.equal(48.8566); + expect(ortbRequest.device.geo.lon).to.deep.equal(2.3522); + }); + + it('should properly build a request with source object', function () { + const expectedSchain = { id: 'prebid' }; + const ortb2 = { + source: { + pchain: 'sonarads', + schain: expectedSchain + } + }; + const bidRequests = [ + { + bidder: 'sonarads', + bidId: 'bidId', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + }, + }, + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.source.schain).to.deep.equal(expectedSchain); + expect(ortbRequest.source.pchain).to.equal('sonarads'); + }); + + it('should properly user object', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + } + }, + ]; + const br = { + ...bidderRequest, + ortb2: { + user: { + yob: 2012, + keyowrds: 'test test', + gender: 'M', + customdata: 'test no', + geo: { + lat: 48.8566, + lon: 2.3522 + }, + ext: { + eids: [ + { + source: 'sonarads.com', + uids: [{ + id: 'iid', + atype: 1 + }] + } + ] + } + } + } + } + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); + const ortbRequest = request.data; + expect(ortbRequest.user.yob).to.deep.equal(2012); + expect(ortbRequest.user.keyowrds).to.deep.equal('test test'); + expect(ortbRequest.user.gender).to.deep.equal('M'); + expect(ortbRequest.user.customdata).to.deep.equal('test no'); + expect(ortbRequest.user.geo.lat).to.deep.equal(48.8566); + expect(ortbRequest.user.geo.lon).to.deep.equal(2.3522); + expect(ortbRequest.user.ext.eids).to.deep.equal([ + { + source: 'sonarads.com', + uids: [{ + id: 'iid', + atype: 1 + }] + } + ]); + }); + + it('should properly build a request regs object', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + }, + }, + ]; + const ortb2 = { + regs: { + coppa: 1, + gpp: 'consent_string', + gpp_sid: [0, 1, 2], + us_privacy: 'yes us privacy applied' + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.regs.coppa).to.equal(1); + expect(ortbRequest.regs.gpp).to.equal('consent_string'); + expect(ortbRequest.regs.gpp_sid).to.deep.equal([0, 1, 2]); + expect(ortbRequest.regs.us_privacy).to.deep.equal('yes us privacy applied'); + }); + + it('gdpr test', function () { + // using privacy params from global bidder Request + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + }, + }, + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.ext.gdpr).to.deep.equal(1); + expect(ortbRequest.user.ext.consent).to.equal('consentDataString'); + }); + + it('should properly set tmax if available', function () { + // using tmax from global bidder Request + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'bid-1', + transactionId: 'trans-1', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + } + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.tmax).to.equal(bidderRequest.timeout); + }); + + it('should properly build a request with bcat field', function () { + const bcat = ['IAB1', 'IAB2']; + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + }, + }, + ]; + const bidderRequest = { + ortb2: { + bcat + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bcat).to.deep.equal(bcat); + }); + + it('should properly build a request with badv field', function () { + const badv = ['nike.com']; + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + }, + }, + ]; + const bidderRequest = { + ortb2: { + badv + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.badv).to.deep.equal(badv); + }); + + it('should properly build a request with bapp field', function () { + const bapp = ['nike.com']; + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + }, + }, + ]; + const bidderRequest = { + ortb2: { + bapp + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bapp).to.deep.equal(bapp); + }); + + it('banner request test', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidId: 'bidId', + bidder: 'sonarads', + adUnitCode: 'bid-12', + mediaTypes: { + banner: { + sizes: [[320, 250]], + pos: 1, + topframe: 0, + } + }, + params: { + siteId: 'site-id-12' + }, + ortb2Imp: { + banner: { + api: [1, 2, 3], + mimes: ['image/jpg', 'image/gif'] + } + } + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].banner).not.to.be.null; + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(320); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(250); + expect(ortbRequest.imp[0].banner.pos).to.equal(1); + expect(ortbRequest.imp[0].banner.topframe).to.equal(0); + expect(ortbRequest.imp[0].banner.api).to.deep.equal([1, 2, 3]); + expect(ortbRequest.imp[0].banner.mimes).to.deep.equal(['image/jpg', 'image/gif']); + }); + + it('banner request test with sizes > 1', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidId: 'bidId', + bidder: 'sonarads', + adUnitCode: 'bid-12', + mediaTypes: { + banner: { + sizes: [[336, 336], [720, 50]] + } + }, + params: { + siteId: 'site-id-12' + } + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].banner).not.to.be.null; + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(336); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(336); + expect(ortbRequest.imp[0].banner.format[1].w).to.equal(720); + expect(ortbRequest.imp[0].banner.format[1].h).to.equal(50); + }); + + it('should properly build a request when coppa is true', function () { + const bidRequests = []; + const bidderRequest = {}; + config.setConfig({ coppa: true }); + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.coppa).to.equal(1); + }); + + it('should properly build a request when coppa is false', function () { + const bidRequests = []; + const bidderRequest = {}; + config.setConfig({ coppa: false }); + let buildRequests = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = buildRequests.data; + expect(ortbRequest.regs.coppa).to.equal(0); + }); + + it('should properly build a request when coppa is not defined', function () { + const bidRequests = []; + const bidderRequest = {}; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs?.coppa).to.be.undefined; + }); + + it('build a banner request with bidFloor', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50], [720, 90]] + } + }, + params: { + siteId: 'site-id-12', + bidfloor: 1, + bidfloorcur: 'USD' + } + } + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].bidfloor).to.deep.equal(1); + expect(ortbRequest.imp[0].bidfloorcur).to.deep.equal('USD'); + }); + + it('build a banner request with getFloor', function () { + const bidRequests = [ + { + bidder: 'sonarads', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12' + }, + getFloor: () => { + return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' }; + } + } + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].bidfloor).equal(1.23); + expect(ortbRequest.imp[0].bidfloorcur).equal('USD'); + }); + }); + + describe('interpretResponse', function () { + const bidderRequest = { + refererInfo: { + page: 'https://sonarads.com/home', + ref: 'https://referrer' + }, + timeout: 1000, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '1300': 1 + }, + }, + apiVersion: 1, + }, + } + + function mockResponse(bidId, mediaType) { + return { + id: 'sonarads-response-id-hash-123123', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: 'sonarads-seatbid-bid-id-hash-123qaasd34', + impid: bidId, + price: 1.12, + adomain: ['advertiserDomain.sandbox.sonarads.com'], + crid: 'd3868d0b2f43c11e4bd60333e5e5ee8be471c4061b3b0cd454539c7edb68a1ca', + w: 320, + h: 250, + adm: 'test-ad', + mtype: mediaType, + nurl: 'https://et-l.w.sonarads.com/c.asm/', + api: 3, + cat: [], + ext: { + prebid: { + meta: { + advertiserDomains: ['advertiserDomain.sandbox.sonarads.com'], + networkName: 'sonarads' + } + } + } + } + ] + } + ], + ext: { + prebidjs: { + urls: [ + { url: 'https://sync.sonarads.com/prebidjs' }, + { url: 'https://eus.rubiconproject.com/usync.html?p=test' } + ] + } + } + } + } + + it('should returns an empty array when bid response is empty', function () { + const bidRequests = []; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const serverResponse = { + headers: { + get: function () { + return undefined + } + }, + body: {} + }; + + const interpretedBids = spec.interpretResponse(serverResponse, request); + expect(interpretedBids).to.have.lengthOf(0); + expect(spec.reportEventsEnabled).to.equal(false); + }); + + it('should return an empty array when there is no bid response', function () { + const bidRequests = []; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const serverResponse = { + headers: { + get: function () { + return undefined + } + }, + body: { seatbid: [] } + }; + + const interpretedBids = spec.interpretResponse(serverResponse, request); + expect(interpretedBids).to.have.lengthOf(0); + expect(spec.reportEventsEnabled).to.equal(false); + }); + + it('return banner response', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + } + }]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const serverResponse = { + headers: { + get: function () { + return undefined + } + }, + body: mockResponse('bidId', 1) + }; + const interpretedBids = spec.interpretResponse(serverResponse, request); + expect(spec.reportEventsEnabled).to.equal(false); + expect(interpretedBids).to.have.length(1); + expect(interpretedBids[0].currency).to.deep.equal('USD'); + expect(interpretedBids[0].mediaType).to.deep.equal('banner'); + expect(interpretedBids[0].requestId).to.deep.equal('bidId'); + expect(interpretedBids[0].seatBidId).to.deep.equal('sonarads-seatbid-bid-id-hash-123qaasd34'); + expect(interpretedBids[0].cpm).to.equal(1.12); + expect(interpretedBids[0].width).to.equal(320); + expect(interpretedBids[0].height).to.equal(250); + expect(interpretedBids[0].creativeId).to.deep.equal('d3868d0b2f43c11e4bd60333e5e5ee8be471c4061b3b0cd454539c7edb68a1ca'); + expect(interpretedBids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.sonarads.com'); + }); + + it('should set the reportEventsEnabled to true as part of the response', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 320], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + siteId: 'site-id-12', + }, + }, { + adUnitCode: 'impId', + bidId: 'bidId2', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + siteId: 'site-id-12', + } + }]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const serverResponse = { + headers: { + get: function (header) { + if (header === 'reportEventsEnabled') { + return 1; + } + } + }, + body: mockResponse('bidId2', 1) + }; + + const interpretedBids = spec.interpretResponse(serverResponse, request); + expect(spec.reportEventsEnabled).to.equal(true); + expect(interpretedBids).to.have.length(1); + }); + + it('bid response when banner wins among two ad units', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 320], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + siteId: 'site-id-12', + }, + }, { + adUnitCode: 'impId', + bidId: 'bidId2', + mediaTypes: { + banner: { + sizes: [[320, 250]] + } + }, + params: { + siteId: 'site-id-12', + } + }]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const serverResponse = { + headers: { + get: function (header) { + if (header === 'reportEventsEnabled') { + return 1; + } + } + }, + body: mockResponse('bidId2', 1) + }; + + const interpretedBids = spec.interpretResponse(serverResponse, request); + expect(spec.reportEventsEnabled).to.equal(true); + expect(interpretedBids[0].currency).to.deep.equal('USD'); + expect(interpretedBids[0].mediaType).to.deep.equal('banner'); + expect(interpretedBids[0].requestId).to.deep.equal('bidId2'); + expect(interpretedBids[0].cpm).to.equal(1.12); + expect(interpretedBids[0].seatBidId).to.deep.equal('sonarads-seatbid-bid-id-hash-123qaasd34'); + expect(interpretedBids[0].creativeId).to.deep.equal('d3868d0b2f43c11e4bd60333e5e5ee8be471c4061b3b0cd454539c7edb68a1ca'); + expect(interpretedBids[0].width).to.equal(320); + expect(interpretedBids[0].height).to.equal(250); + expect(interpretedBids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.sonarads.com'); + }); + }); + + describe('getUserSyncs', function () { + it('should return empty if iframe disabled and pixelEnabled is false', function () { + const res = spec.getUserSyncs({}); + expect(res).to.deep.equal([]); + }); + + it('should return user sync if iframe enabled is true', function () { + const res = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }); + expect(res).to.deep.equal([{ type: 'iframe', url: `${SERVER_PATH_US1_SYNC}` }]); + }); + + it('should return user sync if iframe enabled is true and gdpr not present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false }); + expect(res).to.deep.equal([{ type: 'iframe', url: `${SERVER_PATH_US1_SYNC}` }]); + }); + + it('should return user sync if iframe enabled is true and gdpr = 1 and gdpr consent present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'GDPR_CONSENT' }); + expect(res).to.deep.equal([{ type: 'iframe', url: `${SERVER_PATH_US1_SYNC}?gdpr=1&gdpr_consent=GDPR_CONSENT` }]); + }); + + it('should return user sync if iframe enabled is true and gdpr = 1 , gdpr consent present and usp_consent present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'GDPR_CONSENT' }, 'USP_CONSENT'); + expect(res).to.deep.equal([{ type: 'iframe', url: `${SERVER_PATH_US1_SYNC}?gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT` }]); + }); + + it('should return user sync if iframe enabled is true usp_consent present and gppConsent present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false, consentString: undefined }, 'USP_CONSENT', { gppString: 'GPP_STRING', applicableSections: [32, 51] }); + expect(res).to.deep.equal([{ type: 'iframe', url: `${SERVER_PATH_US1_SYNC}?us_privacy=USP_CONSENT&gpp=GPP_STRING&gpp_sid=32%2C51` }]); + }); + }); + + describe('onTimeout', function () { + it('onTimeout function should be defined', function () { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + + it('should invoke onTimeout, resolving the eventType and domain', function () { + const bid = [ + { + bidder: 'sonarads', + bidId: 'sonar-bid-id-1231232', + adUnitCode: 'sonar-ad-1231232', + timeout: 1000, + auctionId: 'sonar-auction-1231232' + } + ]; + spec.reportEventsEnabled = true; + spec.onTimeout(bid); + + // expected url and payload + const expectedUrl = `${SERVER_PATH_US1_EVENTS}`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: 'onTimeout', + eventPayload: bid + }); + + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + + const fetchArgs = fetchStub.getCall(0).args; + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'application/json', + }); + }); + + it('onTimeout should not be called if the bid data is null', function () { + // Call onTimeout with null data + spec.onTimeout(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('onSetTargeting', function () { + it('onSetTargeting function should be defined', function () { + expect(spec.onSetTargeting).to.exist.and.to.be.a('function'); + }); + + it('should invoke onSetTargeting, resolving the eventType and domain', function () { + const bid = [ + { + bidder: 'sonarads', + bidId: 'sonar-bid-id-1231232', + adUnitCode: 'sonar-ad-1231232', + timeout: 1000, + auctionId: 'sonar-auction-1231232' + } + ]; + spec.reportEventsEnabled = true; + spec.onSetTargeting(bid); + + // expected url and payload + const expectedUrl = `${SERVER_PATH_US1_EVENTS}`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: 'onSetTargeting', + eventPayload: bid + }); + + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + + const fetchArgs = fetchStub.getCall(0).args; + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'application/json', + }); + }); + + it('onSetTargeting should not be called if the bid data is null', function () { + // Call onSetTargeting with null data + spec.onSetTargeting(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('onAdRenderSucceeded', function () { + it('onAdRenderSucceeded function should be defined', function () { + expect(spec.onAdRenderSucceeded).to.exist.and.to.be.a('function'); + }); + + it('should invoke onAdRenderSucceeded, resolving the eventType and domain', function () { + const bid = [ + { + bidder: 'sonarads', + bidId: 'sonar-bid-id-1231232', + adUnitCode: 'sonar-ad-1231232', + timeout: 1000, + auctionId: 'sonar-auction-1231232' + } + ]; + + spec.reportEventsEnabled = true; + spec.onAdRenderSucceeded(bid); + + // expected url and payload + const expectedUrl = `${SERVER_PATH_US1_EVENTS}`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: 'onAdRenderSucceeded', + eventPayload: bid + }); + + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + + const fetchArgs = fetchStub.getCall(0).args; + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'application/json', + }); + }); + + it('onAdRenderSucceeded should not be called if the bid data is null', function () { + // Call onAdRenderSucceeded with null data + spec.onAdRenderSucceeded(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('onBidderError', function () { + it('onBidderError function should be defined', function () { + expect(spec.onBidderError).to.exist.and.to.be.a('function'); + }); + + it('should invoke onBidderError, resolving the eventType and domain', function () { + const bid = [ + { + bidder: 'sonarads', + bidId: 'sonar-bid-id-1231232', + adUnitCode: 'sonar-ad-1231232', + timeout: 1000, + auctionId: 'sonar-auction-1231232' + } + ]; + spec.reportEventsEnabled = true; + spec.onBidderError(bid); + + // expected url and payload + const expectedUrl = `${SERVER_PATH_US1_EVENTS}`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: 'onBidderError', + eventPayload: bid + }); + + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + + const fetchArgs = fetchStub.getCall(0).args; + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'application/json', + }); + }); + + it('onBidderError should not be called if the bid data is null', function () { + // Call onBidderError with null data + spec.onBidderError(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('onBidWon', function () { + it('onBidWon function should be defined', function () { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + + it('should invoke onBidWon, resolving the eventType and domain', function () { + const bid = [ + { + bidder: 'sonarads', + bidId: 'sonar-bid-id-1231232', + adUnitCode: 'sonar-ad-1231232', + timeout: 1000, + auctionId: 'sonar-auction-1231232' + } + ]; + spec.reportEventsEnabled = true; + spec.onBidWon(bid); + + // expected url and payload + const expectedUrl = `${SERVER_PATH_US1_EVENTS}`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + prebidVersion: '$prebid.version$', + eventType: 'onBidWon', + eventPayload: bid + }); + + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + + const fetchArgs = fetchStub.getCall(0).args; + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'application/json', + }); + }); + + it('onBidWon should not be called if the bid data is null', function () { + // Call onBidWon with null data + spec.onBidWon(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); +}); diff --git a/test/spec/modules/carodaBidAdapter_spec.js b/test/spec/modules/carodaBidAdapter_spec.js index bf4557d7a8a..780c81ebe9f 100644 --- a/test/spec/modules/carodaBidAdapter_spec.js +++ b/test/spec/modules/carodaBidAdapter_spec.js @@ -3,6 +3,8 @@ import { assert } from 'chai'; import { spec } from 'modules/carodaBidAdapter.js'; import { config } from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; +import { setConfig as setCurrencyConfig } from '../../../modules/currency'; +import { addFPDToBidderRequest } from '../../helpers/fpd'; describe('Caroda adapter', function () { let bids = []; @@ -185,12 +187,14 @@ describe('Caroda adapter', function () { }); it('should send currency if defined', function () { - config.setConfig({ currency: { adServerCurrency: 'EUR' } }); + setCurrencyConfig({ adServerCurrency: 'EUR' }); let validBidRequests = [{ params: {} }]; - let refererInfo = { page: 'page' }; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo })[0].data); - - assert.deepEqual(request.currency, 'EUR'); + const bidderRequest = { refererInfo: { page: 'page' } }; + return addFPDToBidderRequest(bidderRequest).then(res => { + let request = JSON.parse(spec.buildRequests(validBidRequests, res)[0].data); + assert.deepEqual(request.currency, 'EUR'); + setCurrencyConfig({}); + }); }); it('should pass extended ids', function () { @@ -301,11 +305,15 @@ describe('Caroda adapter', function () { }); it('should request floor price in adserver currency', function () { - config.setConfig({ currency: { adServerCurrency: 'DKK' } }); const validBidRequests = [ getBidWithFloor() ]; - const imp = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } })[0].data); - assert.equal(imp.bidfloor, undefined); - assert.equal(imp.bidfloorcur, 'DKK'); + setCurrencyConfig({ adServerCurrency: 'DKK' }); + const bidderRequest = { refererInfo: { page: 'page' } }; + return addFPDToBidderRequest(bidderRequest).then(res => { + const imp = JSON.parse(spec.buildRequests(validBidRequests, res)[0].data); + assert.equal(imp.bidfloor, undefined); + assert.equal(imp.bidfloorcur, 'DKK'); + setCurrencyConfig({}); + }); }); it('should add correct floor values', function () { diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index 073e889d172..4b0eefd7e8d 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -755,6 +755,48 @@ describe('Consumable BidAdapter', function () { expect(data.user.eids).to.deep.equal(bidderRequest.bidRequest[0].userIdAsEids); }); + it('Request should remove non-objects for userIdAsEids', function () { + bidderRequest.bidRequest[0].userId = {}; + bidderRequest.bidRequest[0].userId.tdid = 'TTD_ID'; + bidderRequest.bidRequest[0].userIdAsEids = [ + { + source: 'adserver.org', + uids: [ + { + id: 'TTD_ID_FROM_USER_ID_MODULE', + atype: 1, + ext: { + rtiPartner: 'TDID', + }, + }, + ], + }, + 'RANDOM_IDENTIFIER_STRING' + ]; + let scrubbedEids = [ + { + source: 'adserver.org', + uids: [ + { + id: 'TTD_ID_FROM_USER_ID_MODULE', + atype: 1, + ext: { + rtiPartner: 'TDID', + }, + }, + ], + }, + ]; + let request = spec.buildRequests( + bidderRequest.bidRequest, + BIDDER_REQUEST_1 + ); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal( + scrubbedEids + ); + }); + it('Request should NOT have adsrvrOrgId params if userId is NOT object', function() { let request = spec.buildRequests(bidderRequest.bidRequest, BIDDER_REQUEST_1); let data = JSON.parse(request.data); diff --git a/test/spec/modules/contxtfulRtdProvider_spec.js b/test/spec/modules/contxtfulRtdProvider_spec.js index e31ef554da0..68e38d63364 100644 --- a/test/spec/modules/contxtfulRtdProvider_spec.js +++ b/test/spec/modules/contxtfulRtdProvider_spec.js @@ -4,12 +4,15 @@ import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; import { getStorageManager } from '../../../src/storageManager.js'; import { MODULE_TYPE_UID } from '../../../src/activities/modules.js'; import * as events from '../../../src/events'; +import * as utils from 'src/utils.js'; import Sinon from 'sinon'; +import { deepClone } from '../../../src/utils.js'; const MODULE_NAME = 'contxtful'; const VERSION = 'v1'; const CUSTOMER = 'CUSTOMER'; +const SM = 'SM'; const CONTXTFUL_CONNECTOR_ENDPOINT = `https://api.receptivity.io/${VERSION}/prebid/${CUSTOMER}/connector/rxConnector.js`; const RX_FROM_SESSION_STORAGE = { ReceptivityState: 'Receptive', test_info: 'rx_from_session_storage' }; @@ -62,6 +65,8 @@ describe('contxtfulRtdProvider', function () { eventsEmitSpy = sandbox.spy(events, ['emit']); + sandbox.stub(utils, 'generateUUID').returns(SM); + let tagId = CUSTOMER; sessionStorage.clear(); }); @@ -534,6 +539,7 @@ describe('contxtfulRtdProvider', function () { name: 'contxtful', ext: { rx: RX_FROM_API, + sm: SM, params: { ev: config.params?.version, ci: config.params?.customer, @@ -549,11 +555,40 @@ describe('contxtfulRtdProvider', function () { expect(data.name).to.deep.equal(expectedData.name); expect(data.ext.rx).to.deep.equal(expectedData.ext.rx); + expect(data.ext.sm).to.deep.equal(expectedData.ext.sm); expect(data.ext.params).to.deep.equal(expectedData.ext.params); done(); }, TIMEOUT); }); + it('does not change the sm', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let firstReqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + let secondReqBidsConfigObj = deepClone(firstReqBidsConfigObj); + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(firstReqBidsConfigObj, onDoneSpy, config); + contxtfulSubmodule.getBidRequestData(secondReqBidsConfigObj, onDoneSpy, config); + + let firstData = firstReqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0]; + let secondData = secondReqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0]; + + expect(firstData.ext.sm).to.equal(secondData.ext.sm); + + done(); + }, TIMEOUT); + }); + describe('before rxApi is loaded', function () { const moveEventTheories = [ [ @@ -628,7 +663,7 @@ describe('contxtfulRtdProvider', function () { }); describe('after rxApi is loaded', function () { - it('does not add event', function (done) { + it('should add event', function (done) { let config = buildInitConfig(VERSION, CUSTOMER); contxtfulSubmodule.init(config); window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); @@ -648,7 +683,7 @@ describe('contxtfulRtdProvider', function () { let events = ext.events; - expect(events).to.be.undefined; + expect(events).to.be.not.undefined; done(); }, TIMEOUT); }); diff --git a/test/spec/modules/craftBidAdapter_spec.js b/test/spec/modules/craftBidAdapter_spec.js index aeb17f37161..d74477c5b8b 100644 --- a/test/spec/modules/craftBidAdapter_spec.js +++ b/test/spec/modules/craftBidAdapter_spec.js @@ -158,7 +158,7 @@ describe('craftAdapter', function () { height: 250, mediaType: 'banner', meta: null, - netRevenue: false, + netRevenue: true, requestId: '0396fae4eb5f47', ttl: 360, width: 300, diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index 149a4380036..8685bc1266f 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -17,6 +17,7 @@ import * as utils from 'src/utils.js'; import {EVENTS, STATUS, REJECTION_REASON} from '../../../src/constants.js'; import {server} from '../../mocks/xhr.js'; import * as events from 'src/events.js'; +import { enrichFPD } from '../../../src/fpd/enrichment.js'; import {requestBidsHook} from '../../../modules/currency.js'; var assert = require('chai').assert; @@ -525,6 +526,19 @@ describe('currency', function () { }); }); + describe('enrichFpd', function() { + function fpd(ortb2 = {}) { + return enrichFPD(Promise.resolve(ortb2)); + } + it('should set adServerCurrency on ortb', function () { + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); + setConfig({ adServerCurrency: 'EUR' }); + return fpd({}).then((ortb) => { + expect(ortb.ext.prebid.adServerCurrency).to.eql('EUR') + }) + }) + }); + describe('auctionDelay param', () => { const continueAuction = sinon.stub(); let logWarnSpy; diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 092cd1ff0f3..75765771d1a 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -271,7 +271,7 @@ describe('The DFP video support module', function () { video: { playbackmethod: [7, 1] }, - expected: undefined + expected: '2' } ], vpa: [ diff --git a/test/spec/modules/dianomiBidAdapter_spec.js b/test/spec/modules/dianomiBidAdapter_spec.js index b1ba5f60540..ef9283d3dad 100644 --- a/test/spec/modules/dianomiBidAdapter_spec.js +++ b/test/spec/modules/dianomiBidAdapter_spec.js @@ -3,6 +3,8 @@ import { assert } from 'chai'; import { spec } from 'modules/dianomiBidAdapter.js'; import { config } from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; +import { setConfig as setCurrencyConfig } from '../../../modules/currency'; +import { addFPDToBidderRequest } from '../../helpers/fpd'; describe('Dianomi adapter', () => { let bids = []; @@ -267,12 +269,14 @@ describe('Dianomi adapter', () => { }); it('should send currency if defined', () => { - config.setConfig({ currency: { adServerCurrency: 'EUR' } }); + setCurrencyConfig({ adServerCurrency: 'EUR' }) let validBidRequests = [{ params: { smartadId: 1234 } }]; let refererInfo = { page: 'page' }; - let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo }).data); - - assert.deepEqual(request.cur, ['EUR']); + return addFPDToBidderRequest({ refererInfo }).then(res => { + let request = JSON.parse(spec.buildRequests(validBidRequests, res).data); + assert.deepEqual(request.cur, ['EUR']); + setCurrencyConfig({}); + }); }); it('should pass supply chain object', () => { @@ -394,12 +398,18 @@ describe('Dianomi adapter', () => { }); it('should request floor price in adserver currency', () => { - config.setConfig({ currency: { adServerCurrency: 'GBP' } }); - const validBidRequests = [getBidWithFloor()]; - let imp = getRequestImps(validBidRequests)[0]; - - assert.equal(imp.bidfloor, undefined); - assert.equal(imp.bidfloorcur, 'GBP'); + setCurrencyConfig({ adServerCurrency: 'GBP' }) + let validBidRequests = [getBidWithFloor()]; + let refererInfo = { page: 'page' }; + return addFPDToBidderRequest({ refererInfo }).then(res => { + let imp = JSON.parse( + spec.buildRequests(validBidRequests, res).data + ).imp[0]; + + assert.equal(imp.bidfloor, undefined); + assert.equal(imp.bidfloorcur, 'GBP'); + setCurrencyConfig({}); + }); }); it('should add correct floor values', () => { diff --git a/test/spec/modules/docereeAdManagerBidAdapter_spec.js b/test/spec/modules/docereeAdManagerBidAdapter_spec.js index 704b9c48d3a..6f7da056681 100644 --- a/test/spec/modules/docereeAdManagerBidAdapter_spec.js +++ b/test/spec/modules/docereeAdManagerBidAdapter_spec.js @@ -18,7 +18,6 @@ describe('docereeadmanager', function () { city: '', state: '', zipcode: '', - hashedNPI: '', hashedhcpid: '', hashedemail: '', hashedmobile: '', @@ -146,7 +145,6 @@ describe('docereeadmanager', function () { city: 'Xxxxx', state: 'Xxxxxx', zipcode: 'XXXXXX', - hashedNPI: 'xxxxxx', hashedhcpid: 'xxxxxxx', hashedemail: 'xxxxxxx', hashedmobile: 'xxxxxxx', @@ -155,6 +153,7 @@ describe('docereeadmanager', function () { dob: 'xx-xx-xxxx', platformUid: 'Xx.xxx.xxxxxx', mobile: 'XXXXXXXXXX', + userconsent: 1 } bid = { ...bid, params: { ...bid.params, placementId: 'DOC-19-1' } } const buildRequests = { @@ -177,7 +176,6 @@ describe('docereeadmanager', function () { 'city', 'state', 'zipcode', - 'hashedNPI', 'pb', 'adunit', 'requestId', @@ -187,7 +185,7 @@ describe('docereeadmanager', function () { 'country', 'organization', 'dob', - 'userconsent', + 'upref', 'mobile', 'pageurl', 'consent' @@ -202,9 +200,8 @@ describe('docereeadmanager', function () { expect(payloadData.city).to.equal('Xxxxx'); expect(payloadData.state).to.equal('Xxxxxx'); expect(payloadData.zipcode).to.equal('XXXXXX'); - expect(payloadData.hashedNPI).to.equal('xxxxxx'); expect(payloadData.pb).to.equal(1); - expect(payloadData.userconsent).to.equal(1); + expect(payloadData.upref).to.equal(1); expect(payloadData.dob).to.equal('xx-xx-xxxx'); expect(payloadData.organization).to.equal('Xxxxxx'); expect(payloadData.country).to.equal('Xxxxxx'); @@ -214,7 +211,7 @@ describe('docereeadmanager', function () { expect(payloadData.requestId).to.equal('testing'); expect(payloadData.mobile).to.equal('XXXXXXXXXX'); expect(payloadData.adunit).to.equal('DOC-19-1'); - expect(payloadData.pageurl).to.equal('xxxxxx.com/xxxx'); + expect(payloadData.pageurl).to.equal('http://localhost:9876/context.html'); expect(payloadData.consent.gdprstr).to.equal('COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw'); expect(payloadData.consent.gdpr).to.equal(0); }) diff --git a/test/spec/modules/dsp_genieeBidAdapter_spec.js b/test/spec/modules/dsp_genieeBidAdapter_spec.js index 576fd9e404b..b708acffc0b 100644 --- a/test/spec/modules/dsp_genieeBidAdapter_spec.js +++ b/test/spec/modules/dsp_genieeBidAdapter_spec.js @@ -1,6 +1,8 @@ import { expect } from 'chai'; import { spec } from 'modules/dsp_genieeBidAdapter.js'; import { config } from 'src/config'; +import { setConfig as setCurrencyConfig } from '../../../modules/currency'; +import { addFPDToBidderRequest } from '../../helpers/fpd'; describe('Geniee adapter tests', () => { const validBidderRequest = { @@ -74,13 +76,16 @@ describe('Geniee adapter tests', () => { config.resetConfig(); }); it('uncomfortable (currency)', () => { - config.setConfig({ currency: { adServerCurrency: 'TWD' } }); - const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest); - expect(request).deep.equal({ - method: 'GET', - url: 'https://rt.gsspat.jp/prebid_uncomfortable', + setCurrencyConfig({ adServerCurrency: 'TWD' }); + return addFPDToBidderRequest(validBidderRequest).then(res => { + const request = spec.buildRequests(validBidderRequest.bids, res); + expect(request).deep.equal({ + method: 'GET', + url: 'https://rt.gsspat.jp/prebid_uncomfortable', + }); + setCurrencyConfig({}); + config.resetConfig(); }); - config.resetConfig(); }); }); describe('interpretResponse function test', () => { diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index a381d7644a1..9f46c57e422 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -53,6 +53,68 @@ describe('E-Planning Adapter', function () { 'adUnitCode': ADUNIT_CODE2, 'sizes': [[300, 250], [300, 600]], }; + const validBidWithSchain = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], + 'schain': { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1, + name: 'publisher', + domain: 'publisher.com' + } + ] + } + }; + const validBidWithSchainNodes = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE2, + 'sizes': [[300, 250], [300, 600]], + 'schain': { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'directseller.com', + sid: '00001', + rid: 'BidRequest1', + hp: 1, + name: 'publisher', + domain: 'publisher.com' + }, + { + asi: 'reseller.com', + sid: 'aaaaa', + rid: 'BidRequest2', + hp: 1, + name: 'publisher2', + domain: 'publisher2.com' + }, + { + asi: 'reseller3.com', + sid: 'aaaaab', + rid: 'BidRequest3', + hp: 1, + name: 'publisher3', + domain: 'publisher3.com' + } + ] + } + }; const ML = '1'; const validBidMappingLinear = { 'bidder': 'eplanning', @@ -727,7 +789,18 @@ describe('E-Planning Adapter', function () { expect(data.vctx).to.equal(2); expect(data.vv).to.equal(3); }); - + it('should return sch parameter', function () { + let bidRequests = [validBidWithSchain], schainExpected, schain; + schain = validBidWithSchain.schain; + schainExpected = schain.ver + ',' + schain.complete + '!' + schain.nodes.map(node => node.asi + ',' + node.sid + ',' + node.hp + ',' + node.rid + ',' + node.name + ',' + node.domain).join('!'); + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.sch).to.deep.equal(schainExpected); + }); + it('should not return sch parameter', function () { + let bidRequests = [validBidWithSchainNodes]; + const data = spec.buildRequests(bidRequests, bidderRequest).data; + expect(data.sch).to.equal(undefined); + }); it('should return correct e parameter with linear mapping attribute with more than one adunit', function () { let bidRequestsML = [validBidMappingLinear]; const NEW_CODE = ADUNIT_CODE + '2'; diff --git a/test/spec/modules/equativBidAdapter_spec.js b/test/spec/modules/equativBidAdapter_spec.js index c52507a000b..2784b5c4946 100644 --- a/test/spec/modules/equativBidAdapter_spec.js +++ b/test/spec/modules/equativBidAdapter_spec.js @@ -1,9 +1,20 @@ -import { BANNER } from 'src/mediaTypes.js'; -import { getBidFloor } from 'libraries/equativUtils/equativUtils.js' +import { getBidFloor } from 'libraries/equativUtils/equativUtils.js'; import { converter, spec, storage } from 'modules/equativBidAdapter.js'; +import { BANNER } from 'src/mediaTypes.js'; +import * as utils from '../../../src/utils.js'; describe('Equativ bid adapter tests', () => { - const DEFAULT_BID_REQUESTS = [ + let sandBox; + + beforeEach(() => { + sandBox = sinon.createSandbox(); + sandBox.stub(utils, 'logError'); + sandBox.stub(utils, 'logWarn'); + }); + + afterEach(() => sandBox.restore()); + + const DEFAULT_BANNER_BID_REQUESTS = [ { adUnitCode: 'eqtv_42', bidId: 'abcd1234', @@ -25,12 +36,119 @@ describe('Equativ bid adapter tests', () => { tid: 'zsfgzzg', }, }, + } + ]; + + const DEFAULT_VIDEO_BID_REQUESTS = [ + { + adUnitCode: 'eqtv_43', + bidId: 'efgh5678', + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + pos: 3, + skip: 1, + linearity: 1, + minduration: 10, + maxduration: 30, + minbitrate: 300, + maxbitrate: 600, + w: 640, + h: 480, + playbackmethod: [1], + api: [3], + mimes: ['video/x-flv', 'video/mp4'], + // protocols: [2, 3], // used in older adapter ... including as comment for reference + startdelay: 42, + battr: [13, 14], + placement: 1, + }, + }, + bidder: 'equativ', + params: { + networkId: 111, + }, + requestId: 'abcd1234', + ortb2Imp: { + ext: { + tid: 'zsgzgzz', + }, + }, + } + ]; + + const nativeOrtbRequest = { + assets: [{ + id: 0, + required: 1, + title: { + len: 140 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 600 + } }, + { + id: 2, + required: 1, + data: { + type: 1 + } + }], + context: 1, + eventtrackers: [{ + event: 1, + methods: [1, 2] + }], + plcmttype: 1, + privacy: 1, + ver: '1.2', + }; + const DEFAULT_NATIVE_BID_REQUESTS = [ + { + adUnitCode: 'equativ_native_42', + bidId: 'equativ_native_bidid_42', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + }, + }, + nativeOrtbRequest, + bidder: 'equativ', + params: { + networkId: 777, + }, + requestId: 'equativ_native_reqid_42', + ortb2Imp: { + ext: { + tid: 'equativ_native_tid_42', + }, + }, + } ]; - const DEFAULT_BIDDER_REQUEST = { + const DEFAULT_BANNER_BIDDER_REQUEST = { bidderCode: 'equativ', - bids: DEFAULT_BID_REQUESTS, + bids: DEFAULT_BANNER_BID_REQUESTS, + }; + + const DEFAULT_VIDEO_BIDDER_REQUEST = { + bidderCode: 'equativ', + bids: DEFAULT_VIDEO_BID_REQUESTS, + }; + + const DEFAULT_NATIVE_BIDDER_REQUEST = { + bidderCode: 'equativ', + bids: DEFAULT_NATIVE_BID_REQUESTS, }; const SAMPLE_RESPONSE = { @@ -62,25 +180,18 @@ describe('Equativ bid adapter tests', () => { }, }; - // const RESPONSE_WITH_DSP_PIXELS = { - // ...SAMPLE_RESPONSE, - // body: { - // dspPixels: ['1st-pixel', '2nd-pixel', '3rd-pixel'] - // } - // }; - describe('buildRequests', () => { - it('should build correct request using ORTB converter', () => { + it('should build correct requests using ORTB converter', () => { const request = spec.buildRequests( - DEFAULT_BID_REQUESTS, - DEFAULT_BIDDER_REQUEST + DEFAULT_BANNER_BID_REQUESTS, + DEFAULT_BANNER_BIDDER_REQUEST ); const dataFromConverter = converter.toORTB({ - bidderRequest: DEFAULT_BIDDER_REQUEST, - bidRequests: DEFAULT_BID_REQUESTS, + bidderRequest: DEFAULT_BANNER_BIDDER_REQUEST, + bidRequests: DEFAULT_BANNER_BID_REQUESTS, }); - expect(request).to.deep.equal({ - data: { ...dataFromConverter, id: request.data.id }, + expect(request[0]).to.deep.equal({ + data: { ...dataFromConverter, id: request[0].data.id }, method: 'POST', url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169', }); @@ -88,10 +199,10 @@ describe('Equativ bid adapter tests', () => { it('should add ext.bidder to imp object when siteId is defined', () => { const bidRequests = [ - { ...DEFAULT_BID_REQUESTS[0], params: { siteId: 123 } }, + { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { siteId: 123 } }, ]; - const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.imp[0].ext.bidder).to.deep.equal({ siteId: 123, }); @@ -99,10 +210,10 @@ describe('Equativ bid adapter tests', () => { it('should add ext.bidder to imp object when pageId is defined', () => { const bidRequests = [ - { ...DEFAULT_BID_REQUESTS[0], params: { pageId: 123 } }, + { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { pageId: 123 } }, ]; - const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.imp[0].ext.bidder).to.deep.equal({ pageId: 123, }); @@ -110,33 +221,33 @@ describe('Equativ bid adapter tests', () => { it('should add ext.bidder to imp object when formatId is defined', () => { const bidRequests = [ - { ...DEFAULT_BID_REQUESTS[0], params: { formatId: 123 } }, + { ...DEFAULT_BANNER_BID_REQUESTS[0], params: { formatId: 123 } }, ]; - const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.imp[0].ext.bidder).to.deep.equal({ formatId: 123, }); }); it('should not add ext.bidder to imp object when siteId, pageId, formatId are not defined', () => { - const bidRequests = [{ ...DEFAULT_BID_REQUESTS[0], params: {} }]; - const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const bidRequests = [{ ...DEFAULT_BANNER_BID_REQUESTS[0], params: {} }]; + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.imp[0].ext.bidder).to.be.undefined; }); it('should add site.publisher.id param', () => { const request = spec.buildRequests( - DEFAULT_BID_REQUESTS, - DEFAULT_BIDDER_REQUEST - ); + DEFAULT_BANNER_BID_REQUESTS, + DEFAULT_BANNER_BIDDER_REQUEST + )[0]; expect(request.data.site.publisher.id).to.equal(111); }); it('should pass ortb2.site.publisher.id', () => { const bidRequests = [{ - ...DEFAULT_BID_REQUESTS[0], + ...DEFAULT_BANNER_BID_REQUESTS[0], ortb2: { site: { publisher: { @@ -146,28 +257,28 @@ describe('Equativ bid adapter tests', () => { } }]; delete bidRequests[0].params; - const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.site.publisher.id).to.equal(98); }); it('should pass networkId as site.publisher.id', () => { const bidRequests = [{ - ...DEFAULT_BID_REQUESTS[0], + ...DEFAULT_BANNER_BID_REQUESTS[0], ortb2: { site: { publisher: {} } } }]; - const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.site.publisher.id).to.equal(111); }); it('should pass ortb2.app.publisher.id', () => { const bidRequests = [{ - ...DEFAULT_BID_REQUESTS[0], + ...DEFAULT_BANNER_BID_REQUESTS[0], ortb2: { app: { publisher: { @@ -177,28 +288,28 @@ describe('Equativ bid adapter tests', () => { } }]; delete bidRequests[0].params; - const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.app.publisher.id).to.equal(27); }); it('should pass networkId as app.publisher.id', () => { const bidRequests = [{ - ...DEFAULT_BID_REQUESTS[0], + ...DEFAULT_BANNER_BID_REQUESTS[0], ortb2: { app: { publisher: {} } } }]; - const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.app.publisher.id).to.equal(111); }); it('should pass ortb2.dooh.publisher.id', () => { const bidRequests = [{ - ...DEFAULT_BID_REQUESTS[0], + ...DEFAULT_BANNER_BID_REQUESTS[0], ortb2: { dooh: { publisher: { @@ -208,55 +319,55 @@ describe('Equativ bid adapter tests', () => { } }]; delete bidRequests[0].params; - const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.dooh.publisher.id).to.equal(35); }); it('should pass networkId as dooh.publisher.id', () => { const bidRequests = [{ - ...DEFAULT_BID_REQUESTS[0], + ...DEFAULT_BANNER_BID_REQUESTS[0], ortb2: { dooh: { publisher: {} } } }]; - const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.dooh.publisher.id).to.equal(111); }); it('should send default floor of 0.0', () => { const request = spec.buildRequests( - DEFAULT_BID_REQUESTS, - DEFAULT_BIDDER_REQUEST - ); + DEFAULT_BANNER_BID_REQUESTS, + DEFAULT_BANNER_BIDDER_REQUEST + )[0]; expect(request.data.imp[0]).to.have.property('bidfloor').that.eq(0.0); }); it('should send secure connection', () => { const request = spec.buildRequests( - DEFAULT_BID_REQUESTS, - DEFAULT_BIDDER_REQUEST - ); + DEFAULT_BANNER_BID_REQUESTS, + DEFAULT_BANNER_BIDDER_REQUEST + )[0]; expect(request.data.imp[0]).to.have.property('secure').that.eq(1); }); it('should have tagid', () => { const request = spec.buildRequests( - DEFAULT_BID_REQUESTS, - DEFAULT_BIDDER_REQUEST - ); - expect(request.data.imp[0]).to.have.property('tagid').that.eq(DEFAULT_BID_REQUESTS[0].adUnitCode); + DEFAULT_BANNER_BID_REQUESTS, + DEFAULT_BANNER_BIDDER_REQUEST + )[0]; + expect(request.data.imp[0]).to.have.property('tagid').that.eq(DEFAULT_BANNER_BID_REQUESTS[0].adUnitCode); }); it('should remove dt', () => { const bidRequests = [ - { ...DEFAULT_BID_REQUESTS[0], ortb2Imp: { dt: 1728377558235 } } + { ...DEFAULT_BANNER_BID_REQUESTS[0], ortb2Imp: { dt: 1728377558235 } } ]; - const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; - const request = spec.buildRequests(bidRequests, bidderRequest); + const bidderRequest = { ...DEFAULT_BANNER_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.data.imp[0]).to.not.have.property('dt'); }); @@ -268,9 +379,9 @@ describe('Equativ bid adapter tests', () => { getCookieStub.callsFake(cookieName => cookieData[cookieName]); const request = spec.buildRequests( - DEFAULT_BID_REQUESTS, - DEFAULT_BIDDER_REQUEST - ); + DEFAULT_BANNER_BID_REQUESTS, + DEFAULT_BANNER_BIDDER_REQUEST + )[0]; expect(request.data.user).to.have.property('buyeruid').that.eq(cookieData['eqt_pid']); @@ -282,9 +393,9 @@ describe('Equativ bid adapter tests', () => { getCookieStub.callsFake(() => null); const request = spec.buildRequests( - DEFAULT_BID_REQUESTS, - DEFAULT_BIDDER_REQUEST - ); + DEFAULT_BANNER_BID_REQUESTS, + DEFAULT_BANNER_BIDDER_REQUEST + )[0]; expect(request.data).to.not.have.property('user'); @@ -296,25 +407,255 @@ describe('Equativ bid adapter tests', () => { getCookieStub.callsFake(() => undefined); const bidRequest = { - ...DEFAULT_BIDDER_REQUEST, + ...DEFAULT_BANNER_BIDDER_REQUEST, ortb2: { user: { buyeruid: 'buyeruid-provided-by-publisher' } } }; - const request = spec.buildRequests([ DEFAULT_BID_REQUESTS[0] ], bidRequest); + const request = spec.buildRequests([ DEFAULT_BANNER_BID_REQUESTS[0] ], bidRequest)[0]; expect(request.data.user.buyeruid).to.deep.eq(bidRequest.ortb2.user.buyeruid); getCookieStub.restore(); }); + + it('should build a video request properly under normal circumstances', () => { + // ASSEMBLE + if (FEATURES.VIDEO) { + // ACT + const request = spec.buildRequests(DEFAULT_VIDEO_BID_REQUESTS, {})[0].data; + + // ASSERT + expect(request.imp[0]).to.have.property('video'); + + const videoObj = request.imp[0].video; + + expect(videoObj).to.have.property('api').and.to.deep.equal([3]); + expect(videoObj).to.have.property('battr').and.to.deep.equal([13, 14]); + expect(videoObj).to.have.property('linearity').and.to.equal(1); + expect(videoObj).to.have.property('mimes').and.to.deep.equal(['video/x-flv', 'video/mp4']); + expect(videoObj).to.have.property('minbitrate').and.to.equal(300); + expect(videoObj).to.have.property('maxbitrate').and.to.equal(600); + expect(videoObj).to.have.property('minduration').and.to.equal(10); + expect(videoObj).to.have.property('maxduration').and.to.equal(30); + expect(videoObj).to.have.property('placement').and.to.equal(1); + expect(videoObj).to.have.property('playbackmethod').and.to.deep.equal([1]); + expect(videoObj).to.have.property('pos').and.to.equal(3); + expect(videoObj).to.have.property('skip').and.to.equal(1); + expect(videoObj).to.have.property('startdelay').and.to.equal(42); + expect(videoObj).to.have.property('w').and.to.equal(640); + expect(videoObj).to.have.property('h').and.to.equal(480); + expect(videoObj).not.to.have.property('ext'); + } + }); + + it('should read and pass ortb2Imp.rwdd', () => { + // ASSEMBLE + if (FEATURES.VIDEO) { + const bidRequestsWithOrtb2ImpRwdd = [ + { + ...DEFAULT_VIDEO_BID_REQUESTS[0], + ortb2Imp: { + rwdd: 1 + } + } + ]; + // ACT + const request = spec.buildRequests(bidRequestsWithOrtb2ImpRwdd, {})[0].data; + + // ASSERT + expect(request.imp[0]).to.have.property('rwdd').and.to.equal(1); + } + }); + + it('should read mediaTypes.video.ext.rewarded and pass as rwdd', () => { + // ASSEMBLE + if (FEATURES.VIDEO) { + const bidRequestsWithExtReworded = [ + { + ...DEFAULT_VIDEO_BID_REQUESTS[0], + mediaTypes: { + video: { + ext: { + rewarded: 1 + } + } + } + } + ]; + // ACT + const request = spec.buildRequests(bidRequestsWithExtReworded, {})[0].data; + + // ASSERT + expect(request.imp[0]).to.have.property('rwdd').and.to.equal(1); + } + }); + + it('should prioritize ortb2Imp.rwdd over mediaTypes.video.ext.rewarded', () => { + // ASSEMBLE + if (FEATURES.VIDEO) { + const bidRequestsWithBothRewordedParams = [ + { + ...DEFAULT_VIDEO_BID_REQUESTS[0], + mediaTypes: { + video: { + ext: { + rewarded: 1 + } + } + }, + ortb2Imp: { + rwdd: 2 + } + } + ]; + // ACT + const request = spec.buildRequests(bidRequestsWithBothRewordedParams, {})[0].data; + + // ASSERT + expect(request.imp[0]).to.have.property('rwdd').and.to.equal(2); + } + }); + + it('should warn about missing required properties for video requests', () => { + // ASSEMBLE + const missingRequiredVideoRequest = DEFAULT_VIDEO_BID_REQUESTS[0]; + + // removing required properties + delete missingRequiredVideoRequest.mediaTypes.video.mimes; + delete missingRequiredVideoRequest.mediaTypes.video.placement; + + const bidRequests = [ missingRequiredVideoRequest ]; + const bidderRequest = { ...DEFAULT_VIDEO_BIDDER_REQUEST, bids: bidRequests }; + + // ACT + spec.buildRequests(bidRequests, bidderRequest); + + // ASSERT + expect(utils.logWarn.callCount).to.equal(2); + expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes('"mediaTypes.video.mimes" is missing')); + expect(utils.logWarn.getCall(1).args[0]).to.satisfy(arg => arg.includes('"mediaTypes.video.placement" is missing')); + }); + + it('should not send a video request when it has an empty body and no other impressions with any media types are defined', () => { + // ASSEMBLE + const emptyVideoRequest = { + ...DEFAULT_VIDEO_BID_REQUESTS[0], + mediaTypes: { + video: {} + } + }; + const bidRequests = [ emptyVideoRequest ]; + const bidderRequest = { ...DEFAULT_VIDEO_BIDDER_REQUEST, bids: bidRequests }; + + // ACT + const request = spec.buildRequests(bidRequests, bidderRequest); + + // ASSERT + expect(utils.logError.calledOnce).to.equal(true); + expect(utils.logError.args[0][0]).to.satisfy(arg => arg.includes('No request')); + expect(request).to.be.undefined; + }); + + it('should build a native request properly under normal circumstances', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const expectedResult = true; + + // ACT + const request = spec.buildRequests(DEFAULT_NATIVE_BID_REQUESTS, {})[0].data; + + // ASSERT + expect(request.imp[0]).to.have.property('native'); + + const nativeObj = request.imp[0].native; + expect(nativeObj).to.have.property('ver').and.to.equal('1.2'); + expect(nativeObj).to.have.property('request').and.to.be.a('string'); + + const requestObj = JSON.parse(nativeObj.request); + expect(requestObj).to.have.property('assets').and.to.be.an('array'); + expect(requestObj).to.have.property('eventtrackers').and.to.be.an('array'); + expect(requestObj).to.have.property('plcmttype').and.to.equal(1); + expect(requestObj).to.have.property('privacy').and.to.equal(1); + expect(requestObj).to.have.property('ver').and.to.equal('1.2'); + } + }); + + it('should not send a native request when it has an empty body and no other impressions with any media types are defined', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const emptyNativeRequest = { + ...DEFAULT_NATIVE_BID_REQUESTS[0], + mediaTypes: { + native: {} + } + }; + const bidRequests = [ emptyNativeRequest ]; + const bidderRequest = { ...DEFAULT_NATIVE_BIDDER_REQUEST, bids: bidRequests }; + + // ACT + const request = spec.buildRequests(bidRequests, bidderRequest); + + // ASSERT + expect(utils.logError.calledOnce).to.equal(true); + expect(utils.logError.args[0][0]).to.satisfy(arg => arg.includes('No request')); + expect(request).to.be.undefined; + } + }); + + it('should warn about missing "assets" property for native requests', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const missingRequiredNativeRequest = DEFAULT_NATIVE_BID_REQUESTS[0]; + + // removing just "assets" for this test + delete missingRequiredNativeRequest.nativeOrtbRequest.assets; + const bidRequests = [ missingRequiredNativeRequest ]; + const bidderRequest = { ...DEFAULT_NATIVE_BIDDER_REQUEST, bids: bidRequests }; + + // this value comes from native.js, part of the ortbConverter library + const warningMsgFromLibrary = 'mediaTypes.native is set, but no assets were specified. Native request skipped.' + + // ACT + spec.buildRequests(bidRequests, bidderRequest); + + // ASSERT + expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes(warningMsgFromLibrary)); + } + }); + + it('should warn about other missing required properties for native requests', () => { + if (FEATURES.NATIVE) { + // ASSEMBLE + const missingRequiredNativeRequest = DEFAULT_NATIVE_BID_REQUESTS[0]; + + // ortbConverter library will warn about missing assets; we supply warnings for these properties here + delete missingRequiredNativeRequest.mediaTypes.native.ortb.eventtrackers; + delete missingRequiredNativeRequest.mediaTypes.native.ortb.plcmttype; + delete missingRequiredNativeRequest.mediaTypes.native.ortb.privacy; + + const bidRequests = [ missingRequiredNativeRequest ]; + const bidderRequest = { ...DEFAULT_NATIVE_BIDDER_REQUEST, bids: bidRequests }; + + // ACT + spec.buildRequests(bidRequests, bidderRequest); + + // ASSERT + expect(utils.logWarn.callCount).to.equal(4); // the first message, regarding missing assets, is supplied by the ortbConverter library + expect(utils.logWarn.getCall(0).args[0]).to.satisfy(arg => arg.includes('no assets were specified')); + expect(utils.logWarn.getCall(1).args[0]).to.satisfy(arg => arg.includes('"mediaTypes.native.ortb.privacy" is missing')); + expect(utils.logWarn.getCall(2).args[0]).to.satisfy(arg => arg.includes('"mediaTypes.native.ortb.plcmttype" is missing')); + expect(utils.logWarn.getCall(3).args[0]).to.satisfy(arg => arg.includes('"mediaTypes.native.ortb.eventtrackers" is missing')); + } + }); }); describe('getBidFloor', () => { it('should return floor of 0.0 if floor module not available', () => { const bid = { - ...DEFAULT_BID_REQUESTS[0], + ...DEFAULT_BANNER_BID_REQUESTS[0], getFloor: false, }; expect(getBidFloor(bid)).to.deep.eq(0.0); @@ -330,7 +671,7 @@ describe('Equativ bid adapter tests', () => { it('should return proper min floor', () => { const bid = { - ...DEFAULT_BID_REQUESTS[0], + ...DEFAULT_BANNER_BID_REQUESTS[0], getFloor: data => { if (data.size[0] === 300 && data.size[1] === 250) { return { floor: 1.13 }; @@ -346,7 +687,7 @@ describe('Equativ bid adapter tests', () => { it('should return global media type floor if no rule for size', () => { const bid = { - ...DEFAULT_BID_REQUESTS[0], + ...DEFAULT_BANNER_BID_REQUESTS[0], getFloor: data => { if (data.size[0] === 728 && data.size[1] === 90) { return { floor: 1.13 }; @@ -362,7 +703,7 @@ describe('Equativ bid adapter tests', () => { it('should return floor of 0 if no rule for size', () => { const bid = { - ...DEFAULT_BID_REQUESTS[0], + ...DEFAULT_BANNER_BID_REQUESTS[0], getFloor: data => { if (data.size[0] === 728 && data.size[1] === 90) { return { floor: 1.13 }; @@ -466,9 +807,9 @@ describe('Equativ bid adapter tests', () => { describe('interpretResponse', () => { it('should return data returned by ORTB converter', () => { const request = spec.buildRequests( - DEFAULT_BID_REQUESTS, - DEFAULT_BIDDER_REQUEST - ); + DEFAULT_BANNER_BID_REQUESTS, + DEFAULT_BANNER_BIDDER_REQUEST + )[0]; const bids = spec.interpretResponse(SAMPLE_RESPONSE, request); expect(bids).to.deep.equal( converter.fromORTB({ diff --git a/test/spec/modules/escalaxBidAdapter_spec.js b/test/spec/modules/escalaxBidAdapter_spec.js new file mode 100644 index 00000000000..bc375ff3dae --- /dev/null +++ b/test/spec/modules/escalaxBidAdapter_spec.js @@ -0,0 +1,303 @@ +import { expect } from 'chai'; +import { spec } from 'modules/escalaxBidAdapter'; +import 'modules/priceFloors.js'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { config } from '../../../src/config.js'; +import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; + +import 'src/prebid.js'; +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; + +const SIMPLE_BID_REQUEST = { + bidder: 'escalax', + params: { + sourceId: 'sourceId', + accountId: 'accountId', + }, + mediaTypes: { + banner: { + sizes: [ + [320, 250], + [300, 600], + ], + }, + }, + adUnitCode: 'div-gpt-ad-1234567890123-0', + transactionId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + bidId: 'abcdef1234567890', + bidderRequestId: '1234567890abcdef', + auctionId: 'abcdef1234567890', + sizes: [[300, 250], [160, 600]], + gdprConsent: { + apiVersion: 2, + consentString: 'CONSENT', + vendorData: { purpose: { consents: { 1: true } } }, + gdprApplies: true, + addtlConsent: '1~1.35.41.101', + }, +} + +const BANNER_BID_REQUEST = { + bidder: 'escalax', + params: { + sourceId: 'sourceId', + accountId: 'accountId', + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + code: 'banner_example', + timeout: 1000, +} + +const VIDEO_BID_REQUEST = { + placementCode: '/DfpAccount1/slotVideo', + bidId: 'test-bid-id-2', + mediaTypes: { + video: { + playerSize: [400, 300], + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + }, + bidder: 'escalax', + params: { + sourceId: '123', + accountId: '123', + }, + adUnitCode: '/adunit-code/test-path', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + timeout: 1000, +} + +const NATIVE_BID_REQUEST = { + code: 'native_example', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bidder: 'escalax', + params: { + sourceId: 'sourceId', + accountId: 'accountId', + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + timeout: 1000, + uspConsent: 'uspConsent' +}; + +const bidderRequest = { + refererInfo: { + page: 'https://publisher.com/home', + ref: 'https://referrer' + } +}; + +const gdprConsent = { + apiVersion: 2, + consentString: 'CONSENT', + vendorData: { purpose: { consents: { 1: true } } }, + gdprApplies: true, + addtlConsent: '1~1.35.41.101', +} + +describe('escalaxAdapter', function () { + const adapter = newBidder(spec); + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('with user privacy regulations', function () { + it('should send the Coppa "required" flag set to "1" in the request', function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(serverRequest.data.regs.coppa).to.equal(1); + config.getConfig.restore(); + }); + + it('should send the GDPR Consent data in the request', function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({ ...bidderRequest, gdprConsent })); + expect(serverRequest.data.regs.ext.gdpr).to.exist.and.to.equal(1); + expect(serverRequest.data.user.ext.consent).to.equal('CONSENT'); + }); + + it('should send the CCPA data in the request', function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({...bidderRequest, ...{ uspConsent: '1YYY' }})); + expect(serverRequest.data.regs.ext.us_privacy).to.equal('1YYY'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(BANNER_BID_REQUEST)).to.equal(true); + }); + + it('should return false when sourceId/accountId is missing', function () { + let localbid = Object.assign({}, BANNER_BID_REQUEST); + delete localbid.params.sourceId; + delete localbid.params.accountId; + expect(spec.isBidRequestValid(BANNER_BID_REQUEST)).to.equal(false); + }); + }); + + describe('build request', function () { + it('should return an empty array when no bid requests', function () { + const bidRequest = spec.buildRequests([], syncAddFPDToBidderRequest(bidderRequest)); + expect(bidRequest).to.be.an('array'); + expect(bidRequest.length).to.equal(0); + }); + + it('should return a valid bid request object', function () { + const request = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request).to.not.equal('array'); + expect(request.data).to.be.an('object'); + expect(request.method).to.equal('POST'); + expect(request.url).to.not.equal(''); + expect(request.url).to.not.equal(undefined); + expect(request.url).to.not.equal(null); + + expect(request.data.site).to.have.property('page'); + expect(request.data.site).to.have.property('domain'); + expect(request.data).to.have.property('id'); + expect(request.data).to.have.property('imp'); + expect(request.data).to.have.property('device'); + }); + + it('should return a valid bid BANNER request object', function () { + const request = spec.buildRequests([BANNER_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0].banner).to.exist; + expect(request.data.imp[0].banner.format[0].w).to.be.an('number'); + expect(request.data.imp[0].banner.format[0].h).to.be.an('number'); + }); + + if (FEATURES.VIDEO) { + it('should return a valid bid VIDEO request object', function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0].video).to.exist; + expect(request.data.imp[0].video.w).to.be.an('number'); + expect(request.data.imp[0].video.h).to.be.an('number'); + }); + } + + it('should return a valid bid NATIVE request object', function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0]).to.be.an('object'); + }); + }) + + describe('interpretResponse', function () { + let bidRequests, bidderRequest; + beforeEach(function () { + bidRequests = [{ + 'bidId': '28ffdk2B952532', + 'bidder': 'escalax', + 'userId': { + 'freepassId': { + 'userIp': '172.21.0.1', + 'userId': '123', + 'commonId': 'commonIdValue' + } + }, + 'adUnitCode': 'adunit-code', + 'params': { + 'publisherId': 'publisherIdValue' + } + }]; + bidderRequest = {}; + }); + + it('Empty response must return empty array', function () { + const emptyResponse = null; + let response = spec.interpretResponse(emptyResponse, BANNER_BID_REQUEST); + + expect(response).to.be.an('array').that.is.empty; + }) + + it('Should interpret banner response', function () { + const serverResponse = { + body: { + 'cur': 'USD', + 'seatbid': [{ + 'bid': [{ + 'impid': '28ffdk2B952532', + 'price': 97, + 'adm': '', + 'w': 300, + 'h': 250, + 'crid': 'creative0' + }] + }] + } + }; + it('should interpret server response', function () { + const bidRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse(serverResponse, bidRequest); + expect(bids).to.be.an('array'); + const bid = bids[0]; + expect(bid).to.be.an('object'); + expect(bid.currency).to.equal('USD'); + expect(bid.cpm).to.equal(97); + expect(bid.ad).to.equal(ad) + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('creative0'); + }); + }) + }); +}); diff --git a/test/spec/modules/excoBidAdapter_spec.js b/test/spec/modules/excoBidAdapter_spec.js index 39844f0bc6a..be85d7735d6 100644 --- a/test/spec/modules/excoBidAdapter_spec.js +++ b/test/spec/modules/excoBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -117,24 +147,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -315,6 +328,7 @@ describe('ExcoBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -386,6 +400,7 @@ describe('ExcoBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/genericAnalyticsAdapter_spec.js b/test/spec/modules/genericAnalyticsAdapter_spec.js index f574a33bf86..8ec61b70810 100644 --- a/test/spec/modules/genericAnalyticsAdapter_spec.js +++ b/test/spec/modules/genericAnalyticsAdapter_spec.js @@ -120,7 +120,7 @@ describe('Generic analytics', () => { recv = arg; }); events.emit(BID_RESPONSE, {i: 1}); - expect(recv).to.eql([{eventType: BID_RESPONSE, args: {i: 1}}]); + sinon.assert.match(recv, [sinon.match({eventType: BID_RESPONSE, args: {i: 1}})]) }); it('should not cause infinite recursion, if handler triggers more events', () => { diff --git a/test/spec/modules/goldbachBidAdapter_spec.js b/test/spec/modules/goldbachBidAdapter_spec.js index 6ea84ed6931..744379efd19 100644 --- a/test/spec/modules/goldbachBidAdapter_spec.js +++ b/test/spec/modules/goldbachBidAdapter_spec.js @@ -1,16 +1,424 @@ import { expect } from 'chai'; +import sinon from 'sinon'; import { spec } from 'modules/goldbachBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import * as bidderFactory from 'src/adapters/bidderFactory.js'; import { auctionManager } from 'src/auctionManager.js'; import { deepClone } from 'src/utils.js'; -import { config } from 'src/config.js'; +import { VIDEO } from 'src/mediaTypes.js'; +import * as ajaxLib from 'src/ajax.js'; -const ENDPOINT = 'https://ib.adnxs.com/ut/v3/prebid'; -const PRICING_ENDPOINT = 'https://templates.da-services.ch/01_universal/burda_prebid/1.0/json/sizeCPMMapping.json'; +const BIDDER_NAME = 'goldbach' +const ENDPOINT = 'https://goldlayer-api.prod.gbads.net/bid/pbjs'; -describe('GoldbachXandrAdapter', function () { +/* Eids */ +let eids = [ + { + source: 'goldbach.com', + uids: [ + { + id: '0d862e87-14e9-47a4-9e9b-886b7d7a9d1b', + atype: 1, + ext: { stype: 'ppuid' } + } + ] + }, + { + source: 'niceid.live', + uids: [ + { + id: '0d862e87-14e9-47a4-9e9b-886b7d7a9d1a', + atype: 1, + ext: { stype: 'ppuid' } + } + ] + }, + { + source: 'otherid.live', + uids: [ + { + id: '0d862e87-14e9-47a4-9e9b-886b7d7a9d1a', + atype: 1, + ext: { stype: 'other-id' } + } + ] + } +]; + +const validNativeAd = { + link: { + url: 'https://example.com/cta', + }, + imptrackers: [ + 'https://example.com/impression1', + 'https://example.com/impression2', + ], + assets: [ + { + id: 1, + title: { + text: 'Amazing Product - Don’t Miss Out!', + }, + }, + { + id: 2, + img: { + url: 'https://example.com/main-image.jpg', + w: 300, + h: 250, + }, + }, + { + id: 3, + img: { + url: 'https://example.com/icon-image.jpg', + w: 50, + h: 50, + }, + }, + { + id: 4, + data: { + value: 'This is the description of the product. Its so good youll love it!', + }, + }, + { + id: 5, + data: { + value: 'Sponsored by ExampleBrand', + }, + }, + { + id: 6, + data: { + value: 'Shop Now', + }, + }, + ], +}; + +/* Ortb2 bid information */ +let ortb2 = { + device: { + ip: '133.713.371.337', + connectiontype: 6, + w: 1512, + h: 982, + ifa: '23575619-ef35-4908-b468-ffc4000cdf07', + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36', + geo: {lat: 47.318054, lon: 8.582883, zip: '8700'} + }, + site: { + domain: 'publisher-page.ch', + page: 'https://publisher-page.ch/home', + publisher: { domain: 'publisher-page.ch' }, + ref: 'https://publisher-page.ch/home' + }, + user: { + ext: { + eids: eids + } + } +}; + +/* Minimal bidderRequest */ +let validBidderRequest = { + auctionId: '7570fb24-810d-4c26-9f9c-acd0b6977f60', + start: 1731680672810, + auctionStart: 1731680672808, + ortb2: ortb2, + bidderCode: BIDDER_NAME, + gdprConsent: { + gdprApplies: true, + consentString: 'trust-me-i-consent' + }, + timeout: 300 +}; + +/* Minimal validBidRequests */ +let validBidRequests = [ + { + bidder: BIDDER_NAME, + adUnitCode: 'au-1', + adUnitId: 'c3400db6-c4c5-465e-bf67-1545751944b7', + auctionId: '7570fb24-810d-4c26-9f9c-acd0b6977f60', + bidId: '3d52a1909b972a', + bidderRequestId: '2b63a1826ab946', + userIdAsEids: eids, + ortb2: ortb2, + mediaTypes: { + banner: { + sizes: [[300, 50], [300, 250], [300, 600], [320, 50], [320, 480], [320, 64], [320, 160], [320, 416], [336, 280]] + } + }, + sizes: [[300, 50], [300, 250], [300, 600], [320, 50], [320, 480], [320, 64], [320, 160], [320, 416], [336, 280]], + params: { + publisherId: 'de-publisher.ch-ios', + slotId: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test', + customTargeting: { + language: 'de' + } + } + }, + { + bidder: BIDDER_NAME, + adUnitCode: 'au-2', + adUnitId: 'c3400db6-c4c5-465e-bf67-1545751944b8', + auctionId: '7570fb24-810d-4c26-9f9c-acd0b6977f60', + bidId: '3d52a1909b972b', + bidderRequestId: '2b63a1826ab946', + userIdAsEids: eids, + ortb2: ortb2, + mediaTypes: { + video: { + sizes: [[640, 480]] + } + }, + sizes: [[640, 480]], + params: { + publisherId: 'de-publisher.ch-ios', + slotId: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test/video', + video: { + maxduration: 30, + }, + customTargeting: { + language: 'de' + } + } + }, + { + bidder: BIDDER_NAME, + adUnitCode: 'au-3', + adUnitId: 'c3400db6-c4c5-465e-bf67-1545751944b9', + auctionId: '7570fb24-810d-4c26-9f9c-acd0b6977f60', + bidId: '3d52a1909b972c', + bidderRequestId: '2b63a1826ab946', + userIdAsEids: eids, + ortb2: ortb2, + mediaTypes: { + native: { + title: { + required: true, + len: 50 + }, + image: { + required: true, + sizes: [300, 157] + }, + icon: { + required: true, + sizes: [30, 30] + }, + body: { + required: true, + len: 150 + }, + cta: { + required: true, + len: 15 + }, + sponsoredBy: { + required: true, + len: 25 + }, + } + }, + params: { + publisherId: 'de-publisher.ch-ios', + slotId: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test/native', + customTargeting: { + language: 'de' + } + } + } +]; + +/* Creative request send to server */ +let validCreativeRequest = { + mock: false, + debug: false, + timestampStart: 1731680672811, + timestampEnd: 1731680675811, + config: { + publisher: { + id: 'de-20minuten.ch', + }, + }, + gdpr: {}, + contextInfo: { + contentUrl: 'http://127.0.0.1:5500/sample-request.html', + }, + appInfo: { + id: '127.0.0.1:5500', + }, + userInfo: { + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36', + ifa: '23575619-ef35-4908-b468-ffc4000cdf07', + ppid: [ + { + source: 'oneid.live', + id: '0d862e87-14e9-47a4-9e9b-886b7d7a9d1b', + }, + { + source: 'goldbach.com', + id: 'aa07ead5044f47bb28894ffa0346ed2c', + }, + ], + }, + slots: [ + { + id: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test', + sizes: [ + [300, 50], + [300, 250], + [300, 600], + [320, 50], + [320, 480], + [320, 64], + [320, 160], + [320, 416], + [336, 280], + ], + targetings: { + gpsenabled: 'false', + fr: 'false', + pagetype: 'story', + darkmode: 'false', + userloggedin: 'false', + iosbuild: '24110', + language: 'de', + storyId: '103211763', + connection: 'wifi', + }, + }, + { + id: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test/video', + sizes: [[640, 480]], + targetings: { + gpsenabled: 'false', + fr: 'false', + pagetype: 'story', + darkmode: 'false', + userloggedin: 'false', + iosbuild: '24110', + language: 'de', + storyId: '103211763', + connection: 'wifi', + duration: 'XL', + }, + }, + ], + targetings: { + long: 8.582883, + lat: 47.318054, + connection: '4G', + zip: '8700', + }, +}; + +/* Creative response received from server */ +let validCreativeResponse = { + creatives: { + '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test': [ + { + cpm: 32.2, + currency: 'USD', + width: 1, + height: 1, + creativeId: '1', + ttl: 3600, + mediaType: 'native', + netRevenue: true, + contextType: 'native', + ad: JSON.stringify(validNativeAd), + meta: { + advertiserDomains: ['example.com'], + mediaType: 'native' + } + }, + { + cpm: 21.9, + currency: 'USD', + width: 300, + height: 50, + creativeId: '2', + ttl: 3600, + mediaType: 'banner', + netRevenue: true, + contextType: 'banner', + ad: 'banner-ad', + meta: { + advertiserDomains: ['example.com'], + mediaType: 'banner' + } + } + ], + '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test/video': [ + { + cpm: 44.2, + currency: 'USD', + width: 1, + height: 1, + creativeId: '3', + ttl: 3600, + mediaType: 'video', + netRevenue: true, + contextType: 'video_preroll', + ad: 'video-ad', + meta: { + advertiserDomains: ['example.com'], + mediaType: 'video' + } + } + ], + '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test/native': [ + { + cpm: 10.2, + currency: 'USD', + width: 1, + height: 1, + creativeId: '4', + ttl: 3600, + mediaType: 'native', + netRevenue: true, + contextType: 'native', + ad: JSON.stringify(validNativeAd), + meta: { + advertiserDomains: ['example.com'], + mediaType: 'native' + } + } + ], + } +}; + +/* composed request */ +let validRequest = { + url: ENDPOINT, + method: 'POST', + data: validCreativeRequest, + options: { + contentType: 'application/json', + withCredentials: false + }, + bidderRequest: { + ...validBidderRequest, + bids: validBidRequests + } +} + +describe('GoldbachBidAdapter', function () { const adapter = newBidder(spec); + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(ajaxLib, 'ajax'); + sinon.stub(Math, 'random').returns(0); + }); + + afterEach(() => { + ajaxStub.restore(); + Math.random.restore(); + }); describe('inherited functions', function () { it('exists and is a function', function () { @@ -20,37 +428,23 @@ describe('GoldbachXandrAdapter', function () { describe('isBidRequestValid', function () { let bid = { - 'bidder': 'goldbach', - 'params': { - 'placementId': '10433394' + bidder: BIDDER_NAME, + params: { + publisherId: 'de-publisher.ch-ios', }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + adUnitCode: '/46753895/publisher.ch/inside-full-content-pos1/pbjs-test', + sizes: [[300, 250], [300, 600]] }; it('should return true when required params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return true when required params found', function () { - let invalidBid = Object.assign({}, bid); - delete invalidBid.params; - invalidBid.params = { - 'member': '1234', - 'invCode': 'ABCD' - }; - - expect(spec.isBidRequestValid(invalidBid)).to.equal(true); - }); - it('should return false when required params are not passed', function () { let invalidBid = Object.assign({}, bid); delete invalidBid.params; invalidBid.params = { - 'placementId': 0 + publisherId: undefined }; expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); @@ -58,20 +452,6 @@ describe('GoldbachXandrAdapter', function () { describe('buildRequests', function () { let getAdUnitsStub; - let bidRequests = [ - { - 'bidder': 'goldbach', - 'params': { - 'placementId': '10433394' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' - } - ]; beforeEach(function() { getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { @@ -83,1258 +463,353 @@ describe('GoldbachXandrAdapter', function () { getAdUnitsStub.restore(); }); - it('should parse out private sizes', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - privateSizes: [300, 250] - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); + it('should use defined endpoint', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - expect(payload.tags[0].private_sizes).to.exist; - expect(payload.tags[0].private_sizes).to.deep.equal([{width: 300, height: 250}]); - }); - - it('should add publisher_id in request', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - publisherId: '1231234' - } - }); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].publisher_id).to.exist; - expect(payload.tags[0].publisher_id).to.deep.equal(1231234); - expect(payload.publisher_id).to.exist; - expect(payload.publisher_id).to.deep.equal(1231234); + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal(ENDPOINT); }) - it('should add source and verison to the tag', function () { - const request = spec.buildRequests(bidRequests)[1]; - const payload = JSON.parse(request.data); - expect(payload.sdk).to.exist; - expect(payload.sdk).to.deep.equal({ - source: 'pbjs', - version: '$prebid.version$' - }); - }); + it('should parse all bids to valid slots', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - it('should populate the ad_types array on all requests', function () { - let adUnits = [{ - code: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bids: [{ - bidder: 'goldbach', - params: { - placementId: '10433394' - } - }], - transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' - }]; - - ['banner', 'video', 'native'].forEach(type => { - getAdUnitsStub.callsFake(function(...args) { - return adUnits; - }); - - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes[type] = {}; - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].ad_types).to.deep.equal([type]); - - if (type === 'banner') { - delete adUnits[0].mediaTypes; - } - }); - }); - - it('should not populate the ad_types array when adUnit.mediaTypes is undefined', function() { - const bidRequest = Object.assign({}, bidRequests[0]); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].ad_types).to.not.exist; - }); - - it('should populate the ad_types array on outstream requests', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes.video = {context: 'outstream'}; - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].ad_types).to.deep.equal(['video']); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); - - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(bidRequests)[1]; - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('POST'); - }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - it('sends bid request to ENDPOINT via GET', function () { - const request = spec.buildRequests(bidRequests)[0]; - expect(request.url).to.equal(PRICING_ENDPOINT); - expect(request.method).to.equal('GET'); + expect(payload.slots).to.exist; + expect(Array.isArray(payload.slots)).to.be.true; + expect(payload.slots.length).to.equal(3); + expect(payload.slots[0].id).to.equal(bidRequests[0].params.slotId); + expect(Array.isArray(payload.slots[0].sizes)).to.be.true; + expect(payload.slots[0].sizes.length).to.equal(bidRequests[0].sizes.length); + expect(payload.slots[1].id).to.equal(bidRequests[1].params.slotId); + expect(Array.isArray(payload.slots[1].sizes)).to.be.true; }); - it('should attach valid video params to the tag', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - video: { - id: 123, - minduration: 100, - foobar: 'invalid' - } - } - } - ); + it('should parse all video bids to valid video slots (use video sizes)', function () { + let bidRequests = validBidRequests.map(request => Object.assign({}, [])); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - id: 123, - minduration: 100 - }); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); - - it('should include ORTB video values when video params were not set', function() { - let bidRequest = deepClone(bidRequests[0]); - bidRequest.params = { - placementId: '1234235', - video: { - skippable: true, - playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], - context: 'outstream' - } - }; - bidRequest.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'], - skip: 0, - minduration: 5, - api: [1, 5, 6], - playbackmethod: [2, 4] - } - }; - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].video).to.deep.equal({ - minduration: 5, - playback_method: 2, - skippable: true, - context: 4 - }); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) - }); - - it('should add video property when adUnit includes a renderer', function () { - const videoData = { + const requests = spec.buildRequests([{ + ...bidRequests[1], + sizes: [], mediaTypes: { - video: { - context: 'outstream', - mimes: ['video/mp4'] - } - }, - params: { - placementId: '10433394', - video: { - skippable: true, - playback_method: ['auto_play_sound_off'] + [VIDEO]: { + sizes: [[640, 480]] } } - }; + }], bidderRequest); + const payload = requests[0].data; - let bidRequest1 = deepClone(bidRequests[0]); - bidRequest1 = Object.assign({}, bidRequest1, videoData, { - renderer: { - url: 'https://test.renderer.url', - render: function () {} - } - }); - - let bidRequest2 = deepClone(bidRequests[0]); - bidRequest2.adUnitCode = 'adUnit_code_2'; - bidRequest2 = Object.assign({}, bidRequest2, videoData); - - const request = spec.buildRequests([bidRequest1, bidRequest2])[1]; - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - skippable: true, - playback_method: 2, - custom_renderer_present: true - }); - expect(payload.tags[1].video).to.deep.equal({ - skippable: true, - playback_method: 2 - }); + expect(payload.slots.length).to.equal(1); + expect(payload.slots[0].sizes.length).to.equal(1); + expect(payload.slots[0].sizes[0][0]).to.equal(640); + expect(payload.slots[0].sizes[0][1]).to.equal(480); }); - it('should attach valid user params to the tag', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - user: { - externalUid: '123', - segments: [123, { id: 987, value: 876 }], - foobar: 'invalid' - } - } - } - ); + it('should set timestamps on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - expect(payload.user).to.exist; - expect(payload.user).to.deep.equal({ - external_uid: '123', - segments: [{id: 123}, {id: 987, value: 876}] - }); + expect(payload.timestampStart).to.exist; + expect(payload.timestampStart).to.be.greaterThan(1) + expect(payload.timestampEnd).to.exist; + expect(payload.timestampEnd).to.be.greaterThan(1) }); - it('should attach reserve param when either bid param or getFloor function exists', function () { - let getFloorResponse = { currency: 'USD', floor: 3 }; - let request, payload = null; - let bidRequest = deepClone(bidRequests[0]); - - // 1 -> reserve not defined, getFloor not defined > empty - request = spec.buildRequests([bidRequest])[1]; - payload = JSON.parse(request.data); - - expect(payload.tags[0].reserve).to.not.exist; - - // 2 -> reserve is defined, getFloor not defined > reserve is used - bidRequest.params = { - 'placementId': '10433394', - 'reserve': 0.5 - }; - request = spec.buildRequests([bidRequest])[1]; - payload = JSON.parse(request.data); - - expect(payload.tags[0].reserve).to.exist.and.to.equal(0.5); - - // 3 -> reserve is defined, getFloor is defined > getFloor is used - bidRequest.getFloor = () => getFloorResponse; + it('should set config on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - request = spec.buildRequests([bidRequest])[1]; - payload = JSON.parse(request.data); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - expect(payload.tags[0].reserve).to.exist.and.to.equal(3); + expect(payload.config.publisher.id).to.equal(bidRequests[0].params.publisherId); }); - it('should duplicate adpod placements into batches and set correct maxduration', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); + it('should set config on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - // 300 / 15 = 20 total - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload1.tags[0].video.maxduration).to.equal(30); - - expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload2.tags[0].video.maxduration).to.equal(30); + expect(payload.config.publisher.id).to.equal(bidRequests[0].params.publisherId); }); - it('should round down adpod placements when numbers are uneven', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 123, - durationRangeSec: [45], - } - } - } - ); + it('should set gdpr on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(2); - }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - it('should duplicate adpod placements when requireExactDuration is set', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - requireExactDuration: true, - } - } - } - ); - - // 20 total placements with 15 max impressions = 2 requests - const request = spec.buildRequests([bidRequest])[1]; - expect(request.length).to.equal(2); - - // 20 spread over 2 requests = 15 in first request, 5 in second - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - // 10 placements should have max/min at 15 - // 10 placemenst should have max/min at 30 - const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); - const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); - expect(payload1tagsWith15.length).to.equal(10); - expect(payload1tagsWith30.length).to.equal(5); - - // 5 placemenst with min/max at 30 were in the first request - // so 5 remaining should be in the second - const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); - expect(payload2tagsWith30.length).to.equal(5); + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consent).to.equal(bidderRequest.gdprConsent.gdprApplies); + expect(payload.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); }); - it('should set durations for placements when requireExactDuration is set and numbers are uneven', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 105, - durationRangeSec: [15, 30, 60], - requireExactDuration: true, - } - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(7); - - const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); - const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); - const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); - expect(tagsWith15.length).to.equal(3); - expect(tagsWith30.length).to.equal(3); - expect(tagsWith60.length).to.equal(1); - }); - - it('should break adpod request into batches', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 225, - durationRangeSec: [5], - } - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - const payload3 = JSON.parse(request[2].data); - - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(15); - expect(payload3.tags.length).to.equal(15); - }); - - // it('should contain hb_source value for adpod', function() { - // let bidRequest = Object.assign({}, - // bidRequests[0], - // { - // params: { placementId: '14542875' } - // }, - // { - // mediaTypes: { - // video: { - // context: 'adpod', - // playerSize: [640, 480], - // adPodDurationSec: 300, - // durationRangeSec: [15, 30], - // } - // } - // } - // ); - // const request = spec.buildRequests([bidRequest])[1]; - // const payload = JSON.parse(request.data); - // expect(payload.tags[0].hb_source).to.deep.equal(7); - // }); - - it('should contain hb_source value for other media', function() { - let bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'banner', - params: { - sizes: [[300, 250], [300, 600]], - placementId: 13144370 - } - } - ); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); - - it('adds brand_category_exclusion to request when set', function() { - let bidRequest = Object.assign({}, bidRequests[0]); - sinon - .stub(config, 'getConfig') - .withArgs('adpod.brandCategoryExclusion') - .returns(true); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.brand_category_uniqueness).to.equal(true); - - config.getConfig.restore(); - }); - - it('should attach native params to the request', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'native', - nativeParams: { - title: {required: true}, - body: {required: true}, - body2: {required: true}, - image: {required: true, sizes: [100, 100]}, - icon: {required: true}, - cta: {required: false}, - rating: {required: true}, - sponsoredBy: {required: true}, - privacyLink: {required: true}, - displayUrl: {required: true}, - address: {required: true}, - downloads: {required: true}, - likes: {required: true}, - phone: {required: true}, - price: {required: true}, - salePrice: {required: true} - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].native.layouts[0]).to.deep.equal({ - title: {required: true}, - description: {required: true}, - desc2: {required: true}, - main_image: {required: true, sizes: [{ width: 100, height: 100 }]}, - icon: {required: true}, - ctatext: {required: false}, - rating: {required: true}, - sponsored_by: {required: true}, - privacy_link: {required: true}, - displayurl: {required: true}, - address: {required: true}, - downloads: {required: true}, - likes: {required: true}, - phone: {required: true}, - price: {required: true}, - saleprice: {required: true}, - privacy_supported: true - }); - expect(payload.tags[0].hb_source).to.equal(1); - }); - - it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'native', - nativeParams: { - image: { required: true } - } - } - ); - bidRequest.sizes = [[150, 100], [300, 250]]; - - let request = spec.buildRequests([bidRequest])[1]; - let payload = JSON.parse(request.data); - expect(payload.tags[0].sizes).to.deep.equal([{width: 150, height: 100}, {width: 300, height: 250}]); - - delete bidRequest.sizes; - - request = spec.buildRequests([bidRequest])[1]; - payload = JSON.parse(request.data); - - expect(payload.tags[0].sizes).to.deep.equal([{width: 1, height: 1}]); - }); - - it('should convert keyword params to proper form and attaches to request', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - keywords: { - single: 'val', - singleArr: ['val'], - singleArrNum: [5], - multiValMixed: ['value1', 2, 'value3'], - singleValNum: 123, - emptyStr: '', - emptyArr: [''], - badValue: {'foo': 'bar'} // should be dropped - } - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'singleArrNum', - 'value': ['5'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'singleValNum', - 'value': ['123'] - }, { - 'key': 'emptyStr' - }, { - 'key': 'emptyArr' - }]); - }); + it('should set contextInfo on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - it('should add payment rules to the request', function () { - let bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - usePaymentRule: true - } - } - ); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].use_pmt_rule).to.equal(true); - }); - - it('should add gpid to the request', function () { - let testGpid = '/12345/my-gpt-tag-0'; - let bidRequest = deepClone(bidRequests[0]); - bidRequest.ortb2Imp = { ext: { data: { pbadslot: testGpid } } }; - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - - expect(payload.tags[0].gpid).to.exist.and.equal(testGpid) - }); - - it('should add gdpr consent information to the request', function () { - let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - let bidderRequest = { - 'bidderCode': 'goldbach', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - gdprApplies: true, - addtlConsent: '1~7.12.35.62.66.70.89.93.108' - } - }; - bidderRequest.bids = bidRequests; - - const request = spec.buildRequests(bidRequests, bidderRequest)[1]; - expect(request.options).to.deep.equal({withCredentials: true}); - const payload = JSON.parse(request.data); - - expect(payload.gdpr_consent).to.exist; - expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString); - expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; - expect(payload.gdpr_consent.addtl_consent).to.exist.and.to.deep.equal([7, 12, 35, 62, 66, 70, 89, 93, 108]); - }); - - it('should add us privacy string to payload', function() { - let consentString = '1YA-'; - let bidderRequest = { - 'bidderCode': 'goldbach', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'uspConsent': consentString - }; - bidderRequest.bids = bidRequests; - - const request = spec.buildRequests(bidRequests, bidderRequest)[1]; - const payload = JSON.parse(request.data); - - expect(payload.us_privacy).to.exist; - expect(payload.us_privacy).to.exist.and.to.equal(consentString); - }); - - it('supports sending hybrid mobile app parameters', function () { - let appRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - app: { - id: 'B1O2W3M4AN.com.prebid.webview', - geo: { - lat: 40.0964439, - lng: -75.3009142 - }, - device_id: { - idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', // Apple advertising identifier - aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', // Android advertising identifier - md5udid: '5756ae9022b2ea1e47d84fead75220c8', // MD5 hash of the ANDROID_ID - sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', // SHA1 hash of the ANDROID_ID - windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' // Windows advertising identifier - } - } - } - } - ); - const request = spec.buildRequests([appRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.app).to.exist; - expect(payload.app).to.deep.equal({ - appid: 'B1O2W3M4AN.com.prebid.webview' - }); - expect(payload.device.device_id).to.exist; - expect(payload.device.device_id).to.deep.equal({ - aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', - idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', - md5udid: '5756ae9022b2ea1e47d84fead75220c8', - sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', - windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' - }); - expect(payload.device.geo).to.exist; - expect(payload.device.geo).to.deep.equal({ - lat: 40.0964439, - lng: -75.3009142 - }); - }); - - it('should add referer info to payload', function () { - const bidRequest = Object.assign({}, bidRequests[0]) - const bidderRequest = { - refererInfo: { - topmostLocation: 'https://example.com/page.html', - reachedTop: true, - numIframes: 2, - stack: [ - 'https://example.com/page.html', - 'https://example.com/iframe1.html', - 'https://example.com/iframe2.html' - ] - } - } - const request = spec.buildRequests([bidRequest], bidderRequest)[1]; - const payload = JSON.parse(request.data); - - expect(payload.referrer_detection).to.exist; - expect(payload.referrer_detection).to.deep.equal({ - rd_ref: 'https%3A%2F%2Fexample.com%2Fpage.html', - rd_top: true, - rd_ifs: 2, - rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') - }); - }); - - it('should populate schain if available', function () { - const bidRequest = Object.assign({}, bidRequests[0], { - schain: { - ver: '1.0', - complete: 1, - nodes: [ - { - 'asi': 'blob.com', - 'sid': '001', - 'hp': 1 - } - ] - } - }); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.schain).to.deep.equal({ - ver: '1.0', - complete: 1, - nodes: [ - { - 'asi': 'blob.com', - 'sid': '001', - 'hp': 1 - } - ] - }); + expect(payload.contextInfo.contentUrl).to.exist; + expect(payload.contextInfo.contentUrl).to.equal(bidderRequest.ortb2.site.page); }); - it('should populate coppa if set in config', function () { - let bidRequest = Object.assign({}, bidRequests[0]); - sinon.stub(config, 'getConfig') - .withArgs('coppa') - .returns(true); + it('should set appInfo on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - expect(payload.user.coppa).to.equal(true); - - config.getConfig.restore(); + expect(payload.appInfo.id).to.exist; + expect(payload.appInfo.id).to.equal(bidderRequest.ortb2.site.domain); }); - it('should set the X-Is-Test customHeader if test flag is enabled', function () { - let bidRequest = Object.assign({}, bidRequests[0]); - sinon.stub(config, 'getConfig') - .withArgs('apn_test') - .returns(true); + it('should set userInfo on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - const request = spec.buildRequests([bidRequest])[1]; - expect(request.options.customHeaders).to.deep.equal({'X-Is-Test': 1}); + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - config.getConfig.restore(); + expect(payload.userInfo).to.exist; + expect(payload.userInfo.ua).to.equal(bidderRequest.ortb2.device.ua); + expect(payload.userInfo.ip).to.equal(bidderRequest.ortb2.device.ip); + expect(payload.userInfo.ifa).to.equal(bidderRequest.ortb2.device.ifa); + expect(Array.isArray(payload.userInfo.ppid)).to.be.true; + expect(payload.userInfo.ppid.length).to.equal(2); }); - it('should always set withCredentials: true on the request.options', function () { - let bidRequest = Object.assign({}, bidRequests[0]); - const request = spec.buildRequests([bidRequest])[1]; - expect(request.options.withCredentials).to.equal(true); - }); + it('should set mapped general targetings on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); - it('should set simple domain variant if purpose 1 consent is not given', function () { - let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - let bidderRequest = { - 'bidderCode': 'goldbach', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - gdprApplies: true, - apiVersion: 2, - vendorData: { - purpose: { - consents: { - 1: false - } - } - } - } - }; - bidderRequest.bids = bidRequests; + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; - const request = spec.buildRequests(bidRequests, bidderRequest)[1]; - expect(request.url).to.equal('https://ib.adnxs-simple.com/ut/v3/prebid'); + expect(payload.slots[0].targetings['duration']).to.not.exist; + expect(payload.slots[1].targetings['duration']).to.exist; + expect(payload.targetings['duration']).to.not.exist; + expect(payload.targetings['lat']).to.exist; + expect(payload.targetings['long']).to.exist; + expect(payload.targetings['zip']).to.exist; + expect(payload.targetings['connection']).to.exist; }); - it('should populate eids when supported userIds are available', function () { - const bidRequest = Object.assign({}, bidRequests[0], { - userId: { - tdid: 'sample-userid', - uid2: { id: 'sample-uid2-value' }, - criteoId: 'sample-criteo-userid', - netId: 'sample-netId-userid', - idl_env: 'sample-idl-userid' - } - }); - - const request = spec.buildRequests([bidRequest])[1]; - const payload = JSON.parse(request.data); - expect(payload.eids).to.deep.include({ - source: 'adserver.org', - id: 'sample-userid', - rti_partner: 'TDID' - }); - - expect(payload.eids).to.deep.include({ - source: 'criteo.com', - id: 'sample-criteo-userid', - }); - - expect(payload.eids).to.deep.include({ - source: 'netid.de', - id: 'sample-netId-userid', - }); - - expect(payload.eids).to.deep.include({ - source: 'liveramp.com', - id: 'sample-idl-userid' - }); + it('should set mapped video duration targetings on request', function () { + let bidRequests = deepClone(validBidRequests); + let videoRequest = deepClone(validBidRequests[1]); + let bidderRequest = deepClone(validBidderRequest); - expect(payload.eids).to.deep.include({ - source: 'uidapi.com', - id: 'sample-uid2-value', - rti_partner: 'UID2' - }); - }); - - it('should populate iab_support object at the root level if omid support is detected', function () { - // with bid.params.frameworks - let bidRequest_A = Object.assign({}, bidRequests[0], { + bidRequests.push({ + ...videoRequest, params: { - frameworks: [1, 2, 5, 6], + ...videoRequest.params, video: { - frameworks: [1, 2, 5, 6] + maxduration: 10 } } - }); - let request = spec.buildRequests([bidRequest_A])[1]; - let payload = JSON.parse(request.data); - expect(payload.iab_support).to.be.an('object'); - expect(payload.iab_support).to.deep.equal({ - omidpn: 'Appnexus', - omidpv: '$prebid.version$' - }); - expect(payload.tags[0].banner_frameworks).to.be.an('array'); - expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video_frameworks).to.be.an('array'); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video.frameworks).to.not.exist; - - // without bid.params.frameworks - const bidRequest_B = Object.assign({}, bidRequests[0]); - request = spec.buildRequests([bidRequest_B])[1]; - payload = JSON.parse(request.data); - expect(payload.iab_support).to.not.exist; - expect(payload.tags[0].banner_frameworks).to.not.exist; - expect(payload.tags[0].video_frameworks).to.not.exist; - - // with video.frameworks but it is not an array - const bidRequest_C = Object.assign({}, bidRequests[0], { + }) + + bidRequests.push({ + ...videoRequest, params: { + ...videoRequest.params, video: { - frameworks: "'1', '2', '3', '6'" + maxduration: 35 } } + }) + + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; + + expect(payload.slots[0].targetings['duration']).to.not.exist; + expect(payload.slots[1].targetings['duration']).to.exist; + expect(payload.slots[1].targetings['duration']).to.equal('XL'); + expect(payload.slots[3].targetings['duration']).to.equal('M'); + expect(payload.slots[4].targetings['duration']).to.equal('XXL'); + }); + + it('should set mapped connection targetings on request', function () { + let bidRequests = deepClone(validBidRequests); + let bidderRequest = deepClone(validBidderRequest); + + const bidderRequestEthernet = deepClone(bidderRequest); + bidderRequestEthernet.ortb2.device.connectiontype = 1; + const payloadEthernet = spec.buildRequests(bidRequests, bidderRequestEthernet)[0].data; + + const bidderRequestWifi = deepClone(bidderRequest); + bidderRequestWifi.ortb2.device.connectiontype = 2; + const payloadWifi = spec.buildRequests(bidRequests, bidderRequestWifi)[0].data; + + const bidderRequest2G = deepClone(bidderRequest); + bidderRequest2G.ortb2.device.connectiontype = 4; + const payload2G = spec.buildRequests(bidRequests, bidderRequest2G)[0].data; + + const bidderRequest3G = deepClone(bidderRequest); + bidderRequest3G.ortb2.device.connectiontype = 5; + const payload3G = spec.buildRequests(bidRequests, bidderRequest3G)[0].data; + + const bidderRequest4G = deepClone(bidderRequest); + bidderRequest4G.ortb2.device.connectiontype = 6; + const payload4G = spec.buildRequests(bidRequests, bidderRequest4G)[0].data; + + const bidderRequestNoConnection = deepClone(bidderRequest); + bidderRequestNoConnection.ortb2.device.connectiontype = undefined; + const payloadNoConnection = spec.buildRequests(bidRequests, bidderRequestNoConnection)[0].data; + + expect(payloadEthernet.targetings['connection']).to.equal('ethernet'); + expect(payloadWifi.targetings['connection']).to.equal('wifi'); + expect(payload2G.targetings['connection']).to.equal('2G'); + expect(payload3G.targetings['connection']).to.equal('3G'); + expect(payload4G.targetings['connection']).to.equal('4G'); + expect(payloadNoConnection.targetings['connection']).to.equal(undefined); + }); + + it('should create a request with minimal information', function () { + let bidderRequest = Object.assign({}, validBidderRequest); + let bidRequests = validBidRequests.map(request => Object.assign({}, request)); + + // Removing usable bidderRequest values + bidderRequest.gdprConsent = undefined; + bidderRequest.ortb2.device.connectiontype = undefined; + bidderRequest.ortb2.device.geo = undefined; + bidderRequest.ortb2.device.ip = undefined; + bidderRequest.ortb2.device.ifa = undefined; + bidderRequest.ortb2.device.ua = undefined; + + // Removing usable bidRequests values + bidRequests = bidRequests.map(request => { + request.ortb2.device.connectiontype = undefined; + request.ortb2.device.geo = undefined; + request.ortb2.device.ip = undefined; + request.ortb2.device.ifa = undefined; + request.ortb2.device.ua = undefined; + request.userIdAsEids = undefined; + request.params = { + publisherId: 'de-publisher.ch-ios' + }; + return request; }); - request = spec.buildRequests([bidRequest_C])[1]; - payload = JSON.parse(request.data); - expect(payload.iab_support).to.not.exist; - expect(payload.tags[0].banner_frameworks).to.not.exist; - expect(payload.tags[0].video_frameworks).to.not.exist; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + const payload = requests[0].data; + + // bidderRequest mappings + expect(payload.gdpr).to.exist; + expect(payload.gdpr.consent).to.not.exist; + expect(payload.gdpr.consentString).to.not.exist; + expect(payload.userInfo).to.exist; + expect(payload.userInfo.ua).to.exist; + expect(payload.userInfo.ip).to.not.exist; + expect(payload.userInfo.ifa).to.not.exist; + expect(payload.userInfo.ppid.length).to.equal(0); + expect(payload.targetings).to.exist; + expect(payload.targetings['connection']).to.not.exist; + expect(payload.targetings['lat']).to.not.exist; + expect(payload.targetings['long']).to.not.exist; + expect(payload.targetings['zip']).to.not.exist; + + // bidRequests mapping + expect(payload.slots).to.exist; + expect(payload.slots.length).to.equal(3); + expect(payload.slots[0].targetings).to.exist + expect(payload.slots[1].targetings).to.exist }); - }) + }); describe('interpretResponse', function () { - let response = { - 'version': '3.0.0', - 'tags': [ - { - 'uuid': '3db3773286ee59', - 'tag_id': 10433394, - 'auction_id': '4534722592064951574', - 'nobid': false, - 'no_ad_url': 'https://lax1-ib.adnxs.com/no-ad', - 'timeout_ms': 10000, - 'ad_profile_id': 27079, - 'ads': [ - { - 'content_source': 'rtb', - 'ad_type': 'banner', - 'buyer_member_id': 958, - 'creative_id': 29681110, - 'media_type_id': 1, - 'media_subtype_id': 1, - 'cpm': 0.5, - 'cpm_publisher_currency': 0.5, - 'publisher_currency_code': '$', - 'client_initiated_ad_counting': true, - 'viewability': { - 'config': '' - }, - 'rtb': { - 'banner': { - 'content': '', - 'width': 300, - 'height': 250 - }, - 'trackers': [ - { - 'impression_urls': [ - 'https://lax1-ib.adnxs.com/impression' - ], - 'video_events': {} - } - ] - } - } - ] - } - ] - }; - - it('should get correct bid response', function () { - let expectedResponse = [ - { - 'requestId': '3db3773286ee59', - 'cpm': 0.5, - 'creativeId': 29681110, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '', - 'mediaType': 'banner', - 'currency': 'USD', - 'ttl': 300, - 'netRevenue': true, - 'adUnitCode': 'code', - 'appnexus': { - 'buyerMemberId': 958 - } - } - ]; - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); - }); + it('should map response to valid bids (amount)', function () { + let request = deepClone(validRequest); + let bidResponse = deepClone({body: validCreativeResponse}); - it('handles nobid responses', function () { - let response = { - 'version': '0.0.1', - 'tags': [{ - 'uuid': '84ab500420319d', - 'tag_id': 5976557, - 'auction_id': '297492697822162468', - 'nobid': true - }] - }; - let bidderRequest; + const response = spec.interpretResponse(bidResponse, request); - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result.length).to.equal(0); + expect(response).to.exist; + expect(response.length).to.equal(3); + expect(response.filter(bid => bid.requestId === validBidRequests[0].bidId).length).to.equal(1) + expect(response.filter(bid => bid.requestId === validBidRequests[1].bidId).length).to.equal(1) }); - it('handles outstream video responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'content': '' - } - }, - 'javascriptTrackers': '' - }] - }] - }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'outstream' - } - } - }] - } + it('should attach a custom video renderer ', function () { + let request = deepClone(validRequest); + let bidResponse = deepClone({body: validCreativeResponse}); + bidResponse.body.creatives[validBidRequests[1].params.slotId][0].mediaType = 'video'; + bidResponse.body.creatives[validBidRequests[1].params.slotId][0].vastXml = ''; + bidResponse.body.creatives[validBidRequests[1].params.slotId][0].contextType = 'video_outstream'; - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result[0]).to.have.property('vastXml'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); + const response = spec.interpretResponse(bidResponse, request); - it('handles instream video responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/vid' - } - }, - 'javascriptTrackers': '' - }] - }] - }; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'instream' - } - } - }] - } - - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); + expect(response).to.exist; + expect(response.filter(bid => !!bid.renderer).length).to.equal(1); }); - it('handles adpod responses', function () { - let response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'brand_category_id': 10, - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/adpod', - 'duration_ms': 30000, - } - }, - 'viewability': { - 'config': '' - } - }] - }] - }; + it('should not attach a custom video renderer when VAST url/xml is missing', function () { + let request = deepClone(validRequest); + let bidResponse = deepClone({body: validCreativeResponse}); + bidResponse.body.creatives[validBidRequests[1].params.slotId][0].mediaType = 'video'; + bidResponse.body.creatives[validBidRequests[1].params.slotId][0].contextType = 'video_outstream'; - let bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' - } - } - }] - }; + const response = spec.interpretResponse(bidResponse, request); - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0].video.context).to.equal('adpod'); - expect(result[0].video.durationSeconds).to.equal(30); + expect(response).to.exist; + expect(response.filter(bid => !!bid.renderer).length).to.equal(0); }); + }); - it('handles native responses', function () { - let response1 = deepClone(response); - response1.tags[0].ads[0].ad_type = 'native'; - response1.tags[0].ads[0].rtb.native = { - 'title': 'Native Creative', - 'desc': 'Cool description great stuff', - 'desc2': 'Additional body text', - 'ctatext': 'Do it', - 'sponsored': 'AppNexus', - 'icon': { - 'width': 0, - 'height': 0, - 'url': 'https://cdn.adnxs.com/icon.png' - }, - 'main_img': { - 'width': 2352, - 'height': 1516, - 'url': 'https://cdn.adnxs.com/img.png' - }, - 'link': { - 'url': 'https://www.appnexus.com', - 'fallback_url': '', - 'click_trackers': ['https://nym1-ib.adnxs.com/click'] - }, - 'impression_trackers': ['https://example.com'], - 'rating': '5', - 'displayurl': 'https://AppNexus.com/?url=display_url', - 'likes': '38908320', - 'downloads': '874983', - 'price': '9.99', - 'saleprice': 'FREE', - 'phone': '1234567890', - 'address': '28 W 23rd St, New York, NY 10010', - 'privacy_link': 'https://appnexus.com/?url=privacy_url', - 'javascriptTrackers': '' - }; - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - - let result = spec.interpretResponse({ body: response1 }, {bidderRequest}); - expect(result[0].native.title).to.equal('Native Creative'); - expect(result[0].native.body).to.equal('Cool description great stuff'); - expect(result[0].native.cta).to.equal('Do it'); - expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); + describe('sendLogs', function () { + it('should not send logs when percentage is not met', function () { + Math.random.returns(1); + spec.onTimeout([]); + expect(ajaxStub.calledOnce).to.be.false; }); + }); - it('supports configuring outstream renderers', function () { - const outstreamResponse = deepClone(response); - outstreamResponse.tags[0].ads[0].rtb.video = {}; - outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; - - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - renderer: { - options: { - adText: 'configured' - } - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }] - }; - - const result = spec.interpretResponse({ body: outstreamResponse }, {bidderRequest}); - expect(result[0].renderer.config).to.deep.equal( - bidderRequest.bids[0].renderer.options - ); + describe('onTimeout', function () { + it('should send logs on timeout', function () { + spec.onTimeout([]); + expect(ajaxStub.calledOnce).to.be.true; }); + }); - it('should add deal_priority and deal_code', function() { - let responseWithDeal = deepClone(response); - responseWithDeal.tags[0].ads[0].ad_type = 'video'; - responseWithDeal.tags[0].ads[0].deal_priority = 5; - responseWithDeal.tags[0].ads[0].deal_code = '123'; - responseWithDeal.tags[0].ads[0].rtb.video = { - duration_ms: 1500, - player_width: 640, - player_height: 340, - }; - - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' - } - } - }] - } - let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); - expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); - expect(result[0].video.dealTier).to.equal(5); + describe('onBidWon', function () { + it('should send logs on won', function () { + spec.onBidWon([]); + expect(ajaxStub.calledOnce).to.be.true; }); + }); - it('should add advertiser id', function() { - let responseAdvertiserId = deepClone(response); - responseAdvertiserId.tags[0].ads[0].advertiser_id = '123'; - - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); - expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); + describe('onSetTargeting', function () { + it('should send logs on targeting', function () { + spec.onSetTargeting([]); + expect(ajaxStub.calledOnce).to.be.true; }); + }); - it('should add advertiserDomains', function() { - let responseAdvertiserId = deepClone(response); - responseAdvertiserId.tags[0].ads[0].adomain = ['123']; + describe('onBidderError', function () { + it('should send logs on bidder error', function () { + spec.onBidderError([]); + expect(ajaxStub.calledOnce).to.be.true; + }); + }); - let bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); - expect(Object.keys(result[0].meta)).to.include.members(['advertiserDomains']); - expect(Object.keys(result[0].meta.advertiserDomains)).to.deep.equal([]); + describe('onAdRenderSucceeded', function () { + it('should send logs on render succeeded', function () { + spec.onAdRenderSucceeded([]); + expect(ajaxStub.calledOnce).to.be.true; }); }); }); diff --git a/test/spec/modules/greenbidsBidAdapter_specs.js b/test/spec/modules/greenbidsBidAdapter_specs.js new file mode 100644 index 00000000000..014c3545cad --- /dev/null +++ b/test/spec/modules/greenbidsBidAdapter_specs.js @@ -0,0 +1,1051 @@ +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { spec } from 'modules/greenbidsBidAdapter.js'; +const ENDPOINT = 'https://d.greenbids.ai/hb/bid-request'; +const AD_SCRIPT = '"'; + +describe('greenbidsBidAdapter', () => { + const adapter = newBidder(spec); + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('inherited functions', () => { + it('exists and is a function', () => { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'greenbids', + 'params': { + 'placementId': 4242 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not found', function () { + let bidNonGbCompatible = { + 'bidder': 'greenbids', + }; + expect(spec.isBidRequestValid(bidNonGbCompatible)).to.equal(false); + }); + + it('should return false when the placement is not a number', function () { + let bidNonGbCompatible = { + 'bidder': 'greenbids', + 'params': { + 'placementId': 'toto' + }, + }; + expect(spec.isBidRequestValid(bidNonGbCompatible)).to.equal(false); + }); + }) + describe('buildRequests', function () { + it('should send bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should not send auctionId in bid request ', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.data[0].auctionId).to.not.exist + }); + + it('should send US Privacy to endpoint', function () { + let usPrivacy = 'OHHHFCP1' + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': usPrivacy + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.us_privacy).to.exist; + expect(payload.us_privacy).to.equal(usPrivacy); + }); + + it('should send GPP values to endpoint when available and valid', function () { + let consentString = 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'; + let applicableSectionIds = [7, 8]; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + 'gppString': consentString, + 'applicableSections': applicableSectionIds + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.exist; + expect(payload.gpp.consentString).to.equal(consentString); + expect(payload.gpp.applicableSectionIds).to.have.members(applicableSectionIds); + }); + + it('should send default GPP values to endpoint when available but invalid', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + 'gppString': undefined, + 'applicableSections': ['a'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.exist; + expect(payload.gpp.consentString).to.equal(''); + expect(payload.gpp.applicableSectionIds).to.have.members([]); + }); + + it('should not set the GPP object in the request sent to the endpoint when not present', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000 + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.not.exist; + }); + + it('should send GDPR to endpoint', function () { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'isServiceSpecific': true + }, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(12); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should add referer info to payload', function () { + const bidRequest = Object.assign({}, bidRequests[0]) + const bidderRequest = { + refererInfo: { + page: 'https://example.com/page.html', + reachedTop: true, + numIframes: 2 + } + } + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.referrer).to.exist; + expect(payload.referrer).to.deep.equal('https://example.com/page.html') + }); + + const originalConnection = window.navigator.connection; + const mockConnection = { downlink: 10 }; + + const setNavigatorConnection = (connection) => { + Object.defineProperty(window.navigator, 'connection', { + value: connection, + configurable: true, + }); + }; + + try { + setNavigatorConnection(mockConnection); + + const requestWithConnection = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithConnection = JSON.parse(requestWithConnection.data); + + expect(payloadWithConnection.networkBandwidth).to.exist; + expect(payloadWithConnection.networkBandwidth).to.deep.equal(mockConnection.downlink.toString()); + + setNavigatorConnection(undefined); + + const requestWithoutConnection = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithoutConnection = JSON.parse(requestWithoutConnection.data); + + expect(payloadWithoutConnection.networkBandwidth).to.exist; + expect(payloadWithoutConnection.networkBandwidth).to.deep.equal(''); + } finally { + setNavigatorConnection(originalConnection); + } + + it('should add pageReferrer info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageReferrer).to.exist; + expect(payload.pageReferrer).to.deep.equal(document.referrer); + }); + + it('should add width info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const deviceWidth = screen.width + + expect(payload.deviceWidth).to.exist; + expect(payload.deviceWidth).to.deep.equal(deviceWidth); + }); + + it('should add height info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const deviceHeight = screen.height + + expect(payload.deviceHeight).to.exist; + expect(payload.deviceHeight).to.deep.equal(deviceHeight); + }); + + it('should add pixelRatio info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const pixelRatio = window.top.devicePixelRatio + + expect(payload.devicePixelRatio).to.exist; + expect(payload.devicePixelRatio).to.deep.equal(pixelRatio); + }); + + it('should add screenOrientation info to payload', function () { + const originalScreenOrientation = window.top.screen.orientation; + + const mockScreenOrientation = (type) => { + Object.defineProperty(window.top.screen, 'orientation', { + value: { type }, + configurable: true, + }); + }; + + try { + const mockType = 'landscape-primary'; + mockScreenOrientation(mockType); + + const requestWithOrientation = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithOrientation = JSON.parse(requestWithOrientation.data); + + expect(payloadWithOrientation.screenOrientation).to.exist; + expect(payloadWithOrientation.screenOrientation).to.deep.equal(mockType); + + mockScreenOrientation(undefined); + + const requestWithoutOrientation = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithoutOrientation = JSON.parse(requestWithoutOrientation.data); + + expect(payloadWithoutOrientation.screenOrientation).to.not.exist; + } finally { + Object.defineProperty(window.top.screen, 'orientation', { + value: originalScreenOrientation, + configurable: true, + }); + } + }); + + it('should add historyLength info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.historyLength).to.exist; + expect(payload.historyLength).to.deep.equal(window.top.history.length); + }); + + it('should add viewportHeight info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.viewportHeight).to.exist; + expect(payload.viewportHeight).to.deep.equal(window.top.visualViewport.height); + }); + + it('should add viewportWidth info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.viewportWidth).to.exist; + expect(payload.viewportWidth).to.deep.equal(window.top.visualViewport.width); + }); + + it('should add viewportHeight info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.viewportHeight).to.exist; + expect(payload.viewportHeight).to.deep.equal(window.top.visualViewport.height); + }); + + it('should add ortb2 device data to payload', function () { + const ortb2DeviceBidderRequest = { + ...bidderRequestDefault, + ...{ + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: { fiftyonedegrees_deviceId: '17595-133085-133468-18092' }, + }, + }, + }, + }; + const request = spec.buildRequests(bidRequests, ortb2DeviceBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.device).to.deep.equal(ortb2DeviceBidderRequest.ortb2.device); + }); + + it('should add hardwareConcurrency info to payload', function () { + const originalHardwareConcurrency = window.top.navigator.hardwareConcurrency; + + const mockHardwareConcurrency = (value) => { + Object.defineProperty(window.top.navigator, 'hardwareConcurrency', { + value, + configurable: true, + }); + }; + + try { + const mockValue = 8; + mockHardwareConcurrency(mockValue); + + const requestWithHardwareConcurrency = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithHardwareConcurrency = JSON.parse(requestWithHardwareConcurrency.data); + + expect(payloadWithHardwareConcurrency.hardwareConcurrency).to.exist; + expect(payloadWithHardwareConcurrency.hardwareConcurrency).to.deep.equal(mockValue); + + mockHardwareConcurrency(undefined); + + const requestWithoutHardwareConcurrency = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithoutHardwareConcurrency = JSON.parse(requestWithoutHardwareConcurrency.data); + + expect(payloadWithoutHardwareConcurrency.hardwareConcurrency).to.not.exist; + } finally { + Object.defineProperty(window.top.navigator, 'hardwareConcurrency', { + value: originalHardwareConcurrency, + configurable: true, + }); + } + }); + + it('should add deviceMemory info to payload', function () { + const originalDeviceMemory = window.top.navigator.deviceMemory; + + const mockDeviceMemory = (value) => { + Object.defineProperty(window.top.navigator, 'deviceMemory', { + value, + configurable: true, + }); + }; + + try { + const mockValue = 4; + mockDeviceMemory(mockValue); + + const requestWithDeviceMemory = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithDeviceMemory = JSON.parse(requestWithDeviceMemory.data); + + expect(payloadWithDeviceMemory.deviceMemory).to.exist; + expect(payloadWithDeviceMemory.deviceMemory).to.deep.equal(mockValue); + + mockDeviceMemory(undefined); + + const requestWithoutDeviceMemory = spec.buildRequests(bidRequests, bidderRequestDefault); + const payloadWithoutDeviceMemory = JSON.parse(requestWithoutDeviceMemory.data); + + expect(payloadWithoutDeviceMemory.deviceMemory).to.not.exist; + } finally { + Object.defineProperty(window.top.navigator, 'deviceMemory', { + value: originalDeviceMemory, + configurable: true, + }); + } + }); + }); + + describe('pageTitle', function () { + it('should add pageTitle info to payload based on document title', function () { + const testText = 'This is a title'; + sandbox.stub(window.top.document, 'title').value(testText); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.deep.equal(testText); + }); + + it('should add pageTitle info to payload based on open-graph title', function () { + const testText = 'This is a title from open-graph'; + sandbox.stub(window.top.document, 'title').value(''); + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[property="og:title"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.deep.equal(testText); + }); + + it('should add pageTitle info to payload sliced on 300 first characters', function () { + const testText = Array(500).join('a'); + sandbox.stub(window.top.document, 'title').value(testText); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.have.length(300); + }); + + it('should add pageTitle info to payload when fallbacking from window.top', function () { + const testText = 'This is a fallback title'; + sandbox.stub(window.top.document, 'querySelector').throws(); + sandbox.stub(document, 'title').value(testText); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.deep.equal(testText); + }); + }); + + describe('pageDescription', function () { + it('should add pageDescription info to payload based on open-graph description', function () { + const testText = 'This is a description'; + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[name="description"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.deep.equal(testText); + }); + + it('should add pageDescription info to payload based on open-graph description', function () { + const testText = 'This is a description from open-graph'; + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[property="og:description"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.deep.equal(testText); + }); + + it('should add pageDescription info to payload sliced on 300 first characters', function () { + const testText = Array(500).join('a'); + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[name="description"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.have.length(300); + }); + + it('should add pageDescription info to payload when fallbacking from window.top', function () { + const testText = 'This is a fallback description'; + sandbox.stub(window.top.document, 'querySelector').throws(); + sandbox.stub(document, 'querySelector').withArgs('meta[name="description"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.deep.equal(testText); + }); + + it('should add timeToFirstByte info to payload for Navigation Timing V2', function () { + // Mock `performance` object with Navigation Timing V2 data + const mockPerformance = { + getEntriesByType: () => [ + { requestStart: 100, responseStart: 150 }, + ], + }; + + // Override the global performance object + const originalPerformance = window.performance; + window.performance = mockPerformance; + + // Execute the code + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + // Calculate expected TTFB for V2 + const ttfbExpected = Math.round( + mockPerformance.getEntriesByType('navigation')[0].responseStart - + mockPerformance.getEntriesByType('navigation')[0].requestStart + ).toString(); + + // Assertions + expect(payload.timeToFirstByte).to.exist; + expect(payload.timeToFirstByte).to.deep.equal(ttfbExpected); + + // Restore the original performance object + window.performance = originalPerformance; + }); + + it('should add timeToFirstByte info to payload for Navigation Timing V1', function () { + // Mock `performance` object with Navigation Timing V1 data + const mockPerformance = { + timing: { + requestStart: 100, + responseStart: 150, + }, + getEntriesByType: () => [], + }; + + // Override the global performance object + const originalPerformance = window.performance; + window.performance = mockPerformance; + + // Execute the code + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + // Calculate expected TTFB for V1 + const ttfbExpected = ( + mockPerformance.timing.responseStart - mockPerformance.timing.requestStart + ).toString(); + + // Assertions + expect(payload.timeToFirstByte).to.exist; + expect(payload.timeToFirstByte).to.deep.equal(ttfbExpected); + + // Restore the original performance object + window.performance = originalPerformance; + }); + + it('should send GDPR to endpoint with 11 status', function () { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'isServiceSpecific': false, + }, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(11); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should send GDPR TCF2 to endpoint with 12 status', function () { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'isServiceSpecific': true + }, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(12); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should send GDPR to endpoint with 22 status', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': undefined, + 'gdprApplies': undefined, + 'vendorData': undefined, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(''); + expect(payload.gdpr_iab.status).to.equal(22); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should send GDPR to endpoint with 0 status', function () { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': false, + 'vendorData': { + 'hasGlobalScope': false + }, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(0); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should send GDPR to endpoint with 0 status when gdprApplies = false (vendorData = undefined)', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': undefined, + 'gdprApplies': false, + 'vendorData': undefined, + 'apiVersion': 2 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(''); + expect(payload.gdpr_iab.status).to.equal(0); + expect(payload.gdpr_iab.apiVersion).to.equal(2); + }); + + it('should send GDPR to endpoint with 12 status when apiVersion = 0', function () { + let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gdprConsent': { + 'consentString': consentString, + 'gdprApplies': true, + 'vendorData': { + 'isServiceSpecific': true + }, + 'apiVersion': 0 + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gdpr_iab).to.exist; + expect(payload.gdpr_iab.consent).to.equal(consentString); + expect(payload.gdpr_iab.status).to.equal(12); + expect(payload.gdpr_iab.apiVersion).to.equal(0); + }); + + it('should add schain info to payload if available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '00001', + hp: 1 + }] + } + }); + + const request = spec.buildRequests([bidRequest], bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.schain).to.exist; + expect(payload.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '00001', + hp: 1 + }] + }); + }); + + it('should add userAgentClientHints info to payload if available', function () { + const sua = { + source: 2, + platform: { + brand: 'macOS', + version: ['12', '4', '0'] + }, + browsers: [ + { + brand: 'Chromium', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] + } + ], + mobile: 0, + model: '', + bitness: '64', + architecture: 'x86' + } + + const bidRequest = Object.assign({}, bidRequests[0], { + ortb2: { + device: { + sua: sua + } + } + }); + + const requestWithUserAgentClientHints = spec.buildRequests([bidRequest], bidderRequestDefault); + const payload = JSON.parse(requestWithUserAgentClientHints.data); + + expect(payload.userAgentClientHints).to.exist; + expect(payload.userAgentClientHints).to.deep.equal(sua); + + const defaultRequest = spec.buildRequests(bidRequests, bidderRequestDefault); + expect(JSON.parse(defaultRequest.data).userAgentClientHints).to.not.exist; + }); + + it('should use good mediaTypes banner sizes', function () { + const mediaTypesBannerSize = { + 'mediaTypes': { + 'banner': { + 'sizes': [300, 250] + } + } + }; + checkMediaTypesSizes(mediaTypesBannerSize, '300x250'); + }); + }); + + describe('Global Placement Id', function () { + let bidRequests = [ + { + 'bidder': 'greenbids', + 'params': { + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + 'deviceWidth': 1680 + }, + { + 'bidder': 'greenbids', + 'params': { + }, + 'adUnitCode': 'adunit-code-2', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1f', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ef', + 'deviceWidth': 1680 + } + ]; + + it('should add gpid if ortb2Imp.ext.gpid is present and is non empty', function () { + const updatedBidRequests = bidRequests.map(function (bidRequest, index) { + return { + ...bidRequest, + ortb2Imp: { + ext: { + gpid: '1111/home-left-' + index + } + } + }; + } + ); + const request = spec.buildRequests(updatedBidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.data[0].gpid).to.equal('1111/home-left-0'); + expect(payload.data[1].gpid).to.equal('1111/home-left-1'); + }); + + it('should not add gpid if ortb2Imp.ext.gpid is present but empty', function () { + const updatedBidRequests = bidRequests.map(bidRequest => ({ + ...bidRequest, + ortb2Imp: { + ext: { + gpid: '' + } + } + })); + + const request = spec.buildRequests(updatedBidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + payload.data.forEach(bid => { + expect(bid).not.to.have.property('gpid'); + }); + }); + + it('should not add gpid if ortb2Imp.ext.gpid is not present', function () { + const updatedBidRequests = bidRequests.map(bidRequest => ({ + ...bidRequest, + ortb2Imp: { + ext: { + } + } + })); + + const request = spec.buildRequests(updatedBidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + payload.data.forEach(bid => { + expect(bid).not.to.have.property('gpid'); + }); + }); + + it('should add dsa info to payload if available', function () { + const bidRequestWithDsa = Object.assign({}, bidderRequestDefault, { + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }] + } + } + } + } + }); + + const requestWithDsa = spec.buildRequests(bidRequests, bidRequestWithDsa); + const payload = JSON.parse(requestWithDsa.data); + + expect(payload.dsa).to.exist; + expect(payload.dsa).to.deep.equal( + { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }] + } + ); + + const defaultRequest = spec.buildRequests(bidRequests, bidderRequestDefault); + expect(JSON.parse(defaultRequest.data).dsa).to.not.exist; + }); + }); + + describe('interpretResponse', function () { + it('should get correct bid responses', function () { + let bids = { + 'body': { + 'responses': [{ + 'ad': AD_SCRIPT, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 250, + 'bidId': '3ede2a3fa0db94', + 'ttl': 360, + 'width': 300, + 'creativeId': 'er2ee', + 'placementId': 4242 + }, { + 'ad': AD_SCRIPT, + 'cpm': 0.5, + 'currency': 'USD', + 'height': 200, + 'bidId': '4fef3b4gb1ec15', + 'ttl': 360, + 'width': 350, + 'creativeId': 'fs3ff', + 'placementId': 4242, + 'dealId': 'ABC_123', + 'ext': { + 'dsa': { + 'behalf': 'some-behalf', + 'paid': 'some-paid', + 'transparency': [{ + 'domain': 'test.com', + 'dsaparams': [1, 2, 3] + }], + 'adrender': 1 + } + } + }] + } + }; + let expectedResponse = [ + { + 'cpm': 0.5, + 'width': 300, + 'height': 250, + 'currency': 'USD', + 'netRevenue': true, + 'meta': { + advertiserDomains: [] + }, + 'ttl': 360, + 'ad': AD_SCRIPT, + 'requestId': '3ede2a3fa0db94', + 'creativeId': 'er2ee', + 'placementId': 4242 + }, { + 'cpm': 0.5, + 'width': 350, + 'height': 200, + 'currency': 'USD', + 'netRevenue': true, + 'meta': { + advertiserDomains: [], + dsa: { + behalf: 'some-behalf', + paid: 'some-paid', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }], + adrender: 1 + } + }, + 'ttl': 360, + 'ad': AD_SCRIPT, + 'requestId': '4fef3b4gb1ec15', + 'creativeId': 'fs3ff', + 'placementId': 4242, + 'dealId': 'ABC_123' + } + ] + ; + + let result = spec.interpretResponse(bids); + expect(result).to.eql(expectedResponse); + }); + + it('handles nobid responses', function () { + let bids = { + 'body': { + 'responses': [] + } + }; + + let result = spec.interpretResponse(bids); + expect(result.length).to.equal(0); + }); + }); +}); + +let bidderRequestDefault = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000 +}; + +let bidRequests = [ + { + 'bidder': 'greenbids', + 'params': { + 'placementId': 4242 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'creativeId': 'er2ee', + 'deviceWidth': 1680 + } +]; + +function checkMediaTypesSizes(mediaTypes, expectedSizes) { + const bidRequestWithBannerSizes = Object.assign(bidRequests[0], mediaTypes); + const requestWithBannerSizes = spec.buildRequests([bidRequestWithBannerSizes], bidderRequestDefault); + const payloadWithBannerSizes = JSON.parse(requestWithBannerSizes.data); + + return payloadWithBannerSizes.data.forEach(bid => { + if (Array.isArray(expectedSizes)) { + expect(JSON.stringify(bid.sizes)).to.equal(JSON.stringify(expectedSizes)); + } else { + expect(bid.sizes[0]).to.equal(expectedSizes); + } + }); +} diff --git a/test/spec/modules/illuminBidAdapter_spec.js b/test/spec/modules/illuminBidAdapter_spec.js index 3cd79c7468d..b4578a8d61f 100644 --- a/test/spec/modules/illuminBidAdapter_spec.js +++ b/test/spec/modules/illuminBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -117,24 +147,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -315,6 +328,7 @@ describe('IlluminBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -386,6 +400,7 @@ describe('IlluminBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 46e07bacbe4..f6266503297 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1006,7 +1006,7 @@ describe('Improve Digital Adapter Tests', function () { width: 728, height: 90, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '  ', creativeId: '510265', dealId: 320896, netRevenue: false, @@ -1016,7 +1016,7 @@ describe('Improve Digital Adapter Tests', function () { const multiFormatExpectedBid = [ Object.assign({}, expectedBid[0], { - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]},"native":{},"video":{"context":"outstream","playerSize":[640,480]}},"sizes":[[300,250],[160,600]],"nativeParams":{"body":{"required":true}}},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '  ', }) ]; @@ -1029,7 +1029,7 @@ describe('Improve Digital Adapter Tests', function () { width: 300, height: 250, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '  ', creativeId: '479163', dealId: 320896, netRevenue: false, diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index e0162617be3..e0fc7d5affd 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -4,6 +4,7 @@ import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { spec, storage, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY, REQUESTED_FEATURE_TOGGLES, combineImps, bidToVideoImp, bidToNativeImp, deduplicateImpExtFields, removeSiteIDs, addDeviceInfo } from '../../../modules/ixBidAdapter.js'; import { deepAccess, deepClone } from '../../../src/utils.js'; +import * as ajaxLib from 'src/ajax.js'; describe('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://htlb.casalemedia.com/openrtb/pbjs'; @@ -615,7 +616,8 @@ describe('IndexexchangeAdapter', function () { dspid: 50, pricelevel: '_100', advbrandid: 303325, - advbrand: 'OECTA' + advbrand: 'OECTA', + ibv: true }, adm: '' } @@ -2088,7 +2090,11 @@ describe('IndexexchangeAdapter', function () { it('request should be made to IX endpoint with POST method and siteId in query param', function () { expect(requestMethod).to.equal('POST'); expect(requestUrl).to.equal(IX_SECURE_ENDPOINT + '?s=' + DEFAULT_BANNER_VALID_BID[0].params.siteId); - expect(request.option.contentType).to.equal('text/plain') + }); + + it('request made to IX endpoint with POST method should have correct options fields set', function () { + expect(request.options.contentType).to.equal('text/plain') + expect(request.options.withCredentials).to.equal(true) }); it('auction type should be set correctly', function () { @@ -3603,6 +3609,9 @@ describe('IndexexchangeAdapter', function () { brandId: 303325, brandName: 'OECTA', advertiserDomains: ['www.abc.com'] + }, + ext: { + ibv: true } } ]; @@ -3714,6 +3723,9 @@ describe('IndexexchangeAdapter', function () { brandId: 303325, brandName: 'OECTA', advertiserDomains: ['www.abc.com'] + }, + ext: { + ibv: true } } ]; @@ -3770,6 +3782,9 @@ describe('IndexexchangeAdapter', function () { brandId: 303325, brandName: 'OECTA', advertiserDomains: ['www.abc.com'] + }, + ext: { + ibv: true } } ]; @@ -5003,6 +5018,7 @@ describe('IndexexchangeAdapter', function () { expect(result['b8c6b5d5-76a1-4a90-b635-0c7eae1bfaa7'].ixImps.length).to.equal(1); }); }); + describe('apply floors test', function () { it('video test', function() { const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); @@ -5367,6 +5383,7 @@ describe('IndexexchangeAdapter', function () { expect(removeSiteIDs(request)).to.deep.equal(expected); }); }); + describe('addDeviceInfo', () => { it('should add device to request when device already exists', () => { let r = { @@ -5385,4 +5402,53 @@ describe('IndexexchangeAdapter', function () { expect(r.device.h).to.exist; }); }); + + describe('fetch requests', function () { + let ajaxStub; + + beforeEach(function () { + ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { + return sinon.spy(function (url, callback, data, options) { + callback.success('OK'); + }); + }); + }); + + afterEach(function () { + ajaxStub.restore(); + }); + + it('should send the correct headers in the actual fetch call', function (done) { + const requests = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); + const request = requests[0]; + const ajax = ajaxLib.ajaxBuilder(); + + ajax( + request.url, + { + success: () => { + try { + sinon.assert.calledOnce(ajaxStub); + const ajaxCall = ajaxStub.returnValues[0]; + sinon.assert.calledOnce(ajaxCall); + const [calledUrl, callback, calledData, calledOptions] = ajaxCall.getCall(0).args; + + expect(calledUrl).to.equal(request.url); + expect(calledData).to.equal(request.data); + + expect(calledOptions.contentType).to.equal('text/plain'); + expect(calledOptions.withCredentials).to.be.true; + + done(); + } catch (err) { + done(err); + } + }, + error: (err) => done(err || new Error('Ajax request failed')), + }, + request.data, + request.options + ); + }); + }); }); diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 74c0a1f5967..8bee7c0e2cb 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -1,9 +1,11 @@ import {expect} from 'chai'; -import {spec} from 'modules/koblerBidAdapter.js'; +import {pageViewId, spec} from 'modules/koblerBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {getRefererInfo} from 'src/refererDetection.js'; +import { setConfig as setCurrencyConfig } from '../../../modules/currency'; +import { addFPDToBidderRequest } from '../../helpers/fpd'; function createBidderRequest(auctionId, timeout, pageUrl, addGdprConsent) { const gdprConsent = addGdprConsent ? { @@ -245,6 +247,21 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.site.page).to.be.equal(testUrl); }); + it('should reuse the same page view ID on subsequent calls', function () { + const testUrl = 'kobler.no'; + const auctionId1 = '8319af54-9795-4642-ba3a-6f57d6ff9100'; + const auctionId2 = 'e19f2d0c-602d-4969-96a1-69a22d483f47'; + const timeout = 5000; + const validBidRequests = [createValidBidRequest()]; + const bidderRequest1 = createBidderRequest(auctionId1, timeout, testUrl); + const bidderRequest2 = createBidderRequest(auctionId2, timeout, testUrl); + + const openRtbRequest1 = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest1).data); + expect(openRtbRequest1.ext.kobler.page_view_id).to.be.equal(pageViewId); + const openRtbRequest2 = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest2).data); + expect(openRtbRequest2.ext.kobler.page_view_id).to.be.equal(pageViewId); + }); + it('should read data from valid bid requests', function () { const firstSize = [400, 800]; const secondSize = [450, 950]; @@ -536,7 +553,8 @@ describe('KoblerAdapter', function () { ext: { kobler: { tcf_purpose_2_given: true, - tcf_purpose_3_given: false + tcf_purpose_3_given: false, + page_view_id: pageViewId } } }; @@ -598,7 +616,7 @@ describe('KoblerAdapter', function () { cur: 'USD' } }; - const bids = spec.interpretResponse(responseWithTwoBids) + const bids = spec.interpretResponse(responseWithTwoBids, {}) const expectedBids = [ { @@ -665,25 +683,30 @@ describe('KoblerAdapter', function () { }); it('Should trigger pixel with replaced nurl if nurl is not empty', function () { - config.setConfig({ - 'currency': { - 'adServerCurrency': 'NOK' - } - }); - spec.onBidWon({ - originalCpm: 1.532, - cpm: 8.341, - currency: 'NOK', - nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', - adserverTargeting: { + setCurrencyConfig({ adServerCurrency: 'NOK' }); + let validBidRequests = [{ params: {} }]; + let refererInfo = { page: 'page' }; + const bidderRequest = { refererInfo }; + return addFPDToBidderRequest(bidderRequest).then(res => { + JSON.parse(spec.buildRequests(validBidRequests, res).data); + const bids = spec.interpretResponse({ body: { seatbid: [{ bid: [{ + originalCpm: 1.532, + price: 8.341, + currency: 'NOK', + nurl: 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=${AUCTION_PRICE}&sp_cur=${AUCTION_PRICE_CURRENCY}&asp=${AD_SERVER_PRICE}&asp_cur=${AD_SERVER_PRICE_CURRENCY}', + }]}]}}, { bidderRequest: res }); + const bidToWon = bids[0]; + bidToWon.adserverTargeting = { hb_pb: 8 } - }); + spec.onBidWon(bidToWon); - expect(utils.triggerPixel.callCount).to.be.equal(1); - expect(utils.triggerPixel.firstCall.args[0]).to.be.equal( - 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=8.341&sp_cur=NOK&asp=8&asp_cur=NOK' - ); + expect(utils.triggerPixel.callCount).to.be.equal(1); + expect(utils.triggerPixel.firstCall.args[0]).to.be.equal( + 'https://atag.essrtb.com/serve/prebid_win_notification?payload=sdhfusdaobfadslf234324&sp=8.341&sp_cur=NOK&asp=8&asp_cur=NOK' + ); + setCurrencyConfig({}); + }); }); }); diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index 2df5e299aeb..0bb65d0aef0 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -2,7 +2,9 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, - storage + storage, + getAndSetFirstPartyData, + createFirstPartyData, } from 'modules/kueezRtbBidAdapter.js'; import * as utils from 'src/utils.js'; import {version} from 'package.json'; @@ -91,6 +93,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -114,24 +146,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -251,6 +266,7 @@ describe('KueezRtbBidAdapter', function () { describe('build requests', function () { let sandbox; + let createFirstPartyDataStub; before(function () { $$PREBID_GLOBAL$$.bidderSettings = { kueezrtb: { @@ -259,6 +275,10 @@ describe('KueezRtbBidAdapter', function () { }; sandbox = sinon.sandbox.create(); sandbox.stub(Date, 'now').returns(1000); + createFirstPartyDataStub = sandbox.stub(adapter, 'createFirstPartyData').returns({ + pcid: 'pcid', + pcidDate: 1000 + }); }); it('should build video request', function () { @@ -295,6 +315,8 @@ describe('KueezRtbBidAdapter', function () { referrer: 'https://www.somereferrer.com', res: `${window.top.screen.width}x${window.top.screen.height}`, schain: VIDEO_BID.schain, + iiqpcid: 'pcid', + iiqpcidDate: 1000, sizes: ['545x307'], sua: { 'source': 2, @@ -312,6 +334,7 @@ describe('KueezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -363,6 +386,8 @@ describe('KueezRtbBidAdapter', function () { auctionId: 'auction_id', bidRequestsCount: 4, bidderRequestsCount: 3, + iiqpcid: 'pcid', + iiqpcidDate: 1000, bidderWinsCount: 1, bidderTimeout: 3000, bidderRequestId: '1fdb5ff1b6eaa7', @@ -383,6 +408,7 @@ describe('KueezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -668,4 +694,36 @@ describe('KueezRtbBidAdapter', function () { expect(parsed).to.be.equal(value); }); }); + + describe('First party data', () => { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + kueezrtb: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + storage.removeDataFromLocalStorage('_iiq_fdata'); + }) + + it('should create first party data', function () { + const data = createFirstPartyData(); + expect(data).to.have.property('pcid'); + expect(data).to.have.property('pcidDate'); + }); + + it('should get and set first party data', function () { + storage.removeDataFromLocalStorage('_iiq_fdata'); + + const data = getAndSetFirstPartyData(); + expect(data).to.have.property('pcid'); + expect(data).to.have.property('pcidDate'); + + const stored = storage.getDataFromLocalStorage('_iiq_fdata'); + const parsed = tryParseJSON(stored); + expect(parsed).to.deep.equal(data); + }); + }); }); diff --git a/test/spec/modules/lassoBidAdapter_spec.js b/test/spec/modules/lassoBidAdapter_spec.js index 15dee20e566..94ec86aba69 100644 --- a/test/spec/modules/lassoBidAdapter_spec.js +++ b/test/spec/modules/lassoBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/lassoBidAdapter.js'; import { server } from '../../mocks/xhr'; const ENDPOINT_URL = 'https://trc.lhmos.com/prebid'; +const GET_IUD_URL = 'https://secure.adnxs.com/getuid?'; const bid = { bidder: 'lasso', @@ -78,7 +79,7 @@ describe('lassoBidAdapter', function () { }); }); - describe('buildRequests', function () { + describe('buildRequests with standard flow', function () { let validBidRequests, bidRequest; before(() => { validBidRequests = spec.buildRequests([bid], bidderRequest); @@ -97,6 +98,177 @@ describe('lassoBidAdapter', function () { expect(bidRequest.method).to.exist; expect(bidRequest.method).to.equal('GET'); }); + + it('should send request to get uid and trc via get request', () => { + expect(bidRequest.data.test).to.equal(false) + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(GET_IUD_URL + ENDPOINT_URL + '/request'); + }); + }); + + describe('buildRequests with dgid', function () { + let validBidRequests, bidRequest; + before(() => { + const updateBidParams = Object.assign({}, bid, { + params: { + adUnitId: 123456, + dgid: '123' + } + }); + validBidRequests = spec.buildRequests([updateBidParams], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + bidRequest = validBidRequests[0]; + }) + + it('Returns valid bidRequest', function () { + expect(bidRequest).to.exist; + expect(bidRequest.method).to.exist; + expect(bidRequest.url).to.exist; + expect(bidRequest.data).to.exist; + }); + + it('Returns GET method', function() { + expect(bidRequest.method).to.exist; + expect(bidRequest.method).to.equal('GET'); + }); + + it('should send request to trc via get request with dgid', () => { + expect(bidRequest.data.test).to.equal(false) + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); + }); + }); + + describe('buildRequests with npi', function () { + let validBidRequests, bidRequest; + before(() => { + const updateBidParams = Object.assign({}, bid, { + params: { + adUnitId: 123456, + npi: '123' + } + }); + validBidRequests = spec.buildRequests([updateBidParams], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + bidRequest = validBidRequests[0]; + }) + + it('Returns valid bidRequest', function () { + expect(bidRequest).to.exist; + expect(bidRequest.method).to.exist; + expect(bidRequest.url).to.exist; + expect(bidRequest.data).to.exist; + }); + + it('Returns GET method', function() { + expect(bidRequest.method).to.exist; + expect(bidRequest.method).to.equal('GET'); + }); + + it('should send request to trc via get request with npi', () => { + expect(bidRequest.data.test).to.equal(false) + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); + }); + }); + + describe('buildRequests with test npi', function () { + let validBidRequests, bidRequest; + before(() => { + const updateBidParams = Object.assign({}, bid, { + params: { + adUnitId: 123456, + testNPI: '123' + } + }); + validBidRequests = spec.buildRequests([updateBidParams], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + bidRequest = validBidRequests[0]; + }) + + it('Returns valid bidRequest', function () { + expect(bidRequest).to.exist; + expect(bidRequest.method).to.exist; + expect(bidRequest.url).to.exist; + expect(bidRequest.data).to.exist; + }); + + it('Returns GET method', function() { + expect(bidRequest.method).to.exist; + expect(bidRequest.method).to.equal('GET'); + }); + + it('should send request to trc via get request with npi and test param', () => { + expect(bidRequest.data.test).to.equal(true) + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); + }); + }); + + describe('buildRequests with test dgid', function () { + let validBidRequests, bidRequest; + before(() => { + const updateBidParams = Object.assign({}, bid, { + params: { + adUnitId: 123456, + testDGID: '123' + } + }); + validBidRequests = spec.buildRequests([updateBidParams], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + bidRequest = validBidRequests[0]; + }) + + it('Returns valid bidRequest', function () { + expect(bidRequest).to.exist; + expect(bidRequest.method).to.exist; + expect(bidRequest.url).to.exist; + expect(bidRequest.data).to.exist; + }); + + it('Returns GET method', function() { + expect(bidRequest.method).to.exist; + expect(bidRequest.method).to.equal('GET'); + }); + + it('should send request to trc via get request with dgid and test param', () => { + expect(bidRequest.data.test).to.equal(true) + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); + }); + }); + + describe('buildRequests with npi hash', function () { + let validBidRequests, bidRequest; + before(() => { + const updateBidParams = Object.assign({}, bid, { + params: { + adUnitId: 123456, + npiHash: '123' + } + }); + validBidRequests = spec.buildRequests([updateBidParams], bidderRequest); + expect(validBidRequests).to.be.an('array').that.is.not.empty; + bidRequest = validBidRequests[0]; + }) + + it('Returns valid bidRequest', function () { + expect(bidRequest).to.exist; + expect(bidRequest.method).to.exist; + expect(bidRequest.url).to.exist; + expect(bidRequest.data).to.exist; + }); + + it('Returns GET method', function() { + expect(bidRequest.method).to.exist; + expect(bidRequest.method).to.equal('GET'); + }); + + it('should send request to trc via get request with npi', () => { + expect(bidRequest.data.test).to.equal(false) + expect(bidRequest.method).to.equal('GET'); + expect(bidRequest.url).to.equal(ENDPOINT_URL + '/request'); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/liveIntentExternalIdSystem_spec.js b/test/spec/modules/liveIntentExternalIdSystem_spec.js index 3e49eb5fc4b..b751f288b33 100644 --- a/test/spec/modules/liveIntentExternalIdSystem_spec.js +++ b/test/spec/modules/liveIntentExternalIdSystem_spec.js @@ -269,11 +269,6 @@ describe('LiveIntentExternalId', function() { expect(window.liQHub).to.have.length(1) // instead of 2 }); - it('should not return a decoded identifier when the unifiedId is not present in the value', function() { - const result = liveIntentExternalIdSubmodule.decode({ fireEventDelay: 1, additionalData: 'data' }); - expect(result).to.be.eql({}); - }); - it('should decode a unifiedId to lipbId and remove it', function() { const result = liveIntentExternalIdSubmodule.decode({ unifiedId: 'data' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'data'}}); @@ -316,6 +311,11 @@ describe('LiveIntentExternalId', function() { }) }); + it('should decode values with the segments but no nonId', function() { + const result = liveIntentExternalIdSubmodule.decode({segments: ['tak']}, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'segments': ['tak']}}); + }); + it('should decode a uid2 to a separate object when present', function() { const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); @@ -323,7 +323,7 @@ describe('LiveIntentExternalId', function() { it('should decode values with uid2 but no nonId', function() { const result = liveIntentExternalIdSubmodule.decode({ uid2: 'bar' }, defaultConfigParams); - expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(result).to.eql({'lipb': {'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { @@ -456,8 +456,18 @@ describe('LiveIntentExternalId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a triplelift id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', triplelift: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + + it('should decode the segments as part of lipb', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'segments': ['bar']}}); + }); }); diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index a859b3e7995..810b6a23a20 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -49,11 +49,6 @@ describe('LiveIntentMinimalId', function() { expect(server.requests[0]).to.eql(undefined) }); - it('should not return a decoded identifier when the unifiedId is not present in the value', function() { - const result = liveIntentIdSubmodule.decode({ additionalData: 'data' }); - expect(result).to.be.eql({}); - }); - it('should initialize LiveConnect and send no data', function() { liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); @@ -245,6 +240,11 @@ describe('LiveIntentMinimalId', function() { expect(callBackSpy.calledOnce).to.be.true; }); + it('should decode values with the segments but no nonId', function() { + const result = liveIntentIdSubmodule.decode({segments: ['tak']}, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'segments': ['tak']}}); + }); + it('should decode a uid2 to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); @@ -252,7 +252,7 @@ describe('LiveIntentMinimalId', function() { it('should decode values with uid2 but no nonId', function() { const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); - expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(result).to.eql({'lipb': {'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { @@ -324,8 +324,18 @@ describe('LiveIntentMinimalId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a triplelift id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', triplelift: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + + it('should decode the segments as part of lipb', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'segments': ['bar']}}); + }); }); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 50f51bd3dc8..8f7a3465d88 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -188,11 +188,6 @@ describe('LiveIntentId', function() { }, 300); }); - it('should not return a decoded identifier when the unifiedId is not present in the value', function() { - const result = liveIntentIdSubmodule.decode({ params: { fireEventDelay: 1, additionalData: 'data' } }); - expect(result).to.be.eql({}); - }); - it('should fire an event when decode', function(done) { liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); setTimeout(() => { @@ -422,9 +417,14 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode values with the segments but no nonId', function() { + const result = liveIntentIdSubmodule.decode({segments: ['tak']}, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'segments': ['tak']}}); + }); + it('should decode values with uid2 but no nonId', function() { const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }, { params: defaultConfigParams }); - expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + expect(result).to.eql({'lipb': {'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { @@ -469,6 +469,11 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); }); + it('should decode the segments as part of lipb', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', 'segments': ['bar'] }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'segments': ['bar']}}); + }); + it('should allow disabling nonId resolution', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { @@ -536,6 +541,11 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a triplelift id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', triplelift: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'triplelift': 'bar'}, 'triplelift': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a vidazoo id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); @@ -882,6 +892,39 @@ describe('LiveIntentId', function() { }); }); + it('triplelift', function () { + const userId = { + triplelift: { 'id': 'sample_id' } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.triplelift.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('triplelift with ext', function () { + const userId = { + triplelift: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.triplelift.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + it('vidazoo', function () { const userId = { vidazoo: { 'id': 'sample_id' } diff --git a/test/spec/modules/liveIntentRtdProvider_spec.js b/test/spec/modules/liveIntentRtdProvider_spec.js new file mode 100644 index 00000000000..d3c34830dd0 --- /dev/null +++ b/test/spec/modules/liveIntentRtdProvider_spec.js @@ -0,0 +1,116 @@ +import {liveIntentRtdSubmodule} from 'modules/liveIntentRtdProvider.js'; +import * as utils from 'src/utils.js'; +import { expect } from 'chai'; + +describe('LiveIntent Rtd Provider', function () { + const SUBMODULE_NAME = 'liveintent'; + + describe('submodule `init`', function () { + const config = { + name: SUBMODULE_NAME, + }; + it('init returns true when the subModuleName is defined', function () { + const value = liveIntentRtdSubmodule.init(config); + expect(value).to.equal(true); + }); + }) + + describe('submodule `onBidRequestEvent`', function () { + const bidRequestExample = { + bidderCode: 'appnexus', + auctionId: '8dbd7cb1-7f6d-4f84-946c-d0df4837234a', + bidderRequestId: '2a038c6820142b', + bids: [ + { + bidder: 'appnexus', + userId: { + lipb: { + segments: [ + 'asa_1231', + 'lalo_4311', + 'liurl_99123' + ] + } + } + } + ] + } + + it('exists', function () { + expect(liveIntentRtdSubmodule.onBidRequestEvent).to.be.a('function'); + }); + + it('undefined segments field does not change the ortb2', function() { + const bidRequest = { + bidderCode: 'appnexus', + auctionId: '8dbd7cb1-7f6d-4f84-946c-d0df4837234a', + bidderRequestId: '2a038c6820142b', + bids: [ + { + bidder: 'appnexus', + ortb2: {} + } + ] + } + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + expect(ortb2).to.deep.equal({}); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data when the ortb2 is undefined', function() { + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {user: {data: [{name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data when user is undefined', function() { + bidRequestExample.bids[0].ortb2 = { source: {} } + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {source: {}, user: {data: [{name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data when data is undefined', function() { + bidRequestExample.bids[0].ortb2 = { + source: {}, + user: {} + } + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {source: {}, user: {data: [{name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + + it('extracts segments and move them to the bidRequest.ortb2.user.data with the existing data', function() { + bidRequestExample.bids[0].ortb2 = { + source: {}, + user: { + data: [ + { + name: 'example.com', + segment: [ + { id: 'a_1231' }, + { id: 'b_4311' } + ] + } + ] + } + } + const bidRequest = utils.deepClone(bidRequestExample); + + liveIntentRtdSubmodule.onBidRequestEvent(bidRequest); + const ortb2 = bidRequest.bids[0].ortb2; + const expectedOrtb2 = {source: {}, user: {data: [{name: 'example.com', segment: [{id: 'a_1231'}, {id: 'b_4311'}]}, {name: 'liveintent.com', segment: [{id: 'asa_1231'}, {id: 'lalo_4311'}, {id: 'liurl_99123'}]}]}} + expect(ortb2).to.deep.equal(expectedOrtb2); + }); + }); +}); diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index 1e5f089ca53..4b7f7bc2bac 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -59,6 +59,24 @@ const BID2 = Object.assign({}, BID1, { dealId: undefined }); +const BID2_2 = Object.assign({}, BID2, { + width: 320, + height: 320, + cpm: 10.0, + originalCpm: 20.0, + currency: 'USD', + originalCurrency: 'FOO', + timeToRespond: 300, + bidId: '3ecff0db240758', + requestId: '3ecff0db240757', + adId: '3ecff0db240758', + mediaType: 'video', + meta: { + data: 'value2_2' + }, + dealId: 'deal2_2' +}); + const BID3 = { bidId: '4ecff0db240757', requestId: '4ecff0db240757', @@ -99,7 +117,8 @@ const MOCK = { }, BID_RESPONSE: [ BID1, - BID2 + BID2, + BID2_2 ], AUCTION_END: { }, @@ -281,6 +300,7 @@ function performStandardAuction() { events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[2]); events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); events.emit(AUCTION_END, MOCK.AUCTION_END); events.emit(SET_TARGETING, MOCK.SET_TARGETING); @@ -643,5 +663,44 @@ describe('Livewrapped analytics adapter', function () { expect(message.ext).to.not.equal(null); expect(message.ext.testparam).to.equal(123); }); + + it('should forward the correct winning bid from a multi-bid response', function () { + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[2]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, Object.assign({}, BID2_2, { + 'status': 'rendered', + 'requestId': '3ecff0db240757' + })); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + expect(message.wins.length).to.equal(1); + expect(message.wins[0]).to.deep.equal({ + timeStamp: 1519149562216, + adUnit: 'box_d_1', + adUnitId: 'adunitid', + bidder: 'livewrapped', + width: 320, + height: 320, + cpm: 10.0, + orgCpm: 200, + mediaType: 4, + dealId: 'deal2_2', + gdpr: 0, + auctionId: 0, + meta: { + data: 'value2_2' + } + }); + }); }); }); diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index 5ab00859d81..7a3cd26dbe7 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -3,6 +3,8 @@ import {spec, storage} from 'modules/livewrappedBidAdapter.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import { NATIVE, VIDEO } from 'src/mediaTypes.js'; +import { setConfig as setCurrencyConfig } from '../../../modules/currency'; +import { addFPDToBidderRequest } from '../../helpers/fpd'; describe('Livewrapped adapter tests', function () { let sandbox, @@ -1178,50 +1180,21 @@ describe('Livewrapped adapter tests', function () { sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); - let origGetConfig = config.getConfig; - sandbox.stub(config, 'getConfig').callsFake(function (key) { - if (key === 'currency.adServerCurrency') { - return 'EUR'; - } - return origGetConfig.apply(config, arguments); - }); - + setCurrencyConfig({ adServerCurrency: 'EUR' }); let testbidRequest = clone(bidderRequest); let bids = testbidRequest.bids.map(b => { b.getFloor = function () { return { floor: 10, currency: 'EUR' }; } return b; }); - let result = spec.buildRequests(bids, testbidRequest); - let data = JSON.parse(result.data); - - expect(result.url).to.equal('https://lwadm.com/ad'); - - let expectedQuery = { - auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', - publisherId: '26947112-2289-405D-88C1-A7340C57E63E', - userId: 'user id', - url: 'https://www.domain.com', - seats: {'dsp': ['seat 1']}, - version: '1.4', - width: 100, - height: 100, - cookieSupport: true, - flrCur: 'EUR', - adRequests: [{ - adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', - callerAdUnitId: 'panorama_d_1', - bidId: '2ffb201a808da7', - rtbData: { - ext: { - tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' - }, - }, - formats: [{width: 980, height: 240}, {width: 980, height: 120}], - flr: 10 - }] - }; - expect(data).to.deep.equal(expectedQuery); + return addFPDToBidderRequest(testbidRequest).then(res => { + let result = spec.buildRequests(bids, res); + let data = JSON.parse(result.data); + expect(result.url).to.equal('https://lwadm.com/ad'); + expect(data.adRequests[0].flr).to.eql(10) + expect(data.flrCur).to.eql('EUR') + setCurrencyConfig({}); + }); }); it('getFloor returns valid floor - default currency', function() { diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js index 664c888b45e..3d9be5a40bf 100755 --- a/test/spec/modules/luponmediaBidAdapter_spec.js +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -188,6 +188,9 @@ describe('luponmediaBidAdapter', function () { 'price': 0.43, 'adm': ' ', 'adid': '56380110', + 'adomain': [ + 'mi.betrivers.com' + ], 'cid': '44724710', 'crid': '443801010', 'w': 300, @@ -232,7 +235,15 @@ describe('luponmediaBidAdapter', function () { 'netRevenue': false, 'ttl': 300, 'referrer': '', - 'ad': ' ' + 'ad': ' ', + 'adomain': [ + 'mi.betrivers.com' + ], + 'meta': { + 'advertiserDomains': [ + 'mi.betrivers.com' + ] + } } ]; diff --git a/test/spec/modules/madsenseBidAdapter_spec.js b/test/spec/modules/madsenseBidAdapter_spec.js new file mode 100644 index 00000000000..eeb8c2d6f25 --- /dev/null +++ b/test/spec/modules/madsenseBidAdapter_spec.js @@ -0,0 +1,188 @@ +import { expect } from 'chai'; +import { spec } from 'modules/madsenseBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { generateUUID } from '../../../src/utils.js'; + +const getCommonParams = () => ({ + company_id: '1234567' +}); + +const getVideoParams = () => ({ + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + sid: 134, + rewarded: 1, + placement: 1, + plcmt: 1, + hp: 1, + inventoryid: 123, + }, + site: { + id: 1, + page: 'https://test.io', + referrer: 'http://test.io', + }, +}); + +const getBannerRequest = () => ({ + bidderCode: 'madsense', + auctionId: generateUUID(), + bidderRequestId: 'bidderRequestId', + bids: [ + { + bidder: 'madsense', + params: getCommonParams(), + auctionId: generateUUID(), + placementCode: 'dummy-placement-code', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bidId: 'a1b2c3d4', + bidderRequestId: 'bidderRequestId', + }, + ], + start: Date.now(), + auctionStart: Date.now() - 1, + timeout: 3000, +}); + +const getVideoBid = (bidId) => ({ + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + }, + }, + bidder: 'madsense', + sizes: [640, 480], + bidId, + adUnitCode: 'video1', + params: { + ...getVideoParams(), + ...getCommonParams(), + }, +}); + +const getVideoRequest = () => ({ + bidderCode: 'madsense', + auctionId: generateUUID(), + bidderRequestId: 'q1w2e3r4', + bids: [getVideoBid('i8u7y6t5'), getVideoBid('i8u7y6t5')], + auctionStart: Date.now(), + timeout: 5000, + start: Date.now() + 4, + refererInfo: { + numIframes: 1, + reachedTop: true, + referer: 'test.io', + }, +}); + +const getBidderResponse = () => ({ + headers: null, + body: { + id: 'bid-response', + seatbid: [ + { + bid: [ + { + id: 'a1b2c3d4', + impid: 'a1b2c3d4', + price: 0.18, + adm: '', + adid: '144762342', + adomain: ['https://test.io'], + iurl: 'iurl', + cid: '109', + crid: 'creativeId', + w: 300, + h: 250, + ext: { + prebid: { type: 'banner' }, + bidder: { + appnexus: { + brand_id: 321654987, + auction_id: 321654987000000, + bidder_id: 2, + bid_ad_type: 0, + }, + }, + }, + }, + ], + seat: 'madsense', + }, + ] + }, +}); + +describe('madsenseBidAdapter', function () { + describe('interpretResponse', function () { + context('when mediaType is banner', function () { + it('handles empty response', function () { + const bidderRequest = getBannerRequest(); + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + const EMPTY_RESP = { ...getBidderResponse(), body: {} }; + const bids = spec.interpretResponse(EMPTY_RESP, bidRequest); + + expect(bids).to.be.empty; + }); + + it('validates banner bid', function () { + const bidderRequest = getBannerRequest(); + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + const bidderResponse = getBidderResponse(); + const bids = spec.interpretResponse(bidderResponse, bidRequest); + + expect(bids).to.be.an('array').that.is.not.empty; + validateBid(bids[0], bidderResponse.body.seatbid[0].bid[0]); + }); + }); + + context('when mediaType is video', function () { + it('handles empty response', function () { + const bidderRequest = getVideoRequest(); + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + const EMPTY_RESP = { ...getBidderResponse(), body: {} }; + const bids = spec.interpretResponse(EMPTY_RESP, bidRequest); + + expect(bids).to.be.empty; + }); + + it('returns no bids if required fields are missing', function () { + const bidderRequest = getVideoRequest(); + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + + const MISSING_FIELDS_RESP = { + ...getBidderResponse(), + body: { seatbid: [{ bid: [{ price: 6.01 }] }] }, + }; + const bids = spec.interpretResponse(MISSING_FIELDS_RESP, bidRequest); + expect(bids.length).to.equal(0); + }); + }); + }); +}); + +function validateBid(actualBid, expectedBid) { + expect(actualBid).to.include({ + currency: 'USD', + requestId: expectedBid.impid, + cpm: expectedBid.price, + width: expectedBid.w, + height: expectedBid.h, + ad: expectedBid.adm, + creativeId: expectedBid.crid, + ttl: 55, + netRevenue: true, + }); + expect(actualBid.meta).to.have.property('advertiserDomains'); +} diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 5f2efc7864f..009af5dbd66 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -30,7 +30,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1, }, { 'bidder': 'medianet', 'params': { @@ -57,7 +57,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BID_REQUEST_WITH_CRID = [{ @@ -87,7 +87,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -115,7 +115,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BID_REQUEST_WITH_ORTB2 = [{ 'bidder': 'medianet', @@ -144,7 +144,7 @@ let VALID_BID_REQUEST = [{ 'data': {'pbadslot': '/12345/my-gpt-tag-0'} } }, - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -173,7 +173,7 @@ let VALID_BID_REQUEST = [{ 'data': {'pbadslot': '/12345/my-gpt-tag-0'} } }, - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], // Protected Audience API Request VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP = [{ @@ -203,7 +203,7 @@ let VALID_BID_REQUEST = [{ 'ae': 1 } }, - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BID_REQUEST_WITH_USERID = [{ @@ -235,7 +235,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -263,7 +263,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BID_REQUEST_WITH_USERIDASEIDS = [{ 'bidder': 'medianet', @@ -301,7 +301,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -329,7 +329,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BID_REQUEST_INVALID_BIDFLOOR = [{ @@ -359,7 +359,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -386,7 +386,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_NATIVE_BID_REQUEST = [{ 'bidder': 'medianet', @@ -414,7 +414,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1, + 'auctionsCount': 1, 'nativeParams': { 'image': { 'required': true, @@ -471,7 +471,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1, + 'auctionsCount': 1, 'nativeParams': { 'image': { 'required': true, @@ -1244,7 +1244,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_PAYLOAD_PAGE_META = (() => { @@ -1646,7 +1646,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }, { 'bidder': 'medianet', 'params': { @@ -1673,7 +1673,7 @@ let VALID_BID_REQUEST = [{ 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', - 'bidRequestsCount': 1 + 'auctionsCount': 1 }], VALID_BIDDER_REQUEST_WITH_GDPR = { 'gdprConsent': { diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index f2bdd3b6c9d..4e5cd4883d3 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/minutemediaBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-mm-multi'; const TEST_ENDPOINT = 'https://hb.minutemedia-prebid.com/hb-multi-mm-test'; @@ -63,7 +64,6 @@ describe('minutemediaAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -80,7 +80,59 @@ describe('minutemediaAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -151,10 +203,10 @@ describe('minutemediaAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -170,12 +222,21 @@ describe('minutemediaAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -455,6 +516,28 @@ describe('minutemediaAdapter', function () { nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -494,10 +577,42 @@ describe('minutemediaAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -509,6 +624,11 @@ describe('minutemediaAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index 6a143fa49ef..2ca262dcf7f 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -7,6 +7,7 @@ import * as autoplay from 'libraries/autoplayDetection/autoplay.js'; const REFERRER = 'https://referer'; const REFERRER2 = 'https://referer2'; const COOKIE_DEPRECATION_LABEL = 'test'; +const API_KEY = 'PA-XXXXXX'; describe('Missena Adapter', function () { $$PREBID_GLOBAL$$.bidderSettings = { @@ -22,7 +23,6 @@ describe('Missena Adapter', function () { const bid = { bidder: 'missena', bidId: bidId, - sizes: [[1, 1]], mediaTypes: { banner: { sizes: [[1, 1]] } }, ortb2: { device: { @@ -30,7 +30,7 @@ describe('Missena Adapter', function () { }, }, params: { - apiKey: 'PA-34745704', + apiKey: API_KEY, placement: 'sticky', formats: ['sticky-banner'], }, @@ -54,14 +54,14 @@ describe('Missena Adapter', function () { const bidWithoutFloor = { bidder: 'missena', bidId: bidId, - sizes: [[1, 1]], - mediaTypes: { banner: { sizes: [[1, 1]] } }, + mediaTypes: { banner: { sizes: [1, 1] } }, params: { - apiKey: 'PA-34745704', + apiKey: API_KEY, placement: 'sticky', formats: ['sticky-banner'], }, }; + const consentString = 'AAAAAAAAA=='; const bidderRequest = { @@ -172,6 +172,21 @@ describe('Missena Adapter', function () { expect(payload.ik).to.equal(window.msna_ik); }); + it('should send screen', function () { + expect(payload.screen.width).to.equal(screen.width); + expect(payload.screen.height).to.equal(screen.height); + }); + + it('should send size', function () { + expect(payload.sizes[0].width).to.equal(1); + expect(payload.sizes[0].height).to.equal(1); + }); + + it('should send single size', function () { + expect(payloadNoFloor.sizes[0].width).to.equal(1); + expect(payloadNoFloor.sizes[0].height).to.equal(1); + }); + getDataFromLocalStorageStub.restore(); getDataFromLocalStorageStub = sinon.stub( storage, @@ -299,7 +314,7 @@ describe('Missena Adapter', function () { expect(userSync.length).to.be.equal(1); expect(userSync[0].type).to.be.equal('iframe'); - expect(userSync[0].url).to.be.equal(syncFrameUrl); + expect(userSync[0].url).to.be.equal(`${syncFrameUrl}?t=${API_KEY}`); }); it('should return empty array when iframeEnabled is false', function () { @@ -312,7 +327,7 @@ describe('Missena Adapter', function () { gdprApplies: true, consentString, }); - const expectedUrl = `${syncFrameUrl}?gdpr=1&gdpr_consent=${consentString}`; + const expectedUrl = `${syncFrameUrl}?t=${API_KEY}&gdpr=1&gdpr_consent=${consentString}`; expect(userSync.length).to.be.equal(1); expect(userSync[0].type).to.be.equal('iframe'); expect(userSync[0].url).to.be.equal(expectedUrl); @@ -322,7 +337,7 @@ describe('Missena Adapter', function () { gdprApplies: false, consentString, }); - const expectedUrl = `${syncFrameUrl}?gdpr=0&gdpr_consent=${consentString}`; + const expectedUrl = `${syncFrameUrl}?t=${API_KEY}&gdpr=0&gdpr_consent=${consentString}`; expect(userSync.length).to.be.equal(1); expect(userSync[0].type).to.be.equal('iframe'); expect(userSync[0].url).to.be.equal(expectedUrl); diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js index 796a79e4e1c..0794e99151d 100644 --- a/test/spec/modules/mobianRtdProvider_spec.js +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -4,10 +4,19 @@ import * as ajax from 'src/ajax.js'; import * as gptUtils from 'libraries/gptUtils/gptUtils.js'; import { CONTEXT_KEYS, + AP_VALUES, + CATEGORIES, + EMOTIONS, + GENRES, + RISK, + SENTIMENT, + THEMES, + TONES, extendBidRequestConfig, fetchContextData, getConfig, getContextData, + makeContextDataToKeyValuesReducer, makeDataFromResponse, setTargeting, } from 'modules/mobianRtdProvider.js'; @@ -35,14 +44,29 @@ describe('Mobian RTD Submodule', function () { }); const mockContextData = { - apValues: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] }, - categories: [], - emotions: ['affection'], - genres: [], - risk: 'low', - sentiment: 'positive', - themes: [], - tones: [], + [AP_VALUES]: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] }, + [CATEGORIES]: [], + [EMOTIONS]: ['affection'], + [GENRES]: [], + [RISK]: 'low', + [SENTIMENT]: 'positive', + [THEMES]: [], + [TONES]: [], + } + + const mockKeyValues = { + 'mobian_ap_a1': ['2313', '12'], + 'mobian_ap_p0': ['1231231', '212'], + 'mobian_ap_p1': ['231', '419'], + 'mobian_emotions': ['affection'], + 'mobian_risk': 'low', + 'mobian_sentiment': 'positive', + } + + const mockConfig = { + prefix: 'mobian', + publisherTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES], + advertiserTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES], } beforeEach(function () { @@ -79,10 +103,6 @@ describe('Mobian RTD Submodule', function () { describe('makeDataFromResponse', function () { it('should format context data response', async function () { - ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { - callbacks.success(mockResponse); - }); - const data = makeDataFromResponse(mockResponse); expect(data).to.deep.equal(mockContextData); }); @@ -103,14 +123,14 @@ describe('Mobian RTD Submodule', function () { it('should set targeting key-value pairs as per config', function () { const parsedConfig = { prefix: 'mobian', - publisherTargeting: ['apValues', 'emotions', 'risk', 'sentiment', 'themes', 'tones', 'genres'], + publisherTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES], }; setTargeting(parsedConfig, mockContextData); expect(setKeyValueSpy.callCount).to.equal(6); - expect(setKeyValueSpy.calledWith('mobian_ap_a1', [2313, 12])).to.equal(true); - expect(setKeyValueSpy.calledWith('mobian_ap_p0', [1231231, 212])).to.equal(true); - expect(setKeyValueSpy.calledWith('mobian_ap_p1', [231, 419])).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_ap_a1', ['2313', '12'])).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_ap_p0', ['1231231', '212'])).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_ap_p1', ['231', '419'])).to.equal(true); expect(setKeyValueSpy.calledWith('mobian_emotions', ['affection'])).to.equal(true); expect(setKeyValueSpy.calledWith('mobian_risk', 'low')).to.equal(true); expect(setKeyValueSpy.calledWith('mobian_sentiment', 'positive')).to.equal(true); @@ -124,7 +144,7 @@ describe('Mobian RTD Submodule', function () { it('should not set key-value pairs if context data is empty', function () { const parsedConfig = { prefix: 'mobian', - publisherTargeting: ['apValues', 'emotions', 'risk', 'sentiment', 'themes', 'tones', 'genres'], + publisherTargeting: [AP_VALUES, EMOTIONS, RISK, SENTIMENT, THEMES, TONES, GENRES], }; setTargeting(parsedConfig, {}); @@ -134,7 +154,7 @@ describe('Mobian RTD Submodule', function () { it('should only set key-value pairs for the keys specified in config', function () { const parsedConfig = { prefix: 'mobian', - publisherTargeting: ['emotions', 'risk'], + publisherTargeting: [EMOTIONS, RISK], }; setTargeting(parsedConfig, mockContextData); @@ -155,8 +175,8 @@ describe('Mobian RTD Submodule', function () { describe('extendBidRequestConfig', function () { it('should extend bid request config with context data', function () { - const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData); - expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockContextData); + const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData, mockConfig); + expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockKeyValues); }); it('should not override existing data', function () { @@ -164,17 +184,17 @@ describe('Mobian RTD Submodule', function () { existing: 'data' }; - const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData); + const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData, mockConfig); expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal({ existing: 'data', - ...mockContextData + ...mockKeyValues }); }); it('should create data object if missing', function () { delete bidReqConfig.ortb2Fragments.global.site.ext.data; - const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData); - expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockContextData); + const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData, mockConfig); + expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockKeyValues); }); }); @@ -184,14 +204,14 @@ describe('Mobian RTD Submodule', function () { name: 'mobianBrandSafety', params: { prefix: 'mobiantest', - publisherTargeting: ['apValues'], - advertiserTargeting: ['emotions'], + publisherTargeting: [AP_VALUES], + advertiserTargeting: [EMOTIONS], } }); expect(config).to.deep.equal({ prefix: 'mobiantest', - publisherTargeting: ['apValues'], - advertiserTargeting: ['emotions'], + publisherTargeting: [AP_VALUES], + advertiserTargeting: [EMOTIONS], }); }); @@ -199,12 +219,12 @@ describe('Mobian RTD Submodule', function () { const config = getConfig({ name: 'mobianBrandSafety', params: { - publisherTargeting: ['apValues'], + publisherTargeting: [AP_VALUES], } }); expect(config).to.deep.equal({ prefix: 'mobian', - publisherTargeting: ['apValues'], + publisherTargeting: [AP_VALUES], advertiserTargeting: [], }); }); @@ -242,4 +262,20 @@ describe('Mobian RTD Submodule', function () { }); }); }); + + describe('makeContextDataToKeyValuesReducer', function () { + it('should format context data to key-value pairs', function () { + const config = getConfig({ + name: 'mobianBrandSafety', + params: { + prefix: 'mobian', + publisherTargeting: true, + advertiserTargeting: true, + } + }); + const keyValues = Object.entries(mockContextData).reduce(makeContextDataToKeyValuesReducer(config), []); + const keyValuesObject = Object.fromEntries(keyValues); + expect(keyValuesObject).to.deep.equal(mockKeyValues); + }); + }); }); diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index a5c5f5f714d..c7fabd562c7 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { getImp, + setImpPos, getSourceObj, replaceUsersyncMacros, setConsentStrings, @@ -28,6 +29,7 @@ describe('nextMillenniumBidAdapterTests', () => { data: {sizes: [[300, 250], [320, 250]]}, bidfloorcur: 'EUR', bidfloor: 1.11, + pos: 3, }, }, }, @@ -37,7 +39,12 @@ describe('nextMillenniumBidAdapterTests', () => { bidfloorcur: 'EUR', bidfloor: 1.11, ext: {prebid: {storedrequest: {id: '123'}}}, - banner: {w: 300, h: 250, format: [{w: 300, h: 250}, {w: 320, h: 250}]}, + banner: { + pos: 3, + w: 300, + h: 250, + format: [{w: 300, h: 250}, {w: 320, h: 250}], + }, }, }, @@ -56,6 +63,7 @@ describe('nextMillenniumBidAdapterTests', () => { video: { data: {playerSize: [400, 300], api: [2], placement: 1, plcmt: 1}, bidfloorcur: 'USD', + pos: 0, }, }, }, @@ -71,6 +79,7 @@ describe('nextMillenniumBidAdapterTests', () => { plcmt: 1, w: 400, h: 300, + pos: 0, }, }, }, @@ -100,6 +109,72 @@ describe('nextMillenniumBidAdapterTests', () => { video: {w: 640, h: 480, mimes: ['video/mp4', 'video/x-ms-wmv', 'application/javascript']}, }, }, + + { + title: 'imp with gpid', + data: { + id: '123', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, + bid: { + mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, + adUnitCode: 'test-gpid-1', + bidId: 'e36ea395f67a', + ortb2Imp: {ext: {gpid: 'imp-gpid-123'}}, + }, + + mediaTypes: { + banner: { + data: {sizes: [[300, 250], [320, 250]]}, + }, + }, + }, + + expected: { + id: 'e36ea395f67a', + ext: { + prebid: {storedrequest: {id: '123'}}, + gpid: 'imp-gpid-123' + }, + banner: {w: 300, h: 250, format: [{w: 300, h: 250}, {w: 320, h: 250}]}, + }, + }, + + { + title: 'imp with pbadslot', + data: { + id: '123', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, + bid: { + mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, + adUnitCode: 'test-gpid-1', + bidId: 'e36ea395f67a', + ortb2Imp: { + ext: { + data: { + pbadslot: 'slot-123' + } + } + }, + }, + + mediaTypes: { + banner: { + data: {sizes: [[300, 250], [320, 250]]}, + }, + }, + }, + + expected: { + id: 'e36ea395f67a', + ext: { + prebid: {storedrequest: {id: '123'}}, + data: { + pbadslot: 'slot-123' + } + }, + banner: {w: 300, h: 250, format: [{w: 300, h: 250}, {w: 320, h: 250}]}, + }, + }, ]; for (let {title, data, expected} of dataTests) { @@ -111,6 +186,35 @@ describe('nextMillenniumBidAdapterTests', () => { } }); + describe('function setImpPos', () => { + const tests = [ + { + title: 'position is - 1', + pos: 0, + expected: {pos: 0}, + }, + + { + title: 'position is - 2', + pos: 7, + expected: {pos: 7}, + }, + + { + title: 'position is empty', + expected: {}, + }, + ]; + + for (const {title, pos, expected} of tests) { + it(title, () => { + const obj = {}; + setImpPos(obj, pos); + expect(obj).to.deep.equal(expected); + }); + }; + }); + describe('function getSourceObj', () => { const dataTests = [ { diff --git a/test/spec/modules/nexverseBidAdapter_spec.js b/test/spec/modules/nexverseBidAdapter_spec.js new file mode 100644 index 00000000000..fd20a37cc0d --- /dev/null +++ b/test/spec/modules/nexverseBidAdapter_spec.js @@ -0,0 +1,203 @@ +import { expect } from 'chai'; +import { spec } from 'modules/nexverseBidAdapter.js'; +import { getDeviceModel, buildEndpointUrl, parseNativeResponse } from '../../../libraries/nexverseUtils/index.js'; +import { getOsVersion } from '../../../libraries/advangUtils/index.js'; + +const BIDDER_ENDPOINT = 'https://rtb.nexverse.ai'; + +describe('nexverseBidAdapterTests', () => { + describe('isBidRequestValid', function () { + let sbid = { + 'adUnitCode': 'div', + 'bidder': 'nexverse', + 'params': { + 'uid': '77d4a2eb3d209ce6c7691dc79fcab358', + 'pubId': '24051' + }, + }; + + it('should not accept bid without required params', function () { + let isValid = spec.isBidRequestValid(sbid); + expect(isValid).to.equal(false); + }); + + it('should return false when params are not passed', function () { + let bid = Object.assign({}, sbid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when valid params are not passed', function () { + let bid = Object.assign({}, sbid); + delete bid.params; + bid.params = {uid: '', pubId: '', pubEpid: ''}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when valid params are not passed', function () { + let bid = Object.assign({}, sbid); + delete bid.params; + bid.adUnitCode = ''; + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.params = {uid: '77d4a2eb3d209ce6c7691dc79fcab358', pubId: '24051'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return true when valid params are passed as nums', function () { + let bid = Object.assign({}, sbid); + delete bid.params; + bid.mediaTypes = { + banner: { + sizes: [[300, 250]] + } + }; + bid.params = {uid: '77d4a2eb3d209ce6c7691dc79fcab358', pubId: '24051', pubEpid: '34561'}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('getDeviceModel', () => { + it('should return "iPhone" for iPhone userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)', configurable: true }); + expect(getDeviceModel()).to.equal('iPhone'); + }); + + it('should return "iPad" for iPad userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (iPad; CPU OS 14_0 like Mac OS X)', configurable: true }); + expect(getDeviceModel()).to.equal('iPad'); + }); + + it('should return the Android device name for Android userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Linux; Android 10; Pixel 3 Build/QQ2A.200305.003) AppleWebKit/537.36', configurable: true }); + expect(getDeviceModel()).to.equal('Pixel 3'); + }); + + it('should return "Unknown Android Device" if device name is missing in Android userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Linux; Android 10;) AppleWebKit/537.36', configurable: true }); + expect(getDeviceModel()).to.equal('Unknown Android Device'); + }); + + it('should return "Mac" for Mac userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)', configurable: true }); + expect(getDeviceModel()).to.equal('Mac'); + }); + + it('should return "Linux" for Linux userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (X11; Linux x86_64)', configurable: true }); + expect(getDeviceModel()).to.equal('Linux'); + }); + + it('should return "Windows PC" for Windows userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', configurable: true }); + expect(getDeviceModel()).to.equal('Windows PC'); + }); + + it('should return "Unknown Device" for an unrecognized userAgent', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Unknown OS)', configurable: true }); + expect(getDeviceModel()).to.equal(''); + }); + }); + + describe('buildEndpointUrl', () => { + it('should construct the URL with uid, pubId, and pubEpid', function () { + const bid = { + params: { + uid: '12345', + pubId: '67890', + pubEpid: 'abcdef' + }, + isDebug: false + }; + const expectedUrl = `${BIDDER_ENDPOINT}?uid=12345&pub_id=67890&pub_epid=abcdef`; + expect(buildEndpointUrl(BIDDER_ENDPOINT, bid)).to.equal(expectedUrl); + }); + }); + + describe('buildEndpointUrl', () => { + it('should construct the test URL with uid, pubId, and pubEpid', function () { + const bid = { + params: { + uid: '12345', + pubId: '67890', + pubEpid: 'abcdef' + }, + isDebug: true + }; + const expectedUrl = `${BIDDER_ENDPOINT}?uid=12345&pub_id=67890&pub_epid=abcdef&test=1`; + expect(buildEndpointUrl(BIDDER_ENDPOINT, bid)).to.equal(expectedUrl); + }); + }); + + describe('parseNativeResponse', () => { + it('should parse and return the empty json object from a invalid JSON string', function () { + const adm = 'Nexverse test ad'; + const result = parseNativeResponse(adm); + expect(result).to.deep.equal({}); + }); + it('should parse and return the native object from a valid JSON string', function () { + const adm = '{"native": "sample native ad"}'; // JSON string + const result = parseNativeResponse(adm); + expect(result).to.deep.equal('sample native ad'); + }); + }); + + describe('getOsVersion', () => { + it('should detect Android OS', function () { + Object.defineProperty(navigator, 'userAgent', { value: 'Mozilla/5.0 (Linux; Android 10; Pixel 3 Build/QQ2A.200305.003) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Mobile Safari/537.36', configurable: true }); + expect(getOsVersion()).to.equal('Android'); + }); + it('should detect iOS', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1', + configurable: true, + }); + expect(getOsVersion()).to.equal('iOS'); + }); + it('should detect Mac OS X', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + configurable: true, + }); + expect(getOsVersion()).to.equal('Mac OS X'); + }); + it('should detect Windows 10', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + configurable: true, + }); + expect(getOsVersion()).to.equal('Windows 10'); + }); + it('should detect Linux', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + configurable: true, + }); + expect(getOsVersion()).to.equal('Linux'); + }); + it('should detect Windows 7', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + configurable: true, + }); + expect(getOsVersion()).to.equal('Windows 7'); + }); + it('should detect Search Bot', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)', + configurable: true, + }); + expect(getOsVersion()).to.equal('Search Bot'); + }); + it('should return unknown for an unrecognized user agent', function () { + Object.defineProperty(navigator, 'userAgent', { + value: 'Mozilla/5.0 (Unknown OS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', + configurable: true, + }); + expect(getOsVersion()).to.equal('unknown'); + }); + }); +}); diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index dc2d8ffebb6..54d744131cd 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -130,6 +130,45 @@ describe('omsBidAdapter', function () { expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); }); + it('sets the proper video object when ad unit media type is video', function () { + const bidRequests = [ + { + 'bidder': 'oms', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [640, 480] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + }, + } + ] + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].video.context).to.equal('instream'); + expect(payload.imp[0].video.playerSize).to.deep.equal([640, 480]); + }); + it('accepts a single array as a size', function () { bidRequests[0].mediaTypes.banner.sizes = [300, 250]; const request = spec.buildRequests(bidRequests); @@ -379,6 +418,45 @@ describe('omsBidAdapter', function () { expect(result[0]).to.deep.equal(expectedResponse[0]); }); + it('should get the correct bid response for video bids', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'video', + 'ad': `
`, + 'ttl': 300, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + const response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250, + 'adomain': ['example.com'], + 'mtype': 2 + }] + }] + } + }; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + it('crid should default to the bid id if not on the response', function () { let expectedResponse = [{ 'requestId': '283a9f4cd2415d', diff --git a/test/spec/modules/openwebBidAdapter_spec.js b/test/spec/modules/openwebBidAdapter_spec.js index f6f6ad22476..9ab8f608598 100644 --- a/test/spec/modules/openwebBidAdapter_spec.js +++ b/test/spec/modules/openwebBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/openwebBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.openwebmp.com/hb-multi'; const TEST_ENDPOINT = 'https://hb.openwebmp.com/hb-multi-test'; @@ -74,7 +75,6 @@ describe('openwebAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -91,7 +91,59 @@ describe('openwebAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -160,10 +212,10 @@ describe('openwebAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -179,12 +231,21 @@ describe('openwebAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -464,6 +525,28 @@ describe('openwebAdapter', function () { nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id-3', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -503,10 +586,42 @@ describe('openwebAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id-3', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -518,6 +633,11 @@ describe('openwebAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 5dc60b25ab0..dde9e2bd7c8 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -249,12 +249,119 @@ describe('OpenxRtbAdapter', function () { }); }); }); + + describe('when request is for a native ad', function () { + const nativeOrtbRequest = { + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + } + describe('and request config uses mediaTypes', () => { + const nativeBidWithMediaTypes = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(nativeBidWithMediaTypes)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let invalidNativeBidWithMediaTypes = Object.assign({}, nativeBidWithMediaTypes); + invalidNativeBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidNativeBidWithMediaTypes)).to.equal(false); + }); + }); + + describe('and request config uses both delDomain and platform', () => { + const nativeBidWithDelDomainAndPlatform = { + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest, + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(nativeBidWithDelDomainAndPlatform)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let invalidNativeBidWithMediaTypes = Object.assign({}, nativeBidWithDelDomainAndPlatform); + invalidNativeBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidNativeBidWithMediaTypes)).to.equal(false); + }); + }); + }); }); describe('buildRequests()', function () { let bidRequestsWithMediaTypes; - let bidRequestsWithPlatform; let mockBidderRequest; + const nativeOrtbRequest = { + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + }; + const nativeBidRequest = { + bidder: 'openx', + params: { + unit: '33', + delDomain: 'test-del-domain', + platform: '1cabba9e-cafe-3665-beef-f00f00f00f00', + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } + } + }, + nativeOrtbRequest, + bidId: 'test-bid-id-3', + bidderRequestId: 'test-bid-request-3', + auctionId: 'test-auction-3', + transactionId: 'test-transactionId-3' + }; beforeEach(function () { mockBidderRequest = {refererInfo: {}}; @@ -302,16 +409,64 @@ describe('OpenxRtbAdapter', function () { }); context('common requests checks', function() { - it('should be able to handle multiformat requests', () => { + it('should be able to handle multiformat request - banner + video', () => { const multiformat = utils.deepClone(bidRequestsWithMediaTypes[0]); multiformat.mediaTypes.video = { context: 'outstream', playerSize: [640, 480] + }; + const requests = spec.buildRequests([multiformat], mockBidderRequest); + expect(requests).to.have.length(2); + expect(requests[0].data.imp).to.have.length(1); + expect(requests[0].data.imp[0].banner).to.exist; + expect(requests[0].data.imp[0].video).to.not.exist; + expect(requests[0].data.imp[0].native).to.not.exist; + expect(requests[1].data.imp).to.have.length(1); + expect(requests[1].data.imp[0].banner).to.not.exist; + expect(requests[1].data.imp[0].native).to.not.exist; + if (FEATURES.VIDEO) { + expect(requests[1].data.imp[0].video).to.exist; + } + }) + + it('should be able to handle multiformat request - banner + native', () => { + const multiformat = utils.deepClone(nativeBidRequest); + multiformat.mediaTypes.banner = { + sizes: [[300, 250], [300, 600]] + } + const requests = spec.buildRequests([multiformat], mockBidderRequest); + expect(requests).to.have.length(1); + expect(requests[0].data.imp).to.have.length(1); + expect(requests[0].data.imp[0].banner).to.exist; + expect(requests[0].data.imp[0].video).to.not.exist; + if (FEATURES.NATIVE) { + expect(requests[0].data.imp[0].native).to.exist; + } + }) + + it('should be able to handle multiformat request - banner + video + native', () => { + const multiformat = utils.deepClone(nativeBidRequest); + multiformat.mediaTypes.video = { + context: 'outstream', + playerSize: [640, 480] + }; + multiformat.mediaTypes.banner = { + sizes: [[300, 250]] } const requests = spec.buildRequests([multiformat], mockBidderRequest); - const outgoingFormats = requests.flatMap(rq => rq.data.imp.flatMap(imp => ['banner', 'video'].filter(k => imp[k] != null))); - const expected = FEATURES.VIDEO ? ['banner', 'video'] : ['banner'] - expect(outgoingFormats).to.have.members(expected); + expect(requests).to.have.length(2); + expect(requests[0].data.imp).to.have.length(1); + expect(requests[0].data.imp[0].banner).to.exist; + expect(requests[0].data.imp[0].video).to.not.exist; + if (FEATURES.NATIVE) { + expect(requests[0].data.imp[0].native).to.exist; + } + expect(requests[1].data.imp).to.have.length(1); + expect(requests[1].data.imp[0].banner).to.not.exist; + expect(requests[1].data.imp[0].native).to.not.exist; + if (FEATURES.VIDEO) { + expect(requests[1].data.imp[0].video).to.exist; + } }) it('should send bid request to openx url via POST', function () { @@ -329,11 +484,11 @@ describe('OpenxRtbAdapter', function () { it('should send platform id, if available', function () { bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; - bidRequestsWithMediaTypes[1].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; + bidRequestsWithMediaTypes[1].params.platform = '51ca3159-abc2-4035-8e00-fe26eaa09397'; const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); - expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); + expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[1].params.platform); }); it('should send openx adunit codes', function () { @@ -1226,6 +1381,7 @@ describe('OpenxRtbAdapter', function () { crid: 'test-creative-id', dealid: 'test-deal-id', adm: 'test-ad-markup', + mtype: 1, adomain: ['brand.com'], ext: { dsp_id: '123', @@ -1389,7 +1545,7 @@ describe('OpenxRtbAdapter', function () { expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); }); - it('should return the proper mediaType', function () { + it('should return the proper vastUrl', function () { const winUrl = 'https//my.win.url'; bidResponse.seatbid[0].bid[0].nurl = winUrl bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; @@ -1399,6 +1555,78 @@ describe('OpenxRtbAdapter', function () { }); } + if (FEATURES.NATIVE) { + context('when the response is a native', function() { + beforeEach(function () { + const nativeOrtbRequest = { + ver: '1.2', + assets: [{ + id: 1, + required: 1, + title: { + len: 80 + } + }] + }; + bidRequestConfigs = [{ + bidder: 'openx', + params: { + unit: '12345678', + delDomain: 'test-del-domain' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + native: { + ...nativeOrtbRequest + }, + }, + nativeOrtbRequest, + bidId: 'test-bid-id', + bidderRequestId: 'test-bidder-request-id', + auctionId: 'test-auction-id' + }]; + + bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + + bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test-bid-id', + price: 2, + mtype: 4, + adm: '{"ver": "1.2", "assets": [{"id": 1, "required": 1,"title": {"text": "OpenX (Title)"}}], "link": {"url": "https://www.openx.com/"}, "eventtrackers":[{"event":1,"method":1,"url":"http://example.com/impression"}]}', + }] + }], + cur: 'AUS' + }; + }); + + it('should return the proper mediaType', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); + }); + + it('should return parsed adm JSON in native.ortb response field', function () { + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; + + expect(bid.native.ortb).to.deep.equal({ + ver: '1.2', + assets: [{ + id: 1, + required: 1, + title: {text: 'OpenX (Title)'} + }], + link: {url: 'https://www.openx.com/'}, + eventtrackers: [{ + event: 1, + method: 1, + url: 'http://example.com/impression' + }] + }); + }); + }); + } + context('when the response contains FLEDGE interest groups config', function() { let response; diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 2dd80537f1f..fc0f9ed7b68 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -37,6 +37,11 @@ import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {deepSetValue} from '../../../src/utils.js'; import {ACTIVITY_TRANSMIT_UFPD} from '../../../src/activities/activities.js'; import {MODULE_TYPE_PREBID} from '../../../src/activities/modules.js'; +import { + consolidateEids, + extractEids, + getPBSBidderConfig +} from '../../../modules/prebidServerBidAdapter/bidderConfig.js'; let CONFIG = { accountId: '1', @@ -2065,37 +2070,112 @@ describe('S2S Adapter', function () { }); }); - it('should pass user.ext.eids from FPD', function () { - config.setConfig({s2sConfig: CONFIG}); - const req = { - ...REQUEST, - ortb2Fragments: { - global: { - user: { - ext: { - eids: [{id: 1}, {id: 2}] - } - } - }, - bidder: { - appnexus: { + describe('user.ext.eids', () => { + let req; + beforeEach(() => { + const s2sConfig = { + ...CONFIG, + bidders: ['appnexus', 'rubicon'] + } + config.setConfig({s2sConfig}); + req = { + ...REQUEST, + s2sConfig, + ortb2Fragments: { + global: { user: { ext: { - eids: [{id: 3}] + eids: [{source: 'idA', id: 1}, {source: 'idB', id: 2}] + } + } + }, + bidder: { + appnexus: { + user: { + ext: { + eids: [{source: 'idC', id: 3}] + } } } } } } - } - adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); - const payload = JSON.parse(server.requests[0].requestBody); - expect(payload.user.ext.eids).to.eql([{id: 1}, {id: 2}]); - expect(payload.ext.prebid.bidderconfig).to.eql([{ - bidders: ['appnexus'], - config: {ortb2: {user: {ext: {eids: [{id: 3}]}}}} - }]); - }); + }) + it('should get picked up from from FPD', function () { + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const payload = JSON.parse(server.requests[0].requestBody); + expect(payload.user.ext.eids).to.eql([ + {source: 'idA', id: 1}, + {source: 'idB', id: 2}, + {source: 'idC', id: 3} + ]); + expect(payload.ext.prebid.data.eidpermissions).to.eql([{ + bidders: ['appnexus'], + source: 'idC' + }]); + }); + + it('should not set eidpermissions for unrequested bidders', () => { + req.ortb2Fragments.bidder.unknown = { + user: { + eids: [{source: 'idC', id: 3}, {source: 'idD', id: 4}] + } + } + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const payload = JSON.parse(server.requests[0].requestBody); + expect(payload.ext.prebid.data.eidpermissions).to.eql([{ + bidders: ['appnexus'], + source: 'idC' + }, { + bidders: [], + source: 'idD' + }]); + }); + + it('should repeat global EIDs when bidder-specific EIDs conflict', () => { + BID_REQUESTS.push({ + ...BID_REQUESTS[0], + bidderCode: 'rubicon', + bids: [{ + bidder: 'rubicon', + params: {} + }] + }) + req.ortb2Fragments.bidder.rubicon = { + user: { + ext: { + eids: [{source: 'idC', id: 4}] + } + } + } + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const payload = JSON.parse(server.requests[0].requestBody); + const globalEids = [ + {source: 'idA', id: 1}, + {source: 'idB', id: 2}, + ] + expect(payload.user.ext.eids).to.eql(globalEids); + expect(payload.ext.prebid?.data?.eidpermissions).to.not.exist; + expect(payload.ext.prebid.bidderconfig).to.have.deep.members([ + { + bidders: ['appnexus'], + config: { + ortb2: { + user: {ext: {eids: globalEids.concat([{source: 'idC', id: 3}])}} + } + } + }, + { + bidders: ['rubicon'], + config: { + ortb2: { + user: {ext: {eids: globalEids.concat([{source: 'idC', id: 4}])}} + } + } + } + ]) + }) + }) it('when config \'currency.adServerCurrency\' value is a string: ORTB has property \'cur\' value set to a single item array', function () { config.setConfig({ @@ -2967,32 +3047,6 @@ describe('S2S Adapter', function () { }) }) - describe('calls done', () => { - let success, error; - beforeEach(() => { - const mockAjax = function (_, callback) { - ({success, error} = callback); - } - config.setConfig({ s2sConfig: CONFIG }); - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, mockAjax); - }) - - it('passing timedOut = false on succcess', () => { - success({}); - sinon.assert.calledWith(done, false); - }); - - Object.entries({ - 'timeouts': true, - 'other errors': false - }).forEach(([t, timedOut]) => { - it(`passing timedOut = ${timedOut} on ${t}`, () => { - error('', {timedOut}); - sinon.assert.calledWith(done, timedOut); - }) - }) - }) - // TODO: test dependent on pbjs_api_spec. Needs to be isolated it('does not call addBidResponse and calls done when ad unit not set', function () { config.setConfig({ s2sConfig: CONFIG }); @@ -4331,4 +4385,365 @@ describe('S2S Adapter', function () { expect(requestBid.ext.prebid.floors).to.deep.equal({ enabled: true, floorMin: 1, floorMinCur: 'CUR' }); }); }); + + describe('getPBSBidderConfig', () => { + [ + { + t: 'does not alter config when there are no conflicts', + global: { + k1: 'val' + }, + bidder: { + bidderA: { + k2: 'val' + } + }, + expected: { + bidderA: { + k2: 'val' + } + } + }, + { + t: 'uses bidder config on type mismatch (scalar/object)', + global: { + k1: 'val', + k2: 'val' + }, + bidder: { + bidderA: { + k1: {k3: 'val'} + } + }, + expected: { + bidderA: { + k1: {k3: 'val'} + } + } + }, + { + t: 'uses bidder config on type mismatch (array/object)', + global: { + k: [1, 2] + }, + bidder: { + bidderA: { + k: {inner: 'val'} + } + }, + expected: { + bidderA: { + k: {inner: 'val'} + } + } + }, + { + t: 'uses bidder config on type mismatch (object/array)', + global: { + k: {inner: 'val'} + }, + bidder: { + bidderA: { + k: [1, 2] + } + }, + expected: { + bidderA: { + k: [1, 2] + } + } + }, + { + t: 'uses bidder config on type mismatch (array/null)', + global: { + k: [1, 2] + }, + bidder: { + bidderA: { + k: null + } + }, + expected: { + bidderA: { + k: null + } + } + }, + { + t: 'uses bidder config on type mismatch (null/array)', + global: {}, + bidder: { + bidderA: { + k: [1, 2] + } + }, + expected: { + bidderA: { + k: [1, 2] + } + } + }, + { + t: 'concatenates arrays', + global: { + key: 'value', + array: [1] + }, + bidder: { + bidderA: { + array: [2] + } + }, + expected: { + bidderA: { + array: [1, 2] + } + } + }, + { + t: 'concatenates nested arrays', + global: { + nested: { + array: [1] + } + }, + bidder: { + bidderA: { + key: 'value', + nested: { + array: [2] + } + } + }, + expected: { + bidderA: { + key: 'value', + nested: { + array: [1, 2] + } + } + } + }, + { + t: 'does not repeat equal elements', + global: { + array: [{id: 1}] + }, + bidder: { + bidderA: { + array: [{id: 1}, {id: 2}] + } + }, + expected: { + bidderA: { + array: [{id: 1}, {id: 2}] + } + } + } + ].forEach(({t, global, bidder, expected}) => { + it(t, () => { + expect(getPBSBidderConfig({global, bidder})).to.eql(expected); + }) + }) + }); + describe('EID handling', () => { + function mkEid(source, value = source) { + return {source, value}; + } + + function eidEntry(source, value = source, bidders = false) { + return {eid: {source, value}, bidders}; + } + + describe('extractEids', () => { + [ + { + t: 'no bidder-specific eids', + global: { + user: { + ext: { + eids: [ + mkEid('idA', 'id1'), + mkEid('idA', 'id2') + ] + }, + eids: [mkEid('idB')] + } + }, + expected: { + eids: [ + eidEntry('idA', 'id1'), + eidEntry('idA', 'id2'), + eidEntry('idB') + ], + conflicts: ['idA'] + } + }, + { + t: 'bidder-specific eids', + global: { + user: { + eids: [ + mkEid('idA') + ] + }, + }, + bidder: { + bidderA: { + user: { + ext: { + eids: [ + mkEid('idB') + ] + } + } + } + }, + expected: { + eids: [ + eidEntry('idA'), + eidEntry('idB', 'idB', ['bidderA']) + ] + } + }, + { + t: 'conflicting bidder-specific eids', + global: { + user: { + eids: [mkEid('idA', 'idA1')] + }, + }, + bidder: { + bidderA: { + user: { + eids: [mkEid('idA', 'idA2'), mkEid('idB', 'idB1'), mkEid('idD')] + }, + }, + bidderB: { + user: { + ext: { + eids: [mkEid('idB', 'idB2'), mkEid('idC'), mkEid('idD')] + } + } + }, + }, + expected: { + eids: [ + eidEntry('idA', 'idA1'), + eidEntry('idA', 'idA2', ['bidderA']), + eidEntry('idB', 'idB1', ['bidderA']), + eidEntry('idB', 'idB2', ['bidderB']), + eidEntry('idC', 'idC', ['bidderB']), + eidEntry('idD', 'idD', ['bidderA', 'bidderB']) + ], + conflicts: ['idA', 'idB'] + } + }, + { + t: 'duplicated bidder-specific eids', + bidder: { + bidderA: { + user: { + eids: [mkEid('id'), mkEid('id')] + } + } + }, + expected: { + eids: [ + eidEntry('id', 'id', ['bidderA']) + ] + } + } + ].forEach(({t, global = {}, bidder = {}, expected}) => { + it(t, () => { + const {eids, conflicts} = extractEids({global, bidder}); + expect(eids).to.have.deep.members(expected.eids); + expect(Array.from(conflicts)).to.have.members(expected.conflicts || []); + }) + }); + }); + describe('consolidateEids', () => { + it('returns global EIDs without permissions', () => { + expect(consolidateEids({ + eids: [eidEntry('idA'), eidEntry('idB')] + })).to.eql({ + global: [mkEid('idA'), mkEid('idB')], + permissions: [], + bidder: {} + }) + }); + + it('returns conflicting, but global EIDs', () => { + expect(consolidateEids({ + eids: [eidEntry('idA', 'idA1'), eidEntry('idA', 'idA2')], + conflicts: new Set(['idA']) + })).to.eql({ + global: [mkEid('idA', 'idA1'), mkEid('idA', 'idA2')], + permissions: [], + bidder: {} + }) + }) + + it('sets permissions for bidder-speficic EIDS', () => { + expect(consolidateEids({ + eids: [ + eidEntry('idA'), + eidEntry('idB', 'idB', ['bidderB']) + ] + })).to.eql({ + global: [mkEid('idA'), mkEid('idB')], + permissions: [{source: 'idB', bidders: ['bidderB']}], + bidder: {} + }) + }) + + it('does not consolidate conflicting bidder-specific EIDs', () => { + expect(consolidateEids({ + eids: [ + eidEntry('global'), + eidEntry('idA', 'idA1', ['bidderA']), + eidEntry('idA', 'idA2', ['bidderB']) + ], + conflicts: new Set(['idA']) + })).to.eql({ + global: [mkEid('global')], + permissions: [], + bidder: { + bidderA: [mkEid('idA', 'idA1')], + bidderB: [mkEid('idA', 'idA2')] + } + }) + }) + + it('does not set permissions for conflicting bidder-specific eids', () => { + expect(consolidateEids({ + eids: [eidEntry('idA', 'idA1'), eidEntry('idA', 'idA2', ['bidderA'])], + conflicts: new Set(['idA']) + })).to.eql({ + global: [mkEid('idA', 'idA1')], + permissions: [], + bidder: { + bidderA: [mkEid('idA', 'idA2')] + } + }) + }); + + it('can do partial consolidation when only some IDs are conflicting', () => { + expect(consolidateEids({ + eids: [ + eidEntry('idA', 'idA1'), + eidEntry('idB', 'idB', ['bidderB']), + eidEntry('idA', 'idA2', ['bidderA']) + ], + conflicts: new Set(['idA']) + })).to.eql({ + global: [mkEid('idA', 'idA1'), mkEid('idB')], + permissions: [{source: 'idB', bidders: ['bidderB']}], + bidder: { + bidderA: [mkEid('idA', 'idA2')] + } + }); + }); + }) + }); }); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index f77b167a3e9..263d0416897 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -571,13 +571,13 @@ describe('pubmatic analytics adapter', function () { expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sid).not.to.be.undefined; - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); @@ -609,9 +609,6 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -789,14 +786,14 @@ describe('pubmatic analytics adapter', function () { expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); expect(data.tgid).to.equal(0); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); expect(data.s[0].sid).not.to.be.undefined; - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); @@ -915,15 +912,13 @@ describe('pubmatic analytics adapter', function () { let request = requests[1]; // logger is executed late, trackers execute first let data = getLoggerJsonFromRequest(request.requestBody); expect(data.tgid).to.equal(0);// test group id should be an INT between 0-15 else set to 0 + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].sid).not.to.be.undefined; - - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); - expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1004,12 +999,13 @@ describe('pubmatic analytics adapter', function () { expect(requests.length).to.equal(1); // 1 logger and 0 win-tracker let request = requests[0]; let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); + expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1119,12 +1115,12 @@ describe('pubmatic analytics adapter', function () { let request = requests[2]; // logger is executed late, trackers execute first expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1239,12 +1235,12 @@ describe('pubmatic analytics adapter', function () { let request = requests[2]; // logger is executed late, trackers execute first expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1357,14 +1353,14 @@ describe('pubmatic analytics adapter', function () { let request = requests[1]; // logger is executed late, trackers execute first expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 2 // Testing only for rejected bid as other scenarios will be covered under other TCs expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); @@ -1438,13 +1434,13 @@ describe('pubmatic analytics adapter', function () { expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); @@ -1477,9 +1473,6 @@ describe('pubmatic analytics adapter', function () { // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); @@ -1570,13 +1563,13 @@ describe('pubmatic analytics adapter', function () { expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); + expect(data.ffs).to.equal(1); + expect(data.fsrc).to.equal(2); + expect(data.fp).to.equal('pubmatic'); // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); diff --git a/test/spec/modules/rediadsBidAdapter_spec.js b/test/spec/modules/rediadsBidAdapter_spec.js new file mode 100644 index 00000000000..97b810974be --- /dev/null +++ b/test/spec/modules/rediadsBidAdapter_spec.js @@ -0,0 +1,133 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/rediadsBidAdapter'; + +describe('rediads Bid Adapter', function () { + const BIDDER_CODE = 'rediads'; + const STAGING_ENDPOINT_URL = 'https://stagingbidding.rediads.com/openrtb2/auction'; + + const bidRequest = { + bidder: BIDDER_CODE, + params: { + account_id: '12345', + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]], + }, + }, + adUnitCode: 'adunit-code', + bidId: '2ab03f1234', + auctionId: '123456789', + }; + + const bidderRequest = { + bidderCode: BIDDER_CODE, + refererInfo: { + referer: 'http://example.com', + }, + }; + + const resetHash = (originalHash) => { + // Reset the hash, ensuring no trailing # + if (originalHash) { + location.hash = originalHash; + } else { + history.replaceState(null, '', location.pathname + location.search); + } + } + + describe('isBidRequestValid', function () { + it('should return true for valid bid requests', function () { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false if account_id is missing', function () { + const invalidBid = { ...bidRequest, params: {} }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('should build a valid request with correct data', function () { + const requests = spec.buildRequests([bidRequest], bidderRequest); + expect(requests).to.be.an('array').that.is.not.empty; + + const request = requests[0]; + expect(request.method).to.equal('POST'); + expect(request.url).that.is.not.empty; + expect(request.data).to.have.property('ext'); + expect(request.data.ext.rediads.params).to.deep.equal(bidRequest.params); + }); + + it('should include test flag if testBidsRequested is true', function () { + const originalHash = location.hash; + location.hash = '#rediads-test-bids'; + + const requests = spec.buildRequests([bidRequest], bidderRequest); + expect(requests[0].data.test).to.equal(1); + + resetHash(originalHash); + }); + + it('should set staging environtment if stagingEnvRequested is true', function () { + const originalHash = location.hash; + location.hash = '#rediads-staging'; + + const requests = spec.buildRequests([bidRequest], bidderRequest); + expect(requests[0].url).to.equal(STAGING_ENDPOINT_URL); + + resetHash(originalHash); + }); + }); + + describe('interpretResponse', function () { + it('should interpret and return valid bid responses for banner bid', function () { + const serverResponse = { + body: { + seatbid: [ + { + bid: [ + { + price: 1.23, + impid: '2ab03f1234', + adm: '
Ad
', + crid: 'creative123', + w: 300, + h: 250, + }, + ], + }, + ], + }, + }; + const requestObj = spec.buildRequests([bidRequest], bidderRequest); + const bids = spec.interpretResponse(serverResponse, requestObj[0]); + expect(bids).to.be.an('array').that.is.not.empty; + + const bid = bids[0]; + expect(bid).to.include({ + requestId: '2ab03f1234', + cpm: 1.23, + creativeId: 'creative123', + width: 300, + height: 250, + ad: '
Ad
', + }); + expect(bid.mediaType).to.equal('banner'); + }); + + it('should return an empty array for invalid responses', function () { + const invalidResponse = { body: {} }; + const updatedBidRequest = {...bidRequest, params: undefined} + const requestObj = spec.buildRequests([updatedBidRequest], bidderRequest); + const bids = spec.interpretResponse(invalidResponse, requestObj[0]); + expect(bids).to.be.an('array').that.is.empty; + }); + }); + + describe('Miscellaneous', function () { + it('should support multiple media types', function () { + expect(spec.supportedMediaTypes).to.include.members(['banner', 'native', 'video']); + }); + }); +}); diff --git a/test/spec/modules/responsiveAdsBidAdapter_spec.js b/test/spec/modules/responsiveAdsBidAdapter_spec.js new file mode 100644 index 00000000000..83202ad0916 --- /dev/null +++ b/test/spec/modules/responsiveAdsBidAdapter_spec.js @@ -0,0 +1,145 @@ +import { expect } from 'chai'; +import { spec } from 'modules/responsiveAdsBidAdapter.js'; +import * as utils from 'src/utils.js'; + +describe('responsiveAdsBidAdapter', function() { + let bidRequests; + let bidderRequest; + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'isSafeFrameWindow').returns(false); + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + bidRequests = [{ + bidder: 'responsiveads', + params: { + placementId: '1', + }, + adUnitCode: '/3434399/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bidId: '123', + auctionId: '456', + bidderRequestId: '789', + transactionId: '123' + }]; + + bidderRequest = { + timeout: 3000, + } + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('Check if bid is valid', function() { + it('Should accept valid bid', function() { + const validBid = { + bidder: 'responsiveads', + params: {}, + }; + + const isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('Should not reject bid if missing placementId', function() { + const validBid = { + bidder: 'responsiveads', + params: {} + }; + + const isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + }); + + describe('Build requests', function () { + it('Should not bit on safeframe', function() { + utils.isSafeFrameWindow.restore(); + sandbox.stub(utils, 'isSafeFrameWindow').returns(true); + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.be.null; + }); + + it('Should not bit if cant access window top', function () { + utils.canAccessWindowTop.restore(); + sandbox.stub(utils, 'canAccessWindowTop').returns(false); + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.be.null; + }); + + it('Should use POST and have URL', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.method).to.exist; + expect(request.method).to.equal('POST'); + expect(request.url).to.exist; + }); + + it('Should add adapter version', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.ext.prebid.adapterVersion).to.exist; + }); + }); + + describe('Handling responses', function() { + it('Should return complete bid response', function() { + const serverResponse = { + body: { + id: 'response-id', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '123', + impid: '123', + price: 0.5, + adm: ``, + nurl: 'https://example.com/win', + crid: '662d13e12e0c567af92d0918', + w: 300, + h: 250, + mediaType: 'banner', + adomain: ['responsiveads.com'], + attr: [1], + cat: ['IAB1'] + } + ] + } + ] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(serverResponse, request); + + expect(bids).to.be.lengthOf(1); + expect(bids[0].requestId).to.equal('123'); + expect(bids[0].cpm).to.equal(0.5); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].ad).to.have.length.above(1); + expect(bids[0].meta.advertiserDomains).to.deep.equal(['responsiveads.com']); + }); + + it('should return empty bid response', function () { + const emptyServerResponse = { + body: [] + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const bids = spec.interpretResponse(emptyServerResponse, request); + + expect(bids).to.be.lengthOf(0); + }); + }); +}); diff --git a/test/spec/modules/retailspotBidAdapter_spec.js b/test/spec/modules/retailspotBidAdapter_spec.js index f1fb5ae3fd3..c5cb001c1ba 100644 --- a/test/spec/modules/retailspotBidAdapter_spec.js +++ b/test/spec/modules/retailspotBidAdapter_spec.js @@ -246,7 +246,7 @@ describe('RetailSpot Adapter', function () { ]; const adapter = newBidder(spec); - const DEV_URL = 'http://localhost:8090/'; + const DEV_URL = 'http://localhost:3030/'; describe('inherited functions', function () { it('exists and is a function', function () { @@ -333,7 +333,7 @@ describe('RetailSpot Adapter', function () { const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); const payload = JSON.parse(request.data); - expect(request.url).to.contain('https://ssp.retail-spot.io/prebid'); + expect(request.url).to.contain('https://hbapi.retailspotads.com/'); expect(request.method).to.equal('POST'); expect(payload).to.deep.equal(bidderRequest); @@ -344,7 +344,7 @@ describe('RetailSpot Adapter', function () { const request = spec.buildRequests(bidRequestWithSinglePlacement, bidderRequest); const payload = JSON.parse(request.data); - expect(request.url).to.contain('https://ssp.retail-spot.io/prebid'); + expect(request.url).to.contain('https://hbapi.retailspotads.com/'); expect(request.method).to.equal('POST'); expect(payload).to.deep.equal(bidderRequest); @@ -355,7 +355,7 @@ describe('RetailSpot Adapter', function () { const request = spec.buildRequests(bidRequestMultiPlacements, bidderRequest); const payload = JSON.parse(request.data); - expect(request.url).to.contain('https://ssp.retail-spot.io/prebid'); + expect(request.url).to.contain('https://hbapi.retailspotads.com/'); expect(request.method).to.equal('POST'); expect(payload).to.deep.equal(bidderRequest); diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index cb879231237..a3fef50f825 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/riseBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.yellowblue.io/hb-multi'; const TEST_ENDPOINT = 'https://hb.yellowblue.io/hb-multi-test'; @@ -72,7 +73,6 @@ describe('riseAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -89,7 +89,59 @@ describe('riseAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -111,6 +163,7 @@ describe('riseAdapter', function () { const bidderRequest = { bidderCode: 'rise', + ortb2: {device: {}}, } const placementId = '12345678'; const api = [1, 2]; @@ -174,10 +227,10 @@ describe('riseAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -193,12 +246,21 @@ describe('riseAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -434,6 +496,31 @@ describe('riseAdapter', function () { expect(request.data.bids[0].sua).to.not.exist; }); + it('should send ORTB2 device data in bid request', function() { + const ortb2 = { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, + }, + }; + + const request = spec.buildRequests(bidRequests, { + ...bidderRequest, + ortb2, + }); + + expect(request.data.params.device).to.deep.equal(ortb2.device); + }); + describe('COPPA Param', function() { it('should set coppa equal 0 in bid request if coppa is set to false', function() { const request = spec.buildRequests(bidRequests, bidderRequest); @@ -480,6 +567,28 @@ describe('riseAdapter', function () { creativeId: 'creative-id', nurl: 'http://example.com/win/1234', mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -519,10 +628,42 @@ describe('riseAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -534,6 +675,11 @@ describe('riseAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index 359cbeb4651..71a531aa6c5 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -1,10 +1,12 @@ import {sharedIdSystemSubmodule, storage} from 'modules/sharedIdSystem.js'; import {coppaDataHandler} from 'src/adapterManager'; +import {config} from 'src/config.js'; import sinon from 'sinon'; import * as utils from 'src/utils.js'; import {createEidsArray} from '../../../modules/userId/eids.js'; -import {attachIdSystem} from '../../../modules/userId/index.js'; +import {attachIdSystem, init} from '../../../modules/userId/index.js'; +import {getGlobal} from '../../../src/prebidGlobal.js'; let expect = require('chai').expect; @@ -97,6 +99,9 @@ describe('SharedId System', function () { before(() => { attachIdSystem(sharedIdSystemSubmodule); }); + afterEach(() => { + config.resetConfig(); + }); it('pubCommonId', function() { const userId = { pubcid: 'some-random-id-value' @@ -108,5 +113,24 @@ describe('SharedId System', function () { uids: [{id: 'some-random-id-value', atype: 1}] }); }); + + it('should set inserter, if provided in config', async () => { + config.setConfig({ + userSync: { + userIds: [{ + name: 'sharedId', + params: { + inserter: 'mock-inserter' + }, + value: {pubcid: 'mock-id'} + }] + } + }); + const eids = getGlobal().getUserIdsAsEids(); + sinon.assert.match(eids[0], { + source: 'pubcid.org', + inserter: 'mock-inserter' + }) + }) }) }); diff --git a/test/spec/modules/shinezBidAdapter_spec.js b/test/spec/modules/shinezBidAdapter_spec.js index 4e6c2d3420e..d4ad99359bb 100644 --- a/test/spec/modules/shinezBidAdapter_spec.js +++ b/test/spec/modules/shinezBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/shinezBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.sweetgum.io/hb-sz-multi'; const TEST_ENDPOINT = 'https://hb.sweetgum.io/hb-multi-sz-test'; @@ -61,7 +62,6 @@ describe('shinezAdapter', function () { 'context': 'instream' } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -77,7 +77,59 @@ describe('shinezAdapter', function () { 'banner': { } }, - 'ad': '""' + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, } ]; @@ -130,12 +182,21 @@ describe('shinezAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -305,6 +366,8 @@ describe('shinezAdapter', function () { height: 480, requestId: '21e12606d47ba7', adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', mediaType: VIDEO }, { @@ -314,7 +377,31 @@ describe('shinezAdapter', function () { height: 250, requestId: '21e12606d47ba7', adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', mediaType: BANNER + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -325,7 +412,7 @@ describe('shinezAdapter', function () { width: 640, height: 480, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: VIDEO, @@ -340,10 +427,10 @@ describe('shinezAdapter', function () { requestId: '21e12606d47ba7', cpm: 12.5, currency: 'USD', - width: 640, - height: 480, + width: 300, + height: 250, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: BANNER, @@ -354,10 +441,42 @@ describe('shinezAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); - expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + expect(result[0]).to.deep.equal(expectedVideoResponse); + expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -369,6 +488,11 @@ describe('shinezAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index ebd2e987491..892d88b3b7b 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -119,24 +149,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -317,6 +330,7 @@ describe('ShinezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -388,6 +402,7 @@ describe('ShinezRtbBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 0935b6c8404..f6993b433ad 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -4,6 +4,8 @@ import { config } from 'src/config.js'; import { deepClone } from 'src/utils.js'; import { getBidFloor } from 'libraries/equativUtils/equativUtils.js' import { spec } from 'modules/smartadserverBidAdapter.js'; +import { setConfig as setCurrencyConfig } from '../../../modules/currency'; +import { addFPDToBidderRequest } from '../../helpers/fpd'; // Default params with optional ones describe('Smart bid adapter tests', function () { @@ -249,10 +251,8 @@ describe('Smart bid adapter tests', function () { ]; it('Verify build request', function () { + setCurrencyConfig({ adServerCurrency: 'EUR' }); config.setConfig({ - 'currency': { - 'adServerCurrency': 'EUR' - }, ortb2: { 'user': { 'data': sellerDefinedAudience @@ -265,30 +265,32 @@ describe('Smart bid adapter tests', function () { } }); - const request = spec.buildRequests(DEFAULT_PARAMS); - expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); - expect(request[0]).to.have.property('method').and.to.equal('POST'); - const requestContent = JSON.parse(request[0].data); - - expect(requestContent).to.have.property('siteid').and.to.equal('1234'); - expect(requestContent).to.have.property('pageid').and.to.equal('5678'); - expect(requestContent).to.have.property('formatid').and.to.equal('90'); - expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); - expect(requestContent).to.have.property('bidfloor').and.to.equal(0.42); - expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid'); - expect(requestContent).to.have.property('tagId').and.to.equal('sas_42'); - expect(requestContent).to.have.property('sizes'); - expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); - expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); - expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); - expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(200); - expect(requestContent).to.not.have.property('pageDomain'); - expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; - expect(requestContent).to.have.property('buid').and.to.equal('7569'); - expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); - expect(requestContent).to.have.property('ckid').and.to.equal(42); - expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience); - expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext); + return addFPDToBidderRequest(DEFAULT_PARAMS[0]).then(res => { + const request = spec.buildRequests(DEFAULT_PARAMS, res); + expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('siteid').and.to.equal('1234'); + expect(requestContent).to.have.property('pageid').and.to.equal('5678'); + expect(requestContent).to.have.property('formatid').and.to.equal('90'); + expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestContent).to.have.property('bidfloor').and.to.equal(0.42); + expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid'); + expect(requestContent).to.have.property('tagId').and.to.equal('sas_42'); + expect(requestContent).to.have.property('sizes'); + expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); + expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(200); + expect(requestContent).to.not.have.property('pageDomain'); + expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + expect(requestContent).to.have.property('buid').and.to.equal('7569'); + expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); + expect(requestContent).to.have.property('ckid').and.to.equal(42); + expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience); + expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext); + setCurrencyConfig({}); + }); }); it('Verify parse response with no ad', function () { @@ -648,10 +650,8 @@ describe('Smart bid adapter tests', function () { }; it('Verify instream video build request', function () { + setCurrencyConfig({ adServerCurrency: 'EUR' }); config.setConfig({ - 'currency': { - 'adServerCurrency': 'EUR' - }, ortb2: { 'user': { 'data': sellerDefinedAudience @@ -663,29 +663,33 @@ describe('Smart bid adapter tests', function () { } } }); - const request = spec.buildRequests(INSTREAM_DEFAULT_PARAMS); - expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); - expect(request[0]).to.have.property('method').and.to.equal('POST'); - const requestContent = JSON.parse(request[0].data); - expect(requestContent).to.have.property('siteid').and.to.equal('1234'); - expect(requestContent).to.have.property('pageid').and.to.equal('5678'); - expect(requestContent).to.have.property('formatid').and.to.equal('90'); - expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); - expect(requestContent).to.have.property('bidfloor').and.to.equal(0.42); - expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid'); - expect(requestContent).to.have.property('tagId').and.to.equal('sas_42'); - expect(requestContent).to.not.have.property('pageDomain'); - expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; - expect(requestContent).to.have.property('buid').and.to.equal('7569'); - expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); - expect(requestContent).to.have.property('ckid').and.to.equal(42); - expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience); - expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext); - expect(requestContent).to.have.property('isVideo').and.to.equal(true); - expect(requestContent).to.have.property('videoData'); - expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6); - expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(640); - expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(480); + + return addFPDToBidderRequest(INSTREAM_DEFAULT_PARAMS[0]).then(res => { + const request = spec.buildRequests(INSTREAM_DEFAULT_PARAMS, res); + expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('siteid').and.to.equal('1234'); + expect(requestContent).to.have.property('pageid').and.to.equal('5678'); + expect(requestContent).to.have.property('formatid').and.to.equal('90'); + expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestContent).to.have.property('bidfloor').and.to.equal(0.42); + expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid'); + expect(requestContent).to.have.property('tagId').and.to.equal('sas_42'); + expect(requestContent).to.not.have.property('pageDomain'); + expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + expect(requestContent).to.have.property('buid').and.to.equal('7569'); + expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); + expect(requestContent).to.have.property('ckid').and.to.equal(42); + expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience); + expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext); + expect(requestContent).to.have.property('isVideo').and.to.equal(true); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6); + expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(640); + expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(480); + setCurrencyConfig({}); + }); }); it('Verify instream parse response', function () { @@ -989,10 +993,8 @@ describe('Smart bid adapter tests', function () { }; it('Verify outstream video build request', function () { + setCurrencyConfig({ adServerCurrency: 'EUR' }); config.setConfig({ - 'currency': { - 'adServerCurrency': 'EUR' - }, ortb2: { 'user': { 'data': sellerDefinedAudience @@ -1004,29 +1006,33 @@ describe('Smart bid adapter tests', function () { } } }); - const request = spec.buildRequests(OUTSTREAM_DEFAULT_PARAMS); - expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); - expect(request[0]).to.have.property('method').and.to.equal('POST'); - const requestContent = JSON.parse(request[0].data); - expect(requestContent).to.have.property('siteid').and.to.equal('1234'); - expect(requestContent).to.have.property('pageid').and.to.equal('5678'); - expect(requestContent).to.have.property('formatid').and.to.equal('91'); - expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); - expect(requestContent).to.have.property('bidfloor').and.to.equal(0.43); - expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid-outstream'); - expect(requestContent).to.have.property('tagId').and.to.equal('sas_43'); - expect(requestContent).to.not.have.property('pageDomain'); - expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; - expect(requestContent).to.have.property('buid').and.to.equal('7579'); - expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); - expect(requestContent).to.have.property('ckid').and.to.equal(43); - expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience); - expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext); - expect(requestContent).to.have.property('isVideo').and.to.equal(false); - expect(requestContent).to.have.property('videoData'); - expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(7); - expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(800); - expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(600); + + return addFPDToBidderRequest(OUTSTREAM_DEFAULT_PARAMS[0]).then(res => { + const request = spec.buildRequests(OUTSTREAM_DEFAULT_PARAMS, res); + expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('siteid').and.to.equal('1234'); + expect(requestContent).to.have.property('pageid').and.to.equal('5678'); + expect(requestContent).to.have.property('formatid').and.to.equal('91'); + expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestContent).to.have.property('bidfloor').and.to.equal(0.43); + expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid-outstream'); + expect(requestContent).to.have.property('tagId').and.to.equal('sas_43'); + expect(requestContent).to.not.have.property('pageDomain'); + expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + expect(requestContent).to.have.property('buid').and.to.equal('7579'); + expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); + expect(requestContent).to.have.property('ckid').and.to.equal(43); + expect(requestContent).to.have.property('sda').and.to.deep.equal(sellerDefinedAudience); + expect(requestContent).to.have.property('sdc').and.to.deep.equal(sellerDefinedContext); + expect(requestContent).to.have.property('isVideo').and.to.equal(false); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(7); + expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(800); + expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(600); + setCurrencyConfig({}); + }); }); it('Verify outstream parse response', function () { diff --git a/test/spec/modules/sspBCBidAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js index 623faab5f1e..ceaad85faac 100644 --- a/test/spec/modules/sspBCBidAdapter_spec.js +++ b/test/spec/modules/sspBCBidAdapter_spec.js @@ -4,7 +4,8 @@ import * as utils from 'src/utils.js'; const BIDDER_CODE = 'sspBC'; const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; -const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync'; +const SYNC_URL_IFRAME = 'https://ssp.wp.pl/bidder/usersync'; +const SYNC_URL_IMAGE = 'https://ssp.wp.pl/v1/sync/pixel'; describe('SSPBC adapter', function () { function prepareTestData() { @@ -649,6 +650,25 @@ describe('SSPBC adapter', function () { expect(extAssets1).to.have.property('pbsize').that.equals('750x200_1') expect(extAssets2).to.have.property('pbsize').that.equals('750x200_1') }); + + it('should send supply chain data', function () { + const supplyChain = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'first-seller.com', + sid: '00001', + hp: 1 + }, + ] + } + const bidWithSupplyChain = Object.assign(bids[0], { schain: supplyChain }); + const requestWithSupplyChain = spec.buildRequests([bidWithSupplyChain], bidRequest); + const payloadWithSupplyChain = requestWithSupplyChain ? JSON.parse(requestWithSupplyChain.data) : { site: false, imp: false }; + + expect(payloadWithSupplyChain.source).to.have.property('schain').that.has.keys('ver', 'complete', 'nodes'); + }); }); describe('interpretResponse', function () { @@ -741,13 +761,18 @@ describe('SSPBC adapter', function () { let syncResultImage = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }); let syncResultNone = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: false }); - it('should provide correct url, if frame sync is allowed', function () { + it('should provide correct iframe url, if frame sync is allowed', function () { expect(syncResultAll).to.have.length(1); - expect(syncResultAll[0].url).to.have.string(SYNC_URL); + expect(syncResultAll[0].url).to.have.string(SYNC_URL_IFRAME); + }); + + it('should provide correct image url, if image sync is allowed', function () { + expect(syncResultImage).to.have.length(1); + expect(syncResultImage[0].url).to.have.string(SYNC_URL_IMAGE); }); - it('should send no syncs, if frame sync is not allowed', function () { - expect(syncResultImage).to.have.length(0); + it('should send no syncs, if no sync is allowed', function () { + expect(syncResultNone).to.have.length(0); expect(syncResultNone).to.have.length(0); }); }); diff --git a/test/spec/modules/stnBidAdapter_spec.js b/test/spec/modules/stnBidAdapter_spec.js index de851158ed0..98859385828 100644 --- a/test/spec/modules/stnBidAdapter_spec.js +++ b/test/spec/modules/stnBidAdapter_spec.js @@ -2,8 +2,9 @@ import { expect } from 'chai'; import { spec } from 'modules/stnBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes.js'; import * as utils from 'src/utils.js'; +import {decorateAdUnitsWithNativeParams} from '../../../src/native'; const ENDPOINT = 'https://hb.stngo.com/hb-multi'; const TEST_ENDPOINT = 'https://hb.stngo.com/hb-multi-test'; @@ -63,7 +64,6 @@ describe('stnAdapter', function () { 'plcmt': 1 } }, - 'vastXml': '"..."' }, { 'bidder': spec.code, @@ -80,6 +80,59 @@ describe('stnAdapter', function () { 'banner': { } }, + }, + { + 'bidder': spec.code, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'params': { + 'org': 'jdye8weeyirk00000001' + }, + 'bidId': '299ffc8cca0b87', + 'loop': 1, + 'bidderRequestId': '1144f487e563f9', + 'auctionId': 'bfc420c3-8577-4568-9766-a8a935fb620d', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ 300, 250 ] + ] + }, + 'video': { + 'playerSize': [[640, 480]], + 'context': 'instream', + 'plcmt': 1 + }, + 'native': { + 'ortb': { + 'assets': [ + { + 'id': 1, + 'required': 1, + 'img': { + 'type': 3, + 'w': 300, + 'h': 200, + } + }, + { + 'id': 2, + 'required': 1, + 'title': { + 'len': 80 + } + }, + { + 'id': 3, + 'required': 1, + 'data': { + 'type': 1 + } + } + ] + } + }, + }, 'ad': '""' } ]; @@ -151,10 +204,10 @@ describe('stnAdapter', function () { }); it('should send the correct mimes array', function () { - bidRequests[1].mediaTypes.banner.mimes = mimes; + bidRequests[0].mediaTypes.video.mimes = mimes; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[1].mimes).to.be.an('array'); - expect(request.data.bids[1].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); + expect(request.data.bids[0].mimes).to.be.an('array'); + expect(request.data.bids[0].mimes).to.eql(['application/javascript', 'video/mp4', 'video/quicktime']); }); it('should send the correct protocols array', function () { @@ -170,12 +223,21 @@ describe('stnAdapter', function () { expect(request.data.bids[0].sizes).to.equal(bidRequests[0].sizes) expect(request.data.bids[1].sizes).to.be.an('array'); expect(request.data.bids[1].sizes).to.equal(bidRequests[1].sizes) + expect(request.data.bids[2].sizes).to.be.an('array'); + expect(request.data.bids[2].sizes).to.eql(bidRequests[2].sizes) + }); + + it('should send nativeOrtbRequest in native bid request', function () { + decorateAdUnitsWithNativeParams(bidRequests) + const request = spec.buildRequests(bidRequests, bidderRequest); + assert.deepEqual(request.data.bids[2].nativeOrtbRequest, bidRequests[2].mediaTypes.native.ortb) }); it('should send the correct media type', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.bids[0].mediaType).to.equal(VIDEO) expect(request.data.bids[1].mediaType).to.equal(BANNER) + expect(request.data.bids[2].mediaType.split(',')).to.include.members([VIDEO, NATIVE, BANNER]) }); it('should respect syncEnabled option', function() { @@ -455,6 +517,28 @@ describe('stnAdapter', function () { adomain: ['abc.com'], mediaType: BANNER, nurl: 'http://example.com/win/1234', + }, + { + cpm: 12.5, + width: 300, + height: 200, + requestId: '21e12606d47ba7', + adomain: ['abc.com'], + creativeId: 'creative-id-3', + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + native: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg' + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } }] }; @@ -494,10 +578,42 @@ describe('stnAdapter', function () { ad: '""' }; + const expectedNativeResponse = { + requestId: '21e12606d47ba7', + cpm: 12.5, + currency: 'USD', + width: 300, + height: 200, + ttl: TTL, + creativeId: 'creative-id-3', + netRevenue: true, + nurl: 'http://example.com/win/1234', + mediaType: NATIVE, + meta: { + mediaType: NATIVE, + advertiserDomains: ['abc.com'] + }, + native: { + ortb: { + body: 'Advertise with Rise', + clickUrl: 'https://risecodes.com', + cta: 'Start now', + image: { + width: 300, + height: 200, + url: 'https://sdk.streamrail.com/media/rise-image.jpg', + }, + sponsoredBy: 'Rise', + title: 'Rise Ad Tech Solutions' + } + }, + }; + it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); expect(result[0]).to.deep.equal(expectedVideoResponse); expect(result[1]).to.deep.equal(expectedBannerResponse); + expect(result[2]).to.deep.equal(expectedNativeResponse); }); it('video type should have vastXml key', function () { @@ -509,6 +625,11 @@ describe('stnAdapter', function () { const result = spec.interpretResponse({ body: response }); expect(result[1].ad).to.equal(expectedBannerResponse.ad) }); + + it('native type should have native key', function () { + const result = spec.interpretResponse({ body: response }); + expect(result[2].native).to.eql(expectedNativeResponse.native) + }); }) describe('getUserSyncs', function() { diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index ec3ba4fdbed..440949aeb52 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -74,6 +74,14 @@ describe('symitriDapRtdProvider', function() { 'identity': sampleIdentity } + const sampleX2Config = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x2', + 'domain': 'prebid.org', + 'segtax': 708, + 'identity': sampleIdentity + } + const esampleConfig = { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', @@ -260,6 +268,34 @@ describe('symitriDapRtdProvider', function() { }); }); + describe('dapX2Tokenize', function () { + it('dapX2Tokenize error callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleX2Config)); + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(400, responseHeader, JSON.stringify('error')); + expect(submoduleCallback).to.equal(undefined); + }); + + it('dapX2Tokenize success callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleX2Config)); + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify('success')); + expect(submoduleCallback).to.equal(undefined); + }); + }); + describe('dapTokenize and dapMembership incorrect params', function () { it('Onerror and config are null', function () { expect(dapUtils.dapTokenize(null, 'identity', onDone, null, null)).to.be.equal(undefined); diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 5db0c8cf306..d71a701e93b 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -173,6 +173,11 @@ describe('Taboola Adapter', function () { page: 'https://example.com/ref', ref: 'https://ref', domain: 'example.com', + }, + ortb2: { + device: { + ua: navigator.userAgent, + }, } } @@ -198,9 +203,9 @@ describe('Taboola Adapter', function () { 'bidfloorcur': 'USD', 'ext': {} }], - id: 'mock-uuid', - 'test': 0, 'device': {'ua': navigator.userAgent}, + 'id': 'mock-uuid', + 'test': 0, 'user': { 'buyeruid': 0, 'ext': {}, @@ -366,6 +371,18 @@ describe('Taboola Adapter', function () { wlang: ['de'], user: { id: 'externalUserIdPassed' + }, + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4' } } } @@ -374,6 +391,7 @@ describe('Taboola Adapter', function () { expect(res.data.badv).to.deep.equal(bidderRequest.ortb2.badv) expect(res.data.wlang).to.deep.equal(bidderRequest.ortb2.wlang) expect(res.data.user.id).to.deep.equal(bidderRequest.ortb2.user.id) + expect(res.data.device).to.deep.equal(bidderRequest.ortb2.device); }); it('should pass user entities', function () { diff --git a/test/spec/modules/tagorasBidAdapter_spec.js b/test/spec/modules/tagorasBidAdapter_spec.js index 6ebe2896ffc..8d8c8cb62b7 100644 --- a/test/spec/modules/tagorasBidAdapter_spec.js +++ b/test/spec/modules/tagorasBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -117,24 +147,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } + 'device': ORTB2_DEVICE, } }; @@ -314,6 +327,7 @@ describe('TagorasBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), mediaTypes: { @@ -384,6 +398,7 @@ describe('TagorasBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/tncIdSystem_spec.js b/test/spec/modules/tncIdSystem_spec.js index 4626c940a59..9fbffdddd68 100644 --- a/test/spec/modules/tncIdSystem_spec.js +++ b/test/spec/modules/tncIdSystem_spec.js @@ -1,7 +1,6 @@ import { tncidSubModule } from 'modules/tncIdSystem'; -import {attachIdSystem} from '../../../modules/userId/index.js'; -import {createEidsArray} from '../../../modules/userId/eids.js'; -import {expect} from 'chai/index.mjs'; +import { attachIdSystem } from '../../../modules/userId/index.js'; +import { createEidsArray } from '../../../modules/userId/eids.js'; const consentData = { gdprApplies: true, @@ -40,39 +39,38 @@ describe('TNCID tests', function () { expect(res).to.be.undefined; }); - it('GDPR is OK and page has no TNC script on page, script goes in error, no TNCID is returned', function () { + it('Should NOT give TNCID if there is no TNC script on page and no fallback url in configuration', async function () { const completeCallback = sinon.spy(); const {callback} = tncidSubModule.getId({}, consentData); - return callback(completeCallback).then(() => { - expect(completeCallback.calledOnce).to.be.true; - }) + await callback(completeCallback); + expect(callback).to.be.an('function'); + expect(completeCallback.calledOnceWithExactly()).to.be.true; }); - it('GDPR is OK and page has TNC script with ns: __tnc, present TNCID is returned', function () { - Object.defineProperty(window, '__tnc', { - value: { - ready: (readyFunc) => { readyFunc() }, - tncid: 'TNCID_TEST_ID_1', - providerId: 'TEST_PROVIDER_ID_1', - }, - configurable: true - }); + it('Should NOT give TNCID if fallback script is not loaded correctly', async function () { + const completeCallback = sinon.spy(); + const {callback} = tncidSubModule.getId({ + params: { url: 'www.thenewco.tech' } + }, consentData); + await callback(completeCallback); + expect(completeCallback.calledOnceWithExactly()).to.be.true; + }); + + it(`Should call external script if TNC is not loaded on page`, async function() { const completeCallback = sinon.spy(); - const {callback} = tncidSubModule.getId({}, { gdprApplies: false }); + const {callback} = tncidSubModule.getId({params: {url: 'https://www.thenewco.tech?providerId=test'}}, { gdprApplies: false }); - return callback(completeCallback).then(() => { - expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_1')).to.be.true; - }) + await callback(completeCallback); + expect(window).to.contain.property('__tncPbjs'); }); - it('GDPR is OK and page has TNC script with ns: __tnc but not loaded, TNCID is assigned and returned', function () { + it('TNCID is returned if page has TNC script with ns: __tnc', async function () { Object.defineProperty(window, '__tnc', { value: { - ready: async (readyFunc) => { await readyFunc() }, + ready: (readyFunc) => { readyFunc() }, getTNCID: async (name) => { return 'TNCID_TEST_ID_1' }, - providerId: 'TEST_PROVIDER_ID_1', }, configurable: true }); @@ -80,17 +78,23 @@ describe('TNCID tests', function () { const completeCallback = sinon.spy(); const {callback} = tncidSubModule.getId({}, { gdprApplies: false }); - return callback(completeCallback).then(() => { - expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_1')).to.be.true; - }) + await callback(completeCallback); + expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_1')).to.be.true; }); - it('GDPR is OK and page has TNC script with ns: __tncPbjs, TNCID is returned', function () { + it('TNC script with ns __tncPbjs is created', async function () { + const completeCallback = sinon.spy(); + const {callback} = tncidSubModule.getId({params: {url: 'TEST_URL'}}, consentData); + + await callback(completeCallback); + expect(window).to.contain.property('__tncPbjs'); + }); + + it('TNCID is returned if page has TNC script with ns: __tncPbjs', async function () { Object.defineProperty(window, '__tncPbjs', { value: { - ready: async (readyFunc) => { await readyFunc() }, + ready: (readyFunc) => { readyFunc() }, getTNCID: async (name) => { return 'TNCID_TEST_ID_2' }, - providerId: 'TEST_PROVIDER_ID_1', options: {}, }, configurable: true, @@ -98,13 +102,13 @@ describe('TNCID tests', function () { }); const completeCallback = sinon.spy(); - const {callback} = tncidSubModule.getId({params: {url: 'TEST_URL'}}, consentData); + const {callback} = tncidSubModule.getId({params: {url: 'www.thenewco.tech'}}, consentData); - return callback(completeCallback).then(() => { - expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_2')).to.be.true; - }) + await callback(completeCallback); + expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_2')).to.be.true; }); }); + describe('eid', () => { before(() => { attachIdSystem(tncidSubModule); diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index c15a6d1d909..4ddac3261f1 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -94,6 +94,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -129,24 +159,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - }, + device: ORTB2_DEVICE, user: { data: [ { @@ -341,6 +354,7 @@ describe('TwistDigitalBidAdapter', function () { }, contentLang: 'en', coppa: 0, + device: ORTB2_DEVICE, contentData: [{ 'name': 'example.com', 'ext': { @@ -422,6 +436,7 @@ describe('TwistDigitalBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -510,6 +525,7 @@ describe('TwistDigitalBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/unicornBidAdapter_spec.js b/test/spec/modules/unicornBidAdapter_spec.js index bd9175dac1e..ffde4451bdb 100644 --- a/test/spec/modules/unicornBidAdapter_spec.js +++ b/test/spec/modules/unicornBidAdapter_spec.js @@ -462,7 +462,7 @@ const interpretedBids = [ ad: '
test
', ttl: 1000, creativeId: 'ABCDE', - netRevenue: false, + netRevenue: true, currency: 'JPY' }, { requestId: '31e2b28ced2475', @@ -472,7 +472,7 @@ const interpretedBids = [ ad: '
test
', ttl: 1000, creativeId: 'abcde', - netRevenue: false, + netRevenue: true, currency: 'JPY' }, { requestId: '40a333e047a9bd', @@ -482,7 +482,7 @@ const interpretedBids = [ ad: '
test
', ttl: 1000, creativeId: 'XYZXYZ', - netRevenue: false, + netRevenue: true, currency: 'JPY' } ]; diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index c4f333e56ac..066f00feb8f 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -456,14 +456,54 @@ describe('User ID', function () { {'mockId2v1': {source: 'mock2source', atype: 2, getEidExt: () => ({v: 1})}}), createMockIdSubmodule('mockId2v2', null, null, {'mockId2v2': {source: 'mock2source', atype: 2, getEidExt: () => ({v: 2})}}), + createMockIdSubmodule('mockId2v3', null, null, { + 'mockId2v3'(ids) { + return { + source: 'mock2source', + inserter: 'ins', + ext: {v: 2}, + uids: ids.map(id => ({id, atype: 2})) + } + } + }), + createMockIdSubmodule('mockId2v4', null, null, { + 'mockId2v4'(ids) { + return ids.map(id => ({ + uids: [{id, atype: 0}], + source: 'mock2source', + inserter: 'ins', + ext: {v: 2} + })) + } + }) + ]); + }); + + it('should filter out non-string uid returned by generator functions', () => { + const eids = createEidsArray({ + mockId2v3: [null, 'id1', 123], + }); + expect(eids[0].uids).to.eql([ + { + atype: 2, + id: 'id1' + } ]); }); - it('should group UIDs by source and ext', () => { + it('should filter out entire EID if none of the uids are strings', () => { + const eids = createEidsArray({ + mockId2v3: [null], + }); + expect(eids).to.eql([]); + }) + + it('should group UIDs by everything except uid', () => { const eids = createEidsArray({ mockId1: ['mock-1-1', 'mock-1-2'], mockId2v1: ['mock-2-1', 'mock-2-2'], - mockId2v2: ['mock-2-1', 'mock-2-2'] + mockId2v2: ['mock-2-1', 'mock-2-2'], + mockId2v3: ['mock-2-1', 'mock-2-2'] }); expect(eids).to.eql([ { @@ -510,10 +550,50 @@ describe('User ID', function () { atype: 2, } ] + }, + { + source: 'mock2source', + inserter: 'ins', + ext: {v: 2}, + uids: [ + { + id: 'mock-2-1', + atype: 2, + }, + { + id: 'mock-2-2', + atype: 2, + } + ] } ]) }); + it('should group matching EIDs regardless of entry order', () => { + const eids = createEidsArray({ + mockId2v3: ['id1', 'id2'], + mockId2v4: ['id3'] + }); + expect(eids).to.eql([{ + source: 'mock2source', + inserter: 'ins', + uids: [ + { + id: 'id1', + atype: 2, + }, + { + id: 'id2', + atype: 2 + }, + { + id: 'id3', + atype: 0 + } + ], + ext: {v: 2} + }]) + }) it('when merging with pubCommonId, should not alter its eids', () => { const uid = { pubProvidedId: [ @@ -705,6 +785,27 @@ describe('User ID', function () { }); }); + it('pbjs.getUserIdsAsEids should pass config to eid function', async function () { + const eidFn = sinon.stub(); + init(config); + setSubmoduleRegistry([createMockIdSubmodule('mockId', null, null, { + mockId: eidFn + })]); + const moduleConfig = { + name: 'mockId', + value: {mockId: 'mockIdValue'}, + some: 'config' + }; + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [moduleConfig] + } + }); + await getGlobal().getUserIdsAsync(); + sinon.assert.calledWith(eidFn, ['mockIdValue'], moduleConfig); + }) + it('pbjs.getUserIdsAsEids should prioritize user ids according to config available to core', () => { init(config); setSubmoduleRegistry([ diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 14a49b21cdc..01d7aa20e53 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -98,6 +98,36 @@ const VIDEO_BID = { } } +const ORTB2_DEVICE = { + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, +}; + const BIDDER_REQUEST = { 'gdprConsent': { 'consentString': 'consent_string', @@ -133,24 +163,7 @@ const BIDDER_REQUEST = { 'gpp_sid': [7], 'coppa': 0 }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - }, + device: ORTB2_DEVICE, user: { data: [ { @@ -346,6 +359,7 @@ describe('VidazooBidAdapter', function () { }, contentLang: 'en', coppa: 0, + device: ORTB2_DEVICE, contentData: [{ 'name': 'example.com', 'ext': { @@ -430,6 +444,7 @@ describe('VidazooBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, @@ -523,6 +538,7 @@ describe('VidazooBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + device: ORTB2_DEVICE, url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', cb: 1000, diff --git a/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js index 1a792411497..227e61494b6 100644 --- a/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js @@ -23,7 +23,7 @@ import sinon from 'sinon'; const {AdPlayerProProvider, utils} = require('modules/adplayerproVideoProvider.js'); const { - PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, VPAID_MIME_TYPE + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, VPAID_MIME_TYPE, PLCMT } = require('libraries/video/constants/ortb.js'); function getPlayerMock() { @@ -66,6 +66,8 @@ function getUtilsMock() { getPlacement: function () { }, getPlaybackMethod: function () { + }, + getPlcmt: function () { } }; } @@ -218,10 +220,12 @@ describe('AdPlayerProProvider', function () { const test_media_type = VIDEO_MIME_TYPE.MP4; const test_placement = PLACEMENT.ARTICLE; const test_playback_method = PLAYBACK_METHODS.CLICK_TO_PLAY; + const test_plcmt = PLCMT.OUTSTREAM; utilsMock.getSupportedMediaTypes = () => [test_media_type]; utilsMock.getPlacement = () => test_placement; utilsMock.getPlaybackMethod = () => test_playback_method; + utilsMock.getPlcmt = () => test_plcmt; const provider = AdPlayerProProvider(config, null, null, utilsMock); provider.init(); @@ -242,7 +246,8 @@ describe('AdPlayerProProvider', function () { expect(video.playbackmethod).to.include(test_playback_method); expect(video.playbackend).to.equal(1); expect(video.api).to.have.length(2); - expect(video.api).to.include.members([API_FRAMEWORKS.VPAID_2_0, API_FRAMEWORKS.OMID_1_0]); // + expect(video.api).to.include.members([API_FRAMEWORKS.VPAID_2_0, API_FRAMEWORKS.OMID_1_0]); + expect(video.plcmt).to.equal(test_plcmt); }); }); @@ -409,6 +414,22 @@ describe('AdPlayerProProvider utils', function () { test(true, false, PLAYBACK_METHODS.AUTOPLAY); test(true, true, PLAYBACK_METHODS.AUTOPLAY_MUTED); }); + + it('getPlcmt', function () { + function test(type, autoplay, muted, file, expected) { + expect(utils.getPlcmt({type, autoplay, muted, file})).to.be.equal(expected); + } + + test('inStream', false, false, 'f', PLCMT.INSTREAM); + test(undefined, false, false, 'f', PLCMT.INSTREAM); + test('inStream', false, true, 'f', PLCMT.INSTREAM); + test('inStream', true, false, 'f', PLCMT.INSTREAM); + test('inStream', true, true, 'f', PLCMT.ACCOMPANYING_CONTENT); + + test('rewarded', true, false, undefined, PLCMT.INTERSTITIAL); + test('inView', true, false, undefined, PLCMT.INTERSTITIAL); + test('InPage', true, false, undefined, PLCMT.OUTSTREAM); + }); }); describe('AdPlayerProProvider callbackStorageFactory', function () { diff --git a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js index 3cede6c8eda..1a9643c7d3d 100644 --- a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js @@ -26,6 +26,7 @@ function getPlayerMock() { getVolume: function () {}, getConfig: function () {}, getHeight: function () {}, + getContainer: function () {}, getWidth: function () {}, getFullscreen: function () {}, getPlaylistItem: function () {}, @@ -51,6 +52,9 @@ function makePlayerFactoryMock(playerMock_) { function getUtilsMock() { return { getJwConfig: function () {}, + getPlayerHeight: function () {}, + getPlayerWidth: function () {}, + getPlayerSizeFromAspectRatio: function () {}, getSupportedMediaTypes: function () {}, getStartDelay: function () {}, getPlacement: function () {}, @@ -212,6 +216,8 @@ describe('JWPlayerProvider', function () { player.getWidth = () => test_width; player.getFullscreen = () => true; // + utils.getPlayerHeight = () => 100; + utils.getPlayerWidth = () => 200; utils.getSupportedMediaTypes = () => [test_media_type]; utils.getStartDelay = () => test_start_delay; utils.getPlacement = () => test_placement; @@ -690,6 +696,81 @@ describe('utils', function () { expect(jwConfig.advertising).to.have.property('client', 'vast'); }); }); + + describe('getPlayerHeight', function () { + const getPlayerHeight = utils.getPlayerHeight; + + it('should return height from API when defined', function () { + const expectedHeight = 500; + const playerMock = { getHeight: () => expectedHeight }; + expect(getPlayerHeight(playerMock, {})).to.equal(expectedHeight); + }); + + it('should return height from config when API returns undefined', function () { + const expectedHeight = 500; + const playerMock = { getHeight: () => undefined }; + expect(getPlayerHeight(playerMock, { height: 500 })).to.equal(expectedHeight); + }); + }); + + describe('getPlayerWidth', function () { + const getPlayerWidth = utils.getPlayerWidth; + + it('should return width from API when defined', function () { + const expectedWidth = 1000; + const playerMock = { getWidth: () => expectedWidth }; + expect(getPlayerWidth(playerMock, {})).to.equal(expectedWidth); + }); + + it('should return width from config when API returns undefined', function () { + const expectedWidth = 1000; + const playerMock = { getWidth: () => undefined }; + expect(getPlayerWidth(playerMock, { width: expectedWidth })).to.equal(expectedWidth); + }); + + it('should return undefined when width is string', function () { + const playerMock = { getWidth: () => undefined }; + expect(getPlayerWidth(playerMock, { width: '50%' })).to.be.undefined; + }); + }); + + describe('getPlayerSizeFromAspectRatio', function () { + const getPlayerSizeFromAspectRatio = utils.getPlayerSizeFromAspectRatio; + const testContainer = { + clientWidth: 640, + clientHeight: 480 + }; + + it('should return an empty object when width and aspectratio are not strings', function () { + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {width: 100})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '1:2', width: 100})).to.deep.equal({}); + }); + + it('should return an empty object when aspectratio is malformed', function () { + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '0.5', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '1-2', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '1:', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: ':2', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: ':', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '1:2:3', width: '100%'})).to.deep.equal({}); + }); + + it('should return an empty object when player container cannot be obtained', function () { + expect(getPlayerSizeFromAspectRatio({}, {aspectratio: '1:2', width: '100%'})).to.deep.equal({}); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => undefined }, {aspectratio: '1:2', width: '100%'})).to.deep.equal({}); + }); + + it('should calculate the size given the width percentage and aspect ratio', function () { + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '2:1', width: '100%'})).to.deep.equal({ height: 320, width: 640 }); + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '4:1', width: '70%'})).to.deep.equal({ height: 112, width: 448 }); + }); + + it('should return the container height when smaller than the calculated height', function () { + expect(getPlayerSizeFromAspectRatio({ getContainer: () => testContainer }, {aspectratio: '1:1', width: '100%'})).to.deep.equal({ height: 480, width: 640 }); + }); + }); + describe('getSkipParams', function () { const getSkipParams = utils.getSkipParams; diff --git a/test/spec/modules/viewdeosDXBidAdapter_spec.js b/test/spec/modules/viewdeosDXBidAdapter_spec.js index 31df9244ada..b60037aab4a 100644 --- a/test/spec/modules/viewdeosDXBidAdapter_spec.js +++ b/test/spec/modules/viewdeosDXBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from 'modules/viewdeosDXBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import {cloneDeep} from 'lodash'; const ENDPOINT = 'https://ghb.sync.viewdeos.com/auction/'; @@ -176,13 +177,15 @@ describe('viewdeosDXBidAdapter', function () { describe('user syncs with both types', function () { it('should be returned if pixel and iframe enabled', function () { + const mockedServerResponse = cloneDeep(SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS); + mockedServerResponse.cookieURLs = ['link7', 'link8']; const syncs = spec.getUserSyncs({ iframeEnabled: true, - pixelEnabled: true - }, [{body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS}]); + pixelEnabled: true, + }, [{body: mockedServerResponse}]); - expect(syncs.map(s => s.url)).to.deep.equal(SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs); - expect(syncs.map(s => s.type)).to.deep.equal(SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLSTypes); + expect(syncs.map(s => s.url)).to.deep.equal(mockedServerResponse.cookieURLs); + expect(syncs.map(s => s.type)).to.deep.equal(mockedServerResponse.cookieURLSTypes); }) }) diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index db928e4d802..923ff7e86b2 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -5,6 +5,8 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; import * as utils from 'src/utils.js'; import { makeSlot } from '../integration/faker/googletag.js'; import { mergeDeep } from '../../../src/utils.js'; +import { setConfig as setCurrencyConfig } from '../../../modules/currency.js'; +import { addFPDToBidderRequest } from '../../helpers/fpd.js'; describe('VisxAdapter', function () { const adapter = newBidder(spec); @@ -355,8 +357,7 @@ describe('VisxAdapter', function () { }); it('should add currency from currency.bidderCurrencyDefault', function () { - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'currency.bidderCurrencyDefault.visx' ? 'GBP' : 'USD'); + config.setConfig({currency: {bidderCurrencyDefault: {visx: 'GBP'}}}) const request = spec.buildRequests(bidRequests, bidderRequest); const payload = parseRequest(request.url); expect(payload).to.be.an('object'); @@ -410,66 +411,22 @@ describe('VisxAdapter', function () { } }); - getConfigStub.restore(); + config.resetConfig(); }); it('should add currency from currency.adServerCurrency', function () { - const getConfigStub = sinon.stub(config, 'getConfig').callsFake( - arg => arg === 'currency.bidderCurrencyDefault.visx' ? '' : 'USD'); - const request = spec.buildRequests(bidRequests, bidderRequest); - const payload = parseRequest(request.url); - expect(payload).to.be.an('object'); - expect(payload).to.have.property('auids', '903535,903535,903536,903537'); - - const postData = request.data; - expect(postData).to.be.an('object'); - expect(postData).to.deep.equal({ - 'id': '22edbae2733bf6', - 'imp': expectedFullImps, - 'tmax': 3000, - 'cur': ['USD'], - 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': { - 'domain': 'localhost:9999', - 'publisher': { - 'domain': 'localhost:9999' - }, - 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' - }, - 'device': { - 'w': 1259, - 'h': 934, - 'dnt': 0, - 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', - 'language': 'tr', - 'sua': { - 'source': 1, - 'platform': { - 'brand': 'macOS' - }, - 'browsers': [ - { - 'brand': 'Chromium', - 'version': [ '124' ] - }, - { - 'brand': 'Google Chrome', - 'version': [ '124' ] - }, - { - 'brand': 'Not-A.Brand', - 'version': [ '99' ] - } - ], - 'mobile': 0 - }, - 'ext': { - 'cdep': 'treatment_1.1' - } - }, + setCurrencyConfig({ adServerCurrency: 'USD' }) + return addFPDToBidderRequest(bidderRequest).then(res => { + const request = spec.buildRequests(bidRequests, res); + const payload = parseRequest(request.url); + expect(payload).to.be.an('object'); + expect(payload).to.have.property('auids', '903535,903535,903536,903537'); + + const postData = request.data; + expect(postData).to.be.an('object'); + expect(postData.cur).to.deep.equal(['USD']); + setCurrencyConfig({}) }); - - getConfigStub.restore(); }); it('if gdprConsent is present payload must have gdpr params', function () { diff --git a/test/spec/modules/voxBidAdapter_spec.js b/test/spec/modules/voxBidAdapter_spec.js index 5f4ada06c65..65752837b23 100644 --- a/test/spec/modules/voxBidAdapter_spec.js +++ b/test/spec/modules/voxBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai' import { spec } from 'modules/voxBidAdapter.js' -import {config} from 'src/config.js' +import { setConfig as setCurrencyConfig } from '../../../modules/currency' +import { addFPDToBidderRequest } from '../../helpers/fpd' function getSlotConfigs(mediaTypes, params) { return { @@ -247,14 +248,17 @@ describe('VOX Adapter', function() { }) it('should request floor price in adserver currency', function () { - const configCurrency = 'DKK' - config.setConfig({ currency: { adServerCurrency: configCurrency } }) - const request = spec.buildRequests([ getBidWithFloor() ], bidderRequest) - const data = JSON.parse(request.data) - data.bidRequests.forEach(bid => { - expect(bid.floorInfo.currency).to.equal(configCurrency) - }) - }) + const configCurrency = 'DKK'; + setCurrencyConfig({ adServerCurrency: configCurrency }); + return addFPDToBidderRequest(bidderRequest).then(res => { + const request = spec.buildRequests([ getBidWithFloor() ], res) + const data = JSON.parse(request.data) + data.bidRequests.forEach(bid => { + expect(bid.floorInfo.currency).to.equal(configCurrency) + }) + setCurrencyConfig({}); + }); + }); function getBidWithFloor(floor) { return { diff --git a/test/spec/modules/wurflRtdProvider_spec.js b/test/spec/modules/wurflRtdProvider_spec.js index 0ac324ef8b2..a683be5304c 100644 --- a/test/spec/modules/wurflRtdProvider_spec.js +++ b/test/spec/modules/wurflRtdProvider_spec.js @@ -52,7 +52,8 @@ describe('wurflRtdProvider', function () { pixel_density: 443, pointing_method: 'touchscreen', resolution_height: 1920, - resolution_width: 1080 + resolution_width: 1080, + wurfl_id: 'lg_nexus5_ver1', }; // expected analytics values @@ -100,6 +101,7 @@ describe('wurflRtdProvider', function () { const expectedURL = new URL(altHost); expectedURL.searchParams.set('debug', true); expectedURL.searchParams.set('mode', 'prebid'); + expectedURL.searchParams.set('wurfl_id', true); const callback = () => { const v = { @@ -147,7 +149,8 @@ describe('wurflRtdProvider', function () { pixel_density: 443, pointing_method: 'touchscreen', resolution_height: 1920, - resolution_width: 1080 + resolution_width: 1080, + wurfl_id: 'lg_nexus5_ver1', }, }, }, @@ -187,7 +190,8 @@ describe('wurflRtdProvider', function () { model_name: 'Nexus 5', pixel_density: 443, resolution_height: 1920, - resolution_width: 1080 + resolution_width: 1080, + wurfl_id: 'lg_nexus5_ver1', }, }, }, @@ -203,6 +207,7 @@ describe('wurflRtdProvider', function () { is_mobile: !0, model_name: 'Nexus 5', brand_name: 'Google', + wurfl_id: 'lg_nexus5_ver1', }, }, }, diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index 140be4121ec..e6b2f5cc1f0 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -1,8 +1,9 @@ import { assert, expect } from 'chai'; import { NATIVE_ASSETS, spec } from 'modules/yandexBidAdapter.js'; import * as utils from 'src/utils.js'; -import { config } from '../../../src/config'; +import { setConfig as setCurrencyConfig } from '../../../modules/currency'; import { BANNER, NATIVE } from '../../../src/mediaTypes'; +import { addFPDToBidderRequest } from '../../helpers/fpd'; describe('Yandex adapter', function () { describe('isBidRequestValid', function () { @@ -125,19 +126,21 @@ describe('Yandex adapter', function () { }); it('should send currency if defined', function () { - config.setConfig({ - currency: { - adServerCurrency: 'USD' - } + setCurrencyConfig({ + adServerCurrency: 'USD' }); const bannerRequest = getBidRequest(); - const requests = spec.buildRequests([bannerRequest], bidderRequest); - const { url } = requests[0]; - const parsedRequestUrl = utils.parseUrl(url); - const { search: query } = parsedRequestUrl - expect(query['ssp-cur']).to.equal('USD'); + return addFPDToBidderRequest(bidderRequest).then(res => { + const requests = spec.buildRequests([bannerRequest], res); + const { url } = requests[0]; + const parsedRequestUrl = utils.parseUrl(url); + const { search: query } = parsedRequestUrl + + expect(query['ssp-cur']).to.equal('USD'); + setCurrencyConfig({}); + }); }); it('should send eids and ortb2 user data if defined', function() { diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 25d8f2baec0..92fd20fb37d 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -263,7 +263,7 @@ const REQPARAMS = { const REQPARAMS_GDPR = Object.assign({}, REQPARAMS, { gdpr: true, - consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', + gdpr_consent: 'BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA', }); const REQPARAMS_IAB_CONTENT = Object.assign({}, REQPARAMS, { @@ -457,8 +457,8 @@ describe('yieldlabBidAdapter', () => { }, }); - expect(gdprRequest.url).to.include('consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); - expect(gdprRequest.url).to.include('gdpr=true'); + expect(gdprRequest.url).to.include('&gdpr_consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); + expect(gdprRequest.url).to.include('&gdpr=true'); }); describe('sizes handling', () => { @@ -691,7 +691,7 @@ describe('yieldlabBidAdapter', () => { const result = spec.interpretResponse({body: [RESPONSE]}, {validBidRequests: [bidRequest], queryParams: REQPARAMS_GDPR}); expect(result[0].ad).to.include('&gdpr=true'); - expect(result[0].ad).to.include('&consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); + expect(result[0].ad).to.include('&gdpr_consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); }); it('should append iab_content to adtag', () => { @@ -814,7 +814,7 @@ describe('yieldlabBidAdapter', () => { const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST()], queryParams: REQPARAMS_GDPR}); expect(result[0].vastUrl).to.include('&gdpr=true'); - expect(result[0].vastUrl).to.include('&consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); + expect(result[0].vastUrl).to.include('&gdpr_consent=BN5lERiOMYEdiAKAWXEND1AAAAE6DABACMA'); }); it('should add renderer if outstream context', () => { diff --git a/test/spec/modules/yieldoneBidAdapter_spec.js b/test/spec/modules/yieldoneBidAdapter_spec.js index 4664f77216d..983f67bcdd6 100644 --- a/test/spec/modules/yieldoneBidAdapter_spec.js +++ b/test/spec/modules/yieldoneBidAdapter_spec.js @@ -1,6 +1,8 @@ import { expect } from 'chai'; import { spec } from 'modules/yieldoneBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { getBrowser, getOS } from '../../../libraries/userAgentUtils/index.js'; +import { browserTypes, osTypes } from '../../../libraries/userAgentUtils/userAgentTypes.enums.js'; const ENDPOINT = 'https://y.one.impact-ad.jp/h_bid'; const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync'; @@ -8,7 +10,7 @@ const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/d const DEFAULT_VIDEO_SIZE = {w: 640, h: 360}; -describe('yieldoneBidAdapter', function() { +describe('yieldoneBidAdapter', function () { const adapter = newBidder(spec); describe('isBidRequestValid', function () { @@ -638,12 +640,25 @@ describe('yieldoneBidAdapter', function() { expect(spec.getUserSyncs({})).to.be.undefined; }); - it('should return a sync url if iframe syncs are enabled', function () { - expect(spec.getUserSyncs({ + it('should return a sync url if iframe syncs are enabled and UserAgent is not Safari or iOS', function () { + const result = spec.getUserSyncs({ 'iframeEnabled': true - })).to.deep.equal([{ - type: 'iframe', url: USER_SYNC_URL - }]); + }); + + if (getBrowser() === browserTypes.SAFARI || getOS() === osTypes.IOS) { + expect(result).to.be.undefined; + } else { + expect(result).to.deep.equal([{ + type: 'iframe', url: USER_SYNC_URL + }]); + } + }); + + it('should skip sync request in case GDPR applies', function () { + expect(spec.getUserSyncs({'iframeEnabled': true}, [], { + consentString: 'GDPR_CONSENT_STRING', + gdprApplies: true, + })).to.be.undefined; }); }); }); diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index a308eb44987..04554df560e 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -529,6 +529,7 @@ describe('Zeta Global SSP Analytics Adapter', function () { sandbox = sinon.sandbox.create(); requests = server.requests; sandbox.stub(events, 'getEvents').returns([]); + config.setConfig({ pageUrl: 'https://www.config.domain.com/index.html' }) }); afterEach(function () { @@ -624,8 +625,8 @@ describe('Zeta Global SSP Analytics Adapter', function () { shortname: 'name' } }); - expect(auctionSucceeded.domain).to.eql('test-zeta-ssp.net'); - expect(auctionSucceeded.page).to.eql('test-zeta-ssp.net/zeta-ssp/ssp/_dev/examples/page_banner.html'); + expect(auctionSucceeded.domain).to.eql('config.domain.com'); + expect(auctionSucceeded.page).to.eql('https://www.config.domain.com/index.html'); expect(auctionSucceeded.bid).to.be.deep.equal({ adUnitCode: '/19968336/header-bid-tag-0', adId: '5759bb3ef7be1e8', diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 877306c1c04..7ea9b5949fe 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -22,7 +22,7 @@ import { convertOrtbRequestToProprietaryNative, fromOrtbNativeRequest } from '.. import {auctionManager} from '../../src/auctionManager.js'; import {getRenderingData} from '../../src/adRendering.js'; import {getCreativeRendererSource} from '../../src/creativeRenderers.js'; -import {deepClone, deepSetValue} from '../../src/utils.js'; +import {deepSetValue} from '../../src/utils.js'; const utils = require('src/utils'); const bid = { @@ -402,7 +402,8 @@ describe('native.js', function () { 'returns native data': { renderDataHook(next, bidResponse) { next.bail({ - native: getNativeRenderingData(bidResponse, adUnit) + native: getNativeRenderingData(bidResponse, adUnit), + rendererVersion: 'native-render-version' }); }, renderSourceHook(next) { @@ -433,8 +434,9 @@ describe('native.js', function () { function checkRenderer(message) { if (withRenderer) { expect(message.renderer).to.eql('mock-native-renderer') + expect(message.rendererVersion).to.eql('native-render-version'); Object.entries(message).forEach(([key, val]) => { - if (!['native', 'adId', 'message', 'assets', 'renderer'].includes(key)) { + if (!['native', 'adId', 'message', 'assets', 'renderer', 'rendererVersion'].includes(key)) { expect(message.native[key]).to.eql(val); } }) diff --git a/test/spec/unit/adUnits_spec.js b/test/spec/unit/adUnits_spec.js index 01c5bc49f3f..a1215ba3f52 100644 --- a/test/spec/unit/adUnits_spec.js +++ b/test/spec/unit/adUnits_spec.js @@ -61,4 +61,17 @@ describe('Adunit Counter', function () { adunitCounter.incrementBidderWinsCounter(adCode, BIDDER_ID_2); expect(adunitCounter.getBidderWinsCounter(adCode, BIDDER_ID_2)).to.be.equal(1); }); + it('increments and checks auctions counter of adunit 1', function () { + adunitCounter.incrementAuctionsCounter(ADUNIT_ID_1); + expect(adunitCounter.getAuctionsCounter(ADUNIT_ID_1)).to.be.equal(1); + }); + it('increments and checks auctions counter of adunit 2', function () { + adunitCounter.incrementAuctionsCounter(ADUNIT_ID_2); + expect(adunitCounter.getAuctionsCounter(ADUNIT_ID_2)).to.be.equal(1); + }); + it('increments and checks auctions counter if adUnitCode has a dots in it', function () { + const adUnitCode = 'adunit.1' + adunitCounter.incrementAuctionsCounter(adUnitCode); + expect(adunitCounter.getAuctionsCounter(adUnitCode)).to.be.equal(1); + }); }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 852c84263e9..1aacc518190 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -10,7 +10,8 @@ import { getAdUnits, getServerTestingConfig, getServerTestingsAds, - getBidRequests + getBidRequests, + getTwinAdUnits } from 'test/fixtures/fixtures.js'; import { EVENTS, S2S } from 'src/constants.js'; import * as utils from 'src/utils.js'; @@ -1739,13 +1740,14 @@ describe('adapterManager tests', function () { }); describe('makeBidRequests', function () { - let adUnits; + let adUnits, twinAdUnits; beforeEach(function () { resetAdUnitCounters(); adUnits = utils.deepClone(getAdUnits()).map(adUnit => { adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'rubicon'], bid.bidder)); return adUnit; }) + twinAdUnits = getTwinAdUnits(); }); function makeBidRequests(au = adUnits) { @@ -1862,6 +1864,31 @@ describe('adapterManager tests', function () { }) }); + describe('adUnitAuctionsCounter', () => { + it('should set and increment auctionsCount at adUnitCode level', () => { + const [au1, au2] = adUnits; + makeBidRequests([au1]).flatMap(br => br.bids).forEach(bid => { + expect(bid.auctionsCount).to.eql(1); + }); + makeBidRequests([au1]).flatMap(br => br.bids).forEach(bid => { + expect(bid.auctionsCount).to.eql(2); + }); + makeBidRequests([au1, au2]).flatMap(br => br.bids).forEach(bid => { + expect(bid.auctionsCount).to.eql(bid.adUnitCode === au1.code ? 3 : 1); + }); + }); + + it('should increment the auctionsCount of each adUnitCode exactly once per auction for twin ad units', () => { + const [au1, au2] = twinAdUnits; + makeBidRequests([au1, au2]).flatMap(br => br.bids).forEach(bid => { + expect(bid.auctionsCount).to.eql(1); + }); + makeBidRequests([au1, au2]).flatMap(br => br.bids).forEach(bid => { + expect(bid.auctionsCount).to.eql(2); + }); + }); + }); + describe('and activity controls', () => { let redactOrtb2; let redactBidRequest; diff --git a/test/spec/unit/core/ajax_spec.js b/test/spec/unit/core/ajax_spec.js index 8140123d9fc..a7ecf88d14d 100644 --- a/test/spec/unit/core/ajax_spec.js +++ b/test/spec/unit/core/ajax_spec.js @@ -185,28 +185,30 @@ describe('toFetchRequest', () => { }); }); - describe('browsingTopics', () => { - Object.entries({ - 'browsingTopics = true': [{browsingTopics: true}, true], - 'browsingTopics = false': [{browsingTopics: false}, false], - 'browsingTopics is undef': [{}, false] - }).forEach(([t, [opts, shouldBeSet]]) => { - describe(`when options has ${t}`, () => { - const sandbox = sinon.createSandbox(); - afterEach(() => { - sandbox.restore(); - }); + describe('chrome options', () => { + ['browsingTopics', 'adAuctionHeaders'].forEach(option => { + Object.entries({ + [`${option} = true`]: [{[option]: true}, true], + [`${option} = false`]: [{[option]: false}, false], + [`${option} undef`]: [{}, false] + }).forEach(([t, [opts, shouldBeSet]]) => { + describe(`when options has ${t}`, () => { + const sandbox = sinon.createSandbox(); + afterEach(() => { + sandbox.restore(); + }); - it(`should ${!shouldBeSet ? 'not ' : ''}be set when in a secure context`, () => { - sandbox.stub(window, 'isSecureContext').get(() => true); - toFetchRequest(EXAMPLE_URL, null, opts); - sinon.assert.calledWithMatch(dep.makeRequest, sinon.match.any, {browsingTopics: shouldBeSet ? true : undefined}); - }); - it(`should not be set when not in a secure context`, () => { - sandbox.stub(window, 'isSecureContext').get(() => false); - toFetchRequest(EXAMPLE_URL, null, opts); - sinon.assert.calledWithMatch(dep.makeRequest, sinon.match.any, {browsingTopics: undefined}); - }); + it(`should ${!shouldBeSet ? 'not ' : ''}be set when in a secure context`, () => { + sandbox.stub(window, 'isSecureContext').get(() => true); + toFetchRequest(EXAMPLE_URL, null, opts); + sinon.assert.calledWithMatch(dep.makeRequest, sinon.match.any, {[option]: shouldBeSet ? true : undefined}); + }); + it(`should not be set when not in a secure context`, () => { + sandbox.stub(window, 'isSecureContext').get(() => false); + toFetchRequest(EXAMPLE_URL, null, opts); + sinon.assert.calledWithMatch(dep.makeRequest, sinon.match.any, {[option]: undefined}); + }); + }) }) }) }) diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 1aab16a5e46..036e45d9b86 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -91,53 +91,130 @@ describe('bidderFactory', () => { sandbox.restore(); }); - it('should let registerSyncs run with invalid alias and aliasSync enabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: true - } - }); - spec.code = 'fakeBidder'; - const bidder = newBidder(spec); - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); - }); - - it('should let registerSyncs run with valid alias and aliasSync enabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: true + describe('user syncs', () => { + [ + { + t: 'invalid alias, aliasSync enabled', + alias: false, + aliasSyncEnabled: true, + shouldRegister: true + }, + { + t: 'valid alias, aliasSync enabled', + alias: true, + aliasSyncEnabled: true, + shouldRegister: true + }, + { + t: 'invalid alias, aliasSync disabled', + alias: false, + aliasSyncEnabled: false, + shouldRegister: true, + }, + { + t: 'valid alias, aliasSync disabled', + alias: true, + aliasSyncEnabled: false, + shouldRegister: false } + ].forEach(({t, alias, aliasSyncEnabled, shouldRegister}) => { + describe(t, () => { + it(shouldRegister ? 'should register sync' : 'should NOT register sync', () => { + config.setConfig({ + userSync: { + aliasSyncEnabled + } + }); + spec.code = 'someBidder'; + if (alias) { + aliasRegistry[spec.code] = 'original'; + } + const bidder = newBidder(spec); + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + if (shouldRegister) { + sinon.assert.called(spec.getUserSyncs); + } else { + sinon.assert.notCalled(spec.getUserSyncs); + } + }); + }); }); - spec.code = 'aliasBidder'; - const bidder = newBidder(spec); - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); - }); - it('should let registerSyncs run with invalid alias and aliasSync disabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: false - } - }); - spec.code = 'fakeBidder'; - const bidder = newBidder(spec); - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); - }); + describe('getUserSyncs syncOptions', () => { + [ + { + t: 'all image allowed, specific bidder denied iframe', + userSync: { + syncEnabled: true, + pixelEnabled: true, + iframeEnabled: true, + filterSettings: { + image: { + bidders: '*', + filter: 'include' + }, + iframe: { + bidders: ['bidderB'], + filter: 'include' + } + } + }, + expected: { + bidderA: { + iframeEnabled: false, + pixelEnabled: true + }, + bidderB: { + iframeEnabled: true, + pixelEnabled: true, + } + } + }, + { + t: 'specific bidders allowed specific methods', + userSync: { + syncEnabled: true, + pixelEnabled: true, + iframeEnabled: true, + filterSettings: { + image: { + bidders: ['bidderA'], + filter: 'include' + }, + iframe: { + bidders: ['bidderB'], + filter: 'include' + } + }, + }, + expected: { + bidderA: { + iframeEnabled: false, + pixelEnabled: true + }, + bidderB: { + iframeEnabled: true, + pixelEnabled: false, + } + } + } + ].forEach(({t, userSync, expected}) => { + describe(`when ${t}`, () => { + beforeEach(() => { + config.setConfig({userSync}); + }); - it('should not let registerSyncs run with valid alias and aliasSync disabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: false - } - }); - spec.code = 'aliasBidder'; - const bidder = newBidder(spec); - aliasRegistry = {[spec.code]: CODE}; - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(false); + Object.entries(expected).forEach(([bidderCode, syncOptions]) => { + it(`should pass ${JSON.stringify(syncOptions)} to ${bidderCode}`, () => { + spec.code = bidderCode; + const bidder = newBidder(spec); + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + sinon.assert.calledWith(spec.getUserSyncs, syncOptions); + }) + }) + }) + }) + }) }); describe('transaction IDs', () => { diff --git a/test/spec/unit/core/bidderSettings_spec.js b/test/spec/unit/core/bidderSettings_spec.js index ece18040d1e..7e6446b456b 100644 --- a/test/spec/unit/core/bidderSettings_spec.js +++ b/test/spec/unit/core/bidderSettings_spec.js @@ -19,6 +19,13 @@ describe('ScopedSettings', () => { expect(settings.get('scope', 'key')).to.equal('value'); }); + it('can retrieve nested settings', () => { + data = { + scope: {outer: {key: 'value'}} + } + expect(settings.get('scope', 'outer.key')).to.equal('value'); + }) + it('should fallback to fallback scope', () => { data = { fallback: { diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index f3d0c6df442..2296379ca43 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -861,6 +861,51 @@ describe('targeting tests', function () { }); }); + describe('targetingControls.alwaysIncludeDeals with enableSendAllBids', function () { + beforeEach(function() { + enableSendAllBids = true; + }); + + it('includes bids w/o deal when enableSendAllBids and alwaysIncludeDeals set to true', function () { + config.setConfig({ + enableSendAllBids: true, + targetingControls: { + alwaysIncludeDeals: true + } + }); + + let bid5 = utils.deepClone(bid1); + bid5.adserverTargeting = { + hb_pb: '3.0', + hb_adid: '111111', + hb_bidder: 'pubmatic', + foobar: '300x250' + }; + bid5.bidder = bid5.bidderCode = 'pubmatic'; + bid5.cpm = 3.0; // winning bid! + delete bid5.dealId; // no deal with winner + bidsReceived.push(bid5); + + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + + // Pubmatic wins but no deal. But enableSendAllBids is true. + // So Pubmatic is passed through + expect(targeting['/123456/header-bid-tag-0']).to.deep.equal({ + 'hb_bidder': 'pubmatic', + 'hb_adid': '111111', + 'hb_pb': '3.0', + 'foobar': '300x250', + 'hb_pb_pubmatic': '3.0', + 'hb_adid_pubmatic': '111111', + 'hb_bidder_pubmatic': 'pubmatic', + 'hb_deal_rubicon': '1234', + 'hb_pb_rubicon': '0.53', + 'hb_adid_rubicon': '148018fe5e', + 'hb_bidder_rubicon': 'rubicon' + }); + }); + }); + it('selects the top bid when enableSendAllBids true', function () { enableSendAllBids = true; let targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index e1f5b3b5b88..cec5fd7914c 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1839,6 +1839,29 @@ describe('Unit: Prebid Module', function () { })); }); + it('that cannot alter global config', () => { + configObj.setConfig({ortb2: {value: 'old'}}); + startAuctionStub.callsFake(({ortb2Fragments}) => { + ortb2Fragments.global.value = 'new' + }); + $$PREBID_GLOBAL$$.requestBids({ortb2: auctionFPD}); + expect(configObj.getAnyConfig('ortb2').value).to.eql('old'); + }); + + it('that cannot alter bidder config', () => { + configObj.setBidderConfig({ + bidders: ['mockBidder'], + config: { + ortb2: {value: 'old'} + } + }) + startAuctionStub.callsFake(({ortb2Fragments}) => { + ortb2Fragments.bidder.mockBidder.value = 'new'; + }) + $$PREBID_GLOBAL$$.requestBids({ortb2: auctionFPD}); + expect(configObj.getBidderConfig().mockBidder.ortb2.value).to.eql('old'); + }) + it('enriched through enrichFPD', () => { function enrich(next, fpd) { next.bail(fpd.then(ortb2 => { diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 18955771049..0d2a32659e9 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -2,6 +2,7 @@ import {fillVideoDefaults, isValidVideoBid, validateOrtbVideoFields} from 'src/v import {hook} from '../../src/hook.js'; import {stubAuctionIndex} from '../helpers/indexStub.js'; import * as utils from '../../src/utils.js'; +import { syncOrtb2 } from '../../src/prebid.js'; describe('video.js', function () { let sandbox; @@ -274,4 +275,207 @@ describe('video.js', function () { expect(valid).to.equal(false); }); }) + + describe('syncOrtb2', () => { + if (!FEATURES.VIDEO) { + return; + } + + let logWarnSpy; + + beforeEach(function () { + logWarnSpy = sinon.spy(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logWarn.restore(); + }); + + it('should properly sync fields if both present', () => { + const adUnit = { + mediaTypes: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, // should be overwritten with value from ortb2Imp + w: 100, + h: 200, + foo: 'omitted_value' // should be omitted during copying - not part of video obj spec + } + }, + ortb2Imp: { + video: { + minbitrate: 10, + maxbitrate: 50, + delivery: [1, 2, 3, 4], + linearity: 10, + bar: 'omitted_value' // should be omitted during copying - not part of video obj spec + } + } + }; + + const expected = { + mediaTypes: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + w: 100, + h: 200, + minbitrate: 10, + maxbitrate: 50, + delivery: [1, 2, 3, 4], + linearity: 10, + foo: 'omitted_value' + } + }, + ortb2Imp: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + w: 100, + h: 200, + minbitrate: 10, + maxbitrate: 50, + delivery: [1, 2, 3, 4], + linearity: 10, + bar: 'omitted_value' + } + } + }; + + syncOrtb2(adUnit, 'video'); + expect(adUnit).to.deep.eql(expected); + + assert.ok(logWarnSpy.calledOnce, 'expected warning was logged due to conflicting linearity'); + }); + + it('should omit sync if video mediaType not present on adUnit', () => { + const adUnit = { + mediaTypes: { + native: { + fieldToOmit: 'omitted_value' + } + }, + ortb2Imp: { + native: { + fieldToOmit2: 'omitted_value' + } + } + }; + + syncOrtb2(adUnit, 'video'); + + expect(adUnit.mediaTypes.video).to.be.undefined; + expect(adUnit.ortb2Imp.video).to.be.undefined; + }); + + it('should properly sync if mediaTypes is not present on any of side', () => { + const adUnit = { + mediaTypes: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + foo: 'omitted_value' + } + }, + ortb2Imp: { + // lack of video field + } + }; + + const expected1 = { + mediaTypes: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + foo: 'omitted_value' + } + }, + ortb2Imp: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + } + } + }; + + syncOrtb2(adUnit, 'video'); + expect(adUnit).to.deep.eql(expected1); + + const adUnit2 = { + mediaTypes: { + // lack of video field + }, + ortb2Imp: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + foo: 'omitted_value' + } + } + }; + + const expected2 = { + mediaTypes: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + } + }, + ortb2Imp: { + video: { + minduration: 500, + maxduration: 1000, + protocols: [1, 2, 3], + linearity: 5, + w: 100, + h: 200, + foo: 'omitted_value', + } + } + }; + + syncOrtb2(adUnit2, 'video'); + expect(adUnit2).to.deep.eql(expected2); + }); + + it('should not create empty video object on ortb2Imp if there was nothing to copy', () => { + const adUnit2 = { + mediaTypes: { + video: { + noOrtbVideoField1: 'value', + noOrtbVideoField2: 'value' + } + }, + ortb2Imp: { + // lack of video field + } + }; + syncOrtb2(adUnit2, 'video'); + expect(adUnit2.ortb2Imp.video).to.be.undefined + }); + }); });