Skip to content

Commit 1b3f979

Browse files
Artem-Maliuhamkomorski
authored andcommitted
SmartyTech Bid Adapter: Add userId and consent data support with chunking capability (#13945)
* SmartyTech Bid Adapter: Add userId, consent data support and chunking capability - Add userIdAsEids transmission when available - Add GDPR, CCPA/USP, and COPPA consent support - Add configurable chunking of bid requests to control number of ads per request - Maintain backward compatibility - Add comprehensive test coverage for all new features Results of gulp lint: ✅ No issues Results of gulp test: ✅ All tests pass * Smartytech bid adapter. Avoid send undefined for gdprApplies and guarantee for config get
1 parent 7c10330 commit 1b3f979

File tree

2 files changed

+206
-23
lines changed

2 files changed

+206
-23
lines changed

modules/smartytechBidAdapter.js

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import {buildUrl, deepAccess} from '../src/utils.js'
1+
import {buildUrl, deepAccess, isArray} from '../src/utils.js'
22
import { BANNER, VIDEO } from '../src/mediaTypes.js';
33
import {registerBidder} from '../src/adapters/bidderFactory.js';
4+
import {config} from '../src/config.js';
5+
import {chunk} from '../libraries/chunk/chunk.js';
46

57
const BIDDER_CODE = 'smartytech';
68
export const ENDPOINT_PROTOCOL = 'https';
@@ -80,20 +82,60 @@ export const spec = {
8082
}
8183
}
8284

85+
// Add user IDs if available
86+
const userIds = deepAccess(validBidRequest, 'userIdAsEids');
87+
if (userIds && isArray(userIds) && userIds.length > 0) {
88+
oneRequest.userIds = userIds;
89+
}
90+
91+
// Add GDPR consent if available
92+
if (bidderRequest && bidderRequest.gdprConsent) {
93+
oneRequest.gdprConsent = {
94+
consentString: bidderRequest.gdprConsent.consentString || ''
95+
};
96+
97+
if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') {
98+
oneRequest.gdprConsent.gdprApplies = bidderRequest.gdprConsent.gdprApplies;
99+
}
100+
101+
if (bidderRequest.gdprConsent.addtlConsent) {
102+
oneRequest.gdprConsent.addtlConsent = bidderRequest.gdprConsent.addtlConsent;
103+
}
104+
}
105+
106+
// Add CCPA/USP consent if available
107+
if (bidderRequest && bidderRequest.uspConsent) {
108+
oneRequest.uspConsent = bidderRequest.uspConsent;
109+
}
110+
111+
// Add COPPA flag if configured
112+
const coppa = config.getConfig('coppa');
113+
if (coppa) {
114+
oneRequest.coppa = coppa;
115+
}
116+
83117
return oneRequest
84118
});
85119

86-
const adPartnerRequestUrl = buildUrl({
120+
const smartytechRequestUrl = buildUrl({
87121
protocol: ENDPOINT_PROTOCOL,
88122
hostname: ENDPOINT_DOMAIN,
89123
pathname: ENDPOINT_PATH,
90124
});
91125

92-
return {
126+
// Get chunk size from adapter configuration
127+
const adapterSettings = config.getConfig(BIDDER_CODE) || {};
128+
const chunkSize = deepAccess(adapterSettings, 'chunkSize', 10);
129+
130+
// Split bid requests into chunks
131+
const bidChunks = chunk(bidRequests, chunkSize);
132+
133+
// Return array of request objects, one for each chunk
134+
return bidChunks.map(bidChunk => ({
93135
method: 'POST',
94-
url: adPartnerRequestUrl,
95-
data: bidRequests
96-
};
136+
url: smartytechRequestUrl,
137+
data: bidChunk
138+
}));
97139
},
98140

99141
interpretResponse: function (serverResponse, bidRequest) {

test/spec/modules/smartytechBidAdapter_spec.js

Lines changed: 158 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,43 @@ function mockRefererData() {
182182
}
183183
}
184184

185+
function mockBidderRequestWithConsents() {
186+
return {
187+
refererInfo: {
188+
page: 'https://some-test.page'
189+
},
190+
gdprConsent: {
191+
gdprApplies: true,
192+
consentString: 'COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA',
193+
addtlConsent: '1~1.35.41.101'
194+
},
195+
uspConsent: '1YNN'
196+
}
197+
}
198+
199+
function mockBidRequestWithUserIds(mediaType, size, customSizes) {
200+
const requests = mockBidRequestListData(mediaType, size, customSizes);
201+
return requests.map(request => ({
202+
...request,
203+
userIdAsEids: [
204+
{
205+
source: 'unifiedid.com',
206+
uids: [{
207+
id: 'test-unified-id',
208+
atype: 1
209+
}]
210+
},
211+
{
212+
source: 'pubcid.org',
213+
uids: [{
214+
id: 'test-pubcid',
215+
atype: 1
216+
}]
217+
}
218+
]
219+
}));
220+
}
221+
185222
function mockResponseData(requestData) {
186223
const data = {}
187224
requestData.data.forEach((request, index) => {
@@ -229,20 +266,25 @@ describe('SmartyTechDSPAdapter: buildRequests', () => {
229266
});
230267
it('correct request URL', () => {
231268
const request = spec.buildRequests(mockBidRequest, mockReferer);
232-
expect(request.url).to.be.equal(`${ENDPOINT_PROTOCOL}://${ENDPOINT_DOMAIN}${ENDPOINT_PATH}`)
269+
request.forEach(req => {
270+
expect(req.url).to.be.equal(`${ENDPOINT_PROTOCOL}://${ENDPOINT_DOMAIN}${ENDPOINT_PATH}`)
271+
});
233272
});
234273
it('correct request method', () => {
235274
const request = spec.buildRequests(mockBidRequest, mockReferer);
236-
expect(request.method).to.be.equal(`POST`)
275+
request.forEach(req => {
276+
expect(req.method).to.be.equal(`POST`)
277+
});
237278
});
238279
it('correct request data', () => {
239-
const data = spec.buildRequests(mockBidRequest, mockReferer).data;
240-
data.forEach((request, index) => {
241-
expect(request.adUnitCode).to.be.equal(mockBidRequest[index].adUnitCode);
242-
expect(request.banner).to.be.equal(mockBidRequest[index].mediaTypes.banner);
243-
expect(request.bidId).to.be.equal(mockBidRequest[index].bidId);
244-
expect(request.endpointId).to.be.equal(mockBidRequest[index].params.endpointId);
245-
expect(request.referer).to.be.equal(mockReferer.refererInfo.page);
280+
const request = spec.buildRequests(mockBidRequest, mockReferer);
281+
const data = request.flatMap(resp => resp.data);
282+
data.forEach((req, index) => {
283+
expect(req.adUnitCode).to.be.equal(mockBidRequest[index].adUnitCode);
284+
expect(req.banner).to.be.equal(mockBidRequest[index].mediaTypes.banner);
285+
expect(req.bidId).to.be.equal(mockBidRequest[index].bidId);
286+
expect(req.endpointId).to.be.equal(mockBidRequest[index].params.endpointId);
287+
expect(req.referer).to.be.equal(mockReferer.refererInfo.page);
246288
})
247289
});
248290
});
@@ -256,9 +298,10 @@ describe('SmartyTechDSPAdapter: buildRequests banner custom size', () => {
256298
});
257299

258300
it('correct request data', () => {
259-
const data = spec.buildRequests(mockBidRequest, mockReferer).data;
260-
data.forEach((request, index) => {
261-
expect(request.banner.sizes).to.be.equal(mockBidRequest[index].params.sizes);
301+
const request = spec.buildRequests(mockBidRequest, mockReferer);
302+
const data = request.flatMap(resp => resp.data);
303+
data.forEach((req, index) => {
304+
expect(req.banner.sizes).to.be.equal(mockBidRequest[index].params.sizes);
262305
})
263306
});
264307
});
@@ -272,9 +315,10 @@ describe('SmartyTechDSPAdapter: buildRequests video custom size', () => {
272315
});
273316

274317
it('correct request data', () => {
275-
const data = spec.buildRequests(mockBidRequest, mockReferer).data;
276-
data.forEach((request, index) => {
277-
expect(request.video.sizes).to.be.equal(mockBidRequest[index].params.sizes);
318+
const request = spec.buildRequests(mockBidRequest, mockReferer);
319+
const data = request.flatMap(resp => resp.data);
320+
data.forEach((req, index) => {
321+
expect(req.video.sizes).to.be.equal(mockBidRequest[index].params.sizes);
278322
})
279323
});
280324
});
@@ -287,7 +331,7 @@ describe('SmartyTechDSPAdapter: interpretResponse', () => {
287331
beforeEach(() => {
288332
const brData = mockBidRequestListData('banner', 2, []);
289333
mockReferer = mockRefererData();
290-
request = spec.buildRequests(brData, mockReferer);
334+
request = spec.buildRequests(brData, mockReferer)[0];
291335
mockBidRequest = {
292336
data: brData
293337
}
@@ -333,7 +377,7 @@ describe('SmartyTechDSPAdapter: interpretResponse video', () => {
333377
beforeEach(() => {
334378
const brData = mockBidRequestListData('video', 2, []);
335379
mockReferer = mockRefererData();
336-
request = spec.buildRequests(brData, mockReferer);
380+
request = spec.buildRequests(brData, mockReferer)[0];
337381
mockBidRequest = {
338382
data: brData
339383
}
@@ -359,3 +403,100 @@ describe('SmartyTechDSPAdapter: interpretResponse video', () => {
359403
});
360404
});
361405
});
406+
407+
describe('SmartyTechDSPAdapter: buildRequests with user IDs', () => {
408+
let mockBidRequest;
409+
let mockReferer;
410+
beforeEach(() => {
411+
mockBidRequest = mockBidRequestWithUserIds('banner', 2, []);
412+
mockReferer = mockRefererData();
413+
});
414+
415+
it('should include userIds when available', () => {
416+
const request = spec.buildRequests(mockBidRequest, mockReferer);
417+
const data = request.flatMap(resp => resp.data);
418+
419+
data.forEach((req, index) => {
420+
expect(req).to.have.property('userIds');
421+
expect(req.userIds).to.deep.equal(mockBidRequest[index].userIdAsEids);
422+
});
423+
});
424+
425+
it('should not include userIds when not available', () => {
426+
const bidRequestWithoutUserIds = mockBidRequestListData('banner', 2, []);
427+
const request = spec.buildRequests(bidRequestWithoutUserIds, mockReferer);
428+
const data = request.flatMap(resp => resp.data);
429+
430+
data.forEach((req) => {
431+
expect(req).to.not.have.property('userIds');
432+
});
433+
});
434+
435+
it('should not include userIds when userIdAsEids is undefined', () => {
436+
const bidRequestWithUndefinedUserIds = mockBidRequestListData('banner', 2, []).map(req => {
437+
const {userIdAsEids, ...requestWithoutUserIds} = req;
438+
return requestWithoutUserIds;
439+
});
440+
const request = spec.buildRequests(bidRequestWithUndefinedUserIds, mockReferer);
441+
const data = request.flatMap(resp => resp.data);
442+
443+
data.forEach((req) => {
444+
expect(req).to.not.have.property('userIds');
445+
});
446+
});
447+
448+
it('should not include userIds when userIdAsEids is empty array', () => {
449+
const bidRequestWithEmptyUserIds = mockBidRequestListData('banner', 2, []).map(req => ({
450+
...req,
451+
userIdAsEids: []
452+
}));
453+
const request = spec.buildRequests(bidRequestWithEmptyUserIds, mockReferer);
454+
const data = request.flatMap(resp => resp.data);
455+
456+
data.forEach((req) => {
457+
expect(req).to.not.have.property('userIds');
458+
});
459+
});
460+
});
461+
462+
describe('SmartyTechDSPAdapter: buildRequests with consent data', () => {
463+
let mockBidRequest;
464+
let mockBidderRequest;
465+
beforeEach(() => {
466+
mockBidRequest = mockBidRequestListData('banner', 2, []);
467+
mockBidderRequest = mockBidderRequestWithConsents();
468+
});
469+
470+
it('should include GDPR consent when available', () => {
471+
const request = spec.buildRequests(mockBidRequest, mockBidderRequest);
472+
const data = request.flatMap(resp => resp.data);
473+
474+
data.forEach((req) => {
475+
expect(req).to.have.property('gdprConsent');
476+
expect(req.gdprConsent.gdprApplies).to.be.true;
477+
expect(req.gdprConsent.consentString).to.equal('COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA');
478+
expect(req.gdprConsent.addtlConsent).to.equal('1~1.35.41.101');
479+
});
480+
});
481+
482+
it('should include USP consent when available', () => {
483+
const request = spec.buildRequests(mockBidRequest, mockBidderRequest);
484+
const data = request.flatMap(resp => resp.data);
485+
486+
data.forEach((req) => {
487+
expect(req).to.have.property('uspConsent');
488+
expect(req.uspConsent).to.equal('1YNN');
489+
});
490+
});
491+
492+
it('should not include consent data when not available', () => {
493+
const mockReferer = mockRefererData();
494+
const request = spec.buildRequests(mockBidRequest, mockReferer);
495+
const data = request.flatMap(resp => resp.data);
496+
497+
data.forEach((req) => {
498+
expect(req).to.not.have.property('gdprConsent');
499+
expect(req).to.not.have.property('uspConsent');
500+
});
501+
});
502+
});

0 commit comments

Comments
 (0)