diff --git a/modules/sevioBidAdapter.js b/modules/sevioBidAdapter.js index 21d30a17d4..c142ebb68a 100644 --- a/modules/sevioBidAdapter.js +++ b/modules/sevioBidAdapter.js @@ -101,6 +101,39 @@ const getReferrerInfo = (bidderRequest) => { return bidderRequest?.refererInfo?.page ?? ''; } +function resolveDataType(asset) { + if (typeof asset?.data?.type === 'number') { + return asset.data.type; + } + + if (typeof asset?.id === 'number') { + return asset.id; + } + + return null; +} + +// Helper: resolve the "image type" for an asset +// Returns 1 (icon), 3 (image) or null if unknown +function resolveImageType(asset) { + if (!asset) return null; + + // 1) explicit image type in the img block (preferred) + if (typeof asset.img?.type === 'number') return asset.img.type; + + // 2) fallback to data.type (some bidders put the type here) + if (typeof asset.data?.type === 'number') return asset.data.type; + + // 3) last resort: map legacy asset.id values to image types + // (13 -> icon, 14 -> image) — keep this mapping isolated here + if (typeof asset.id === 'number') { + if (asset.id === 13) return 1; // icon + if (asset.id === 14) return 3; // image + } + + return null; +} + const parseNativeAd = function (bid) { try { const nativeAd = JSON.parse(bid.ad); @@ -112,7 +145,8 @@ const parseNativeAd = function (bid) { } if (asset.data) { const value = asset.data.value; - switch (asset.data.type) { + const type = resolveDataType(asset); + switch (type) { case 1: if (value) native.sponsored = value; break; case 2: if (value) native.desc = value; break; case 3: if (value) native.rating = value; break; @@ -129,13 +163,14 @@ const parseNativeAd = function (bid) { } } if (asset.img) { - const { url, w = 0, h = 0, type } = asset.img; + const { url, w = 0, h = 0 } = asset.img; + const imgType = resolveImageType(asset); - if (type === 1 && url) { + if (imgType === 1 && url) { native.icon = url; native.icon_width = w; native.icon_height = h; - } else if (type === 3 && url) { + } else if (imgType === 3 && url) { native.image = url; native.image_width = w; native.image_height = h; diff --git a/test/spec/modules/sevioBidAdapter_spec.js b/test/spec/modules/sevioBidAdapter_spec.js index 9fbf0b9297..aef3c0d488 100644 --- a/test/spec/modules/sevioBidAdapter_spec.js +++ b/test/spec/modules/sevioBidAdapter_spec.js @@ -510,4 +510,108 @@ describe('sevioBidAdapter', function () { expect(requests[0].data.keywords.tokens).to.deep.equal(['play', 'games', 'fun']); }); }); + + describe('native parsing', function () { + it('parses native assets: title, data->desc (type 2), image (asset.img), clickUrl and trackers', function () { + const serverResponseNative = { + body: { + bids: [ + { + requestId: 'native-1', + cpm: 1.0, + currency: 'EUR', + width: 1, + height: 1, + creativeId: 'native-creative-1', + ad: JSON.stringify({ + ver: '1.2', + assets: [ + { id: 2, title: { text: 'Native Title' } }, + { id: 4, data: { type: 2, value: 'Native body text' } }, + { id: 5, img: { type: 3, url: 'https://img.example/x.png', w: 120, h: 60 } } + ], + eventtrackers: [ + { event: 1, method: 1, url: 'https://impr.example/1' }, + { event: 2, method: 1, url: 'https://view.example/1' } + ], + link: { url: 'https://click.example', clicktrackers: ['https://clickt.example/1'] } + }), + ttl: 300, + netRevenue: true, + mediaType: 'NATIVE', + meta: { advertiserDomains: ['adv.example'] }, + bidder: 'sevio' + } + ] + } + }; + + const result = spec.interpretResponse(serverResponseNative); + expect(result).to.be.an('array').with.lengthOf(1); + + const out = result[0]; + expect(out).to.have.property('native'); + + const native = out.native; + expect(native.title).to.equal('Native Title'); + expect(native.image).to.equal('https://img.example/x.png'); + expect(native.image_width).to.equal(120); + expect(native.image_height).to.equal(60); + expect(native.clickUrl).to.equal('https://click.example'); + + expect(native.impressionTrackers).to.be.an('array').that.includes('https://impr.example/1'); + expect(native.viewableTrackers).to.be.an('array').that.includes('https://view.example/1'); + expect(native.clickTrackers).to.be.an('array').that.includes('https://clickt.example/1'); + + // meta preserved + expect(out.meta).to.have.property('advertiserDomains').that.deep.equals(['adv.example']); + }); + + it('maps legacy asset.id -> image types (13 -> icon, 14 -> image) and sets icon fields', function () { + const serverResponseIcon = { + body: { + bids: [ + { + requestId: 'native-icon', + cpm: 1.0, + currency: 'EUR', + width: 1, + height: 1, + creativeId: 'native-creative-icon', + ad: JSON.stringify({ + ver: '1.2', + assets: [ + // legacy asset id 13 should map to icon (img type 1) + { id: 13, img: { url: 'https://img.example/icon.png', w: 50, h: 50 } }, + // legacy asset id 14 should map to image (img type 3) + { id: 14, img: { url: 'https://img.example/img.png', w: 200, h: 100 } }, + { id: 2, title: { text: 'Legacy Mapping Test' } } + ], + link: { url: 'https://click.example/leg' } + }), + ttl: 300, + netRevenue: true, + mediaType: 'NATIVE', + meta: { advertiserDomains: ['legacy.example'] }, + bidder: 'sevio' + } + ] + } + }; + + const result = spec.interpretResponse(serverResponseIcon); + expect(result).to.be.an('array').with.lengthOf(1); + const native = result[0].native; + + // icon mapped from id 13 + expect(native.icon).to.equal('https://img.example/icon.png'); + expect(native.icon_width).to.equal(50); + expect(native.icon_height).to.equal(50); + + // image mapped from id 14 + expect(native.image).to.equal('https://img.example/img.png'); + expect(native.image_width).to.equal(200); + expect(native.image_height).to.equal(100); + }); + }); });