@mediarithmics/plugins-nodejs-sdk
Version:
This is the mediarithmics nodejs to help plugin developers bootstrapping their plugin without having to deal with most of the plugin boilerplate
396 lines (350 loc) • 14.2 kB
text/typescript
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unused-vars */
import 'mocha';
import { expect } from 'chai';
import sinon from 'sinon';
import request from 'supertest';
import { core } from '../';
import { PropertiesWrapper } from '../mediarithmics';
import { generateEncodedClickUrl } from '../mediarithmics/plugins/ad-renderer/utils/index';
const PLUGIN_AUTHENTICATION_TOKEN = 'Manny';
const PLUGIN_WORKER_ID = 'Calavera';
// set by the plugin runner in production
process.env.PLUGIN_AUTHENTICATION_TOKEN = PLUGIN_AUTHENTICATION_TOKEN;
process.env.PLUGIN_WORKER_ID = PLUGIN_WORKER_ID;
describe('Fetch DisplayAd API', () => {
const requestPromiseProx: sinon.SinonStub = sinon.stub().returns(Promise.resolve('Yolo'));
class MyFakeAdRenderer extends core.AdRendererBasePlugin<core.AdRendererBaseInstanceContext> {
protected async onAdContents(request: core.AdRendererRequest, instanceContext: core.AdRendererBaseInstanceContext) {
const result: core.AdRendererPluginResponse = {
html: `All your HTML is belong to us.`,
};
return Promise.resolve(result);
}
}
//All the magic is here
const plugin = new MyFakeAdRenderer(false);
const runner = new core.TestingPluginRunner(plugin, requestPromiseProx);
it('Check that creativeId is passed correctly in fetchDisplayAd', function (done) {
const fakeCreativeId = '422';
// Creative stub
const creative: core.DataResponse<core.DisplayAd> = {
status: 'ok',
data: {
type: 'DISPLAY_AD',
id: '422',
organisation_id: '1126',
name: 'Toto',
technical_name: 'hello',
archived: false,
editor_version_id: '5',
editor_version_value: '1.0.0',
editor_group_id: 'com.mediarithmics.creative.display',
editor_artifact_id: 'default-editor',
editor_plugin_id: '5',
renderer_version_id: '1054',
renderer_version_value: '1.0.0',
renderer_group_id: 'com.trololo.creative.display',
renderer_artifact_id: 'multi-advertisers-display-ad-renderer',
renderer_plugin_id: '1041',
creation_date: 1492785056278,
subtype: 'BANNER',
format: '300x250',
},
};
requestPromiseProx.withArgs(sinon.match.has('uri', sinon.match(/\/v1\/creatives\/(.){1,10}$/))).returns(creative);
// We try a call to the Gateway
void plugin.fetchDisplayAd(fakeCreativeId).then(() => {
expect(requestPromiseProx.args[0][0].uri).to.be.eq(
`${plugin.outboundPlatformUrl}/v1/creatives/${fakeCreativeId}`,
);
done();
});
});
it('Check that fakeCreativeId is passed correctly in fetchDisplayAdProperties', function (done) {
const fakeCreativeId = '4255';
// We try a call to the Gateway
void plugin.fetchDisplayAdProperties(fakeCreativeId).then(() => {
expect(requestPromiseProx.args[1][0].uri).to.be.eq(
`${plugin.outboundPlatformUrl}/v1/creatives/${fakeCreativeId}/renderer_properties`,
);
done();
});
});
afterEach(() => {
// We clear the cache so that we don't have any processing still running in the background
runner.plugin.pluginCache.clear();
});
});
describe('Ad Contents API test', function () {
// Fake AdRenderer with dummy processing
class MyFakeAdRenderer2 extends core.AdRendererBasePlugin<core.AdRendererBaseInstanceContext> {
protected async onAdContents(request: core.AdRendererRequest, instanceContext: core.AdRendererBaseInstanceContext) {
const response: core.AdRendererPluginResponse = {
html: request.call_id,
};
return Promise.resolve(response);
}
}
const plugin = new MyFakeAdRenderer2(false);
let runner: core.TestingPluginRunner;
it('Check that the plugin is giving good results with a simple adContents handler', function (done) {
const rpMockup = sinon.stub();
rpMockup.onCall(0).returns(
new Promise((resolve, reject) => {
const pluginInfo: core.DataResponse<core.Creative> = {
status: 'ok',
data: {
type: 'DISPLAY_AD',
id: '7168',
organisation_id: '1126',
name: 'Toto',
technical_name: 'hello',
archived: false,
editor_version_id: '5',
editor_version_value: '1.0.0',
editor_group_id: 'com.mediarithmics.creative.display',
editor_artifact_id: 'default-editor',
editor_plugin_id: '5',
renderer_version_id: '1054',
renderer_version_value: '1.0.0',
renderer_group_id: 'com.trololo.creative.display',
renderer_artifact_id: 'multi-advertisers-display-ad-renderer',
renderer_plugin_id: '1041',
creation_date: 1492785056278,
subtype: 'BANNER',
},
};
resolve(pluginInfo);
}),
);
rpMockup.onCall(1).returns(
new Promise((resolve, reject) => {
const pluginInfo: core.PluginPropertyResponse = {
status: 'ok',
count: 45,
data: [
{
technical_name: 'hello_world',
value: {
value: 'Yay',
},
property_type: 'STRING',
origin: 'PLUGIN',
writable: true,
deletable: false,
},
],
};
resolve(pluginInfo);
}),
);
runner = new core.TestingPluginRunner(plugin, rpMockup);
const requestBody = {
call_id: 'auc:goo:58346725000689de0a16ac4f120ecc41-0',
context: 'LIVE',
creative_id: '2757',
campaign_id: '1537',
ad_group_id: '1622',
protocol: 'https',
user_agent: 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MALCJS; rv:11.0) like Gecko',
user_agent_info: {
form_factor: 'PERSONAL_COMPUTER',
os_family: 'WINDOWS',
browser_family: 'IE',
brand: null,
model: null,
os_version: null,
carrier: null,
},
placeholder_id: 'mics_ed54e0e',
user_campaign_id: 'toto',
click_urls_info: [
{
url: 'https://ads.mediarithmics.com/ads/event?caid=auc%3Agoo%3A58346725000689de0a16ac4f120ecc41-0&ctx=LIVE&tid=1093&gid=1622&rid=2757&uaid=tech%3Agoo%3ACAESEANnikq25sbChKLHU7-o7ls&type=clk&ctid=%7B%7BMICS_AD_CONTENT_ID%7D%7D&redirect=',
redirect_count: 1,
},
{
url: 'https://adclick.g.doubleclick.net/aclk?sa=L&ai=CDypOJWc0WN6TGs_YWsGYu5AB4Kmf9UbfuK_coAPAjbcBEAEgAGDVjdOCvAiCARdjYS1wdWItNjE2Mzg1Nzk5Mjk1Njk2NMgBCakCNKXJyWPNsT7gAgCoAwGqBOkBT9DCltAKPa0ltaiH2E0CxRF2Jee8ykOBqRGHBbE8aYS7jODKKPHE3KkGbenZXwSan1UZekvmuIfSdRUg6DFQhnbJnMR_bK57BQlMaMnmd71MXTv6P9Hh0m5cuoj7SlpOoyMX9IG8mNomIve031sZUPKOb5QA_tVKhtrlnm2hYJ7KSVZJH_83YmpK_ShxuxIwiAwQKMhYBnM4tnbvEinl3fROiwH1FFNOlqNJPaNgU4z9kEGCHIpj3RLErIcrxmT5OFLZ3q5AELXCYBJP1zB-UvscTkLrfc3Vl-sOe5f5_Tkkn-MpcijM_Z_gBAGABvDqk_ivqMjMFaAGIagHpr4b2AcA0ggFCIBhEAE&num=1&sig=AOD64_3iMhOr3Xh-A4bP1jvMzeEMGFfwtw&client=ca-pub-6163857992956964&adurl=',
redirect_count: 1,
},
],
display_tracking_url:
'https://ads.mediarithmics.com/ads/event?caid=auc%3Agoo%3A58346725000689de0a16ac4f120ecc41-0&ctx=LIVE&tid=1093&gid=1622&rid=2757&uaid=tech%3Agoo%3ACAESEANnikq25sbChKLHU7-o7ls&type=imp&vid=4080&cb=ef3933a2-591b-4b1e-8fe2-4d9fd75980c4',
latitude: null,
longitude: null,
restrictions: { animation_max_duration: 25 },
};
void request(runner.plugin.app)
.post('/v1/ad_contents')
.send(requestBody)
.end(function (err, res) {
expect(res.status).to.equal(200);
expect(res.text).to.be.eq(requestBody.call_id);
done();
});
});
afterEach(() => {
// We clear the cache so that we don't have any processing still running in the background
runner.plugin.pluginCache.clear();
});
});
describe('Instance Context check', () => {
// Fake AdRenderer with dummy processing
class MyFakeAdRenderer extends core.AdRendererBasePlugin<core.AdRendererBaseInstanceContext> {
protected async instanceContextBuilder(creativeId: string) {
// We check if the method was called once or twice
ICStub();
const IC: core.AdRendererBaseInstanceContext = {
properties: new PropertiesWrapper([]),
displayAd: {
type: 'DISPLAY_AD',
id: '7168',
organisation_id: '1126',
name: 'Toto',
technical_name: undefined,
archived: false,
editor_version_id: '5',
editor_version_value: '1.0.0',
editor_group_id: 'com.mediarithmics.creative.display',
editor_artifact_id: 'default-editor',
editor_plugin_id: '5',
renderer_version_id: '1054',
renderer_version_value: '1.0.0',
renderer_group_id: 'com.trololo.creative.display',
renderer_artifact_id: 'multi-advertisers-display-ad-renderer',
renderer_plugin_id: '1041',
creation_date: 1492785056278,
subtype: 'BANNER',
format: '300x250',
},
};
return Promise.resolve(IC);
}
protected async onAdContents(request: core.AdRendererRequest, instanceContext: core.AdRendererBaseInstanceContext) {
const response: core.AdRendererPluginResponse = {
html: request.call_id,
};
return Promise.resolve(response);
}
}
const ICStub = sinon.stub();
const plugin = new MyFakeAdRenderer(false);
const runner = new core.TestingPluginRunner(plugin);
it('Check that the instanceContext is rebuilt at each call for PREVIEW', function (done) {
// Fake "Preview" AdCall
const adRequest: core.AdRendererRequest = {
call_id: 'auc:goo:58346725000689de0a16ac4f120ecc41-0',
context: 'PREVIEW',
creative_id: '2757',
campaign_id: '1537',
ad_group_id: '1622',
protocol: 'https',
user_agent_id: 'vec:42000',
user_agent: 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MALCJS; rv:11.0) like Gecko',
user_agent_info: {
form_factor: 'PERSONAL_COMPUTER',
os_family: 'WINDOWS',
browser_family: 'IE',
brand: undefined,
model: undefined,
os_version: undefined,
carrier: undefined,
},
placeholder_id: 'mics_ed54e0e',
user_campaign_id: 'toto',
click_urls_info: [
{
url: 'https://ads.mediarithmics.com/ads/event?caid=auc%3Agoo%3A58346725000689de0a16ac4f120ecc41-0&ctx=LIVE&tid=1093&gid=1622&rid=2757&uaid=tech%3Agoo%3ACAESEANnikq25sbChKLHU7-o7ls&type=clk&ctid=%7B%7BMICS_AD_CONTENT_ID%7D%7D&redirect=',
redirect_count: 1,
},
{
url: 'https://adclick.g.doubleclick.net/aclk?sa=L&ai=CDypOJWc0WN6TGs_YWsGYu5AB4Kmf9UbfuK_coAPAjbcBEAEgAGDVjdOCvAiCARdjYS1wdWItNjE2Mzg1Nzk5Mjk1Njk2NMgBCakCNKXJyWPNsT7gAgCoAwGqBOkBT9DCltAKPa0ltaiH2E0CxRF2Jee8ykOBqRGHBbE8aYS7jODKKPHE3KkGbenZXwSan1UZekvmuIfSdRUg6DFQhnbJnMR_bK57BQlMaMnmd71MXTv6P9Hh0m5cuoj7SlpOoyMX9IG8mNomIve031sZUPKOb5QA_tVKhtrlnm2hYJ7KSVZJH_83YmpK_ShxuxIwiAwQKMhYBnM4tnbvEinl3fROiwH1FFNOlqNJPaNgU4z9kEGCHIpj3RLErIcrxmT5OFLZ3q5AELXCYBJP1zB-UvscTkLrfc3Vl-sOe5f5_Tkkn-MpcijM_Z_gBAGABvDqk_ivqMjMFaAGIagHpr4b2AcA0ggFCIBhEAE&num=1&sig=AOD64_3iMhOr3Xh-A4bP1jvMzeEMGFfwtw&client=ca-pub-6163857992956964&adurl=',
redirect_count: 1,
},
],
display_tracking_url:
'https://ads.mediarithmics.com/ads/event?caid=auc%3Agoo%3A58346725000689de0a16ac4f120ecc41-0&ctx=LIVE&tid=1093&gid=1622&rid=2757&uaid=tech%3Agoo%3ACAESEANnikq25sbChKLHU7-o7ls&type=imp&vid=4080&cb=ef3933a2-591b-4b1e-8fe2-4d9fd75980c4',
latitude: undefined,
longitude: undefined,
restrictions: { animation_max_duration: 25 },
};
// Plugin log level to debug
void request(runner.plugin.app)
.put('/v1/log_level')
.send({ level: 'silly' })
.end((err, res) => {
expect(res.status).to.equal(200);
// First AdCall
void request(runner.plugin.app)
.post('/v1/ad_contents')
.send(adRequest)
.end((err, res) => {
expect(res.status).to.eq(200);
// Second AdCall
void request(runner.plugin.app)
.post('/v1/ad_contents')
.send(adRequest)
.end((err, res) => {
expect(res.status).to.eq(200);
// As it's a PREVIEW AdCall, we should have loaded the InstanceContext twice
expect(ICStub.callCount).to.eq(2);
done();
});
});
});
afterEach(() => {
// We clear the cache so that we don't have any processing still running in the background
runner.plugin.pluginCache.clear();
ICStub.reset();
});
});
});
describe('Click url encoding', function () {
it('should properly encode click url (check 1)', () => {
const url1 = 'http://foo.com';
const url2 = 'http://bar.com';
const url3 = 'http://baz.com';
const redirectUrls = [
{
url: url1,
redirect_count: 1,
},
{
url: url2,
redirect_count: 2,
},
{
url: url3,
redirect_count: 0,
},
];
const ur3Encoded = encodeURIComponent(encodeURIComponent(encodeURIComponent(url3)));
const ur2Encoded = encodeURIComponent(url2);
const result = generateEncodedClickUrl(redirectUrls);
expect(result).to.be.eq(`${url1}${ur2Encoded}${ur3Encoded}`);
});
it('should properly encode click url (check 2)', () => {
const url1 = 'http://foo.com';
const url2 = 'http://bar.com';
const url3 = 'http://baz.com';
const redirectUrls = [
{
url: url1,
redirect_count: 2,
},
{
url: url2,
redirect_count: 1,
},
{
url: url3,
redirect_count: 0,
},
];
const ur3Encoded = encodeURIComponent(encodeURIComponent(encodeURIComponent(url3)));
const ur2Encoded = encodeURIComponent(encodeURIComponent(url2));
const result = generateEncodedClickUrl(redirectUrls);
expect(result).to.be.eq(`${url1}${ur2Encoded}${ur3Encoded}`);
});
});