diff --git a/modules/defineMediaBidAdapter.js b/modules/defineMediaBidAdapter.js new file mode 100644 index 00000000000..937ad9fb8d8 --- /dev/null +++ b/modules/defineMediaBidAdapter.js @@ -0,0 +1,280 @@ +/** + * Define Media Bid Adapter for Prebid.js + * + * This adapter connects publishers to Define Media's programmatic advertising platform + * via OpenRTB 2.5 protocol. It supports banner ad formats and includes proper + * supply chain transparency through sellers.json compliance. + * + * @module defineMediaBidAdapter + * @version 1.0.0 + */ + +import {logInfo, logError, 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 { ajax } from '../src/ajax.js'; + +// Bidder identification and compliance constants +const BIDDER_CODE = 'defineMedia'; +const IAB_GVL_ID = 440; // IAB Global Vendor List ID for GDPR compliance +const SUPPORTED_MEDIA_TYPES = [BANNER]; // Currently only banner ads are supported + +// Default bid response configuration +const DEFAULT_TTL = 1000; // Default time-to-live for bids in seconds +const DEFAULT_NET_REVENUE = true; // Revenue is reported as net (after platform fees) + +// Endpoint URLs for different environments +const ENDPOINT_URL_DEV = 'https://rtb-dev.conative.network/openrtb2/auction'; // Development/testing endpoint +const ENDPOINT_URL_PROD = 'https://rtb.conative.network/openrtb2/auction'; // Production endpoint +const METHOD = 'POST'; // HTTP method for bid requests + +/** + * Default ORTB converter instance with standard configuration + * This handles the conversion between Prebid.js bid objects and OpenRTB format + */ +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_TTL + } +}); + +export const spec = { + code: BIDDER_CODE, + gvlid: IAB_GVL_ID, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + + /** + * Determines if a bid request is valid for this adapter + * + * Required parameters: + * - supplierDomainName: Domain name for supply chain transparency + * - mediaTypes.banner: Must include banner media type configuration + * + * Optional parameters: + * - devMode: Boolean flag to use development endpoint + * - ttl: Custom time-to-live for the bid response (only honored when devMode is true) + * + * @param {Object} bid - The bid request object from Prebid.js + * @returns {boolean} True if the bid request is valid + */ + isBidRequestValid: (bid) => { + // Ensure we have a valid bid object + if (!bid || typeof bid !== 'object') { + logInfo(`[${BIDDER_CODE}] isBidRequestValid: Invalid bid object`); + return false; + } + + // Validate required parameters + const hasSupplierDomainName = Boolean(bid?.params?.supplierDomainName); + const hasValidMediaType = Boolean(bid?.mediaTypes && bid.mediaTypes.banner); + const isDevMode = Boolean(bid?.params?.devMode); + + logInfo(`[${BIDDER_CODE}] isBidRequestValid called with:`, { + bidId: bid.bidId, + hasSupplierDomainName, + hasValidMediaType, + isDevMode + }); + + const isValid = hasSupplierDomainName && hasValidMediaType; + logInfo(`[${BIDDER_CODE}] isBidRequestValid returned:`, isValid); + return isValid; + }, + + /** + * Builds OpenRTB bid requests from validated Prebid.js bid requests + * + * This method: + * 1. Creates individual OpenRTB requests for each valid bid + * 2. Sets up dynamic TTL based on bid parameters (only in devMode) + * 3. Configures supply chain transparency (schain) + * 4. Selects appropriate endpoint based on devMode flag + * + * @param {Array} validBidRequests - Array of valid bid request objects + * @param {Object} bidderRequest - Bidder-level request data from Prebid.js + * @returns {Array} Array of bid request objects to send to the server + */ + buildRequests: (validBidRequests, bidderRequest) => { + return validBidRequests?.map(function(req) { + // DeepCopy the request to avoid modifying the original object + const oneBidRequest = [JSON.parse(JSON.stringify(req))]; + + // Get parameters and check devMode first + const params = oneBidRequest[0].params; + const isDevMode = Boolean(params?.devMode); + + // Custom TTL is only allowed in development mode for security and consistency + const ttl = isDevMode && params?.ttl ? params.ttl : DEFAULT_TTL; + + // Create converter with TTL (custom only in devMode, otherwise default) + const dynamicConverter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: ttl + } + }); + + // Convert Prebid.js request to OpenRTB format + const ortbRequest = dynamicConverter.toORTB({ + bidderRequest: bidderRequest, + bidRequests: oneBidRequest + }); + + // Select endpoint based on development mode flag + const endpointUrl = isDevMode ? ENDPOINT_URL_DEV : ENDPOINT_URL_PROD; + + // Configure supply chain transparency (sellers.json compliance) + // Preserve existing schain if present, otherwise create minimal schain + if (bidderRequest?.source?.schain) { + // Preserve existing schain structure from bidderRequest + ortbRequest.source = bidderRequest.source; + } else { + // Create minimal schain only if none exists + if (!ortbRequest.source) { + ortbRequest.source = {}; + } + if (!ortbRequest.source.schain) { + ortbRequest.source.schain = { + complete: 1, // Indicates this is a complete supply chain + nodes: [{ + asi: params.supplierDomainName // Advertising system identifier + }] + }; + } + } + + logInfo(`[${BIDDER_CODE}] Mapped ORTB Request from`, oneBidRequest, ' to ', ortbRequest, ' with bidderRequest ', bidderRequest); + + return { + method: METHOD, + url: endpointUrl, + data: ortbRequest, + converter: dynamicConverter // Attach converter for response processing + } + }); + }, + + /** + * Processes bid responses from the Define Media server + * + * This method: + * 1. Validates the server response structure + * 2. Uses the appropriate ORTB converter (request-specific or default) + * 3. Converts OpenRTB response back to Prebid.js bid format + * 4. Handles errors gracefully and returns empty array on failure + * + * @param {Object} serverResponse - Response from the bid server + * @param {Object} request - Original request object containing converter + * @returns {Array} Array of bid objects for Prebid.js + */ + interpretResponse: (serverResponse, request) => { + logInfo(`[${BIDDER_CODE}] interpretResponse called with:`, { serverResponse, request }); + + // Validate server response structure + if (!serverResponse?.body) { + logWarn(`[${BIDDER_CODE}] No response body received`); + return []; + } + + try { + // Use the converter from the request if available (with custom TTL), otherwise use default + const responseConverter = request.converter || converter; + const bids = responseConverter.fromORTB({response: serverResponse.body, request: request.data}).bids; + logInfo(`[${BIDDER_CODE}] Successfully parsed ${bids.length} bids`); + return bids; + } catch (error) { + logError(`[${BIDDER_CODE}] Error parsing response:`, error); + return []; + } + }, + + /** + * Handles bid request timeouts + * Currently logs timeout events for monitoring and debugging + * + * @param {Array|Object} timeoutData - Timeout data from Prebid.js + */ + onTimeout: (timeoutData) => { + logInfo(`[${BIDDER_CODE}] onTimeout called with:`, timeoutData); + }, + + /** + * Handles successful bid wins + * + * This method: + * 1. Fires win notification URL (burl) if present in bid + * 2. Logs win event for analytics and debugging + * + * @param {Object} bid - The winning bid object + */ + onBidWon: (bid) => { + // Fire win notification URL for server-side tracking + if (bid?.burl) { + ajax(bid.burl, null, null); + } + logInfo(`[${BIDDER_CODE}] onBidWon called with bid:`, bid); + }, + + /** + * Handles bidder errors with comprehensive error categorization + * + * This method: + * 1. Categorizes errors by type (timeout, network, client/server errors) + * 2. Collects relevant context for debugging + * 3. Logs structured error information for monitoring + * + * Error categories: + * - timeout: Request exceeded time limit + * - network: Network connectivity issues + * - client_error: 4xx HTTP status codes + * - server_error: 5xx HTTP status codes + * - unknown: Uncategorized errors + * + * @param {Object} params - Error parameters + * @param {Object} params.error - Error object + * @param {Object} params.bidderRequest - Original bidder request + */ + onBidderError: ({ error, bidderRequest }) => { + // Collect comprehensive error information for debugging + const errorInfo = { + message: error?.message || 'Unknown error', + type: error?.type || 'general', + code: error?.code || null, + bidderCode: BIDDER_CODE, + auctionId: bidderRequest?.auctionId || 'unknown', + bidderRequestId: bidderRequest?.bidderRequestId || 'unknown', + timeout: bidderRequest?.timeout || null, + bids: bidderRequest?.bids?.length || 0 + }; + + // Categorize error types for better debugging and monitoring + if (error?.message?.includes('timeout')) { + errorInfo.category = 'timeout'; + } else if (error?.message?.includes('network')) { + errorInfo.category = 'network'; + } else if (error?.code >= 400 && error?.code < 500) { + errorInfo.category = 'client_error'; + } else if (error?.code >= 500) { + errorInfo.category = 'server_error'; + } else { + errorInfo.category = 'unknown'; + } + + logError(`[${BIDDER_CODE}] Bidder error occurred:`, errorInfo); + }, + + /** + * Handles successful ad rendering events + * Currently logs render success for analytics and debugging + * + * @param {Object} bid - The successfully rendered bid object + */ + onAdRenderSucceeded: (bid) => { + logInfo(`[${BIDDER_CODE}] onAdRenderSucceeded called with bid:`, bid); + } +}; + +// Register the bidder with Prebid.js +registerBidder(spec); diff --git a/modules/defineMediaBidAdapter.md b/modules/defineMediaBidAdapter.md new file mode 100644 index 00000000000..7930446e305 --- /dev/null +++ b/modules/defineMediaBidAdapter.md @@ -0,0 +1,49 @@ +# Overview + +``` +Module Name: Define Media Bid Adapter +Module Type: Bidder Adapter +Maintainer: m.klumpp@definemedia.de +``` + +# Description + +This is the official Define Media Bid Adapter for Prebid.js. It currently supports **Banner**. Delivery is handled by Define Media’s own RTB server. +Publishers are onboarded and activated via Define Media **Account Management** (no self-service keys required). + +# Bid Parameters + +| Name | Scope | Type | Description | Example | +|---------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------| +| `supplierDomainName`| required | string | **Identifier used for the supply chain (schain)**. Populates `source.schain.nodes[0].asi` to attribute traffic to Define Media’s supply path. **Publishers do not need to host a sellers.json under this domain.** | `definemedia.de` | +| `devMode` | optional | boolean | Sends requests to the development endpoint. Requests with `devMode: true` are **not billable**. | `true` | + + +# How it works + +- The adapter converts Prebid bid requests to ORTB and sets: + - `source.schain.complete = 1` + - `source.schain.nodes[0].asi = supplierDomainName` +- This ensures buyers can resolve the **supply chain** correctly without requiring any sellers.json hosted by the publisher. + +# Example Prebid Configuration + +```js +pbjs.addAdUnits([{ + code: 'div-gpt-ad-123', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + bids: [{ + bidder: 'defineMedia', + params: { + supplierDomainName: 'definemedia.de', + // set only for non-billable tests + devMode: false + } + }] +}]); +``` + +# Notes + +- **Onboarding**: Publishers must be enabled by Define Media Account Management before traffic is accepted. +- **Transparency**: Seller transparency is enforced on Define Media’s side via account setup and standard industry mechanisms (e.g., schain). No publisher-hosted sellers.json is expected or required. diff --git a/package-lock.json b/package-lock.json index b915263103a..a4a54012b9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7217,7 +7217,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chai": { "version": "4.4.1", diff --git a/test/spec/modules/defineMediaBidAdapter_spec.js b/test/spec/modules/defineMediaBidAdapter_spec.js new file mode 100755 index 00000000000..a2adc024526 --- /dev/null +++ b/test/spec/modules/defineMediaBidAdapter_spec.js @@ -0,0 +1,813 @@ +// jshint esversion: 6, es3: false, node: true +import { assert } from 'chai'; +import { spec } from 'modules/defineMediaBidAdapter.js'; +import { deepClone } from 'src/utils.js'; + +describe('Define Media Bid Adapter', function () { + const mockValidBids = [ + { + "bidder": "defineMedia", + "params": { + "supplierDomainName": "traffective.com", + "devMode": false + }, + "mediaTypes": { + "banner": { + "sizes": [ + [ + 1, + 1 + ], + [ + 300, + 250 + ] + ] + } + }, + "adUnitCode": "custom-adunit-code", + "transactionId": "9af02bbf-558f-4328-a7b3-0b67bac44dbc", + "adUnitId": "e9a971c1-7ce9-4bcf-8b64-611e79f6e35c", + "sizes": [ + [ + 1, + 1 + ], + [ + 300, + 250 + ] + ], + "bidId": "464ae0039a4147", + "bidderRequestId": "3a7736f5f19f638", + "auctionId": "586233c7-4e5d-4231-9f06-b1ff37b0db53", + "src": "client", + "auctionsCount": 1, + "bidRequestsCount": 22, + "bidderRequestsCount": 1, + "bidderWinsCount": 0, + "deferBilling": false, + }, + { + "bidder": "defineMedia", + "params": { + "supplierDomainName": "traffective.com", + "devMode": false + }, + "mediaTypes": { + "banner": { + "sizes": [ + [ + 300, + 250 + ], + [ + 1, + 1 + ] + ] + } + }, + "adUnitCode": "custim-adunit-code-2", + "transactionId": "3f7fa504-f29f-49cc-8edb-31f8b404e27f", + "adUnitId": "1e5fdfe3-b5c7-4dd4-83d1-770bce897773", + "sizes": [ + [ + 300, + 250 + ], + [ + 1, + 1 + ] + ], + "bidId": "53836dbf7d7aac8", + "bidderRequestId": "3a7736f5f19f638", + "auctionId": "586233c7-4e5d-4231-9f06-b1ff37b0db53", + "src": "client", + "auctionsCount": 1, + "bidRequestsCount": 19, + "bidderRequestsCount": 1, + "bidderWinsCount": 0, + "deferBilling": false, + } + ] + + const mockBidderRequest = { + "bidderCode": "defineMedia", + "auctionId": "586233c7-4e5d-4231-9f06-b1ff37b0db53", + "bidderRequestId": "3a7736f5f19f638", + "bids": mockValidBids, + "auctionStart": 1753448647982, + "timeout": 1500, + "refererInfo": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [], + "topmostLocation": "https://www.any-random-page.com/", + "location": "https://www.any-random-page.com/", + "canonicalUrl": "https://www.any-random-page.com/", + "page": "https://www.any-random-page.com/", + "domain": "www.any-random-page.com", + "ref": "https://www.any-random-page.com/", + "legacy": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [], + "referer": "https://www.any-random-page.com/", + "canonicalUrl": "https://www.any-random-page.com/" + } + }, + "ortb2": { + "source": {}, + "site": { + "domain": "any-random-page.com", + "publisher": { + "domain": "any-random-page.com" + }, + "page": "https://www.any-random-page.com/", + "ref": "https://www.any-random-page.com/", + "content": { + "data": [ + { + "name": "articlegenius.ai", + "ext": { + "segtax": 7 + }, + "segment": [ + { + "id": "186" + }, + { + "id": "387" + }, + { + "id": "400" + } + ] + } + ], + "language": "de" + }, + "ext": { + "data": { + "pagetype": "article", + "category": "localnews", + "documentLang": "de", + "adg_rtd": { + "uid": "c7910f59-446a-4786-8826-8181e884afd6", + "pageviewId": "915818a5-73f2-4efb-8eff-dd312755dd4a", + "features": { + "page_dimensions": "1235x6597", + "viewport_dimensions": "1250x959", + "user_timestamp": "1753455847", + "dom_loading": "204" + }, + "session": { + "rnd": 0.8341928086704196, + "pages": 4, + "new": false, + "vwSmplg": 0.1, + "vwSmplgNxt": 0.05, + "expiry": 1753450360922, + "lastActivityTime": 1753448560922, + "id": "bd8b3c7a-ff7f-4433-a5fd-a06cf0fa6e1f" + } + } + } + } + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "RANDOMCONSENTSTRING", + "eids": [ + { + "source": "criteo.com", + "uids": [ + { + "id": "random-id", + "atype": 1 + } + ] + } + ] + } + }, + "ext": { + "prebid": { + "adServerCurrency": "EUR" + } + }, + "device": { + "w": 1920, + "h": 1080, + "dnt": 0, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", + "language": "de", + "ext": { + "vpw": 1250, + "vph": 959 + }, + "sua": { + "source": 1, + "platform": { + "brand": "Linux" + }, + "browsers": [ + { + "brand": "Google Chrome", + "version": [ + "137" + ] + }, + { + "brand": "Chromium", + "version": [ + "137" + ] + }, + { + "brand": "Not/A)Brand", + "version": [ + "24" + ] + } + ], + "mobile": 0 + } + } + }, + "gdprConsent": { + "consentString": "RANDOMCONSENTSTRING", + "vendorData": { + "cmpId": 21, + "cmpVersion": 2, + "gdprApplies": true, + "tcfPolicyVersion": 5, + "tcString": "RANDOMTCSTRING", + "listenerId": 12, + "eventStatus": "tcloaded", + "cmpStatus": "loaded", + "isServiceSpecific": true, + "useNonStandardTexts": false, + "publisherCC": "DE", + "purposeOneTreatment": false, + "outOfBand": { + "allowedVendors": {}, + "disclosedVendors": {} + }, + "purpose": { + "consents": { + "1": true, + "2": true, + "3": true, + "4": true, + "5": true, + "6": true, + "7": true, + "8": true, + "9": true, + "10": true, + "11": true + }, + "legitimateInterests": { + "1": false, + "2": true, + "3": false, + "4": false, + "5": false, + "6": false, + "7": true, + "8": true, + "9": true, + "10": true, + "11": true + } + }, + "vendor": { + "consents": { + "755": true, + }, + "legitimateInterests": { + "755": true, + } + }, + "specialFeatureOptins": { + "1": true, + "2": true + }, + "publisher": { + "consents": {}, + "legitimateInterests": {}, + "customPurpose": { + "consents": {}, + "legitimateInterests": {} + }, + "restrictions": {} + }, + "opencmp": { + "consentType": "tcf", + "googleConsent": { + "ad_storage": "granted", + "ad_user_data": "granted", + "ad_personalization": "granted", + "analytics_storage": "granted" + } + }, + "addtlConsent": "2~89~dv.", + "customVendors": { + "consents": { + "45": true + }, + "legitimateInterests": { + "45": false + } + } + }, + "gdprApplies": true, + "apiVersion": 2, + "addtlConsent": "2~89~dv." + }, + "start": 1753448648053 + } + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + for (const bidRequest of mockValidBids) { + assert.isTrue(spec.isBidRequestValid(bidRequest)); + } + }); + + it('should return false when supplierDomainName is not set', function () { + let invalidBids = deepClone(mockValidBids); + for (const bidRequest of invalidBids) { + bidRequest.params = {}; + assert.isFalse(spec.isBidRequestValid(bidRequest)); + } + }); + }); + + describe('buildRequests', function () { + it('should send request with correct structure', function () { + let requests = spec.buildRequests(mockValidBids, mockBidderRequest); + for (const request of requests) { + assert.equal(request.method, 'POST'); + assert.ok(request.data); + } + }); + + it('should have default request structure', function () { + let keys = 'id,imp,site,source,device'.split(','); + let requests = spec.buildRequests(mockValidBids, mockBidderRequest); + + for (const request of requests) { + let data = Object.keys(request.data); + assert.includeDeepMembers(data, keys); + } + }); + + it('Verify the site url', function () { + let siteUrl = 'https://www.yourdomain.tld/your-directory/'; + let bidderRequest = deepClone(mockBidderRequest); + + bidderRequest.ortb2.site.page = siteUrl; + bidderRequest.refererInfo.page = siteUrl; + + let requests = spec.buildRequests(mockValidBids, bidderRequest); + + for (const request of requests) { + assert.equal(request.data.site.page, siteUrl); + } + }); + }); +}) + +describe('interpretResponse', function () { + const formerBids = [ + { + "bidder": "defineMedia", + "params": { + "supplierDomainName": "traffective.com", + "devMode": false + }, + "mediaTypes": { + "banner": { + "sizes": [ + [ + 1, + 1 + ], + [ + 300, + 250 + ] + ] + }, + }, + "adUnitCode": "custom-adunit-code", + "transactionId": null, + "adUnitId": "1eb2de11-c637-4175-b560-002fc4160841", + "sizes": [ + [ + 1, + 1 + ], + [ + 300, + 250 + ] + ], + "bidId": "8566b4bbc519b58", + "bidderRequestId": "7a7870d573e715", + "auctionId": null, + "src": "client", + "auctionsCount": 1, + "bidRequestsCount": 22, + "bidderRequestsCount": 1, + "bidderWinsCount": 0, + "deferBilling": false, + "ortb2": { + "source": {}, + "site": { + "domain": "any-random-page.com", + "publisher": { + "domain": "any-random-page.com" + }, + "page": "https://www.any-random-page.com/", + "ref": "https://www.any-random-page.com/", + "content": { + "data": [ + { + "name": "articlegenius.ai", + "ext": { + "segtax": 7 + }, + "segment": [ + { + "id": "186" + }, + { + "id": "387" + }, + { + "id": "400" + } + ] + } + ], + "language": "de" + }, + "ext": { + "data": { + "pagetype": "article", + "category": "localnews", + "documentLang": "de" + } + } + }, + "device": { + "w": 1920, + "h": 1080, + "dnt": 0, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", + "language": "de", + "ext": { + "vpw": 1250, + "vph": 959 + }, + "sua": { + "source": 1, + "platform": { + "brand": "Linux" + }, + "browsers": [ + { + "brand": "Google Chrome", + "version": [ + "137" + ] + }, + { + "brand": "Chromium", + "version": [ + "137" + ] + }, + { + "brand": "Not/A)Brand", + "version": [ + "24" + ] + } + ], + "mobile": 0 + } + } + } + } + ] + const formerBidRequest = { + "bidderCode": "defineMedia", + "auctionId": "0720d855-f13d-41b7-b5cf-41d6c89454af", + "bidderRequestId": "7a7870d573e715", + "bids": formerBids, + "auctionStart": 1753451739223, + "timeout": 1500, + "refererInfo": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [ + "https://www.any-random-page.com/" + ], + "topmostLocation": "https://www.any-random-page.com/", + "location": "https://www.any-random-page.com/", + "canonicalUrl": "https://www.any-random-page.com/", + "page": "https://www.any-random-page.com/", + "domain": "www.any-random-page.com", + "ref": "https://www.any-random-page.com/", + "legacy": { + "reachedTop": true, + "isAmp": false, + "numIframes": 0, + "stack": [ + "https://www.any-random-page.com/" + ], + "referer": "https://www.any-random-page.com/", + "canonicalUrl": "https://www.any-random-page.com" + } + }, + "ortb2": { + "source": {}, + "site": { + "domain": "any-random-page.com", + "publisher": { + "domain": "any-random-page.com" + }, + "page": "https://www.any-random-page.com/", + "ref": "https://www.any-random-page.com/", + "content": { + "data": [ + { + "name": "articlegenius.ai", + "ext": { + "segtax": 7 + }, + "segment": [ + { + "id": "186" + }, + { + "id": "387" + }, + { + "id": "400" + } + ] + } + ], + "language": "de" + }, + "ext": { + "data": { + "pagetype": "article", + "category": "localnews", + "documentLang": "de" + } + } + }, + "device": { + "w": 1920, + "h": 1080, + "dnt": 0, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", + "language": "de", + "ext": { + "vpw": 1250, + "vph": 959 + }, + "sua": { + "source": 1, + "platform": { + "brand": "Linux" + }, + "browsers": [ + { + "brand": "Google Chrome", + "version": [ + "137" + ] + }, + { + "brand": "Chromium", + "version": [ + "137" + ] + }, + { + "brand": "Not/A)Brand", + "version": [ + "24" + ] + } + ], + "mobile": 0 + } + } + }, + "start": 1753451739307 + } + + const goodBannerRequest = { + "imp": [ + { + "id": "8566b4bbc519b58", + "banner": { + "topframe": 0, + "format": [ + { + "w": 1, + "h": 1 + }, + { + "w": 300, + "h": 250 + } + ] + }, + "secure": 1, + } + ], + "source": { + "schain": { + "complete": 1, + "nodes": [ + { + "asi": "traffective.com" + } + ] + } + }, + "site": { + "domain": "any-random-page.com", + "publisher": { + "domain": "any-random-page.com" + }, + "page": "https://www.any-random-page.com/", + "ref": "https://www.any-random-page.com/", + "content": { + "data": [ + { + "name": "articlegenius.ai", + "ext": { + "segtax": 7 + }, + "segment": [ + { + "id": "186" + }, + { + "id": "387" + }, + { + "id": "400" + } + ] + } + ], + "language": "de" + }, + "ext": { + "data": { + "pagetype": "article", + "category": "localnews", + "documentLang": "de" + } + } + }, + "device": { + "w": 1920, + "h": 1080, + "dnt": 0, + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", + "language": "de", + "ext": { + "vpw": 1250, + "vph": 959 + }, + "sua": { + "source": 1, + "platform": { + "brand": "Linux" + }, + "browsers": [ + { + "brand": "Google Chrome", + "version": [ + "137" + ] + }, + { + "brand": "Chromium", + "version": [ + "137" + ] + }, + { + "brand": "Not/A)Brand", + "version": [ + "24" + ] + } + ], + "mobile": 0 + } + }, + // "id": "15009fd8-a057-458f-9819-5ddcbf474cfe", //this is a random uuid, so it is not set here + "test": 0, + "tmax": 1500 + } + const goodBannerResponse = { + // "id": "15009fd8-a057-458f-9819-5ddcbf474cfe", //this is a random uuid, so it is not set here + "seatbid": [ + { + "bid": [ + { + "id": "23da7c99-8945-4ff9-6426-3da72d25e73a", + "impid": "8566b4bbc519b58", + "price": 1.0, + "burl": "https://somewhere-in-the-internet.com", + "lurl": "https://somewhere-in-the-internet.com", + "adm": "
ad markup
", + "adid": "e44efd3c-0b58-4834-8fc0-d9d3f658fa1c", + "adomain": [ + "definemedia.de" + ], + "crid": "dim_playout$6b3082ae93341939", + "w": 800, + "h": 250, + "mtype": 1, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "definemedia" + } + ], + "cur": "EUR" + } + const goodInterpretedBannerResponses = [ + { + "mediaType": "banner", + "ad": "ad markup
", + "requestId": "8566b4bbc519b58", + "seatBidId": "23da7c99-8945-4ff9-6426-3da72d25e73a", + "cpm": 1.0, + "currency": "EUR", + "width": 800, + "height": 250, + "creative_id": "dim_playout$6b3082ae93341939", + "creativeId": "dim_playout$6b3082ae93341939", + "burl": "https://somewhere-in-the-internet.com", + "ttl": 1000, + "netRevenue": true, + "meta": { "advertiserDomains": ["definemedia.de"] } + } + ] + it('should return null if body is missing or empty', function () { + let serverResponse = { + body: null + } + let request = { + data: deepClone(goodBannerRequest) + } + + const result = spec.interpretResponse(serverResponse, request); + assert.equal(result.length, 0); + }); + + it('should return the correct params', function () { + const computedRequest = spec.buildRequests(formerBids, formerBidRequest)[0] + let computedRequestExpected = deepClone(computedRequest.data); + assert.deepInclude(computedRequestExpected, goodBannerRequest) + let serverResponse = { + body: deepClone(goodBannerResponse) + } + const result = spec.interpretResponse(serverResponse, computedRequest); + assert.notEqual(result, null); + + const bid = result[0].cpm + assert.isAbove(bid, 0.01, "Bid price should be higher 0.0"); + assert.deepInclude(result[0], goodInterpretedBannerResponses[0]) + }); +})