'use strict'; var testHelper = require('../support/test-helper'); var assert = require('assert'); var RedisPool = require('redis-mpool'); var TemplateMaps = require('../../lib/backends/template-maps'); var step = require('step'); var _ = require('underscore'); describe('template_maps', function () { // configure redis pool instance to use in tests var redisPool = new RedisPool(global.environment.redis); var keysToDelete; beforeEach(function () { keysToDelete = {}; }); afterEach(function (done) { testHelper.deleteRedisKeys(keysToDelete, done); }); var wadusLayer = { options: { sql: 'select 1 cartodb_id, null::geometry the_geom_webmercator', cartocss: '#layer { marker-fill:blue; }', cartocss_version: '2.3.0' } }; var LAYER_PLAIN = { type: 'plain', options: { color: 'red' } }; it('does not accept template with unsupported version', function (done) { var tmap = new TemplateMaps(redisPool); assert.ok(tmap); var tpl = { version: '6.6.6', name: 'k', auth: {}, layergroup: { layers: [wadusLayer] } }; step( function () { tmap.addTemplate('me', tpl, this); }, function checkFailed (err) { assert.ok(err); assert.ok(err.message.match(/unsupported.*version/i), err); return null; }, function finish (err) { done(err); } ); }); it('does not accept template with missing name', function (done) { var tmap = new TemplateMaps(redisPool); assert.ok(tmap); var tpl = { version: '0.0.1', auth: {}, layergroup: { layers: [wadusLayer] } }; step( function () { tmap.addTemplate('me', tpl, this); }, function checkFailed (err) { assert.ok(err); assert.ok(err.message.match(/missing.*name/i), err); return null; }, function finish (err) { done(err); } ); }); describe('naming', function () { function createTemplate (name) { return { version: '0.0.1', name: name, auth: {}, layergroup: { layers: [ wadusLayer ] } }; } var templateMaps = new TemplateMaps(redisPool); var invalidNames = ['ab|', 'a b', 'a@b', '-1ab', '_x', '', ' x', 'x ']; invalidNames.forEach(function (invalidName) { it('should NOT accept template with invalid name: ' + invalidName, function (done) { templateMaps.addTemplate('me', createTemplate(invalidName), function (err) { assert.ok(err, "Unexpected success with invalid name '" + invalidName + "'"); assert.ok( err.message.match(/template.*name/i), "Unexpected error message with invalid name '" + invalidName + "': " + err.message ); done(); }); }); }); var validNames = [ 'AB', '1ab', 'DFD19A1A-0AC6-11E5-B0CA-6476BA93D4F6', '25ad8300-0ac7-11e5-b93f-6476ba93d4f6' ]; validNames.forEach(function (validName) { it('should accept template with valid name: ' + validName, function (done) { templateMaps.addTemplate('me', createTemplate(validName), function (err) { assert.ok(!err, "Unexpected error with valid name '" + validName + "': " + err); keysToDelete['map_tpl|me'] = 0; done(); }); }); }); }); it('does not accept template with invalid placeholder name', function (done) { var tmap = new TemplateMaps(redisPool); assert.ok(tmap); var tpl = { version: '0.0.1', name: 'valid', placeholders: {}, auth: {}, layergroup: { layers: [wadusLayer] } }; var invalidnames = ['ab|', 'a b', 'a@b', '1ab', '_x', '', ' x', 'x ']; var testNext = function () { if (!invalidnames.length) { done(); return; } var n = invalidnames.pop(); tpl.placeholders = {}; tpl.placeholders[n] = { type: 'number', default: 1 }; tmap.addTemplate('me', tpl, function (err) { if (!err) { done(new Error("Unexpected success with invalid name '" + n + "'")); } else if (!err.message.match(/invalid.*name/i)) { done(new Error("Unexpected error message with invalid name '" + n + "': " + err)); } else { testNext(); } }); }; testNext(); }); it('does not accept template with missing placeholder default', function (done) { var tmap = new TemplateMaps(redisPool); assert.ok(tmap); var tpl = { version: '0.0.1', name: 'valid', placeholders: { v: {} }, auth: {}, layergroup: { layers: [wadusLayer] } }; tmap.addTemplate('me', tpl, function (err) { if (!err) { done(new Error('Unexpected success with missing placeholder default')); } else if (!err.message.match(/missing default/i)) { done(new Error('Unexpected error message with missing placeholder default: ' + err)); } else { done(); } }); }); it('does not accept template with missing placeholder type', function (done) { var tmap = new TemplateMaps(redisPool); assert.ok(tmap); var tpl = { version: '0.0.1', name: 'valid', placeholders: { v: { default: 1 } }, auth: {}, layergroup: { layers: [wadusLayer] } }; tmap.addTemplate('me', tpl, function (err) { if (!err) { done(new Error('Unexpected success with missing placeholder type')); } else if (!err.message.match(/missing type/i)) { done(new Error('Unexpected error message with missing placeholder default: ' + err)); } else { done(); } }); }); // See http://github.com/CartoDB/Windshaft-cartodb/issues/128 it('does not accept template with invalid token auth (undefined tokens)', function (done) { var tmap = new TemplateMaps(redisPool); assert.ok(tmap); var tpl = { version: '0.0.1', name: 'invalid_auth1', placeholders: { }, auth: { method: 'token' }, layergroup: { layers: [wadusLayer] } }; tmap.addTemplate('me', tpl, function (err) { if (!err) { done(new Error('Unexpected success with invalid token auth (undefined tokens)')); } else if (!err.message.match(/invalid 'token' authentication/i)) { done(new Error('Unexpected error message with invalid token auth (undefined tokens): ' + err)); } else { done(); } }); }); it('add, get and delete a valid template', function (done) { var tmap = new TemplateMaps(redisPool); assert.ok(tmap); var expectedFailure = false; var tplId; var tpl = { version: '0.0.1', name: 'first', auth: {}, layergroup: { layers: [wadusLayer] } }; step( function () { tmap.addTemplate('me', tpl, this); }, function addOmonimousTemplate (err, id) { assert.ifError(err); tplId = id; assert.strictEqual(tplId, 'first'); expectedFailure = true; // should fail, as it already exists tmap.addTemplate('me', tpl, this); }, function getTemplate (err) { if (!expectedFailure && err) { throw err; } assert.ok(err); assert.ok(err.message.match(/already exists/i), err); tmap.getTemplate('me', tplId, this); }, function delTemplate (err, gotTpl) { assert.ifError(err); assert.deepStrictEqual(gotTpl, _.extend({}, tpl, { auth: { method: 'open' }, placeholders: {} })); tmap.delTemplate('me', tplId, this); }, function finish (err) { done(err); } ); }); it('add multiple templates, list them', function (done) { var tmap = new TemplateMaps(redisPool); assert.ok(tmap); var tpl1 = { version: '0.0.1', name: 'first', auth: {}, layergroup: { layers: [wadusLayer] } }; var tpl1Id; var tpl2 = { version: '0.0.1', name: 'second', auth: {}, layergroup: { layers: [wadusLayer] } }; var tpl2Id; step( function addTemplate1 () { tmap.addTemplate('me', tpl1, this); }, function addTemplate2 (err, id) { assert.ifError(err); tpl1Id = id; tmap.addTemplate('me', tpl2, this); }, function listTemplates (err, id) { assert.ifError(err); tpl2Id = id; tmap.listTemplates('me', this); }, function checkTemplates (err, ids) { assert.ifError(err); assert.strictEqual(ids.length, 2); assert.ok(ids.indexOf(tpl1Id) !== -1, ids.join(',')); assert.ok(ids.indexOf(tpl2Id) !== -1, ids.join(',')); return null; }, function delTemplate1 (err) { if (tpl1Id) { var next = this; tmap.delTemplate('me', tpl1Id, function (e) { if (err || e) { next(new Error(err + '; ' + e)); } else { next(); } }); } else { assert.ifError(err); return null; } }, function delTemplate2 (err) { if (tpl2Id) { var next = this; tmap.delTemplate('me', tpl2Id, function (e) { if (err || e) { next(new Error(err + '; ' + e)); } else { next(); } }); } else { assert.ifError(err); return null; } }, function finish (err) { done(err); } ); }); it('update templates', function (done) { var tmap = new TemplateMaps(redisPool); assert.ok(tmap); var expectedFailure = false; var owner = 'me'; var tpl = { version: '0.0.1', name: 'first', auth: { method: 'open' }, layergroup: { layers: [wadusLayer] } }; var tplId; step( function addTemplate () { tmap.addTemplate(owner, tpl, this); }, // Updating template name should fail function updateTemplateName (err, id) { assert.ifError(err); tplId = id; expectedFailure = true; tpl.name = 'second'; tmap.updTemplate(owner, tplId, tpl, this); }, function updateTemplateAuth (err) { if (err && !expectedFailure) { throw err; } expectedFailure = false; assert.ok(err); tpl.name = 'first'; tpl.auth.method = 'token'; tpl.auth.valid_tokens = ['tok1']; tmap.updTemplate(owner, tplId, tpl, this); }, function updateTemplateWithInvalid (err) { assert.ifError(err); tpl.version = '999.999.999'; expectedFailure = true; tmap.updTemplate(owner, tplId, tpl, this); }, function updateUnexistentTemplate (err) { if (err && !expectedFailure) { throw err; } assert.ok(err); assert.ok(err.message.match(/unsupported.*version/i), err); tpl.version = '0.0.1'; expectedFailure = true; tmap.updTemplate(owner, 'unexistent', tpl, this); }, function delTemplate (err) { if (err && !expectedFailure) { throw err; } expectedFailure = false; assert.ok(err); assert.ok(err.message.match(/cannot update name/i), err); tmap.delTemplate(owner, tplId, this); }, function finish (err) { done(err); } ); }); it('instanciate templates', function () { // jshint maxcomplexity:7 var tmap = new TemplateMaps(redisPool); assert.ok(tmap); var tpl1 = { version: '0.0.1', name: 'acceptance1', auth: { method: 'open' }, placeholders: { fill: { type: 'css_color', default: 'red' }, color: { type: 'css_color', default: '#a0fF9A' }, name: { type: 'sql_literal', default: 'test' }, zoom: { type: 'number', default: '0' }, test_number: { type: 'number', default: 23 } }, layergroup: { version: '1.0.0', global_cartocss_version: '2.0.2', layers: [ { options: { sql: "select '<%=name %>' || id, g from t", cartocss: '#layer { marker-fill:<%= fill %>; marker-width: <%=test_number %>; }' } }, { options: { sql: "select fun('<%= name%>') g from x", cartocss: '#layer { line-color:<%= color %>; marker-fill:<%= color %>; }' } }, { options: { sql: 'select g from x', cartocss: '#layer[zoom=<%=zoom%>] { }' } } ] } }; var inst = tmap.instance(tpl1, {}); var lyr = inst.layers[0].options; assert.strictEqual(lyr.sql, "select 'test' || id, g from t"); assert.strictEqual(lyr.cartocss, '#layer { marker-fill:red; marker-width: 23; }'); lyr = inst.layers[1].options; assert.strictEqual(lyr.sql, "select fun('test') g from x"); assert.strictEqual(lyr.cartocss, '#layer { line-color:#a0fF9A; marker-fill:#a0fF9A; }'); inst = tmap.instance(tpl1, { color: 'yellow', name: "it's dangerous" }); lyr = inst.layers[0].options; assert.strictEqual(lyr.sql, "select 'it''s dangerous' || id, g from t"); assert.strictEqual(lyr.cartocss, '#layer { marker-fill:red; marker-width: 23; }'); lyr = inst.layers[1].options; assert.strictEqual(lyr.sql, "select fun('it''s dangerous') g from x"); assert.strictEqual(lyr.cartocss, '#layer { line-color:yellow; marker-fill:yellow; }'); // Invalid css_color var err = null; try { tmap.instance(tpl1, { color: '##ff00ff' }); } catch (e) { err = e; } assert.ok(err); assert.ok(err.message.match(/invalid css_color/i), err); // Invalid css_color 2 (too few digits) err = null; try { tmap.instance(tpl1, { color: '#ff' }); } catch (e) { err = e; } assert.ok(err); assert.ok(err.message.match(/invalid css_color/i), err); // Invalid css_color 3 (too many digits) err = null; try { tmap.instance(tpl1, { color: '#1234567' }); } catch (e) { err = e; } assert.ok(err); assert.ok(err.message.match(/invalid css_color/i), err); // Invalid number err = null; try { tmap.instance(tpl1, { zoom: '#' }); } catch (e) { err = e; } assert.ok(err); assert.ok(err.message.match(/invalid number/i), err); // Invalid number 2 err = null; try { tmap.instance(tpl1, { zoom: '23e' }); } catch (e) { err = e; } assert.ok(err); assert.ok(err.message.match(/invalid number/i), err); // Valid number err = null; try { tmap.instance(tpl1, { zoom: '-.23e10' }); } catch (e) { err = e; } assert.ok(!err); }); // Can set a limit on the number of user templates it('can limit number of user templates', function (done) { var tmap = new TemplateMaps(redisPool, { max_user_templates: 2 }); assert.ok(tmap); var tpl = { version: '0.0.1', auth: {}, layergroup: { layers: [wadusLayer] } }; var expectErr = false; var idMe = []; var idYou = []; step( function oneForMe () { tpl.name = 'oneForMe'; tmap.addTemplate('me', tpl, this); }, function twoForMe (err, id) { assert.ifError(err); assert.ok(id); idMe.push(id); tpl.name = 'twoForMe'; tmap.addTemplate('me', tpl, this); }, function threeForMe (err, id) { assert.ifError(err); assert.ok(id); idMe.push(id); tpl.name = 'threeForMe'; expectErr = true; tmap.addTemplate('me', tpl, this); }, function errForMe (err/*, id */) { if (err && !expectErr) { throw err; } expectErr = false; assert.ok(err); assert.ok(err.message.match(/limit.*template/), err); return null; }, function delOneMe (err) { assert.ifError(err); tmap.delTemplate('me', idMe.shift(), this); }, function threeForMeRetry (err) { assert.ifError(err); tpl.name = 'threeForMe'; tmap.addTemplate('me', tpl, this); }, function oneForYou (err, id) { assert.ifError(err); assert.ok(id); idMe.push(id); tpl.name = 'oneForYou'; tmap.addTemplate('you', tpl, this); }, function twoForYou (err, id) { assert.ifError(err); assert.ok(id); idYou.push(id); tpl.name = 'twoForYou'; tmap.addTemplate('you', tpl, this); }, function threeForYou (err, id) { assert.ifError(err); assert.ok(id); idYou.push(id); tpl.name = 'threeForYou'; expectErr = true; tmap.addTemplate('you', tpl, this); }, function errForYou (err/*, id */) { if (err && !expectErr) { throw err; } expectErr = false; assert.ok(err); assert.ok(err.message.match(/limit.*template/), err); return null; }, function finish (err) { keysToDelete['map_tpl|you'] = 0; keysToDelete['map_tpl|me'] = 0; // TODO: delete all templates done(err); } ); }); describe('emit', function () { var owner = 'me'; var templateName = 'emit'; var template = { version: '0.0.1', name: templateName, auth: { method: 'open' }, placeholders: {}, layergroup: { layers: [ wadusLayer ] } }; var templateUpdated = _.extend({}, template, { layergroup: { layers: [LAYER_PLAIN] } }); var templateMaps; beforeEach(function () { templateMaps = new TemplateMaps(redisPool); }); it('should emit on template update', function (done) { templateMaps.on('update', function (_owner, _templateName, _template) { assert.strictEqual(_owner, owner); assert.strictEqual(_templateName, templateName); assert.deepStrictEqual(_template, templateUpdated); templateMaps.delTemplate(owner, templateName, done); }); templateMaps.addTemplate(owner, template, function (err, templateName, _template) { assert.ok(!err, err); assert.deepStrictEqual(_template, template); templateMaps.updTemplate(owner, templateName, templateUpdated, function () {}); }); }); it('should emit on template deletion', function (done) { templateMaps.on('delete', function (_owner, _templateName) { assert.strictEqual(_owner, owner); assert.strictEqual(_templateName, templateName); done(); }); templateMaps.addTemplate(owner, template, function (err, templateName, _template) { assert.ok(!err, err); assert.deepStrictEqual(_template, template); templateMaps.delTemplate(owner, templateName, function () {}); }); }); it('should NOT emit on template update when template is the same', function (done) { templateMaps.on('update', function (_owner, _templateName, _template) { assert.strictEqual(_owner, owner); assert.strictEqual(_templateName, templateName); assert.deepStrictEqual(_template, templateUpdated); templateMaps.delTemplate(owner, templateName, done); }); templateMaps.addTemplate(owner, template, function (err, templateName, _template) { assert.ok(!err, err); assert.deepStrictEqual(_template, template); templateMaps.updTemplate(owner, templateName, template, function () { templateMaps.updTemplate(owner, templateName, templateUpdated, function () {}); }); }); }); }); });