From f31df189c0f4bda0750dca290b4ed5aed4bac219 Mon Sep 17 00:00:00 2001 From: rororo <16588724+rororo@users.noreply.github.com> Date: Wed, 30 Apr 2025 16:32:22 +0900 Subject: [PATCH 1/3] New adapter: suim --- modules/suimBidAdapter.js | 113 +++++++++++++++++ modules/suimBidAdapter.md | 44 +++++++ test/spec/modules/suimBidAdapter_spec.js | 154 +++++++++++++++++++++++ 3 files changed, 311 insertions(+) create mode 100644 modules/suimBidAdapter.js create mode 100644 modules/suimBidAdapter.md create mode 100644 test/spec/modules/suimBidAdapter_spec.js diff --git a/modules/suimBidAdapter.js b/modules/suimBidAdapter.js new file mode 100644 index 00000000000..65b6fd1bcea --- /dev/null +++ b/modules/suimBidAdapter.js @@ -0,0 +1,113 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getBidIdParameter, isEmpty } from '../src/utils.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 + * @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 = 'suim'; +const ENDPOINT = 'https://bid.suimad.com/api/v1/prebids'; +const SYNC_URL = 'https://bid.suimad.com/api/v1/logs/usersync'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * 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) { + return !!bid.params.ad_space_id; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests} validBidRequests an array of bids + * @param {BidderRequest} bidderRequest + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const refererInfo = bidderRequest.refererInfo; + const url = refererInfo.topmostLocation; + + return validBidRequests.map((request) => { + const adSpaceId = getBidIdParameter('ad_space_id', request.params); + const data = { + bids: [ + { + bidId: request.bidId, + ad_space_id: adSpaceId, + sizes: request.sizes, + src_url: url, + }, + ], + }; + return { + method: 'POST', + url: ENDPOINT, + data: data, + options: { + contentType: 'application/json', + withCredentials: false, + }, + }; + }); + }, + + /** + * 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 (bidderResponse, requests) { + const res = bidderResponse.body; + if (isEmpty(res)) { + return []; + } + + return [ + { + requestId: res.requestId, + cpm: res.cpm, + currency: res.currency, + width: res.width, + height: res.height, + ad: res.ad, + ttl: res.ttl, + creativeId: res.creativeId, + netRevenue: res.netRevenue, + meta: res.meta, + }, + ]; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions, serverResponses) { + return [ + { + url: SYNC_URL, + type: 'image', + }, + ]; + }, +}; + +registerBidder(spec); diff --git a/modules/suimBidAdapter.md b/modules/suimBidAdapter.md new file mode 100644 index 00000000000..008d4b0d344 --- /dev/null +++ b/modules/suimBidAdapter.md @@ -0,0 +1,44 @@ +# Overview + +``` +Module Name: SUIM Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@suimad.com +``` + +# Description + +Module that connects to SUIM AD Platform. +Supports Banner. + +# Test Parameters + +``` +var adUnits = [ + // Banner adUnit + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [ + [1, 1], + [360, 360], + [480, 270], + [320, 50], + [970, 250], + [300, 250], + [728, 90], + [300, 600], + [320, 100], + ] + } + }, + bids: [{ + bidder: 'suim', + params: { + ad_space_id: '01hw085aphq9qdtnwgdnm5q5b8' + } + }] + } +]; +``` diff --git a/test/spec/modules/suimBidAdapter_spec.js b/test/spec/modules/suimBidAdapter_spec.js new file mode 100644 index 00000000000..e06e5875d7c --- /dev/null +++ b/test/spec/modules/suimBidAdapter_spec.js @@ -0,0 +1,154 @@ +import { expect } from 'chai'; +import { spec } from 'modules/suimBidAdapter.js'; + +const ENDPOINT = 'https://bid.suimad.com/api/v1/prebids'; +const SYNC_URL = 'https://bid.suimad.com/api/v1/logs/usersync'; + +describe('SuimAdapter', function () { + describe('isBidRequestValid', function () { + it('should return true when bid contains all required params', function () { + const bid = { + bidder: 'suim', + params: { + ad_space_id: '123456', + }, + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + const invalidBid = { + bidder: 'suim', + params: {}, + }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + bidder: 'suim', + params: { + ad_space_id: '123456', + }, + adUnitCode: 'adunit-code', + sizes: [ + [1, 1], + [320, 50], + [970, 250], + [300, 250], + [728, 90], + [300, 600], + [320, 100], + ], + bidId: '22a91eced2e93a', + bidderRequestId: '20098c23bb863c', + auctionId: '1c0ceb30-c9c9-4988-b9ff-2724cf91e7db', + }, + ]; + + const bidderRequest = { + refererInfo: { + topmostLocation: 'https://example.com', + }, + }; + + it('sends bid request to ENDPOINT via POST', function () { + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].url).to.equal(ENDPOINT); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].data).to.deep.equal({ + bids: [ + { + bidId: '22a91eced2e93a', + ad_space_id: '123456', + sizes: [ + [1, 1], + [320, 50], + [970, 250], + [300, 250], + [728, 90], + [300, 600], + [320, 100], + ], + src_url: 'https://example.com', + } + ] + }); + }); + }); + + describe('interpretResponse', function () { + const bidResponse = { + bidId: '22a91eced2e93a', + cpm: 300, + currency: 'JPY', + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 300, + creativeId: '123456', + netRevenue: true, + meta: { + advertiserDomains: [], + }, + }; + const bidderRequests = { + bids: [{ + bidId: '22a91eced2e93a', + ad_space_id: '123456', + sizes: [ + [1, 1], + [320, 50], + [970, 250], + [300, 250], + [728, 90], + [300, 600], + [320, 100], + ], + src_url: 'https://example.com', + }] + } + + it('should interpret response', function () { + const result = spec.interpretResponse({ body: bidResponse }, bidderRequests); + expect(result).to.have.lengthOf(1); + expect(result[0]).to.deep.equal({ + requestId: bidResponse.bid, + cpm: 300, + currency: 'JPY', + width: 300, + height: 250, + ad: '

I am an ad

', + ttl: 300, + creativeId: '123456', + netRevenue: true, + meta: { + advertiserDomains: [], + }, + }); + }); + + it('should return empty array when response is empty', function () { + const response = []; + const result = spec.interpretResponse({ body: response }, bidderRequests); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + it('should return user syncs', function () { + const syncs = spec.getUserSyncs( + { pixelEnabled: true, iframeEnabled: true }, + {} + ); + expect(syncs).to.deep.equal([ + { + type: 'image', + url: SYNC_URL, + }, + ]); + }); + }); +}); From 130aef8a244b394ca9c6eb4309115906a6e8d082 Mon Sep 17 00:00:00 2001 From: rororo <16588724+rororo@users.noreply.github.com> Date: Wed, 30 Apr 2025 17:00:02 +0900 Subject: [PATCH 2/3] fix comments --- modules/suimBidAdapter.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/suimBidAdapter.js b/modules/suimBidAdapter.js index 65b6fd1bcea..2e7de9a8477 100644 --- a/modules/suimBidAdapter.js +++ b/modules/suimBidAdapter.js @@ -7,6 +7,7 @@ import { getBidIdParameter, isEmpty } from '../src/utils.js'; * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests @@ -67,12 +68,12 @@ export const spec = { /** * 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. + * @param {ServerResponse} serverResponse + * @param {BidRequest} bidRequest + * @returns {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function (bidderResponse, requests) { - const res = bidderResponse.body; + interpretResponse: function (serverResponse, bidRequest) { + const res = serverResponse.body; if (isEmpty(res)) { return []; } From 5f4d6bc1656f44e79436d7a0c492708090981e4f Mon Sep 17 00:00:00 2001 From: rororo <16588724+rororo@users.noreply.github.com> Date: Thu, 1 May 2025 10:15:11 +0900 Subject: [PATCH 3/3] fix bid request content type --- modules/suimBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/suimBidAdapter.js b/modules/suimBidAdapter.js index 2e7de9a8477..0e4374a83f5 100644 --- a/modules/suimBidAdapter.js +++ b/modules/suimBidAdapter.js @@ -59,7 +59,7 @@ export const spec = { url: ENDPOINT, data: data, options: { - contentType: 'application/json', + contentType: 'text/plain', withCredentials: false, }, };