diff --git a/lib/cartodb/models/mapconfig_named_layers_adapter.js b/lib/cartodb/models/mapconfig_named_layers_adapter.js index 0e4c49b3..ceb9c429 100644 --- a/lib/cartodb/models/mapconfig_named_layers_adapter.js +++ b/lib/cartodb/models/mapconfig_named_layers_adapter.js @@ -1,5 +1,6 @@ var queue = require('queue-async'); var _ = require('underscore'); +var Datasource = require('windshaft').Datasource; function MapConfigNamedLayersAdapter(templateMaps) { this.templateMaps = templateMaps; @@ -7,16 +8,16 @@ function MapConfigNamedLayersAdapter(templateMaps) { module.exports = MapConfigNamedLayersAdapter; -MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, callback) { +MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, dbMetadata, callback) { var self = this; var adaptLayersQueue = queue(layers.length); - function adaptLayer(layer, callback) { - if (layer.type === 'named') { + function adaptLayer(layer, done) { + if (isNamedTypeLayer(layer)) { if (!layer.options.name) { - return callback(new Error('Missing Named Map `name` in layer options')); + return done(new Error('Missing Named Map `name` in layer options')); } var templateName = layer.options.name; @@ -25,7 +26,7 @@ MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, cal self.templateMaps.getTemplate(username, templateName, function(err, template) { if (err || !template) { - return callback(new Error("Template '" + templateName + "' of user '" + username + "' not found")); + return done(new Error("Template '" + templateName + "' of user '" + username + "' not found")); } if (self.templateMaps.isAuthorized(template, templateAuthTokens)) { @@ -36,41 +37,84 @@ MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, cal if (nestedNamedLayers.length > 0) { var nestedNamedMapsError = new Error('Nested named layers are not allowed'); // nestedNamedMapsError.http_status = 400; - return callback(nestedNamedMapsError); + return done(nestedNamedMapsError); } try { var templateLayergroupConfig = self.templateMaps.instance(template, templateConfigParams); - return callback(null, templateLayergroupConfig.layers); + return done(null, { + datasource: true, + layers: templateLayergroupConfig.layers + }); } catch (err) { - return callback(err); + return done(err); } } else { var unauthorizedError = new Error("Unauthorized '" + templateName + "' template instantiation"); unauthorizedError.http_status = 403; - return callback(unauthorizedError); + return done(unauthorizedError); } }); + } else { - return callback(null, layer); + return done(null, { + datasource: false, + layers: [layer] + }); } } - layers.forEach(function(layer) { - adaptLayersQueue.defer(adaptLayer, layer); - }); + var datasourceBuilder = new Datasource.Builder(); - function layersAdaptQueueFinish(err, layers) { + function layersAdaptQueueFinish(err, layersResults) { if (err) { return callback(err); } - if (!layers || layers.length === 0) { + if (!layersResults || layersResults.length === 0) { return callback(new Error('Missing layers array from layergroup config')); } - return callback(null, _.flatten(layers)); + var layers = [], + currentLayerIndex = 0; + + layersResults.forEach(function(layersResult) { + + layersResult.layers.forEach(function(layer) { + layers.push(layer); + if (layersResult.datasource) { + datasourceBuilder.withLayerDatasource(currentLayerIndex, { + user: dbAuth.dbuser + }); + } + currentLayerIndex++; + }); + + }); + + return callback(null, layers, datasourceBuilder.build()); + } + + + var dbAuth = {}; + + if (_.some(layers, isNamedTypeLayer)) { + // Lazy load dbAuth + dbMetadata.setDBAuth(username, dbAuth, function(err) { + if (err) { + return callback(err); + } + layers.forEach(function(layer) { + adaptLayersQueue.defer(adaptLayer, layer); + }); + adaptLayersQueue.awaitAll(layersAdaptQueueFinish); + }); + } else { + return callback(null, layers, datasourceBuilder.build()); } - adaptLayersQueue.awaitAll(layersAdaptQueueFinish); }; + +function isNamedTypeLayer(layer) { + return layer.type === 'named'; +} diff --git a/lib/cartodb/server_options.js b/lib/cartodb/server_options.js index 99321634..948349ba 100644 --- a/lib/cartodb/server_options.js +++ b/lib/cartodb/server_options.js @@ -307,13 +307,13 @@ module.exports = function(redisPool) { }; me.beforeLayergroupCreate = function(req, requestMapConfig, callback) { - mapConfigNamedLayersAdapter.getLayers(this.userByReq(req), requestMapConfig.layers, function(err, layers) { + mapConfigNamedLayersAdapter.getLayers(this.userByReq(req), requestMapConfig.layers, this, function(err, layers, datasource) { if (err) { return callback(err); } requestMapConfig.layers = layers; - return callback(null, requestMapConfig) + return callback(null, requestMapConfig, datasource) }); }; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 71666d2b..c4fe8e44 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -97,7 +97,7 @@ }, "inherits": { "version": "2.0.1", - "from": "inherits@~2.0.1" + "from": "inherits@2" } } } @@ -184,8 +184,8 @@ }, "windshaft": { "version": "0.35.2", - "from": "https://github.com/CartoDB/Windshaft/tarball/before-layergroup-step", - "resolved": "https://github.com/CartoDB/Windshaft/tarball/before-layergroup-step", + "from": "https://github.com/CartoDB/Windshaft/tarball/mapconfig-per-layer-datasource", + "resolved": "https://github.com/CartoDB/Windshaft/tarball/mapconfig-per-layer-datasource", "dependencies": { "chronograph": { "version": "0.1.0", @@ -193,9 +193,9 @@ "resolved": "git://github.com/CartoDB/chronographjs.git#0b8c35eee510cfa14a16be24d70533b38ecc1d2d" }, "grainstore": { - "version": "0.22.1", - "from": "https://github.com/CartoDB/grainstore/tarball/0.22.1", - "resolved": "https://github.com/CartoDB/grainstore/tarball/0.22.1", + "version": "0.22.2", + "from": "https://github.com/CartoDB/grainstore/tarball/datasource-extend", + "resolved": "https://github.com/CartoDB/grainstore/tarball/datasource-extend", "dependencies": { "carto": { "version": "0.9.5-cdb2", diff --git a/package.json b/package.json index 7cbc7e87..5eb6f11c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "node-varnish": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0", "underscore" : "~1.6.0", "dot": "~1.0.2", - "windshaft": "https://github.com/CartoDB/Windshaft/tarball/before-layergroup-step", + "windshaft": "https://github.com/CartoDB/Windshaft/tarball/mapconfig-per-layer-datasource", "step": "~0.0.5", "queue-async": "~1.0.7", "request": "~2.9.203", diff --git a/test/acceptance/named_layers.js b/test/acceptance/named_layers.js index ff8f29f9..ec04afbf 100644 --- a/test/acceptance/named_layers.js +++ b/test/acceptance/named_layers.js @@ -109,7 +109,12 @@ suite('named_layers', function() { if (err) { return done(err); } - sqlapiServer = new SQLAPIEmulator(global.environment.sqlapi.port, done); + templateMaps.addTemplate(username, template, function(err) { + if (err) { + return done(err); + } + sqlapiServer = new SQLAPIEmulator(global.environment.sqlapi.port, done); + }); }); }); }); @@ -442,6 +447,66 @@ suite('named_layers', function() { }); + test('should return 403 when private table is accessed from non named layer', function(done) { + + var layergroup = { + version: '1.3.0', + layers: [ + { + type: 'cartodb', + options: { + sql: 'select * from test_table_private_1', + cartocss: '#layer { marker-fill: #cc3300; }', + cartocss_version: '2.3.0' + } + }, + { + type: 'named', + options: { + name: templateName + } + } + ] + }; + + Step( + function createLayergroup() { + var next = this; + assert.response(server, + { + url: '/tiles/layergroup', + method: 'POST', + headers: { + host: 'localhost', + 'Content-Type': 'application/json' + }, + data: JSON.stringify(layergroup) + }, + { + status: 403 + }, + function(res, err) { + next(err, res); + } + ); + }, + function checkLayergroup(err, response) { + if (err) { + throw err; + } + + var parsedBody = JSON.parse(response.body); + assert.ok(parsedBody.errors[0].match(/permission denied for relation test_table_private_1/)); + + return null; + }, + function finish(err) { + done(err); + } + ); + + }); + suiteTeardown(function(done) { templateMaps.delTemplate(username, nestedNamedMapTemplateName, function(err) { @@ -452,7 +517,12 @@ suite('named_layers', function() { if (err) { return done(err); } - sqlapiServer.close(done); + templateMaps.delTemplate(username, templateName, function(err) { + if (err) { + return done(err); + } + sqlapiServer.close(done); + }); }); }); }); diff --git a/test/integration/mapconfig_named_layers_datasource.js b/test/integration/mapconfig_named_layers_datasource.js new file mode 100644 index 00000000..e529a860 --- /dev/null +++ b/test/integration/mapconfig_named_layers_datasource.js @@ -0,0 +1,297 @@ +var test_helper = require('../support/test_helper'); + +var assert = require('assert'); +var RedisPool = require('redis-mpool'); +var TemplateMaps = require('../../lib/cartodb/template_maps.js'); +var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')(); +var MapConfigNamedLayersAdapter = require('../../lib/cartodb/models/mapconfig_named_layers_adapter'); + +var Step = require('step'); +var _ = require('underscore'); + +// configure redis pool instance to use in tests +var redisPool = RedisPool(global.environment.redis); + +var templateMaps = new TemplateMaps(redisPool, { + max_user_templates: global.environment.maxUserTemplates +}); + +var mapConfigNamedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps); + +var wadusSql = 'select 1 wadusLayer, null::geometry the_geom_webmercator'; +var wadusLayer = { + type: 'cartodb', + options: { + sql: wadusSql, + cartocss: '#layer { marker-fill: black; }', + cartocss_version: '2.3.0' + } +}; + +var wadusTemplateSql = 'select 1 wadusTemplateLayer, null::geometry the_geom_webmercator'; +var wadusTemplateLayer = { + type: 'cartodb', + options: { + sql: wadusTemplateSql, + cartocss: '#layer { marker-fill: <%= color %>; }', + cartocss_version: '2.3.0' + } +}; + +var wadusMapnikSql = 'select 1 wadusMapnikLayer, null::geometry the_geom_webmercator'; +var wadusMapnikLayer = { + type: 'mapnik', + options: { + sql: wadusMapnikSql, + cartocss: '#layer { polygon-fill: <%= polygon_color %>; }', + cartocss_version: '2.3.0' + } +}; + +var username = 'localhost'; + +var templateName = 'valid_template'; +var template = { + version: '0.0.1', + name: templateName, + auth: { + method: 'open' + }, + "placeholders": { + "color": { + "type": "css_color", + "default": "#cc3300" + } + }, + layergroup: { + layers: [ + wadusTemplateLayer + ] + } +}; + +var multipleLayersTemplateName = 'multiple_valid_template'; +var multipleLayersTemplate = { + version: '0.0.1', + name: multipleLayersTemplateName, + auth: { + method: 'token', + valid_tokens: ['valid1', 'valid2'] + }, + "placeholders": { + "polygon_color": { + "type": "css_color", + "default": "green" + }, + "color": { + "type": "css_color", + "default": "red" + } + }, + layergroup: { + layers: [ + wadusMapnikLayer, + wadusTemplateLayer + ] + } +}; + +suite('named_layers datasources', function() { + suiteSetup(function(done) { + templateMaps.addTemplate(username, template, function(err) { + if (err) { + return done(err); + } + templateMaps.addTemplate(username, multipleLayersTemplate, done); + }); + }); + + function makeNamedMapLayerConfig(layers) { + return { + version: '1.3.0', + layers: layers + }; + } + + var simpleNamedLayer = { + type: 'named', + options: { + name: templateName + } + }; + + var multipleLayersNamedLayer = { + type: 'named', + options: { + name: multipleLayersTemplateName, + auth_tokens: ['valid2'] + } + }; + + var testScenarios = [ + { + desc: 'without datasource for non-named layers', + config: makeNamedMapLayerConfig([wadusLayer]), + test: function(err, layers, datasource, done) { + assert.ok(!err); + assert.equal(layers.length, 1); + + assert.equal(layers[0].type, 'cartodb'); + assert.equal(layers[0].options.sql, wadusSql); + assert.equal(datasource.getLayerDatasource(0), undefined); + + done(); + } + }, + + { + desc: 'with datasource for the named layer but not for the normal', + config: makeNamedMapLayerConfig([wadusLayer, simpleNamedLayer]), + test: function(err, layers, datasource, done) { + assert.ok(!err); + assert.equal(layers.length, 2); + + assert.equal(layers[0].type, 'cartodb'); + assert.equal(layers[0].options.sql, wadusSql); + assert.equal(datasource.getLayerDatasource(0), undefined); + + assert.equal(layers[1].type, 'cartodb'); + assert.equal(layers[1].options.sql, wadusTemplateSql); + var layerDatasource = datasource.getLayerDatasource(1); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + done(); + } + }, + + { + desc: 'with datasource for the multiple layers in the named but not for the normal', + config: makeNamedMapLayerConfig([wadusLayer, multipleLayersNamedLayer]), + test: function(err, layers, datasource, done) { + assert.ok(!err); + assert.equal(layers.length, 3); + + assert.equal(layers[0].type, 'cartodb'); + assert.equal(layers[0].options.sql, wadusSql); + assert.equal(datasource.getLayerDatasource(0), undefined); + + assert.equal(layers[1].type, 'mapnik'); + assert.equal(layers[1].options.sql, wadusMapnikSql); + var layerDatasource = datasource.getLayerDatasource(1); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + assert.equal(layers[2].type, 'cartodb'); + assert.equal(layers[2].options.sql, wadusTemplateSql); + layerDatasource = datasource.getLayerDatasource(2); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + done(); + } + }, + + { + desc: 'all with datasource because all are named', + config: makeNamedMapLayerConfig([multipleLayersNamedLayer, simpleNamedLayer]), + test: function(err, layers, datasource, done) { + assert.ok(!err); + assert.equal(layers.length, 3); + + assert.equal(layers[0].type, 'mapnik'); + assert.equal(layers[0].options.sql, wadusMapnikSql); + var layerDatasource = datasource.getLayerDatasource(0); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + assert.equal(layers[1].type, 'cartodb'); + assert.equal(layers[1].options.sql, wadusTemplateSql); + layerDatasource = datasource.getLayerDatasource(1); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + assert.equal(layers[2].type, 'cartodb'); + assert.equal(layers[2].options.sql, wadusTemplateSql); + layerDatasource = datasource.getLayerDatasource(2); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + done(); + } + }, + + { + desc: 'with a mix of datasource and no datasource depending if layers are named or not', + config: makeNamedMapLayerConfig([simpleNamedLayer, multipleLayersNamedLayer, wadusLayer, simpleNamedLayer, wadusLayer, multipleLayersNamedLayer]), + test: function(err, layers, datasource, done) { + + assert.ok(!err); + assert.equal(layers.length, 8); + + assert.equal(layers[0].type, 'cartodb'); + assert.equal(layers[0].options.sql, wadusTemplateSql); + var layerDatasource = datasource.getLayerDatasource(0); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + assert.equal(layers[1].type, 'mapnik'); + assert.equal(layers[1].options.sql, wadusMapnikSql); + layerDatasource = datasource.getLayerDatasource(1); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + assert.equal(layers[2].type, 'cartodb'); + assert.equal(layers[2].options.sql, wadusTemplateSql); + layerDatasource = datasource.getLayerDatasource(2); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + assert.equal(layers[3].type, 'cartodb'); + assert.equal(layers[3].options.sql, wadusSql); + assert.equal(datasource.getLayerDatasource(3), undefined); + + assert.equal(layers[4].type, 'cartodb'); + assert.equal(layers[4].options.sql, wadusTemplateSql); + layerDatasource = datasource.getLayerDatasource(4); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + assert.equal(layers[5].type, 'cartodb'); + assert.equal(layers[5].options.sql, wadusSql); + assert.equal(datasource.getLayerDatasource(5), undefined); + + assert.equal(layers[6].type, 'mapnik'); + assert.equal(layers[6].options.sql, wadusMapnikSql); + layerDatasource = datasource.getLayerDatasource(6); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + assert.equal(layers[7].type, 'cartodb'); + assert.equal(layers[7].options.sql, wadusTemplateSql); + layerDatasource = datasource.getLayerDatasource(7); + assert.notEqual(layerDatasource, undefined); + assert.ok(layerDatasource.user); + + done(); + } + } + ]; + + testScenarios.forEach(function(testScenario) { + test('should return a list of layers ' + testScenario.desc, function(done) { + mapConfigNamedLayersAdapter.getLayers(username, testScenario.config.layers, serverOptions, function(err, layers, datasource) { + testScenario.test(err, layers, datasource, done); + }); + }); + }); + + suiteTeardown(function(done) { + templateMaps.delTemplate(username, templateName, function(err) { + if (err) { + return done(err); + } + templateMaps.delTemplate(username, multipleLayersTemplateName, done); + }); + }); +}); diff --git a/test/integration/mapconfig_named_layers_adapter.js b/test/integration/mapconfig_named_layers_expanded.js similarity index 86% rename from test/integration/mapconfig_named_layers_adapter.js rename to test/integration/mapconfig_named_layers_expanded.js index 1402dda6..26b5197a 100644 --- a/test/integration/mapconfig_named_layers_adapter.js +++ b/test/integration/mapconfig_named_layers_expanded.js @@ -1,9 +1,11 @@ +var testHelper = require('../support/test_helper'); + var assert = require('assert'); var RedisPool = require('redis-mpool'); var TemplateMaps = require('../../lib/cartodb/template_maps.js'); +var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')(); var MapConfigNamedLayersAdapter = require('../../lib/cartodb/models/mapconfig_named_layers_adapter'); -var test_helper = require('../support/test_helper'); var Step = require('step'); var _ = require('underscore'); @@ -36,7 +38,7 @@ suite('mapconfig_named_layers_adapter', function() { } }; - var username = 'me'; + var username = 'localhost'; var templateName = 'valid_template'; var template = { @@ -143,10 +145,12 @@ suite('mapconfig_named_layers_adapter', function() { var missingNamedMapLayerConfig = makeNamedMapLayerConfig({ config: {} }); - mapConfigNamedLayersAdapter.getLayers(username, missingNamedMapLayerConfig.layers, function(err, layers) { + mapConfigNamedLayersAdapter.getLayers(username, missingNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) { assert.ok(err); assert.ok(!layers); + assert.ok(!datasource); assert.equal(err.message, 'Missing Named Map `name` in layer options'); + done(); }); }); @@ -156,10 +160,12 @@ suite('mapconfig_named_layers_adapter', function() { var nonExistentNamedMapLayerConfig = makeNamedMapLayerConfig({ name: missingTemplateName }); - mapConfigNamedLayersAdapter.getLayers(username, nonExistentNamedMapLayerConfig.layers, function(err, layers) { + mapConfigNamedLayersAdapter.getLayers(username, nonExistentNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) { assert.ok(err); assert.ok(!layers); + assert.ok(!datasource); assert.equal(err.message, "Template '" + missingTemplateName + "' of user '" + username + "' not found"); + done(); }); }); @@ -173,9 +179,10 @@ suite('mapconfig_named_layers_adapter', function() { var nonAuthTokensNamedMapLayerConfig = makeNamedMapLayerConfig({ name: tokenAuthTemplateName }); - mapConfigNamedLayersAdapter.getLayers(username, nonAuthTokensNamedMapLayerConfig.layers, function(err, layers) { + mapConfigNamedLayersAdapter.getLayers(username, nonAuthTokensNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) { assert.ok(err); assert.ok(!layers); + assert.ok(!datasource); assert.equal(err.message, "Unauthorized '" + tokenAuthTemplateName + "' template instantiation"); templateMaps.delTemplate(username, tokenAuthTemplateName, done); @@ -192,9 +199,10 @@ suite('mapconfig_named_layers_adapter', function() { var nestedNamedMapLayerConfig = makeNamedMapLayerConfig({ name: nestedNamedMapTemplateName }); - mapConfigNamedLayersAdapter.getLayers(username, nestedNamedMapLayerConfig.layers, function(err, layers) { + mapConfigNamedLayersAdapter.getLayers(username, nestedNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) { assert.ok(err); assert.ok(!layers); + assert.ok(!datasource); assert.equal(err.message, 'Nested named layers are not allowed'); templateMaps.delTemplate(username, nestedNamedMapTemplateName, done); @@ -206,10 +214,12 @@ suite('mapconfig_named_layers_adapter', function() { var validNamedMapMapLayerConfig = makeNamedMapLayerConfig({ name: templateName }); - mapConfigNamedLayersAdapter.getLayers(username, validNamedMapMapLayerConfig.layers, function(err, layers) { + mapConfigNamedLayersAdapter.getLayers(username, validNamedMapMapLayerConfig.layers, serverOptions, function(err, layers, datasource) { assert.ok(!err); assert.ok(layers.length, 1); assert.ok(layers[0].type, 'cartodb'); + assert.notEqual(datasource.getLayerDatasource(0), undefined); + done(); }); }); @@ -224,9 +234,10 @@ suite('mapconfig_named_layers_adapter', function() { name: tokenAuthTemplateName, auth_tokens: ['valid1'] }); - mapConfigNamedLayersAdapter.getLayers(username, validAuthTokensNamedMapLayerConfig.layers, function(err, layers) { + mapConfigNamedLayersAdapter.getLayers(username, validAuthTokensNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) { assert.ok(!err); assert.equal(layers.length, 1); + assert.notEqual(datasource.getLayerDatasource(0), undefined); templateMaps.delTemplate(username, tokenAuthTemplateName, done); }); @@ -243,15 +254,17 @@ suite('mapconfig_named_layers_adapter', function() { name: multipleLayersTemplateName, auth_tokens: ['valid2'] }); - mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, function(err, layers) { + mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) { assert.ok(!err); assert.equal(layers.length, 2); assert.equal(layers[0].type, 'mapnik'); assert.equal(layers[0].options.cartocss, '#layer { polygon-fill: green; }'); + assert.notEqual(datasource.getLayerDatasource(0), undefined); assert.equal(layers[1].type, 'cartodb'); assert.equal(layers[1].options.cartocss, '#layer { marker-fill: red; }'); + assert.notEqual(datasource.getLayerDatasource(1), undefined); templateMaps.delTemplate(username, multipleLayersTemplateName, done); }); @@ -275,15 +288,17 @@ suite('mapconfig_named_layers_adapter', function() { }, auth_tokens: ['valid2'] }); - mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, function(err, layers) { + mapConfigNamedLayersAdapter.getLayers(username, multipleLayersNamedMapLayerConfig.layers, serverOptions, function(err, layers, datasource) { assert.ok(!err); assert.equal(layers.length, 2); assert.equal(layers[0].type, 'mapnik'); assert.equal(layers[0].options.cartocss, '#layer { polygon-fill: ' + polygonColor + '; }'); + assert.notEqual(datasource.getLayerDatasource(0), undefined); assert.equal(layers[1].type, 'cartodb'); assert.equal(layers[1].options.cartocss, '#layer { marker-fill: ' + color + '; }'); + assert.notEqual(datasource.getLayerDatasource(1), undefined); templateMaps.delTemplate(username, multipleLayersTemplateName, done); });