diff --git a/modules/adriverBidAdapter.js b/modules/adriverBidAdapter.js
new file mode 100644
index 00000000000..af0a401b355
--- /dev/null
+++ b/modules/adriverBidAdapter.js
@@ -0,0 +1,188 @@
+// ADRIVER BID ADAPTER for Prebid 1.13
+import * as utils from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+
+const BIDDER_CODE = 'adriver';
+const ADRIVER_BID_URL = 'https://pb.adriver.ru/cgi-bin/bid.cgi';
+const TIME_TO_LIVE = 3000;
+
+export const spec = {
+
+ code: BIDDER_CODE,
+
+ /**
+ * Determines whether or not the given bid request is valid.
+ *
+ * @param {object} bid The bid to validate.
+ * @return boolean True if this is a valid bid, and false otherwise.
+ */
+ isBidRequestValid: function (bid) {
+ return !!bid.params.siteid;
+ },
+
+ buildRequests: function (validBidRequests) {
+ utils.logInfo('validBidRequests', validBidRequests);
+
+ let win = utils.getWindowLocation();
+ let customID = Math.round(Math.random() * 999999999) + '-' + Math.round(new Date() / 1000) + '-1-46-';
+ let siteId = utils.getBidIdParameter('siteid', validBidRequests[0].params) + '';
+ let currency = utils.getBidIdParameter('currency', validBidRequests[0].params);
+ currency = 'RUB';
+
+ const payload = {
+ 'at': 1,
+ 'cur': [currency],
+ 'site': {
+ 'name': win.origin,
+ 'domain': win.hostname,
+ 'id': siteId,
+ 'page': win.href
+ },
+ 'id': customID,
+ 'user': {
+ 'buyerid': 0
+ },
+ 'device': {
+ 'ip': '195.209.111.14',
+ 'ua': window.navigator.userAgent
+ },
+ 'imp': []
+ };
+
+ utils._each(validBidRequests, (bid) => {
+ utils._each(bid.sizes, (sizes) => {
+ let width;
+ let height;
+ let par;
+
+ let floorAndCurrency = _getFloor(bid, currency, sizes);
+
+ let bidFloor = floorAndCurrency.floor;
+ let dealId = utils.getBidIdParameter('dealid', bid.params);
+ if (typeof sizes[0] === 'number' && typeof sizes[1] === 'number') {
+ width = sizes[0];
+ height = sizes[1];
+ }
+ par = {
+ 'id': bid.params.placementId,
+ 'ext': {'query': 'bn=15&custom=111=' + bid.bidId},
+ 'banner': {
+ 'w': width || undefined,
+ 'h': height || undefined
+ },
+ 'bidfloor': bidFloor || 0,
+ 'bidfloorcur': floorAndCurrency.currency,
+ 'secure': 0
+ };
+ if (dealId) {
+ par.pmp = {
+ 'private_auction': 1,
+ 'deals': [{
+ 'id': dealId,
+ 'bidfloor': bidFloor || 0,
+ 'bidfloorcur': currency
+ }]
+ };
+ }
+ utils.logInfo('par', par);
+ payload.imp.push(par);
+ });
+ });
+
+ const payloadString = JSON.stringify(payload);
+
+ return {
+ method: 'POST',
+ url: ADRIVER_BID_URL,
+ data: payloadString,
+ };
+ },
+
+ interpretResponse: function (serverResponse, bidRequest) {
+ utils.logInfo('serverResponse.body.seatbid', serverResponse.body.seatbid);
+ const bidResponses = [];
+ let nurl = 0;
+ utils._each(serverResponse.body.seatbid, (seatbid) => {
+ utils.logInfo('_each', seatbid);
+ var bid = seatbid.bid[0];
+ if (bid.nurl !== undefined) {
+ nurl = bid.nurl.split('://');
+ nurl = window.location.protocol + '//' + nurl[1];
+ nurl = nurl.replace(/\$\{AUCTION_PRICE\}/, bid.price);
+ }
+
+ if (bid.price >= 0 && bid.impid !== undefined && nurl !== 0 && bid.dealid === undefined) {
+ let bidResponse = {
+ requestId: bid.ext || undefined,
+ cpm: bid.price,
+ width: bid.w,
+ height: bid.h,
+ creativeId: bid.impid || undefined,
+ currency: serverResponse.body.cur,
+ netRevenue: true,
+ ttl: TIME_TO_LIVE,
+ meta: {
+ advertiserDomains: bid.adomain
+ },
+ ad: ''
+ };
+ utils.logInfo('bidResponse', bidResponse);
+ bidResponses.push(bidResponse);
+ }
+ });
+ return bidResponses;
+ }
+
+};
+registerBidder(spec);
+
+/**
+ * Gets bidfloor
+ * @param {Object} bid
+ * @param currencyPar
+ * @param sizes
+ * @returns {Object} floor
+ */
+function _getFloor(bid, currencyPar, sizes) {
+ const curMediaType = bid.mediaTypes && bid.mediaTypes.video ? 'video' : 'banner';
+ let floor = 0;
+ const currency = currencyPar || 'RUB';
+
+ let currencyResult = '';
+
+ let isSize = false;
+
+ if (typeof sizes[0] === 'number' && typeof sizes[1] === 'number') {
+ isSize = true;
+ }
+
+ if (typeof bid.getFloor === 'function') {
+ const floorInfo = bid.getFloor({
+ currency: currency,
+ mediaType: curMediaType,
+ size: isSize ? sizes : '*'
+ });
+
+ if (typeof floorInfo === 'object' &&
+ !isNaN(parseFloat(floorInfo.floor))) {
+ floor = floorInfo.floor;
+ }
+
+ if (typeof floorInfo === 'object' && floorInfo.currency) {
+ currencyResult = floorInfo.currency;
+ }
+ }
+
+ if (!currencyResult) {
+ currencyResult = currency;
+ }
+
+ if (floor == null) {
+ floor = 0;
+ }
+
+ return {
+ floor: floor,
+ currency: currencyResult
+ };
+}
diff --git a/modules/adriverBidAdapter.md b/modules/adriverBidAdapter.md
new file mode 100644
index 00000000000..e5a8af28647
--- /dev/null
+++ b/modules/adriverBidAdapter.md
@@ -0,0 +1,20 @@
+# Overview
+
+Module Name: AdRiver Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: support@adriver.ru
+
+# Description
+
+Module that connects to AdRiver's demand sources.
+
+# Test Parameters
+
+bids: [{
+ bidder: 'adriver',
+ params: {
+ siteid: '216200',
+ placementId: '55:test_placement',
+ dealid: 'dealidTest'
+ }
+}]
diff --git a/test/spec/modules/adriverBidAdapter_spec.js b/test/spec/modules/adriverBidAdapter_spec.js
new file mode 100644
index 00000000000..c16bc5df5cb
--- /dev/null
+++ b/test/spec/modules/adriverBidAdapter_spec.js
@@ -0,0 +1,323 @@
+import { expect } from 'chai';
+import { spec } from 'modules/adriverBidAdapter.js';
+import { newBidder } from 'src/adapters/bidderFactory.js';
+import * as bidderFactory from 'src/adapters/bidderFactory.js';
+import { auctionManager } from 'src/auctionManager.js';
+const ENDPOINT = 'https://pb.adriver.ru/cgi-bin/bid.cgi';
+
+describe('adriverAdapter', function () {
+ const adapter = newBidder(spec);
+
+ describe('inherited functions', function () {
+ it('exists and is a function', function () {
+ expect(adapter.callBids).to.exist.and.to.be.a('function');
+ });
+ });
+
+ describe('isBidRequestValid', function () {
+ let bid = {
+ 'bidder': 'adriver',
+ 'params': {
+ 'placementId': '55:test_placement',
+ 'siteid': 'testSiteID'
+ },
+ 'adUnitCode': 'adunit-code',
+ 'sizes': [[300, 250], [300, 600], [300, 250]],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475',
+ };
+
+ it('should return true when required params found', function () {
+ expect(spec.isBidRequestValid(bid)).to.equal(true);
+ });
+ });
+
+ describe('buildRequests', function () {
+ let getAdUnitsStub;
+ const floor = 3;
+
+ let bidRequests = [
+ {
+ 'bidder': 'adriver',
+ 'params': {
+ 'placementId': '55:test_placement',
+ 'siteid': 'testSiteID',
+ 'dealid': 'dealidTest'
+ },
+ 'adUnitCode': 'adunit-code',
+ 'sizes': [[300, 250], [300, 600], [300, 250]],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475',
+ 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843'
+ }
+ ];
+
+ let floorTestData = {
+ 'currency': 'USD',
+ 'floor': floor
+ };
+ bidRequests[0].getFloor = _ => {
+ return floorTestData;
+ };
+
+ beforeEach(function() {
+ getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() {
+ return [];
+ });
+ });
+
+ afterEach(function() {
+ getAdUnitsStub.restore();
+ });
+
+ it('should exist currency', function () {
+ const request = spec.buildRequests(bidRequests);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.cur).to.exist;
+ });
+
+ it('should exist at', function () {
+ const request = spec.buildRequests(bidRequests);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.at).to.exist;
+ expect(payload.at).to.deep.equal(1);
+ });
+
+ it('should parse imp', function () {
+ const request = spec.buildRequests(bidRequests);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.imp[0]).to.exist;
+ expect(payload.imp[0].id).to.deep.equal('55:test_placement');
+
+ expect(payload.imp[0].ext).to.exist;
+ expect(payload.imp[0].ext.query).to.deep.equal('bn=15&custom=111=' + '30b31c1838de1e');
+
+ expect(payload.imp[0].banner).to.exist;
+ expect(payload.imp[0].banner.w).to.deep.equal(300);
+ expect(payload.imp[0].banner.h).to.deep.equal(250);
+ });
+
+ it('should parse pmp', function () {
+ const request = spec.buildRequests(bidRequests);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.imp[0].pmp).to.exist;
+
+ expect(payload.imp[0].pmp.deals).to.exist;
+
+ expect(payload.imp[0].pmp.deals[0].bidfloor).to.exist;
+ expect(payload.imp[0].pmp.deals[0].bidfloor).to.deep.equal(3);
+
+ expect(payload.imp[0].pmp.deals[0].bidfloorcur).to.exist;
+ expect(payload.imp[0].pmp.deals[0].bidfloorcur).to.deep.equal('RUB');
+ });
+
+ it('sends bid request to ENDPOINT via POST', function () {
+ const request = spec.buildRequests(bidRequests);
+ expect(request.url).to.equal(ENDPOINT);
+ expect(request.method).to.equal('POST');
+ });
+ });
+
+ describe('interpretResponse', function () {
+ let bfStub;
+ before(function() {
+ bfStub = sinon.stub(bidderFactory, 'getIabSubCategory');
+ });
+
+ after(function() {
+ bfStub.restore();
+ });
+
+ let response = {
+ 'id': '221594457-1615288400-1-46-',
+ 'bidid': 'D8JW8XU8-L5m7qFMNQGs7i1gcuPvYMEDOKsktw6e9uLy5Eebo9HftVXb0VpKj4R2dXa93i6QmRhjextJVM4y1SqodMAh5vFOb_eVkHA',
+ 'seatbid': [{
+ 'bid': [{
+ 'id': '1',
+ 'impid': '/19968336/header-bid-tag-0',
+ 'price': 4.29,
+ 'h': 250,
+ 'w': 300,
+ 'adid': '7121351',
+ 'adomain': ['http://ikea.com'],
+ 'nurl': 'https://ad.adriver.ru/cgi-bin/erle.cgi?expid=D8JW8XU8-L5m7qFMNQGs7i1gcuPvYMEDOKsktw6e9uLy5Eebo9HftVXb0VpKj4R2dXa93i6QmRhjextJVM4y1SqodMAh5vFOb_eVkHA&bid=7121351&wprc=4.29&tuid=-1&custom=207=/19968336/header-bid-tag-0',
+ 'cid': '717570',
+ 'ext': '2c262a7058758d'
+ }]
+ }, {
+ 'bid': [{
+ 'id': '1',
+ 'impid': '/19968336/header-bid-tag-0',
+ 'price': 17.67,
+ 'h': 600,
+ 'w': 300,
+ 'adid': '7121369',
+ 'adomain': ['http://ikea.com'],
+ 'nurl': 'https://ad.adriver.ru/cgi-bin/erle.cgi?expid=DdtToXX5cpTaMMxrJSEsOsUIXt3WmC3jOvuNI5DguDrY8edFG60Jg1M-iMkVNKQ4OiAdHSLPJLQQXMUXZfI9VbjMoGCb-zzOTPiMpshI&bid=7121369&wprc=17.67&tuid=-1&custom=207=/19968336/header-bid-tag-0',
+ 'cid': '717570',
+ 'ext': '2c262a7058758d'
+ }]
+ }],
+ 'cur': 'RUB'
+ };
+
+ it('should get correct bid response', function () {
+ let expectedResponse = [
+ {
+ requestId: '2c262a7058758d',
+ cpm: 4.29,
+ width: 300,
+ height: 250,
+ creativeId: '/19968336/header-bid-tag-0',
+ currency: 'RUB',
+ netRevenue: true,
+ ttl: 3000,
+ meta: {
+ advertiserDomains: ['http://ikea.com']
+ },
+ ad: ''
+ }
+ ];
+ let bidderRequest = {
+ bids: [{
+ bidId: '3db3773286ee59',
+ adUnitCode: 'code'
+ }]
+ };
+ let result = spec.interpretResponse({ body: response }, {bidderRequest});
+ expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0]));
+ });
+
+ it('handles nobid responses', function () {
+ let response = {
+ 'version': '0.0.1',
+ 'tags': [{
+ 'uuid': '84ab500420319d',
+ 'tag_id': 5976557,
+ 'auction_id': '297492697822162468',
+ 'nobid': true
+ }]
+ };
+ let bidderRequest;
+
+ let result = spec.interpretResponse({ body: response }, {bidderRequest});
+ expect(result.length).to.equal(0);
+ });
+ });
+
+ describe('function _getFloor', function () {
+ let bidRequests = [
+ {
+ bidder: 'adriver',
+ params: {
+ placementId: '55:test_placement',
+ siteid: 'testSiteID',
+ dealid: 'dealidTest',
+ },
+ adUnitCode: 'adunit-code',
+ sizes: [[300, 250], [300, 600], [300, 250]],
+ bidId: '30b31c1838de1e',
+ bidderRequestId: '22edbae2733bf6',
+ auctionId: '1d1a030790a475',
+ transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843'
+ }
+ ];
+
+ const floorTestData = {
+ 'currency': 'RUB',
+ 'floor': 1.50
+ };
+
+ const bitRequestStandard = JSON.parse(JSON.stringify(bidRequests));
+
+ bitRequestStandard[0].getFloor = () => {
+ return floorTestData;
+ };
+
+ it('valid BidRequests', function () {
+ const request = spec.buildRequests(bitRequestStandard);
+ const payload = JSON.parse(request.data);
+
+ expect(typeof bitRequestStandard[0].getFloor).to.equal('function');
+ expect(payload.imp[0].bidfloor).to.equal(1.50);
+ expect(payload.imp[0].bidfloorcur).to.equal('RUB');
+ });
+
+ const bitRequestEmptyCurrency = JSON.parse(JSON.stringify(bidRequests));
+
+ const floorTestDataEmptyCurrency = {
+ 'currency': 'RUB',
+ 'floor': 1.50
+ };
+
+ bitRequestEmptyCurrency[0].getFloor = () => {
+ return floorTestDataEmptyCurrency;
+ };
+
+ it('empty currency', function () {
+ const request = spec.buildRequests(bitRequestEmptyCurrency);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.imp[0].bidfloor).to.equal(1.50);
+ expect(payload.imp[0].bidfloorcur).to.equal('RUB');
+ });
+
+ const bitRequestFloorNull = JSON.parse(JSON.stringify(bidRequests));
+
+ const floorTestDataFloorNull = {
+ 'currency': '',
+ 'floor': null
+ };
+
+ bitRequestFloorNull[0].getFloor = () => {
+ return floorTestDataFloorNull;
+ };
+
+ it('empty floor', function () {
+ const request = spec.buildRequests(bitRequestFloorNull);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.imp[0].bidfloor).to.equal(0);
+ });
+
+ const bitRequestGetFloorNotFunction = JSON.parse(JSON.stringify(bidRequests));
+
+ bitRequestGetFloorNotFunction[0].getFloor = 0;
+
+ it('bid.getFloor is not a function', function () {
+ const request = spec.buildRequests(bitRequestGetFloorNotFunction);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.imp[0].bidfloor).to.equal(0);
+ expect(payload.imp[0].bidfloorcur).to.equal('RUB');
+ });
+
+ const bitRequestGetFloorBySized = JSON.parse(JSON.stringify(bidRequests));
+
+ bitRequestGetFloorBySized[0].getFloor = (requestParams = {currency: 'USD', mediaType: '*', size: '*'}) => {
+ if (requestParams.size.length === 2 && requestParams.size[0] === 300 && requestParams.size[1] === 250) {
+ return {
+ 'currency': 'RUB',
+ 'floor': 3.33
+ }
+ } else {
+ return {}
+ }
+ };
+
+ it('bid.getFloor get size', function () {
+ const request = spec.buildRequests(bitRequestGetFloorBySized);
+ const payload = JSON.parse(request.data);
+
+ expect(payload.imp[0].bidfloor).to.equal(3.33);
+ expect(payload.imp[0].bidfloorcur).to.equal('RUB');
+ expect(payload.imp[0].bidfloorcur).to.equal('RUB');
+ });
+ });
+});