862 lines
27 KiB
JavaScript
862 lines
27 KiB
JavaScript
'use strict';
|
|
|
|
var testHelper = require('../support/test-helper');
|
|
|
|
var assert = require('../support/assert');
|
|
var CartodbWindshaft = require('../../lib/server');
|
|
var serverOptions = require('../../lib/server-options');
|
|
|
|
var LayergroupToken = require('../../lib/models/layergroup-token');
|
|
|
|
var RedisPool = require('redis-mpool');
|
|
var TemplateMaps = require('../../lib/backends/template-maps');
|
|
|
|
var step = require('step');
|
|
|
|
describe('named_layers', function () {
|
|
var server;
|
|
|
|
before(function () {
|
|
server = new CartodbWindshaft(serverOptions);
|
|
});
|
|
|
|
// configure redis pool instance to use in tests
|
|
var redisPool = new 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,
|
|
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
|
|
]
|
|
}
|
|
};
|
|
|
|
var keysToDelete;
|
|
|
|
beforeEach(function () {
|
|
keysToDelete = {};
|
|
});
|
|
|
|
afterEach(function (done) {
|
|
testHelper.deleteRedisKeys(keysToDelete, done);
|
|
});
|
|
|
|
beforeEach(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);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
afterEach(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);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('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: '/api/v1/map',
|
|
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.deepStrictEqual(parsedBody.errors, ["Template 'nonexistent' of user 'localhost' not found"]);
|
|
|
|
return null;
|
|
},
|
|
function finish (err) {
|
|
done(err);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('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: '/api/v1/map',
|
|
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.deepStrictEqual(parsedBody.errors, ["Unauthorized 'auth_valid_template' template instantiation"]);
|
|
|
|
return null;
|
|
},
|
|
function finish (err) {
|
|
done(err);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('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: '/api/v1/map',
|
|
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);
|
|
|
|
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
|
keysToDelete['user:localhost:mapviews:global'] = 5;
|
|
|
|
return null;
|
|
},
|
|
function finish (err) {
|
|
done(err);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('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: '/api/v1/map',
|
|
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.deepStrictEqual(parsedBody.errors, ['Nested named layers are not allowed']);
|
|
|
|
return null;
|
|
},
|
|
function finish (err) {
|
|
done(err);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('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: '/api/v1/map',
|
|
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);
|
|
|
|
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
|
keysToDelete['user:localhost:mapviews:global'] = 5;
|
|
|
|
return parsedBody.layergroupid;
|
|
},
|
|
function requestTile (err, layergroupId) {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
|
|
var next = this;
|
|
assert.response(server,
|
|
{
|
|
url: '/api/v1/map/' + 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;
|
|
}
|
|
testHelper.checkCache(res);
|
|
return true;
|
|
},
|
|
function deleteTemplate (err) {
|
|
var next = this;
|
|
templateMaps.delTemplate(username, privateTableTemplateName, function (/* delErr */) {
|
|
// ignore deletion error
|
|
next(err);
|
|
});
|
|
},
|
|
function finish (err) {
|
|
done(err);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should return 200 and layergroup with private tables and interactivity', function (done) {
|
|
var privateTableTemplateNameInteractivity = 'private_table_template_interactivity';
|
|
var privateTableTemplate = {
|
|
version: '0.0.1',
|
|
auth: {
|
|
method: 'open'
|
|
},
|
|
name: privateTableTemplateNameInteractivity,
|
|
layergroup: {
|
|
layers: [
|
|
{
|
|
type: 'cartodb',
|
|
options: {
|
|
attributes: {
|
|
columns: [
|
|
'name'
|
|
],
|
|
id: 'cartodb_id'
|
|
},
|
|
cartocss: '#layer { marker-fill: #cc3300; }',
|
|
cartocss_version: '2.3.0',
|
|
interactivity: 'cartodb_id',
|
|
sql: 'select * from test_table_private_1'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
var layergroup = {
|
|
version: '1.3.0',
|
|
layers: [
|
|
{
|
|
type: 'named',
|
|
options: {
|
|
name: privateTableTemplateNameInteractivity
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
step(
|
|
function createTemplate () {
|
|
templateMaps.addTemplate(username, privateTableTemplate, this);
|
|
},
|
|
function createLayergroup (err) {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
|
|
var next = this;
|
|
assert.response(server,
|
|
{
|
|
url: '/api/v1/map',
|
|
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);
|
|
|
|
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
|
keysToDelete['user:localhost:mapviews:global'] = 5;
|
|
|
|
return parsedBody.layergroupid;
|
|
},
|
|
function requestTile (err, layergroupId) {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
|
|
var next = this;
|
|
assert.response(server,
|
|
{
|
|
url: '/api/v1/map/' + 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;
|
|
}
|
|
testHelper.checkCache(res);
|
|
return true;
|
|
},
|
|
function deleteTemplate (err) {
|
|
var next = this;
|
|
templateMaps.delTemplate(username, privateTableTemplateNameInteractivity, function (/* delErr */) {
|
|
// ignore deletion error
|
|
next(err);
|
|
});
|
|
},
|
|
function finish (err) {
|
|
done(err);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('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: '/api/v1/map',
|
|
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 .+?test_table_private_1/));
|
|
|
|
return null;
|
|
},
|
|
function finish (err) {
|
|
done(err);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should return metadata for named layers', function (done) {
|
|
var layergroup = {
|
|
version: '1.3.0',
|
|
layers: [
|
|
{
|
|
type: 'plain',
|
|
options: {
|
|
color: '#fabada'
|
|
}
|
|
},
|
|
{
|
|
type: 'cartodb',
|
|
options: {
|
|
sql: 'select * from test_table',
|
|
cartocss: '#layer { marker-fill: #cc3300; }',
|
|
cartocss_version: '2.3.0'
|
|
}
|
|
},
|
|
{
|
|
type: 'named',
|
|
options: {
|
|
name: templateName
|
|
}
|
|
},
|
|
{
|
|
type: 'torque',
|
|
options: {
|
|
sql: 'select * from test_table LIMIT 0',
|
|
cartocss: 'Map { -torque-frame-count:1; -torque-resolution:1; ' +
|
|
"-torque-aggregation-function:'count(*)'; -torque-time-attribute:'updated_at'; }"
|
|
}
|
|
}
|
|
]
|
|
};
|
|
|
|
step(
|
|
function createLayergroup () {
|
|
var next = this;
|
|
assert.response(server,
|
|
{
|
|
url: '/api/v1/map',
|
|
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.metadata);
|
|
assert.ok(parsedBody.metadata.layers);
|
|
assert.strictEqual(parsedBody.metadata.layers.length, 5);
|
|
assert.strictEqual(parsedBody.metadata.layers[0].type, 'plain');
|
|
assert.strictEqual(parsedBody.metadata.layers[1].type, 'mapnik');
|
|
assert.strictEqual(parsedBody.metadata.layers[2].type, 'mapnik');
|
|
assert.strictEqual(parsedBody.metadata.layers[3].type, 'mapnik');
|
|
assert.strictEqual(parsedBody.metadata.layers[4].type, 'torque');
|
|
|
|
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
|
keysToDelete['user:localhost:mapviews:global'] = 5;
|
|
|
|
return null;
|
|
},
|
|
function finish (err) {
|
|
done(err);
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should work with named tiles', function (done) {
|
|
var namedTilesTemplateName = 'named_tiles_template';
|
|
var namedTilesTemplate = {
|
|
version: '0.0.1',
|
|
name: namedTilesTemplateName,
|
|
auth: {
|
|
method: 'open'
|
|
},
|
|
layergroup: {
|
|
layers: [
|
|
namedMapLayer,
|
|
{
|
|
type: 'mapnik',
|
|
options: {
|
|
sql: 'select * from test_table_private_1',
|
|
cartocss: '#layer { marker-fill: #cc3300; }',
|
|
cartocss_version: '2.3.0'
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
step(
|
|
function createTemplate () {
|
|
templateMaps.addTemplate(username, namedTilesTemplate, this);
|
|
},
|
|
function createLayergroup (err) {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
|
|
var next = this;
|
|
assert.response(server,
|
|
{
|
|
url: '/api/v1/map/named/' + namedTilesTemplateName + '?api_key=1234',
|
|
method: 'POST',
|
|
headers: {
|
|
host: 'localhost',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
},
|
|
{
|
|
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);
|
|
|
|
assert.strictEqual(parsedBody.metadata.layers[0].type, 'mapnik');
|
|
assert.strictEqual(parsedBody.metadata.layers[1].type, 'mapnik');
|
|
|
|
keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0;
|
|
keysToDelete['user:localhost:mapviews:global'] = 5;
|
|
|
|
return parsedBody.layergroupid;
|
|
},
|
|
function requestTile (err, layergroupId) {
|
|
if (err) {
|
|
throw err;
|
|
}
|
|
|
|
var next = this;
|
|
assert.response(server,
|
|
{
|
|
url: '/api/v1/map/' + layergroupId + '/all/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;
|
|
}
|
|
testHelper.checkCache(res);
|
|
return true;
|
|
},
|
|
function deleteTemplate (err) {
|
|
var next = this;
|
|
templateMaps.delTemplate(username, namedTilesTemplateName, function (/* delErr */) {
|
|
// ignore deletion error
|
|
next(err);
|
|
});
|
|
},
|
|
function finish (err) {
|
|
done(err);
|
|
}
|
|
);
|
|
});
|
|
});
|