Merge pull request #260 from CartoDB/medusa-improvements
Medusa improvements
This commit is contained in:
commit
afa625e3d2
@ -2,7 +2,9 @@ addons:
|
|||||||
postgresql: "9.3"
|
postgresql: "9.3"
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
|
- sudo apt-get update
|
||||||
- sudo apt-get install -y pkg-config libcairo2-dev libjpeg8-dev libgif-dev
|
- sudo apt-get install -y pkg-config libcairo2-dev libjpeg8-dev libgif-dev
|
||||||
|
- sudo apt-get install postgresql-plpython-9.3
|
||||||
- createdb template_postgis
|
- createdb template_postgis
|
||||||
- psql -c "CREATE EXTENSION postgis" template_postgis
|
- psql -c "CREATE EXTENSION postgis" template_postgis
|
||||||
|
|
||||||
|
5
Makefile
5
Makefile
@ -18,7 +18,10 @@ config/environments/test.js: config.status--test
|
|||||||
check-local: config/environments/test.js
|
check-local: config/environments/test.js
|
||||||
./run_tests.sh ${RUNTESTFLAGS} \
|
./run_tests.sh ${RUNTESTFLAGS} \
|
||||||
test/unit/cartodb/*.js \
|
test/unit/cartodb/*.js \
|
||||||
test/acceptance/*.js
|
test/unit/cartodb/cache/model/*.js \
|
||||||
|
test/integration/*.js \
|
||||||
|
test/acceptance/*.js \
|
||||||
|
test/acceptance/cache/*.js
|
||||||
|
|
||||||
check-submodules:
|
check-submodules:
|
||||||
PATH="$$PATH:$(srcdir)/node_modules/.bin/"; \
|
PATH="$$PATH:$(srcdir)/node_modules/.bin/"; \
|
||||||
|
11
NEWS.md
11
NEWS.md
@ -1,6 +1,15 @@
|
|||||||
1.26.3 -- 2015-mm-dd
|
1.27.0 -- 2015-mm-dd
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
Announcements:
|
||||||
|
- Adds default image placeholder for http renderer to use as fallback
|
||||||
|
|
||||||
|
New features:
|
||||||
|
- `named` layers type, see [MapConfig-NamedMaps-extension](docs/MapConfig-NamedMaps-extension.md)
|
||||||
|
- Starts using datasource per layer feature from Windshaft ([2c7bc6a](https://github.com/CartoDB/Windshaft-cartodb/commit/2c7bc6adde561b20ed955b905e3c7bcd6795d128))
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Fixes tests with beforeEach and afterEach triggers
|
||||||
|
|
||||||
1.26.2 -- 2015-01-28
|
1.26.2 -- 2015-01-28
|
||||||
--------------------
|
--------------------
|
||||||
|
BIN
assets/default-placeholder.png
Normal file
BIN
assets/default-placeholder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
assets/default-placeholder@2x.png
Normal file
BIN
assets/default-placeholder@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
@ -94,7 +94,13 @@ var config = {
|
|||||||
proxy: undefined, // the url for a proxy server
|
proxy: undefined, // the url for a proxy server
|
||||||
whitelist: [ // the whitelist of urlTemplates that can be used
|
whitelist: [ // the whitelist of urlTemplates that can be used
|
||||||
'http://{s}.example.com/{z}/{x}/{y}.png'
|
'http://{s}.example.com/{z}/{x}/{y}.png'
|
||||||
]
|
],
|
||||||
|
// image to use as placeholder when urlTemplate is not in the whitelist
|
||||||
|
// if provided the http renderer will use it instead of throw an error
|
||||||
|
fallbackImage: {
|
||||||
|
type: 'fs', // 'fs' and 'url' supported
|
||||||
|
src: __dirname + '/../../assets/default-placeholder.png'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
,millstone: {
|
,millstone: {
|
||||||
|
@ -88,7 +88,13 @@ var config = {
|
|||||||
proxy: undefined, // the url for a proxy server
|
proxy: undefined, // the url for a proxy server
|
||||||
whitelist: [ // the whitelist of urlTemplates that can be used
|
whitelist: [ // the whitelist of urlTemplates that can be used
|
||||||
'http://{s}.example.com/{z}/{x}/{y}.png'
|
'http://{s}.example.com/{z}/{x}/{y}.png'
|
||||||
]
|
],
|
||||||
|
// image to use as placeholder when urlTemplate is not in the whitelist
|
||||||
|
// if provided the http renderer will use it instead of throw an error
|
||||||
|
fallbackImage: {
|
||||||
|
type: 'fs', // 'fs' and 'url' supported
|
||||||
|
src: __dirname + '/../../assets/default-placeholder.png'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
,millstone: {
|
,millstone: {
|
||||||
|
@ -88,7 +88,13 @@ var config = {
|
|||||||
proxy: undefined, // the url for a proxy server
|
proxy: undefined, // the url for a proxy server
|
||||||
whitelist: [ // the whitelist of urlTemplates that can be used
|
whitelist: [ // the whitelist of urlTemplates that can be used
|
||||||
'http://{s}.example.com/{z}/{x}/{y}.png'
|
'http://{s}.example.com/{z}/{x}/{y}.png'
|
||||||
]
|
],
|
||||||
|
// image to use as placeholder when urlTemplate is not in the whitelist
|
||||||
|
// if provided the http renderer will use it instead of throw an error
|
||||||
|
fallbackImage: {
|
||||||
|
type: 'fs', // 'fs' and 'url' supported
|
||||||
|
src: __dirname + '/../../assets/default-placeholder.png'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
,millstone: {
|
,millstone: {
|
||||||
|
@ -90,7 +90,13 @@ var config = {
|
|||||||
'http://{s}.example.com/{z}/{x}/{y}.png',
|
'http://{s}.example.com/{z}/{x}/{y}.png',
|
||||||
// for testing purposes
|
// for testing purposes
|
||||||
'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png'
|
'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png'
|
||||||
]
|
],
|
||||||
|
// image to use as placeholder when urlTemplate is not in the whitelist
|
||||||
|
// if provided the http renderer will use it instead of throw an error
|
||||||
|
fallbackImage: {
|
||||||
|
type: 'fs', // 'fs' and 'url' supported
|
||||||
|
src: __dirname + '/../../assets/default-placeholder.png'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
,millstone: {
|
,millstone: {
|
||||||
|
56
docs/MapConfig-NamedMaps-extension.md
Normal file
56
docs/MapConfig-NamedMaps-extension.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# 1. Purpose
|
||||||
|
|
||||||
|
This specification describes an extension for
|
||||||
|
[MapConfig 1.3.0](https://github.com/CartoDB/Windshaft/blob/master/doc/MapConfig-1.3.0.md) version.
|
||||||
|
|
||||||
|
|
||||||
|
# 2. Changes over specification
|
||||||
|
|
||||||
|
This extension introduces a new layer type so it's possible to use a named map by its name as a layer.
|
||||||
|
|
||||||
|
## 2.1 Named layers definition
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
// REQUIRED
|
||||||
|
// string, `named` is the only supported value
|
||||||
|
type: "named",
|
||||||
|
|
||||||
|
// REQUIRED
|
||||||
|
// object, set `named` map layers configuration
|
||||||
|
options: {
|
||||||
|
|
||||||
|
// REQUIRED
|
||||||
|
// string, the name for the named map to use
|
||||||
|
name: "world_borders",
|
||||||
|
|
||||||
|
// OPTIONAL
|
||||||
|
// object, the replacement values for the named map's template placeholders
|
||||||
|
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#instantiate-1 for more details
|
||||||
|
config: {
|
||||||
|
"color": "#000"
|
||||||
|
},
|
||||||
|
|
||||||
|
// OPTIONAL
|
||||||
|
// string array, the authorized tokens in case the named map has auth method set to `token`
|
||||||
|
// See https://github.com/CartoDB/Windshaft-cartodb/blob/master/docs/Map-API.md#named-maps-1 for more details
|
||||||
|
auth_tokens: [
|
||||||
|
"token1",
|
||||||
|
"token2"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2.2 Limitations
|
||||||
|
|
||||||
|
1. A Named Map will not allow to have `named` type layers inside their templates layergroup's layers definition.
|
||||||
|
2. A `named` layer does not allow Named Maps form other accounts, it's only possible to use Named Maps from the very
|
||||||
|
same user account.
|
||||||
|
|
||||||
|
|
||||||
|
# History
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version
|
@ -1,11 +1,9 @@
|
|||||||
var _ = require('underscore')
|
var _ = require('underscore');
|
||||||
, Step = require('step')
|
var Step = require('step');
|
||||||
, Windshaft = require('windshaft')
|
var Windshaft = require('windshaft');
|
||||||
, TemplateMaps = require('./template_maps.js')
|
var Cache = require('./cache_validator');
|
||||||
, Cache = require('./cache_validator')
|
var os = require('os');
|
||||||
, os = require('os')
|
var HealthCheck = require('./monitoring/health_check');
|
||||||
, HealthCheck = require('./monitoring/health_check')
|
|
||||||
;
|
|
||||||
|
|
||||||
if ( ! process.env['PGAPPNAME'] )
|
if ( ! process.env['PGAPPNAME'] )
|
||||||
process.env['PGAPPNAME']='cartodb_tiler';
|
process.env['PGAPPNAME']='cartodb_tiler';
|
||||||
@ -25,6 +23,8 @@ var CartodbWindshaft = function(serverOptions) {
|
|||||||
|
|
||||||
var cartoData = require('cartodb-redis')({pool: redisPool});
|
var cartoData = require('cartodb-redis')({pool: redisPool});
|
||||||
|
|
||||||
|
var templateMaps = serverOptions.templateMaps;
|
||||||
|
|
||||||
if(serverOptions.cache_enabled) {
|
if(serverOptions.cache_enabled) {
|
||||||
console.log("cache invalidation enabled, varnish on ", serverOptions.varnish_host, ' ', serverOptions.varnish_port);
|
console.log("cache invalidation enabled, varnish on ", serverOptions.varnish_host, ' ', serverOptions.varnish_port);
|
||||||
Cache.init(serverOptions.varnish_host, serverOptions.varnish_port, serverOptions.varnish_secret);
|
Cache.init(serverOptions.varnish_host, serverOptions.varnish_port, serverOptions.varnish_secret);
|
||||||
@ -48,12 +48,6 @@ var CartodbWindshaft = function(serverOptions) {
|
|||||||
//
|
//
|
||||||
var template_baseurl = global.environment.base_url_templated || '(?:/maps/named|/tiles/template)';
|
var template_baseurl = global.environment.base_url_templated || '(?:/maps/named|/tiles/template)';
|
||||||
|
|
||||||
var templateMapsOpts = {
|
|
||||||
max_user_templates: global.environment.maxUserTemplates
|
|
||||||
};
|
|
||||||
var templateMaps = new TemplateMaps(redisPool, templateMapsOpts);
|
|
||||||
serverOptions.templateMaps = templateMaps;
|
|
||||||
|
|
||||||
var SurrogateKeysCache = require('./cache/surrogate_keys_cache'),
|
var SurrogateKeysCache = require('./cache/surrogate_keys_cache'),
|
||||||
NamedMapsCacheEntry = require('./cache/model/named_maps_entry'),
|
NamedMapsCacheEntry = require('./cache/model/named_maps_entry'),
|
||||||
VarnishHttpCacheBackend = require('./cache/backend/varnish_http'),
|
VarnishHttpCacheBackend = require('./cache/backend/varnish_http'),
|
||||||
|
120
lib/cartodb/models/mapconfig_named_layers_adapter.js
Normal file
120
lib/cartodb/models/mapconfig_named_layers_adapter.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
var queue = require('queue-async');
|
||||||
|
var _ = require('underscore');
|
||||||
|
var Datasource = require('windshaft').Datasource;
|
||||||
|
|
||||||
|
function MapConfigNamedLayersAdapter(templateMaps) {
|
||||||
|
this.templateMaps = templateMaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MapConfigNamedLayersAdapter;
|
||||||
|
|
||||||
|
MapConfigNamedLayersAdapter.prototype.getLayers = function(username, layers, dbMetadata, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var adaptLayersQueue = queue(layers.length);
|
||||||
|
|
||||||
|
function adaptLayer(layer, done) {
|
||||||
|
if (isNamedTypeLayer(layer)) {
|
||||||
|
|
||||||
|
if (!layer.options.name) {
|
||||||
|
return done(new Error('Missing Named Map `name` in layer options'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var templateName = layer.options.name;
|
||||||
|
var templateConfigParams = layer.options.config || {};
|
||||||
|
var templateAuthTokens = layer.options.auth_tokens;
|
||||||
|
|
||||||
|
self.templateMaps.getTemplate(username, templateName, function(err, template) {
|
||||||
|
if (err || !template) {
|
||||||
|
return done(new Error("Template '" + templateName + "' of user '" + username + "' not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.templateMaps.isAuthorized(template, templateAuthTokens)) {
|
||||||
|
var nestedNamedLayers = template.layergroup.layers.filter(function(layer) {
|
||||||
|
return layer.type === 'named';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (nestedNamedLayers.length > 0) {
|
||||||
|
var nestedNamedMapsError = new Error('Nested named layers are not allowed');
|
||||||
|
// nestedNamedMapsError.http_status = 400;
|
||||||
|
return done(nestedNamedMapsError);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var templateLayergroupConfig = self.templateMaps.instance(template, templateConfigParams);
|
||||||
|
return done(null, {
|
||||||
|
datasource: true,
|
||||||
|
layers: templateLayergroupConfig.layers
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var unauthorizedError = new Error("Unauthorized '" + templateName + "' template instantiation");
|
||||||
|
unauthorizedError.http_status = 403;
|
||||||
|
return done(unauthorizedError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return done(null, {
|
||||||
|
datasource: false,
|
||||||
|
layers: [layer]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var datasourceBuilder = new Datasource.Builder();
|
||||||
|
|
||||||
|
function layersAdaptQueueFinish(err, layersResults) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!layersResults || layersResults.length === 0) {
|
||||||
|
return callback(new Error('Missing layers array from layergroup config'));
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function isNamedTypeLayer(layer) {
|
||||||
|
return layer.type === 'named';
|
||||||
|
}
|
@ -3,6 +3,8 @@ var Step = require('step');
|
|||||||
var QueryTablesApi = require('./api/query_tables_api');
|
var QueryTablesApi = require('./api/query_tables_api');
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
var LZMA = require('lzma').LZMA;
|
var LZMA = require('lzma').LZMA;
|
||||||
|
var TemplateMaps = require('./template_maps.js');
|
||||||
|
var MapConfigNamedLayersAdapter = require('./models/mapconfig_named_layers_adapter');
|
||||||
|
|
||||||
// This is for backward compatibility with 1.3.3
|
// This is for backward compatibility with 1.3.3
|
||||||
if ( _.isUndefined(global.environment.sqlapi.domain) ) {
|
if ( _.isUndefined(global.environment.sqlapi.domain) ) {
|
||||||
@ -32,8 +34,10 @@ var REQUEST_QUERY_PARAMS_WHITELIST = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
module.exports = function(redisPool) {
|
module.exports = function(redisPool) {
|
||||||
var redisOpts = redisPool ? {pool: redisPool} : global.environment.redis;
|
redisPool = redisPool
|
||||||
var cartoData = require('cartodb-redis')(redisOpts),
|
|| require('redis-mpool')(_.extend(global.environment.redis, {name: 'windshaft:server_options'}));
|
||||||
|
|
||||||
|
var cartoData = require('cartodb-redis')({ pool: redisPool }),
|
||||||
lzmaWorker = new LZMA(),
|
lzmaWorker = new LZMA(),
|
||||||
queryTablesApi = new QueryTablesApi();
|
queryTablesApi = new QueryTablesApi();
|
||||||
|
|
||||||
@ -102,6 +106,13 @@ module.exports = function(redisPool) {
|
|||||||
// Re-use redisPool
|
// Re-use redisPool
|
||||||
me.redis.pool = redisPool;
|
me.redis.pool = redisPool;
|
||||||
|
|
||||||
|
var templateMaps = new TemplateMaps(redisPool, {
|
||||||
|
max_user_templates: global.environment.maxUserTemplates
|
||||||
|
});
|
||||||
|
me.templateMaps = templateMaps;
|
||||||
|
|
||||||
|
var mapConfigNamedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
|
||||||
|
|
||||||
/* This whole block is about generating X-Cache-Channel { */
|
/* This whole block is about generating X-Cache-Channel { */
|
||||||
|
|
||||||
// TODO: review lifetime of elements of this cache
|
// TODO: review lifetime of elements of this cache
|
||||||
@ -294,6 +305,23 @@ module.exports = function(redisPool) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
me.beforeLayergroupCreate = function(req, requestMapConfig, callback) {
|
||||||
|
mapConfigNamedLayersAdapter.getLayers(this.userByReq(req), requestMapConfig.layers, this, function(err, layers, datasource) {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!datasource.isEmpty()) {
|
||||||
|
setContext(req, 'queryTablesApiDatasource', _.find(datasource.layersDbParams, function(layerDbParams) {
|
||||||
|
return !!layerDbParams;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
requestMapConfig.layers = layers;
|
||||||
|
return callback(null, requestMapConfig, datasource)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
me.afterLayergroupCreate = function(req, mapconfig, response, callback) {
|
me.afterLayergroupCreate = function(req, mapconfig, response, callback) {
|
||||||
var token = response.layergroupid;
|
var token = response.layergroupid;
|
||||||
|
|
||||||
@ -343,14 +371,16 @@ module.exports = function(redisPool) {
|
|||||||
|
|
||||||
Step(
|
Step(
|
||||||
function getAffectedTablesAndLastUpdatedTime() {
|
function getAffectedTablesAndLastUpdatedTime() {
|
||||||
queryTablesApi.getAffectedTablesAndLastUpdatedTime(usr, {
|
var queryTablesOpts = {
|
||||||
user: req.params.dbuser,
|
user: req.params.dbuser,
|
||||||
pass: req.params.dbpass,
|
pass: req.params.dbpass,
|
||||||
host: req.params.dbhost,
|
host: req.params.dbhost,
|
||||||
port: req.params.dbport,
|
port: req.params.dbport,
|
||||||
dbname: req.params.dbname,
|
dbname: req.params.dbname,
|
||||||
api_key: key
|
api_key: key
|
||||||
}, sql, this);
|
};
|
||||||
|
_.extend(queryTablesOpts, getContext(req, 'queryTablesApiDatasource'));
|
||||||
|
queryTablesApi.getAffectedTablesAndLastUpdatedTime(usr, queryTablesOpts, sql, this);
|
||||||
},
|
},
|
||||||
function handleAffectedTablesAndLastUpdatedTime(err, result) {
|
function handleAffectedTablesAndLastUpdatedTime(err, result) {
|
||||||
if (req.profiler) req.profiler.done('queryTablesAndLastUpdated');
|
if (req.profiler) req.profiler.done('queryTablesAndLastUpdated');
|
||||||
@ -822,5 +852,36 @@ module.exports = function(redisPool) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*******************************************************************************************************************
|
||||||
|
* Private methods
|
||||||
|
******************************************************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles context for a given Request object
|
||||||
|
* @param {Object|IncomingMessage} req
|
||||||
|
* @param {String} key
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
function getContext(req, key) {
|
||||||
|
return req.context && req.context[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles context for a given Request object
|
||||||
|
* @param {Object|IncomingMessage} req
|
||||||
|
* @param {String} key
|
||||||
|
* @param {*} value
|
||||||
|
* @returns {*} The previous value
|
||||||
|
*/
|
||||||
|
function setContext(req, key, value) {
|
||||||
|
var previousValue;
|
||||||
|
if (value) {
|
||||||
|
req.context = req.context || {};
|
||||||
|
previousValue = req.context[key];
|
||||||
|
req.context[key] = value;
|
||||||
|
}
|
||||||
|
return previousValue;
|
||||||
|
}
|
||||||
|
|
||||||
return me;
|
return me;
|
||||||
};
|
};
|
||||||
|
24
npm-shrinkwrap.json
generated
24
npm-shrinkwrap.json
generated
@ -113,6 +113,11 @@
|
|||||||
"from": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0",
|
"from": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0",
|
||||||
"resolved": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0"
|
"resolved": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0"
|
||||||
},
|
},
|
||||||
|
"queue-async": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"from": "queue-async@~1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/queue-async/-/queue-async-1.0.7.tgz"
|
||||||
|
},
|
||||||
"redis-mpool": {
|
"redis-mpool": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"from": "https://github.com/CartoDB/node-redis-mpool/tarball/0.3.0",
|
"from": "https://github.com/CartoDB/node-redis-mpool/tarball/0.3.0",
|
||||||
@ -178,24 +183,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz"
|
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz"
|
||||||
},
|
},
|
||||||
"windshaft": {
|
"windshaft": {
|
||||||
"version": "0.35.1",
|
"version": "0.36.0",
|
||||||
"from": "https://github.com/CartoDB/Windshaft/tarball/0.35.1",
|
"from": "windshaft@~0.36.0",
|
||||||
"resolved": "https://github.com/CartoDB/Windshaft/tarball/0.35.1",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chronograph": {
|
"chronograph": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"from": "chronograph@git://github.com/CartoDB/chronographjs.git#0.1.0",
|
"from": "chronograph@git://github.com/CartoDB/chronographjs.git#0.1.0",
|
||||||
"resolved": "git://github.com/CartoDB/chronographjs.git#0b8c35eee510cfa14a16be24d70533b38ecc1d2d"
|
"resolved": "git://github.com/CartoDB/chronographjs.git#0b8c35eee510cfa14a16be24d70533b38ecc1d2d"
|
||||||
},
|
},
|
||||||
"queue-async": {
|
|
||||||
"version": "1.0.7",
|
|
||||||
"from": "queue-async@~1.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/queue-async/-/queue-async-1.0.7.tgz"
|
|
||||||
},
|
|
||||||
"grainstore": {
|
"grainstore": {
|
||||||
"version": "0.22.1",
|
"version": "0.23.0",
|
||||||
"from": "https://github.com/CartoDB/grainstore/tarball/0.22.1",
|
"from": "grainstore@~0.23.0",
|
||||||
"resolved": "https://github.com/CartoDB/grainstore/tarball/0.22.1",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"carto": {
|
"carto": {
|
||||||
"version": "0.9.5-cdb2",
|
"version": "0.9.5-cdb2",
|
||||||
@ -1637,9 +1635,9 @@
|
|||||||
"resolved": "https://registry.npmjs.org/connect/-/connect-1.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/connect/-/connect-1.9.2.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"formidable": {
|
"formidable": {
|
||||||
"version": "1.0.16",
|
"version": "1.0.17",
|
||||||
"from": "formidable@1.0.x",
|
"from": "formidable@1.0.x",
|
||||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.16.tgz"
|
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.17.tgz"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -25,8 +25,9 @@
|
|||||||
"node-varnish": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0",
|
"node-varnish": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0",
|
||||||
"underscore" : "~1.6.0",
|
"underscore" : "~1.6.0",
|
||||||
"dot": "~1.0.2",
|
"dot": "~1.0.2",
|
||||||
"windshaft": "https://github.com/CartoDB/Windshaft/tarball/0.35.1",
|
"windshaft": "~0.36.0",
|
||||||
"step": "~0.0.5",
|
"step": "~0.0.5",
|
||||||
|
"queue-async": "~1.0.7",
|
||||||
"request": "~2.9.203",
|
"request": "~2.9.203",
|
||||||
"cartodb-redis": "https://github.com/CartoDB/node-cartodb-redis/tarball/0.11.0",
|
"cartodb-redis": "https://github.com/CartoDB/node-cartodb-redis/tarball/0.11.0",
|
||||||
"cartodb-psql": "https://github.com/CartoDB/node-cartodb-psql/tarball/0.4.0",
|
"cartodb-psql": "https://github.com/CartoDB/node-cartodb-psql/tarball/0.4.0",
|
||||||
|
@ -16,9 +16,13 @@ var serverOptions = ServerOptions();
|
|||||||
|
|
||||||
suite('templates surrogate keys', function() {
|
suite('templates surrogate keys', function() {
|
||||||
|
|
||||||
var redisClient,
|
var sqlApiServer;
|
||||||
sqlApiServer,
|
var redisClient = redis.createClient(global.environment.redis.port);
|
||||||
server;
|
|
||||||
|
// Enable Varnish purge for tests
|
||||||
|
serverOptions.varnish_purge_enabled = true;
|
||||||
|
|
||||||
|
var server = new CartodbWindshaft(serverOptions);
|
||||||
|
|
||||||
var templateOwner = 'localhost',
|
var templateOwner = 'localhost',
|
||||||
templateName = 'acceptance',
|
templateName = 'acceptance',
|
||||||
@ -45,19 +49,12 @@ suite('templates surrogate keys', function() {
|
|||||||
expectedBody = { template_id: expectedTemplateId };
|
expectedBody = { template_id: expectedTemplateId };
|
||||||
|
|
||||||
suiteSetup(function(done) {
|
suiteSetup(function(done) {
|
||||||
// Enable Varnish purge for tests
|
|
||||||
serverOptions.varnish_purge_enabled = true;
|
|
||||||
|
|
||||||
server = new CartodbWindshaft(serverOptions);
|
|
||||||
|
|
||||||
sqlApiServer = new SqlApiEmulator(global.environment.sqlapi.port, done);
|
sqlApiServer = new SqlApiEmulator(global.environment.sqlapi.port, done);
|
||||||
|
|
||||||
redisClient = redis.createClient(global.environment.redis.port);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var surrogateKeysCacheInvalidateFn = SurrogateKeysCache.prototype.invalidate;
|
var surrogateKeysCacheInvalidateFn = SurrogateKeysCache.prototype.invalidate;
|
||||||
|
|
||||||
beforeEach(function(done) {
|
function createTemplate(callback) {
|
||||||
var postTemplateRequest = {
|
var postTemplateRequest = {
|
||||||
url: '/tiles/template?api_key=1234',
|
url: '/tiles/template?api_key=1234',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -90,10 +87,10 @@ suite('templates surrogate keys', function() {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
function finish(err) {
|
function finish(err) {
|
||||||
done(err);
|
callback(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
test("update template calls surrogate keys invalidation", function(done) {
|
test("update template calls surrogate keys invalidation", function(done) {
|
||||||
var cacheEntryKey;
|
var cacheEntryKey;
|
||||||
@ -104,7 +101,13 @@ suite('templates surrogate keys', function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Step(
|
Step(
|
||||||
function putValidTemplate() {
|
function createTemplateToUpdate() {
|
||||||
|
createTemplate(this);
|
||||||
|
},
|
||||||
|
function putValidTemplate(err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
var updateTemplateRequest = {
|
var updateTemplateRequest = {
|
||||||
url: '/tiles/template/' + expectedTemplateId + '/?api_key=1234',
|
url: '/tiles/template/' + expectedTemplateId + '/?api_key=1234',
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
@ -163,7 +166,13 @@ suite('templates surrogate keys', function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Step(
|
Step(
|
||||||
function putValidTemplate() {
|
function createTemplateToDelete() {
|
||||||
|
createTemplate(this);
|
||||||
|
},
|
||||||
|
function deleteValidTemplate(err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
var deleteTemplateRequest = {
|
var deleteTemplateRequest = {
|
||||||
url: '/tiles/template/' + expectedTemplateId + '/?api_key=1234',
|
url: '/tiles/template/' + expectedTemplateId + '/?api_key=1234',
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@ -199,12 +208,10 @@ suite('templates surrogate keys', function() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function(done) {
|
|
||||||
SurrogateKeysCache.prototype.invalidate = surrogateKeysCacheInvalidateFn;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
suiteTeardown(function(done) {
|
suiteTeardown(function(done) {
|
||||||
|
SurrogateKeysCache.prototype.invalidate = surrogateKeysCacheInvalidateFn;
|
||||||
|
// Enable Varnish purge for tests
|
||||||
|
serverOptions.varnish_purge_enabled = false;
|
||||||
sqlApiServer.close(done);
|
sqlApiServer.close(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ var server = new CartodbWindshaft(serverOptions);
|
|||||||
|
|
||||||
suite('health checks', function () {
|
suite('health checks', function () {
|
||||||
|
|
||||||
beforeEach(function (done) {
|
function resetHealthConfig() {
|
||||||
global.environment.health = {
|
global.environment.health = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
username: 'localhost',
|
username: 'localhost',
|
||||||
@ -15,8 +15,7 @@ suite('health checks', function () {
|
|||||||
x: 0,
|
x: 0,
|
||||||
y: 0
|
y: 0
|
||||||
};
|
};
|
||||||
done();
|
}
|
||||||
});
|
|
||||||
|
|
||||||
var healthCheckRequest = {
|
var healthCheckRequest = {
|
||||||
url: '/health',
|
url: '/health',
|
||||||
@ -27,13 +26,14 @@ suite('health checks', function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
test('returns 200 and ok=true with enabled configuration', function (done) {
|
test('returns 200 and ok=true with enabled configuration', function (done) {
|
||||||
|
resetHealthConfig();
|
||||||
|
|
||||||
assert.response(server,
|
assert.response(server,
|
||||||
healthCheckRequest,
|
healthCheckRequest,
|
||||||
{
|
{
|
||||||
status: 200
|
status: 200
|
||||||
},
|
},
|
||||||
function (res, err) {
|
function (res, err) {
|
||||||
console.log(res.body);
|
|
||||||
assert.ok(!err);
|
assert.ok(!err);
|
||||||
|
|
||||||
var parsed = JSON.parse(res.body);
|
var parsed = JSON.parse(res.body);
|
||||||
@ -47,6 +47,8 @@ suite('health checks', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('fails for invalid user because it is not in redis', function (done) {
|
test('fails for invalid user because it is not in redis', function (done) {
|
||||||
|
resetHealthConfig();
|
||||||
|
|
||||||
global.environment.health.username = 'invalid';
|
global.environment.health.username = 'invalid';
|
||||||
|
|
||||||
assert.response(server,
|
assert.response(server,
|
||||||
|
527
test/acceptance/named_layers.js
Normal file
527
test/acceptance/named_layers.js
Normal file
@ -0,0 +1,527 @@
|
|||||||
|
var test_helper = require('../support/test_helper');
|
||||||
|
|
||||||
|
var assert = require('../support/assert');
|
||||||
|
var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft');
|
||||||
|
var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')();
|
||||||
|
var server = new CartodbWindshaft(serverOptions);
|
||||||
|
|
||||||
|
var RedisPool = require('redis-mpool');
|
||||||
|
var TemplateMaps = require('../../lib/cartodb/template_maps.js');
|
||||||
|
|
||||||
|
var Step = require('step');
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
suite('named_layers', function() {
|
||||||
|
// 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 wadusLayer = {
|
||||||
|
type: 'cartodb',
|
||||||
|
options: {
|
||||||
|
sql: 'select 1 cartodb_id, null::geometry the_geom_webmercator',
|
||||||
|
cartocss: '#layer { marker-fill: <%= 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: [
|
||||||
|
wadusLayer
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenAuthTemplateName = 'auth_valid_template';
|
||||||
|
var tokenAuthTemplate = {
|
||||||
|
version: '0.0.1',
|
||||||
|
name: tokenAuthTemplateName,
|
||||||
|
auth: {
|
||||||
|
method: 'token',
|
||||||
|
valid_tokens: ['valid1', 'valid2']
|
||||||
|
},
|
||||||
|
placeholders: {
|
||||||
|
color: {
|
||||||
|
"type": "css_color",
|
||||||
|
"default": "#cc3300"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layergroup: {
|
||||||
|
layers: [
|
||||||
|
wadusLayer
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var namedMapLayer = {
|
||||||
|
type: 'named',
|
||||||
|
options: {
|
||||||
|
name: templateName,
|
||||||
|
config: {},
|
||||||
|
auth_tokens: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var nestedNamedMapTemplateName = 'nested_template';
|
||||||
|
var nestedNamedMapTemplate = {
|
||||||
|
version: '0.0.1',
|
||||||
|
name: nestedNamedMapTemplateName,
|
||||||
|
auth: {
|
||||||
|
method: 'open'
|
||||||
|
},
|
||||||
|
layergroup: {
|
||||||
|
layers: [
|
||||||
|
namedMapLayer
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
suiteSetup(function(done) {
|
||||||
|
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: true};
|
||||||
|
templateMaps.addTemplate(username, nestedNamedMapTemplate, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
templateMaps.addTemplate(username, tokenAuthTemplate, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
templateMaps.addTemplate(username, template, function(err) {
|
||||||
|
return done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail for non-existing template name', function(done) {
|
||||||
|
var layergroup = {
|
||||||
|
version: '1.3.0',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
type: 'named',
|
||||||
|
options: {
|
||||||
|
name: 'nonexistent'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
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: 400
|
||||||
|
},
|
||||||
|
function(res, err) {
|
||||||
|
next(err, res);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function checkLayergroup(err, response) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedBody = JSON.parse(response.body);
|
||||||
|
assert.deepEqual(parsedBody, { errors: ["Template 'nonexistent' of user 'localhost' not found"] });
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
function finish(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return 403 if not properly authorized', function(done) {
|
||||||
|
|
||||||
|
var layergroup = {
|
||||||
|
version: '1.3.0',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
type: 'named',
|
||||||
|
options: {
|
||||||
|
name: tokenAuthTemplateName,
|
||||||
|
config: {},
|
||||||
|
auth_tokens: ['token1']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
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.deepEqual(
|
||||||
|
parsedBody,
|
||||||
|
{ errors: [ "Unauthorized 'auth_valid_template' template instantiation" ] }
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
function finish(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return 200 and layergroup if properly authorized', function(done) {
|
||||||
|
|
||||||
|
var layergroup = {
|
||||||
|
version: '1.3.0',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
type: 'named',
|
||||||
|
options: {
|
||||||
|
name: tokenAuthTemplateName,
|
||||||
|
config: {},
|
||||||
|
auth_tokens: ['valid1']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
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: 200
|
||||||
|
},
|
||||||
|
function(res, err) {
|
||||||
|
next(err, res);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function checkLayergroup(err, response) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedBody = JSON.parse(response.body);
|
||||||
|
assert.ok(parsedBody.layergroupid);
|
||||||
|
assert.ok(parsedBody.last_updated);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
function finish(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return 400 for nested named map layers', function(done) {
|
||||||
|
|
||||||
|
var layergroup = {
|
||||||
|
version: '1.3.0',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
type: 'named',
|
||||||
|
options: {
|
||||||
|
name: nestedNamedMapTemplateName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
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: 400
|
||||||
|
},
|
||||||
|
function(res, err) {
|
||||||
|
next(err, res);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function checkLayergroup(err, response) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedBody = JSON.parse(response.body);
|
||||||
|
assert.deepEqual(parsedBody, { errors: [ 'Nested named layers are not allowed' ] });
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
function finish(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return 200 and layergroup with private tables', function(done) {
|
||||||
|
|
||||||
|
var privateTableTemplateName = 'private_table_template';
|
||||||
|
var privateTableTemplate = {
|
||||||
|
version: '0.0.1',
|
||||||
|
name: privateTableTemplateName,
|
||||||
|
auth: {
|
||||||
|
method: 'open'
|
||||||
|
},
|
||||||
|
layergroup: {
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
type: 'cartodb',
|
||||||
|
options: {
|
||||||
|
sql: 'select * from test_table_private_1',
|
||||||
|
cartocss: '#layer { marker-fill: #cc3300; }',
|
||||||
|
cartocss_version: '2.3.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var layergroup = {
|
||||||
|
version: '1.3.0',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
type: 'named',
|
||||||
|
options: {
|
||||||
|
name: privateTableTemplateName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
Step(
|
||||||
|
function createTemplate() {
|
||||||
|
templateMaps.addTemplate(username, privateTableTemplate, this);
|
||||||
|
},
|
||||||
|
function createLayergroup(err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
var next = this;
|
||||||
|
assert.response(server,
|
||||||
|
{
|
||||||
|
url: '/tiles/layergroup',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
host: 'localhost',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
data: JSON.stringify(layergroup)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 200
|
||||||
|
},
|
||||||
|
function(res, err) {
|
||||||
|
next(err, res);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function checkLayergroup(err, response) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedBody = JSON.parse(response.body);
|
||||||
|
assert.ok(parsedBody.layergroupid);
|
||||||
|
assert.ok(parsedBody.last_updated);
|
||||||
|
|
||||||
|
return parsedBody.layergroupid;
|
||||||
|
},
|
||||||
|
function requestTile(err, layergroupId) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
var next = this;
|
||||||
|
assert.response(server,
|
||||||
|
{
|
||||||
|
url: '/tiles/layergroup/' + layergroupId + '/0/0/0.png',
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
host: 'localhost'
|
||||||
|
},
|
||||||
|
encoding: 'binary'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'content-type': 'image/png'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(res, err) {
|
||||||
|
next(err, res);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function handleTileResponse(err, res) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
//assert.ok(res.headers['X-Cache-Channel']); -> https://github.com/CartoDB/Windshaft-cartodb/issues/253
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
function deleteTemplate(err) {
|
||||||
|
var next = this;
|
||||||
|
templateMaps.delTemplate(username, privateTableTemplate, function(/*delErr*/) {
|
||||||
|
// ignore deletion error
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function finish(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
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) {
|
||||||
|
global.environment.enabledFeatures = {cdbQueryTablesFromPostgres: false};
|
||||||
|
templateMaps.delTemplate(username, nestedNamedMapTemplateName, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
templateMaps.delTemplate(username, tokenAuthTemplateName, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
templateMaps.delTemplate(username, templateName, function(err) {
|
||||||
|
return done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
297
test/integration/mapconfig_named_layers_datasource.js
Normal file
297
test/integration/mapconfig_named_layers_datasource.js
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
311
test/integration/mapconfig_named_layers_expanded.js
Normal file
311
test/integration/mapconfig_named_layers_expanded.js
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
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 Step = require('step');
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
suite('mapconfig_named_layers_adapter', function() {
|
||||||
|
|
||||||
|
// 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 wadusLayer = {
|
||||||
|
type: 'cartodb',
|
||||||
|
options: {
|
||||||
|
sql: 'select 1 cartodb_id, null::geometry the_geom_webmercator',
|
||||||
|
cartocss: '#layer { marker-fill: <%= color %>; }',
|
||||||
|
cartocss_version: '2.3.0'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var wadusMapnikLayer = {
|
||||||
|
type: 'mapnik',
|
||||||
|
options: {
|
||||||
|
sql: 'select 1 cartodb_id, null::geometry the_geom_webmercator',
|
||||||
|
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: [
|
||||||
|
wadusLayer
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var tokenAuthTemplateName = 'auth_valid_template';
|
||||||
|
var tokenAuthTemplate = {
|
||||||
|
version: '0.0.1',
|
||||||
|
name: tokenAuthTemplateName,
|
||||||
|
auth: {
|
||||||
|
method: 'token',
|
||||||
|
valid_tokens: ['valid1', 'valid2']
|
||||||
|
},
|
||||||
|
layergroup: {
|
||||||
|
layers: [
|
||||||
|
wadusLayer
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
wadusLayer
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var namedMapLayer = {
|
||||||
|
type: 'named',
|
||||||
|
options: {
|
||||||
|
name: templateName,
|
||||||
|
config: {},
|
||||||
|
auth_tokens: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var nestedNamedMapTemplateName = 'nested_template';
|
||||||
|
var nestedNamedMapTemplate = {
|
||||||
|
version: '0.0.1',
|
||||||
|
name: nestedNamedMapTemplateName,
|
||||||
|
auth: {
|
||||||
|
method: 'open'
|
||||||
|
},
|
||||||
|
layergroup: {
|
||||||
|
layers: [
|
||||||
|
namedMapLayer
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function makeNamedMapLayerConfig(options) {
|
||||||
|
return {
|
||||||
|
version: '1.3.0',
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
type: 'named',
|
||||||
|
options: options
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suiteSetup(function(done) {
|
||||||
|
templateMaps.addTemplate(username, template, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail for named map layer with missing name', function(done) {
|
||||||
|
var missingNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||||
|
config: {}
|
||||||
|
});
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail for non-existing template name', function(done) {
|
||||||
|
var missingTemplateName = 'wadus';
|
||||||
|
var nonExistentNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||||
|
name: missingTemplateName
|
||||||
|
});
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail if not properly authorized', function(done) {
|
||||||
|
templateMaps.addTemplate(username, tokenAuthTemplate, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonAuthTokensNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||||
|
name: tokenAuthTemplateName
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should fail for nested named map layers', function(done) {
|
||||||
|
templateMaps.addTemplate(username, nestedNamedMapTemplate, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var nestedNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||||
|
name: nestedNamedMapTemplateName
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return an expanded list of layers for a named map layer', function(done) {
|
||||||
|
var validNamedMapMapLayerConfig = makeNamedMapLayerConfig({
|
||||||
|
name: templateName
|
||||||
|
});
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return on auth=token with valid tokens provided', function(done) {
|
||||||
|
templateMaps.addTemplate(username, tokenAuthTemplate, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var validAuthTokensNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||||
|
name: tokenAuthTemplateName,
|
||||||
|
auth_tokens: ['valid1']
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return an expanded list of layers for a named map layer, multiple layers version', function(done) {
|
||||||
|
templateMaps.addTemplate(username, multipleLayersTemplate, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var multipleLayersNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||||
|
name: multipleLayersTemplateName,
|
||||||
|
auth_tokens: ['valid2']
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should replace template params with the given config', function(done) {
|
||||||
|
templateMaps.addTemplate(username, multipleLayersTemplate, function(err) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
var color = '#cc3300',
|
||||||
|
polygonColor = '#ff9900';
|
||||||
|
|
||||||
|
var multipleLayersNamedMapLayerConfig = makeNamedMapLayerConfig({
|
||||||
|
name: multipleLayersTemplateName,
|
||||||
|
config: {
|
||||||
|
polygon_color: polygonColor,
|
||||||
|
color: color
|
||||||
|
},
|
||||||
|
auth_tokens: ['valid2']
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
suiteTeardown(function(done) {
|
||||||
|
templateMaps.delTemplate(username, templateName, done);
|
||||||
|
});
|
||||||
|
});
|
@ -78,6 +78,12 @@ if test x"$PREPARE_PGSQL" = xyes; then
|
|||||||
sed "s/:TESTPASS/${TESTPASS}/" |
|
sed "s/:TESTPASS/${TESTPASS}/" |
|
||||||
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||||
|
|
||||||
|
psql -c "CREATE EXTENSION plpythonu;" ${TEST_DB}
|
||||||
|
curl -L -s https://github.com/CartoDB/cartodb-postgresql/raw/cdb/scripts-available/CDB_QueryStatements.sql -o sql/CDB_QueryStatements.sql
|
||||||
|
curl -L -s https://github.com/CartoDB/cartodb-postgresql/raw/cdb/scripts-available/CDB_QueryTables.sql -o sql/CDB_QueryTables.sql
|
||||||
|
cat sql/CDB_QueryStatements.sql sql/CDB_QueryTables.sql |
|
||||||
|
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test x"$PREPARE_REDIS" = xyes; then
|
if test x"$PREPARE_REDIS" = xyes; then
|
||||||
|
14
test/support/sql/CDB_QueryStatements.sql
Normal file
14
test/support/sql/CDB_QueryStatements.sql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
-- Return an array of statements found in the given query text
|
||||||
|
--
|
||||||
|
-- Regexp curtesy of Hubert Lubaczewski (depesz)
|
||||||
|
-- Implemented in plpython for performance reasons
|
||||||
|
--
|
||||||
|
CREATE OR REPLACE FUNCTION CDB_QueryStatements(query text)
|
||||||
|
RETURNS SETOF TEXT AS $$
|
||||||
|
import re
|
||||||
|
pat = re.compile( r'''((?:[^'"$;]+|"[^"]*"|'[^']*'|(\$[^$]*\$).*?\2)+)''', re.DOTALL )
|
||||||
|
for match in pat.findall(query):
|
||||||
|
cleaned = match[0].strip()
|
||||||
|
if ( cleaned ):
|
||||||
|
yield cleaned
|
||||||
|
$$ language 'plpythonu' IMMUTABLE STRICT;
|
67
test/support/sql/CDB_QueryTables.sql
Normal file
67
test/support/sql/CDB_QueryTables.sql
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
-- Return an array of table names scanned by a given query
|
||||||
|
--
|
||||||
|
-- Requires PostgreSQL 9.x+
|
||||||
|
--
|
||||||
|
CREATE OR REPLACE FUNCTION CDB_QueryTables(query text)
|
||||||
|
RETURNS name[]
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
exp XML;
|
||||||
|
tables NAME[];
|
||||||
|
rec RECORD;
|
||||||
|
rec2 RECORD;
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
tables := '{}';
|
||||||
|
|
||||||
|
FOR rec IN SELECT CDB_QueryStatements(query) q LOOP
|
||||||
|
|
||||||
|
IF NOT ( rec.q ilike 'select %' or rec.q ilike 'with %' ) THEN
|
||||||
|
--RAISE WARNING 'Skipping %', rec.q;
|
||||||
|
CONTINUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
EXECUTE 'EXPLAIN (FORMAT XML, VERBOSE) ' || rec.q INTO STRICT exp;
|
||||||
|
EXCEPTION WHEN others THEN
|
||||||
|
-- TODO: if error is 'relation "xxxxxx" does not exist', take xxxxxx as
|
||||||
|
-- the affected table ?
|
||||||
|
RAISE WARNING 'CDB_QueryTables cannot explain query: % (%: %)', rec.q, SQLSTATE, SQLERRM;
|
||||||
|
RAISE EXCEPTION '%', SQLERRM;
|
||||||
|
CONTINUE;
|
||||||
|
END;
|
||||||
|
|
||||||
|
-- Now need to extract all values of <Relation-Name>
|
||||||
|
|
||||||
|
-- RAISE DEBUG 'Explain: %', exp;
|
||||||
|
|
||||||
|
FOR rec2 IN WITH
|
||||||
|
inp AS (
|
||||||
|
SELECT
|
||||||
|
xpath('//x:Relation-Name/text()', exp, ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']]) as x,
|
||||||
|
xpath('//x:Relation-Name/../x:Schema/text()', exp, ARRAY[ARRAY['x', 'http://www.postgresql.org/2009/explain']]) as s
|
||||||
|
)
|
||||||
|
SELECT unnest(x)::name as p, unnest(s)::name as sc from inp
|
||||||
|
LOOP
|
||||||
|
-- RAISE DEBUG 'tab: %', rec2.p;
|
||||||
|
-- RAISE DEBUG 'sc: %', rec2.sc;
|
||||||
|
tables := array_append(tables, (rec2.sc || '.' || rec2.p)::name);
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
-- RAISE DEBUG 'Tables: %', tables;
|
||||||
|
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
-- RAISE DEBUG 'Tables: %', tables;
|
||||||
|
|
||||||
|
-- Remove duplicates and sort by name
|
||||||
|
IF array_upper(tables, 1) > 0 THEN
|
||||||
|
WITH dist as ( SELECT DISTINCT unnest(tables)::text as p ORDER BY p )
|
||||||
|
SELECT array_agg(p) from dist into tables;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
--RAISE DEBUG 'Tables: %', tables;
|
||||||
|
|
||||||
|
return tables;
|
||||||
|
END
|
||||||
|
$$ LANGUAGE 'plpgsql' VOLATILE STRICT;
|
@ -177,3 +177,12 @@ CREATE TABLE test_table_private_1 (
|
|||||||
INSERT INTO test_table_private_1 SELECT * from test_table;
|
INSERT INTO test_table_private_1 SELECT * from test_table;
|
||||||
|
|
||||||
GRANT ALL ON TABLE test_table_private_1 TO :TESTUSER;
|
GRANT ALL ON TABLE test_table_private_1 TO :TESTUSER;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS
|
||||||
|
CDB_TableMetadata (
|
||||||
|
tabname regclass not null primary key,
|
||||||
|
updated_at timestamp with time zone not null default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
GRANT SELECT ON CDB_TableMetadata TO :PUBLICUSER;
|
||||||
|
GRANT SELECT ON CDB_TableMetadata TO :TESTUSER;
|
||||||
|
Loading…
Reference in New Issue
Block a user