Skip to content

Commit 58d79e5

Browse files
authored
Scattered Bid Adapter: New Adapter (#9295)
* Scattered module skeleton * Review fixes * Remove netRevenue check * Fix test * Review fixes
1 parent d984cd8 commit 58d79e5

File tree

3 files changed

+318
-0
lines changed

3 files changed

+318
-0
lines changed

modules/scatteredBidAdapter.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// jshint esversion: 6, es3: false, node: true
2+
'use strict';
3+
4+
import { registerBidder } from '../src/adapters/bidderFactory.js';
5+
import { BANNER } from '../src/mediaTypes.js';
6+
import { deepAccess, logInfo } from '../src/utils.js';
7+
import { ortbConverter } from '../libraries/ortbConverter/converter.js';
8+
9+
const BIDDER_CODE = 'scattered';
10+
const GVLID = 1179;
11+
export const converter = ortbConverter({
12+
context: {
13+
mediaType: BANNER,
14+
ttl: 360,
15+
netRevenue: true
16+
}
17+
})
18+
19+
export const spec = {
20+
code: BIDDER_CODE,
21+
gvlid: GVLID,
22+
supportedMediaTypes: [BANNER],
23+
24+
// 1.
25+
isBidRequestValid: function (bid) {
26+
const bidderDomain = deepAccess(bid, 'params.bidderDomain')
27+
if (bidderDomain === undefined || bidderDomain === '') {
28+
return false
29+
}
30+
31+
const sizes = deepAccess(bid, 'mediaTypes.banner.sizes')
32+
if (sizes === undefined || sizes.length < 1) {
33+
return false
34+
}
35+
36+
return true
37+
},
38+
39+
// 2.
40+
buildRequests: function (bidRequests, bidderRequest) {
41+
return {
42+
method: 'POST',
43+
url: 'https://' + getKeyOnAny(bidRequests, 'params.bidderDomain'),
44+
data: converter.toORTB({ bidderRequest, bidRequests }),
45+
options: {
46+
contentType: 'application/json'
47+
},
48+
};
49+
},
50+
51+
// 3.
52+
interpretResponse: function (response, request) {
53+
if (!response.body) return;
54+
return converter.fromORTB({ response: response.body, request: request.data }).bids;
55+
},
56+
57+
// 4
58+
onBidWon: function (bid) {
59+
logInfo('onBidWon', bid)
60+
}
61+
}
62+
63+
function getKeyOnAny(collection, key) {
64+
for (let i = 0; i < collection.length; i++) {
65+
const result = deepAccess(collection[i], key);
66+
if (result) {
67+
return result;
68+
}
69+
}
70+
}
71+
72+
registerBidder(spec);

modules/scatteredBidAdapter.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Overview
2+
3+
```
4+
Module Name: Scattered Adapter
5+
Module Type: Bidder Adapter
6+
Maintainer: [email protected]
7+
```
8+
9+
# Description
10+
11+
Module that connects to Scattered's demand sources.
12+
It uses OpenRTB standard to communicate between the adapter and bidding servers.
13+
14+
# Test Parameters
15+
16+
```javascript
17+
var adUnits = [
18+
{
19+
code: 'test-div',
20+
mediaTypes: {
21+
banner: {
22+
sizes: [[300, 250]], // a display size
23+
}
24+
},
25+
bids: [
26+
{
27+
bidder: "scattered",
28+
params: {
29+
bidderDomain: "prebid-test.scattered.eu/bid",
30+
test: 0
31+
}
32+
}
33+
]
34+
}
35+
];
36+
```
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import { spec, converter } from 'modules/scatteredBidAdapter.js';
2+
import { assert } from 'chai';
3+
import { config } from 'src/config.js';
4+
import { deepClone, mergeDeep } from '../../../src/utils';
5+
describe('Scattered adapter', function () {
6+
describe('isBidRequestValid', function () {
7+
// A valid bid
8+
let validBid = {
9+
bidder: 'scattered',
10+
mediaTypes: {
11+
banner: {
12+
sizes: [[300, 250], [760, 400]]
13+
}
14+
},
15+
params: {
16+
bidderDomain: 'https://prebid.scattered.eu',
17+
test: 0
18+
}
19+
};
20+
21+
// Because this valid bid is modified to create invalid bids in following tests we first check it.
22+
// We must be sure it is a valid one, not to get false negatives.
23+
it('should accept a valid bid', function () {
24+
assert.isTrue(spec.isBidRequestValid(validBid));
25+
});
26+
27+
it('should skip if bidderDomain info is missing', function () {
28+
let bid = deepClone(validBid);
29+
30+
delete bid.params.bidderDomain;
31+
assert.isFalse(spec.isBidRequestValid(bid));
32+
});
33+
34+
it('should expect at least one banner size', function () {
35+
let bid = deepClone(validBid);
36+
37+
delete bid.mediaTypes.banner;
38+
assert.isFalse(spec.isBidRequestValid(bid));
39+
40+
// empty sizes array
41+
bid.mediaTypes = {
42+
banner: {
43+
sizes: []
44+
}
45+
};
46+
assert.isFalse(spec.isBidRequestValid(bid));
47+
});
48+
});
49+
50+
describe('buildRequests', function () {
51+
let arrayOfValidBidRequests, validBidderRequest;
52+
53+
beforeEach(function () {
54+
arrayOfValidBidRequests = [{
55+
bidder: 'scattered',
56+
params: {
57+
bidderDomain: 'https://prebid.scattered.eu',
58+
test: 0
59+
},
60+
mediaTypes: {
61+
banner: {
62+
sizes: [[300, 250], [760, 400]]
63+
},
64+
adUnitCode: 'test-div',
65+
transactionId: '32d09c47-c6b8-40b0-9605-2e251a472ea4',
66+
bidId: '21adc5d8765aa1',
67+
bidderRequestId: '130728f7662afc',
68+
auctionId: 'b4a45a23-8371-4d87-9308-39146b29ca32',
69+
},
70+
}];
71+
72+
validBidderRequest = {
73+
bidderCode: 'scattered',
74+
auctionId: 'b4a45a23-8371-4d87-9308-39146b29ca32',
75+
gdprConsent: { consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', gdprApplies: true },
76+
refererInfo: {
77+
domain: 'localhost',
78+
page: 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true',
79+
},
80+
ortb2: {
81+
site: {
82+
publisher: {
83+
name: 'publisher1 INC.'
84+
}
85+
}
86+
}
87+
};
88+
});
89+
90+
it('should validate request format', function () {
91+
let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest);
92+
assert.equal(request.method, 'POST');
93+
assert.deepEqual(request.options, { contentType: 'application/json' });
94+
assert.ok(request.data);
95+
});
96+
97+
it('has the right fields filled', function () {
98+
let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest);
99+
const bidderRequest = request.data;
100+
assert.equal(bidderRequest.id, validBidderRequest.auctionId);
101+
assert.ok(bidderRequest.site);
102+
assert.ok(bidderRequest.device);
103+
assert.ok(bidderRequest.source);
104+
assert.lengthOf(bidderRequest.imp, 1);
105+
});
106+
107+
it('should configure the site object', function () {
108+
let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest);
109+
const site = request.data.site;
110+
assert.equal(site.domain, 'localhost');
111+
assert.equal(site.page, 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true');
112+
});
113+
114+
it('should configure site with ortb2', function () {
115+
mergeDeep(validBidderRequest.ortb2.site, {
116+
id: '876',
117+
publisher: {
118+
domain: 'publisher1.eu'
119+
}
120+
});
121+
122+
let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest);
123+
const site = request.data.site;
124+
assert.deepEqual(site, {
125+
domain: 'localhost',
126+
id: '876',
127+
page: 'http://localhost:9999/integrationExamples/gpt/hello_world.html?pbjs_debug=true',
128+
publisher: {
129+
domain: 'publisher1.eu',
130+
name: 'publisher1 INC.'
131+
}
132+
});
133+
});
134+
135+
it('should send device info', function () {
136+
it('should send info about device', function () {
137+
config.setConfig({
138+
device: { w: 375, h: 273 }
139+
});
140+
141+
let request = spec.buildRequests(arrayOfValidBidRequests, validBidderRequest);
142+
143+
assert.equal(request.device.ua, navigator.userAgent);
144+
assert.equal(request.device.w, 375);
145+
assert.equal(request.device.h, 273);
146+
});
147+
})
148+
})
149+
})
150+
151+
describe('interpretResponse', function () {
152+
const serverResponse = {
153+
body: {
154+
id: 'b4a45a23-8371-4d87-9308-39146b29ca32',
155+
bidid: '11111111-2222-2222-2222-333333333333',
156+
cur: 'PLN',
157+
seatbid: [{
158+
bid: [
159+
{
160+
id: '234234-234234-234234', // bidder generated
161+
impid: '123',
162+
price: '34.2',
163+
nurl: 'https://scattered.eu/nurl',
164+
adm: '<html><img src="https://some_banner.jpeg></img></html>',
165+
cid: '99877',
166+
crid: '2345-2345-23',
167+
w: 345,
168+
h: 456
169+
}
170+
]
171+
}],
172+
}
173+
};
174+
175+
let bidderRequest = {
176+
bids: [
177+
{
178+
bidId: '123',
179+
netRevenue: 'net',
180+
}
181+
]
182+
183+
};
184+
185+
let request;
186+
beforeEach(() => {
187+
request = { data: converter.toORTB({ bidderRequest }) };
188+
});
189+
190+
it('should return if no body in response', function () {
191+
assert.ok(!spec.interpretResponse({}, request));
192+
});
193+
194+
it('should set proper values', function () {
195+
const results = spec.interpretResponse(serverResponse, request);
196+
const expected = {
197+
ad: '<html><img src="https://some_banner.jpeg></img></html><div style="position:absolute;left:0px;top:0px;visibility:hidden;"><img src="https://scattered.eu/nurl"></div>',
198+
cpm: '34.2',
199+
creativeId: '2345-2345-23',
200+
currency: 'PLN',
201+
height: 456,
202+
width: 345,
203+
mediaType: 'banner',
204+
requestId: '123',
205+
ttl: 360,
206+
};
207+
expect(results.length).to.equal(1);
208+
sinon.assert.match(results[0], expected);
209+
});
210+
});

0 commit comments

Comments
 (0)