var testHelper = require('../../support/test_helper'); var assert = require('assert'); var RedisPool = require('redis-mpool'); var TemplateMaps = require('../../../lib/cartodb/backends/template_maps.js'); var step = require('step'); var _ = require('underscore'); describe('template_maps', function() { // configure redis pool instance to use in tests var redis_pool = 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(redis_pool); 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(redis_pool); 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(redis_pool); 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(redis_pool); 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(redis_pool); 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(redis_pool); 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(redis_pool); 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(redis_pool); assert.ok(tmap); var expected_failure = false; var tpl_id; 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); tpl_id = id; assert.equal(tpl_id, 'first'); expected_failure = true; // should fail, as it already exists tmap.addTemplate('me', tpl, this); }, function getTemplate(err) { if ( ! expected_failure && err ) { throw err; } assert.ok(err); assert.ok(err.message.match(/already exists/i), err); tmap.getTemplate('me', tpl_id, this); }, function delTemplate(err, got_tpl) { assert.ifError(err); assert.deepEqual(got_tpl, _.extend({}, tpl, {auth: {method: 'open'}, placeholders: {}})); tmap.delTemplate('me', tpl_id, this); }, function finish(err) { done(err); } ); }); it('add multiple templates, list them', function(done) { var tmap = new TemplateMaps(redis_pool); assert.ok(tmap); var tpl1 = { version:'0.0.1', name: 'first', auth: {}, layergroup: {layers:[wadusLayer]} }; var tpl1_id; var tpl2 = { version:'0.0.1', name: 'second', auth: {}, layergroup: {layers:[wadusLayer]} }; var tpl2_id; step( function addTemplate1() { tmap.addTemplate('me', tpl1, this); }, function addTemplate2(err, id) { assert.ifError(err); tpl1_id = id; tmap.addTemplate('me', tpl2, this); }, function listTemplates(err, id) { assert.ifError(err); tpl2_id = id; tmap.listTemplates('me', this); }, function checkTemplates(err, ids) { assert.ifError(err); assert.equal(ids.length, 2); assert.ok(ids.indexOf(tpl1_id) !== -1, ids.join(',')); assert.ok(ids.indexOf(tpl2_id) !== -1, ids.join(',')); return null; }, function delTemplate1(err) { if ( tpl1_id ) { var next = this; tmap.delTemplate('me', tpl1_id, function(e) { if ( err || e ) { next(new Error(err + '; ' + e)); } else { next(); } }); } else { assert.ifError(err); return null; } }, function delTemplate2(err) { if ( tpl2_id ) { var next = this; tmap.delTemplate('me', tpl2_id, 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(redis_pool); assert.ok(tmap); var expected_failure = false; var owner = 'me'; var tpl = { version:'0.0.1', name: 'first', auth: { method: 'open' }, layergroup: {layers:[wadusLayer]} }; var tpl_id; step( function addTemplate() { tmap.addTemplate(owner, tpl, this); }, // Updating template name should fail function updateTemplateName(err, id) { assert.ifError(err); tpl_id = id; expected_failure = true; tpl.name = 'second'; tmap.updTemplate(owner, tpl_id, tpl, this); }, function updateTemplateAuth(err) { if ( err && ! expected_failure) { throw err; } expected_failure = false; assert.ok(err); tpl.name = 'first'; tpl.auth.method = 'token'; tpl.auth.valid_tokens = [ 'tok1' ]; tmap.updTemplate(owner, tpl_id, tpl, this); }, function updateTemplateWithInvalid(err) { assert.ifError(err); tpl.version = '999.999.999'; expected_failure = true; tmap.updTemplate(owner, tpl_id, tpl, this); }, function updateUnexistentTemplate(err) { if ( err && ! expected_failure) { throw err; } assert.ok(err); assert.ok(err.message.match(/unsupported.*version/i), err); tpl.version = '0.0.1'; expected_failure = true; tmap.updTemplate(owner, 'unexistent', tpl, this); }, function delTemplate(err) { if ( err && ! expected_failure) { throw err; } expected_failure = false; assert.ok(err); assert.ok(err.message.match(/cannot update name/i), err); tmap.delTemplate(owner, tpl_id, this); }, function finish(err) { done(err); } ); }); it('instanciate templates', function() { // jshint maxcomplexity:7 var tmap = new TemplateMaps(redis_pool); 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.equal(lyr.sql, "select 'test' || id, g from t"); assert.equal(lyr.cartocss, '#layer { marker-fill:red; marker-width: 23; }'); lyr = inst.layers[1].options; assert.equal(lyr.sql, "select fun('test') g from x"); assert.equal(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.equal(lyr.sql, "select 'it''s dangerous' || id, g from t"); assert.equal(lyr.cartocss, '#layer { marker-fill:red; marker-width: 23; }'); lyr = inst.layers[1].options; assert.equal(lyr.sql, "select fun('it''s dangerous') g from x"); assert.equal(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(redis_pool, { 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(redis_pool); }); it('should emit on template update', function(done) { templateMaps.on('update', function(_owner, _templateName, _template) { assert.equal(_owner, owner); assert.equal(_templateName, templateName); assert.deepEqual(_template, templateUpdated); templateMaps.delTemplate(owner, templateName, done); }); templateMaps.addTemplate(owner, template, function(err, templateName, _template) { assert.ok(!err, err); assert.deepEqual(_template, template); templateMaps.updTemplate(owner, templateName, templateUpdated, function() {}); }); }); it('should emit on template deletion', function(done) { templateMaps.on('delete', function(_owner, _templateName) { assert.equal(_owner, owner); assert.equal(_templateName, templateName); done(); }); templateMaps.addTemplate(owner, template, function(err, templateName, _template) { assert.ok(!err, err); assert.deepEqual(_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.equal(_owner, owner); assert.equal(_templateName, templateName); assert.deepEqual(_template, templateUpdated); templateMaps.delTemplate(owner, templateName, done); }); templateMaps.addTemplate(owner, template, function(err, templateName, _template) { assert.ok(!err, err); assert.deepEqual(_template, template); templateMaps.updTemplate(owner, templateName, template, function() { templateMaps.updTemplate(owner, templateName, templateUpdated, function() {}); }); }); }); }); });