/* * 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'); } }); });