321 lines
10 KiB
JavaScript
321 lines
10 KiB
JavaScript
|
/*
|
||
|
* JavaScript tracker for Snowplow: tests/functional/helpers.js
|
||
|
*
|
||
|
* Significant portions copyright 2010 Anthon Pang. Remainder copyright
|
||
|
* 2012-2016 Snowplow Analytics Ltd. All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are
|
||
|
* met:
|
||
|
*
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
*
|
||
|
* * Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
*
|
||
|
* * Neither the name of Anthon Pang nor Snowplow Analytics Ltd nor the
|
||
|
* names of their contributors may be used to endorse or promote products
|
||
|
* derived from this software without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*/
|
||
|
|
||
|
define([
|
||
|
'intern!object',
|
||
|
'intern/chai!assert',
|
||
|
'intern/dojo/node!lodash',
|
||
|
'intern/dojo/node!http',
|
||
|
'intern/dojo/node!url',
|
||
|
"intern/dojo/node!js-base64"
|
||
|
], function(registerSuite, assert, lodash, http, url, jsBase64) {
|
||
|
var decodeBase64 = jsBase64.Base64.fromBase64;
|
||
|
|
||
|
/**
|
||
|
* Expected amount of request for each browser
|
||
|
* This must be increased when new tracking call added to
|
||
|
* pages/integration-template.html
|
||
|
*/
|
||
|
var log = [];
|
||
|
|
||
|
function pageViewsHaveDifferentIds () {
|
||
|
var pageViews = lodash.filter(log, function (logLine) {
|
||
|
return logLine.e === 'pv';
|
||
|
});
|
||
|
var contexts = lodash.map(pageViews, function (logLine) {
|
||
|
var data = JSON.parse(decodeBase64(logLine.cx)).data;
|
||
|
return lodash.find(data, function (context) {
|
||
|
return context.schema === 'iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0';
|
||
|
});
|
||
|
});
|
||
|
var ids = lodash.map(contexts, function (wpContext) {
|
||
|
return wpContext.data.id;
|
||
|
});
|
||
|
|
||
|
return lodash.uniq(ids).length >= 2;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if expected payload exists in `log`
|
||
|
*/
|
||
|
function checkExistenceOfExpectedQuerystring(expected) {
|
||
|
function compare(e, other) { // e === expected
|
||
|
var result = lodash.map(e, function (v, k) {
|
||
|
if (lodash.isFunction(v)) { return v(other[k]); }
|
||
|
else { return lodash.isEqual(v, other[k]); }
|
||
|
});
|
||
|
return lodash.every(result);
|
||
|
}
|
||
|
|
||
|
function strip(logLine) {
|
||
|
var expectedKeys = lodash.keys(expected);
|
||
|
var stripped = lodash.pickBy(logLine, function (v, k) { return lodash.includes(expectedKeys, k); });
|
||
|
if (lodash.keys(stripped).length !== expectedKeys.length) { return null; }
|
||
|
else { return stripped; }
|
||
|
}
|
||
|
|
||
|
return lodash.some(log, function (logLine) {
|
||
|
var stripped = strip(logLine);
|
||
|
if (stripped == null) { return false; }
|
||
|
else { return lodash.isEqualWith(expected, stripped, compare); }
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function someTestsFailed(suite) {
|
||
|
return lodash.some(suite.tests, function (test) { return test.error !== null; });
|
||
|
}
|
||
|
|
||
|
// Ngrok must be running to forward requests to localhost
|
||
|
http.createServer(function (request, response) {
|
||
|
response.writeHead(200, {'Content-Type': 'image/gif'});
|
||
|
|
||
|
if (request.method === 'GET') {
|
||
|
var payload = url.parse(request.url, true).query;
|
||
|
log.push(payload);
|
||
|
}
|
||
|
|
||
|
var img = new Buffer('47494638396101000100800000dbdfef00000021f90401000000002c00000000010001000002024401003b', 'hex');
|
||
|
response.end(img, 'binary');
|
||
|
|
||
|
}).listen(8500, function () { console.log("Collector mock running...\n"); });
|
||
|
|
||
|
registerSuite({
|
||
|
|
||
|
teardown: function () {
|
||
|
if (someTestsFailed(this)) {
|
||
|
console.log("Tests failed with following log:");
|
||
|
lodash.forEach(log, function (l) { console.log(l); });
|
||
|
}
|
||
|
console.log("Cleaning log");
|
||
|
log = [];
|
||
|
},
|
||
|
|
||
|
name: 'Test that request_recorder logs meet expectations',
|
||
|
|
||
|
'Check existence of page view in log': function () {
|
||
|
assert.isTrue(checkExistenceOfExpectedQuerystring({
|
||
|
e: 'pv',
|
||
|
p: 'mob',
|
||
|
aid: 'CFe23a',
|
||
|
uid: 'Malcolm',
|
||
|
page: 'My Title',
|
||
|
cx: function (cx) {
|
||
|
var contexts = JSON.parse(decodeBase64(cx)).data;
|
||
|
return lodash.some(contexts,
|
||
|
lodash.matches({
|
||
|
schema:"iglu:com.example_company/user/jsonschema/2-0-0",
|
||
|
data:{
|
||
|
userType:'tester'
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
}), 'A page view should be detected');
|
||
|
},
|
||
|
|
||
|
'Check nonexistence of nonexistent event types in log': function () {
|
||
|
assert.isFalse(checkExistenceOfExpectedQuerystring({
|
||
|
e: 'ad'
|
||
|
}), 'No nonexistent event type should be detected');
|
||
|
},
|
||
|
|
||
|
'Check a structured event was sent': function () {
|
||
|
assert.isTrue(checkExistenceOfExpectedQuerystring({
|
||
|
e: 'se',
|
||
|
se_ca: 'Mixes',
|
||
|
se_ac: 'Play',
|
||
|
se_la: 'MRC/fabric-0503-mix',
|
||
|
se_va: '0.0'
|
||
|
}), 'A structured event should be detected');
|
||
|
},
|
||
|
|
||
|
'Check an unstructured event with true timestamp was sent': function () {
|
||
|
assert.isTrue(checkExistenceOfExpectedQuerystring({
|
||
|
e: 'ue',
|
||
|
ue_px: 'eyJzY2hlbWEiOiJpZ2x1OmNvbS5zbm93cGxvd2FuYWx5dGljcy5zbm93cGxvdy91bnN0cnVjdF9ldmVudC9qc29uc2NoZW1hLzEtMC0wIiwiZGF0YSI6eyJzY2hlbWEiOiJpZ2x1OmNvbS5hY21lX2NvbXBhbnkvdmlld2VkX3Byb2R1Y3QvanNvbnNjaGVtYS81LTAtMCIsImRhdGEiOnsicHJvZHVjdElkIjoiQVNPMDEwNDMifX19',
|
||
|
ttm: '1477401868'
|
||
|
}), 'An unstructured event should be detected');
|
||
|
},
|
||
|
|
||
|
'Check a transaction event was sent': function () {
|
||
|
assert.isTrue(checkExistenceOfExpectedQuerystring({
|
||
|
e: 'tr',
|
||
|
tr_id: 'order-123',
|
||
|
tr_af: 'acme',
|
||
|
tr_tt: '8000',
|
||
|
tr_tx: '100',
|
||
|
tr_ci: 'phoenix',
|
||
|
tr_st: 'arizona',
|
||
|
tr_co: 'USA',
|
||
|
tr_cu: 'JPY'
|
||
|
}), 'A transaction event should be detected');
|
||
|
},
|
||
|
|
||
|
'Check a transaction item event was sent': function () {
|
||
|
assert.isTrue(checkExistenceOfExpectedQuerystring({
|
||
|
e: 'ti',
|
||
|
ti_id: 'order-123',
|
||
|
ti_sk: '1001',
|
||
|
ti_nm: 'Blue t-shirt',
|
||
|
ti_ca: 'clothing',
|
||
|
ti_pr: '2000',
|
||
|
ti_qu: '2',
|
||
|
ti_cu: 'JPY'
|
||
|
}), 'A transaction item event should be detected');
|
||
|
},
|
||
|
|
||
|
'Check an unhandled exception was sent': function () {
|
||
|
assert.isTrue(checkExistenceOfExpectedQuerystring({
|
||
|
ue_px: function (ue) {
|
||
|
var event = JSON.parse(decodeBase64(ue)).data;
|
||
|
// We cannot test more because implementations vary much in old browsers (FF27,IE9)
|
||
|
return (event.schema === 'iglu:com.snowplowanalytics.snowplow/application_error/jsonschema/1-0-1') &&
|
||
|
(event.data.programmingLanguage === 'JAVASCRIPT') &&
|
||
|
(event.data.message != null);
|
||
|
}
|
||
|
}));
|
||
|
},
|
||
|
|
||
|
'Check pageViewId is regenerated for each trackPageView': function () {
|
||
|
assert.isTrue(pageViewsHaveDifferentIds());
|
||
|
},
|
||
|
|
||
|
'Check global contexts are for structured events': function () {
|
||
|
assert.isTrue(checkExistenceOfExpectedQuerystring({
|
||
|
e: 'se',
|
||
|
cx: function (cx) {
|
||
|
var contexts = JSON.parse(decodeBase64(cx)).data;
|
||
|
return 2 === lodash.size(
|
||
|
lodash.filter(contexts,
|
||
|
lodash.overSome(
|
||
|
lodash.matches({
|
||
|
schema: "iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1",
|
||
|
data: {
|
||
|
osType: 'ubuntu'
|
||
|
}
|
||
|
}),
|
||
|
lodash.matches({
|
||
|
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
|
||
|
data: {
|
||
|
'latitude': 40.0,
|
||
|
'longitude': 55.1
|
||
|
}
|
||
|
})
|
||
|
)
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
}));
|
||
|
},
|
||
|
|
||
|
'Check an unstructured event with global context from accept ruleset': function () {
|
||
|
assert.isTrue(checkExistenceOfExpectedQuerystring({
|
||
|
e: 'ue',
|
||
|
ue_px: function (ue_px) {
|
||
|
var event = JSON.parse(decodeBase64(ue_px)).data;
|
||
|
return lodash.isMatch(event,
|
||
|
{
|
||
|
schema:"iglu:com.acme_company/viewed_product/jsonschema/5-0-0",
|
||
|
data:{
|
||
|
productId: 'ASO01042'
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
},
|
||
|
cx: function (cx) {
|
||
|
var contexts = JSON.parse(decodeBase64(cx)).data;
|
||
|
return 2 === lodash.size(
|
||
|
lodash.filter(contexts,
|
||
|
lodash.overSome(
|
||
|
lodash.matches({
|
||
|
schema: "iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1",
|
||
|
data: {
|
||
|
osType: 'ubuntu'
|
||
|
}
|
||
|
}),
|
||
|
lodash.matches({
|
||
|
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
|
||
|
data: {
|
||
|
'latitude': 40.0,
|
||
|
'longitude': 55.1
|
||
|
}
|
||
|
})
|
||
|
)
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
}), 'An unstructured event with global contexts should be detected');
|
||
|
},
|
||
|
|
||
|
'Check an unstructured event missing global context from reject ruleset': function () {
|
||
|
assert.isTrue(checkExistenceOfExpectedQuerystring({
|
||
|
e: 'ue',
|
||
|
ue_px: function (ue_px) {
|
||
|
var event = JSON.parse(decodeBase64(ue_px)).data;
|
||
|
return lodash.isMatch(event,
|
||
|
{
|
||
|
schema:"iglu:com.acme_company/viewed_product/jsonschema/5-0-0",
|
||
|
data:{
|
||
|
productId: 'ASO01041'
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
},
|
||
|
cx: function (cx) {
|
||
|
var contexts = JSON.parse(decodeBase64(cx)).data;
|
||
|
return 0 === lodash.size(
|
||
|
lodash.filter(contexts,
|
||
|
lodash.overSome(
|
||
|
lodash.matches({
|
||
|
schema: "iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1",
|
||
|
data: {
|
||
|
osType: 'ubuntu'
|
||
|
}
|
||
|
}),
|
||
|
lodash.matches({
|
||
|
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
|
||
|
data: {
|
||
|
'latitude': 40.0,
|
||
|
'longitude': 55.1
|
||
|
}
|
||
|
})
|
||
|
)
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
}), 'An unstructured event without global contexts should be detected');
|
||
|
}
|
||
|
});
|
||
|
});
|