Ports acceptance tests from windshaft
@@ -27,6 +27,11 @@ test: config/environments/test.js
|
||||
test/acceptance/*.js \
|
||||
test/acceptance/cache/*.js
|
||||
|
||||
test-ported: config/environments/test.js
|
||||
@echo "***tests ported***"
|
||||
@$(SHELL) ./run_tests.sh ${RUNTESTFLAGS} \
|
||||
test/acceptance/ported/*.js
|
||||
|
||||
test-unit: config/environments/test.js
|
||||
@echo "***tests***"
|
||||
@$(SHELL) ./run_tests.sh ${RUNTESTFLAGS} \
|
||||
@@ -48,7 +53,7 @@ jshint:
|
||||
@echo "***jshint***"
|
||||
@./node_modules/.bin/jshint lib/ test/ app.js
|
||||
|
||||
test-all: jshint test
|
||||
test-all: jshint test test-ported
|
||||
|
||||
coverage:
|
||||
@RUNTESTFLAGS=--with-coverage make test
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @param {String} token might match the following pattern: {user}@{tpl_id}@{token}:{cache_buster}
|
||||
*/
|
||||
function parse(token) {
|
||||
var signer, cacheBuster;
|
||||
|
||||
var tokenSplit = token.split(':');
|
||||
|
||||
token = tokenSplit[0];
|
||||
if (tokenSplit.length > 1) {
|
||||
cacheBuster = tokenSplit[1];
|
||||
}
|
||||
|
||||
tokenSplit = token.split('@');
|
||||
if ( tokenSplit.length > 1 ) {
|
||||
signer = tokenSplit.shift();
|
||||
if ( tokenSplit.length > 1 ) {
|
||||
/*var template_hash = */tokenSplit.shift(); // unused
|
||||
}
|
||||
token = tokenSplit.shift();
|
||||
}
|
||||
|
||||
return {
|
||||
token: token,
|
||||
signer: signer,
|
||||
cacheBuster: cacheBuster
|
||||
};
|
||||
}
|
||||
module.exports.parse = parse;
|
||||
@@ -0,0 +1,311 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var redis = require('redis');
|
||||
var step = require('step');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
|
||||
describe('attributes', function() {
|
||||
|
||||
var server = cartodbServer(PortedServerOptions);
|
||||
server.req2params = PortedServerOptions.req2params;
|
||||
server.setMaxListeners(0);
|
||||
var redis_client = redis.createClient(PortedServerOptions.redis.port);
|
||||
|
||||
var test_mapconfig_1 = {
|
||||
version: '1.1.0',
|
||||
layers: [
|
||||
{ type: 'mapnik', options: {
|
||||
sql: "select 1 as id, 'SRID=4326;POINT(0 0)'::geometry as the_geom",
|
||||
cartocss: '#style { }',
|
||||
cartocss_version: '2.0.1'
|
||||
} },
|
||||
{ type: 'mapnik', options: {
|
||||
sql: "select 1 as i, 6 as n, 'SRID=4326;POINT(0 0)'::geometry as the_geom",
|
||||
attributes: { id:'i', columns: ['n'] },
|
||||
cartocss: '#style { }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
function checkCORSHeaders(res) {
|
||||
assert.equal(
|
||||
res.headers['access-control-allow-headers'],
|
||||
'X-Requested-With, X-Prototype-Version, X-CSRF-Token'
|
||||
);
|
||||
assert.equal(res.headers['access-control-allow-origin'], '*');
|
||||
}
|
||||
|
||||
it("can only be fetched from layer having an attributes spec", function(done) {
|
||||
|
||||
var expected_token;
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(test_mapconfig_1)
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function checkPost(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
// CORS headers should be sent with response
|
||||
// from layergroup creation via POST
|
||||
checkCORSHeaders(res);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
if ( expected_token ) {
|
||||
assert.deepEqual(parsedBody, {layergroupid: expected_token, layercount: 2});
|
||||
} else {
|
||||
expected_token = parsedBody.layergroupid;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
function do_get_attr_0(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + expected_token + '/0/attributes/1',
|
||||
method: 'GET'
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function check_error_0(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(
|
||||
res.statusCode,
|
||||
400,
|
||||
res.statusCode + ( res.statusCode !== 200 ? (': ' + res.body) : '' )
|
||||
);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.equal(parsed.errors[0], "Layer 0 has no exposed attributes");
|
||||
return null;
|
||||
},
|
||||
function do_get_attr_1(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + expected_token + '/1/attributes/1',
|
||||
method: 'GET'
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function check_attr_1(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.deepEqual(parsed, {"n":6});
|
||||
return null;
|
||||
},
|
||||
function do_get_attr_1_404(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + expected_token + '/1/attributes/-666',
|
||||
method: 'GET'
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function check_attr_1_404(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 404, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors);
|
||||
var msg = parsed.errors[0];
|
||||
assert.ok(msg.match(/0 features.*identified by fid -666/), msg);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
var errors = [];
|
||||
if ( err ) {
|
||||
errors.push(''+err);
|
||||
}
|
||||
redis_client.exists("map_cfg|" + expected_token, function(err/*, exists*/) {
|
||||
if ( err ) {
|
||||
errors.push(err.message);
|
||||
}
|
||||
//assert.ok(exists, "Missing expected token " + expected_token + " from redis");
|
||||
redis_client.del("map_cfg|" + expected_token, function(err) {
|
||||
if ( err ) {
|
||||
errors.push(err.message);
|
||||
}
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
} else {
|
||||
done(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft/issues/131
|
||||
it("are checked at map creation time", function(done) {
|
||||
|
||||
// clone the mapconfig test
|
||||
var mapconfig = JSON.parse(JSON.stringify(test_mapconfig_1));
|
||||
// append unexistant attribute name
|
||||
mapconfig.layers[1].options.sql = 'SELECT * FROM test_table';
|
||||
mapconfig.layers[1].options.attributes.id = 'unexistant';
|
||||
mapconfig.layers[1].options.attributes.columns = ['cartodb_id'];
|
||||
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(mapconfig)
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function checkPost(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 404, res.statusCode + ': ' + (res.statusCode===200?'...':res.body));
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
var msg = parsed.errors[0];
|
||||
assert.equal(msg, 'column "unexistant" does not exist');
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("can be used with jsonp", function(done) {
|
||||
|
||||
var expected_token;
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(test_mapconfig_1)
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function checkPost(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
// CORS headers should be sent with response
|
||||
// from layergroup creation via POST
|
||||
checkCORSHeaders(res);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
if ( expected_token ) {
|
||||
assert.deepEqual(parsedBody, {layergroupid: expected_token, layercount: 2});
|
||||
} else {
|
||||
expected_token = parsedBody.layergroupid;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
function do_get_attr_0(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + expected_token +
|
||||
'/0/attributes/1?callback=test',
|
||||
method: 'GET'
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function check_error_0(err, res) {
|
||||
assert.ifError(err);
|
||||
// jsonp errors should be returned with HTTP status 200
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
assert.equal(res.body, 'test({"errors":["Layer 0 has no exposed attributes"]});');
|
||||
return null;
|
||||
},
|
||||
function do_get_attr_1(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + expected_token + '/1/attributes/1',
|
||||
method: 'GET'
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function check_attr_1(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.deepEqual(parsed, {"n":6});
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
var errors = [];
|
||||
if ( err ) {
|
||||
errors.push(''+err);
|
||||
}
|
||||
redis_client.exists("map_cfg|" + expected_token, function(err/*, exists*/) {
|
||||
if ( err ) {
|
||||
errors.push(err.message);
|
||||
}
|
||||
//assert.ok(exists, "Missing expected token " + expected_token + " from redis");
|
||||
redis_client.del("map_cfg|" + expected_token, function(err) {
|
||||
if ( err ) {
|
||||
errors.push(err.message);
|
||||
}
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
} else {
|
||||
done(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Test that you cannot write to the database from an attributes tile request
|
||||
//
|
||||
// Test for http://github.com/CartoDB/Windshaft/issues/130
|
||||
//
|
||||
it("database access is read-only", function(done) {
|
||||
|
||||
// clone the mapconfig test
|
||||
var mapconfig = JSON.parse(JSON.stringify(test_mapconfig_1));
|
||||
mapconfig.layers[1].options.sql +=
|
||||
", test_table_inserter(st_setsrid(st_point(0,0),4326),'write') as w";
|
||||
mapconfig.layers[1].options.attributes.columns.push('w');
|
||||
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(mapconfig)
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function checkPost(err, res) {
|
||||
assert.ifError(err);
|
||||
// TODO: should be 403 Forbidden
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + (res.statusCode===200?'...':res.body));
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
var msg = parsed.errors[0];
|
||||
assert.equal(msg, "cannot execute INSERT in a read-only transaction");
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
describe('blend png renderer', function() {
|
||||
|
||||
var IMAGE_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
function plainTorqueMapConfig(plainColor) {
|
||||
return {
|
||||
version: '1.2.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'plain',
|
||||
options: {
|
||||
color: plainColor
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'torque',
|
||||
options: {
|
||||
sql: "SELECT * FROM populated_places_simple_reduced " +
|
||||
"where the_geom && ST_MakeEnvelope(-90, 0, 90, 65)",
|
||||
cartocss: [
|
||||
'Map {',
|
||||
' buffer-size:0;',
|
||||
' -torque-frame-count:1;',
|
||||
' -torque-animation-duration:30;',
|
||||
' -torque-time-attribute:"cartodb_id";',
|
||||
' -torque-aggregation-function:"count(cartodb_id)";',
|
||||
' -torque-resolution:1;',
|
||||
' -torque-data-aggregation:linear;',
|
||||
'}',
|
||||
'#populated_places_simple_reduced{',
|
||||
' comp-op: multiply;',
|
||||
' marker-fill-opacity: 1;',
|
||||
' marker-line-color: #FFF;',
|
||||
' marker-line-width: 0;',
|
||||
' marker-line-opacity: 1;',
|
||||
' marker-type: rectangle;',
|
||||
' marker-width: 3;',
|
||||
' marker-fill: #FFCC00;',
|
||||
'}'
|
||||
].join(' '),
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
var testScenarios = [
|
||||
{
|
||||
tile: {
|
||||
z: 2,
|
||||
x: 2,
|
||||
y: 1,
|
||||
layer: 'all',
|
||||
format: 'png'
|
||||
},
|
||||
plainColor: 'white'
|
||||
},
|
||||
{
|
||||
tile: {
|
||||
z: 2,
|
||||
x: 1,
|
||||
y: 1,
|
||||
layer: 'all',
|
||||
format: 'png'
|
||||
},
|
||||
plainColor: '#339900'
|
||||
}
|
||||
];
|
||||
|
||||
function blendPngFixture(zxy) {
|
||||
return './test/fixtures/blend/blend-plain-torque-' + zxy.join('.') + '.png';
|
||||
}
|
||||
|
||||
testScenarios.forEach(function(testScenario) {
|
||||
var tileRequest = testScenario.tile;
|
||||
var zxy = [tileRequest.z, tileRequest.x, tileRequest.y];
|
||||
it('tile all/' + zxy.join('/') + '.png', function (done) {
|
||||
testClient.getTileLayer(plainTorqueMapConfig(testScenario.plainColor), tileRequest, function(err, res) {
|
||||
assert.imageEqualsFile(res.body, blendPngFixture(zxy), IMAGE_TOLERANCE_PER_MIL, function(err) {
|
||||
assert.ok(!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,158 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
|
||||
describe('blend layer filtering', function() {
|
||||
|
||||
var IMG_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
var httpRendererResourcesServer;
|
||||
|
||||
before(function(done) {
|
||||
// Start a server to test external resources
|
||||
httpRendererResourcesServer = http.createServer( function(request, response) {
|
||||
var filename = __dirname + '/../../fixtures/http/light_nolabels-1-0-0.png';
|
||||
fs.readFile(filename, {encoding: 'binary'}, function(err, file) {
|
||||
response.writeHead(200);
|
||||
response.write(file, "binary");
|
||||
response.end();
|
||||
});
|
||||
});
|
||||
httpRendererResourcesServer.listen(8033, done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
httpRendererResourcesServer.close(done);
|
||||
});
|
||||
|
||||
var mapConfig = {
|
||||
version: '1.2.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'plain',
|
||||
options: {
|
||||
color: '#fabada'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'http',
|
||||
options: {
|
||||
urlTemplate: 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png',
|
||||
subdomains: ['abcd']
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: 'SELECT * FROM populated_places_simple_reduced',
|
||||
cartocss: '#layer { marker-fill:red; } #layer { marker-width: 2; }',
|
||||
cartocss_version: '2.3.0',
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'torque',
|
||||
options: {
|
||||
sql: "SELECT * FROM populated_places_simple_reduced",
|
||||
cartocss: [
|
||||
'Map {',
|
||||
' buffer-size:0;',
|
||||
' -torque-frame-count:1;',
|
||||
' -torque-animation-duration:30;',
|
||||
' -torque-time-attribute:"cartodb_id";',
|
||||
' -torque-aggregation-function:"count(cartodb_id)";',
|
||||
' -torque-resolution:1;',
|
||||
' -torque-data-aggregation:linear;',
|
||||
'}',
|
||||
'#populated_places_simple_reduced{',
|
||||
' comp-op: multiply;',
|
||||
' marker-fill-opacity: 1;',
|
||||
' marker-line-color: #FFF;',
|
||||
' marker-line-width: 0;',
|
||||
' marker-line-opacity: 1;',
|
||||
' marker-type: rectangle;',
|
||||
' marker-width: 3;',
|
||||
' marker-fill: #FFCC00;',
|
||||
'}'
|
||||
].join(' '),
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'http',
|
||||
options: {
|
||||
urlTemplate: 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png',
|
||||
subdomains: ['abcd']
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'torque',
|
||||
options: {
|
||||
sql: "SELECT * FROM populated_places_simple_reduced " +
|
||||
"where the_geom && ST_MakeEnvelope(-90, 0, 90, 65)",
|
||||
cartocss: [
|
||||
'Map {',
|
||||
' buffer-size:0;',
|
||||
' -torque-frame-count:1;',
|
||||
' -torque-animation-duration:30;',
|
||||
' -torque-time-attribute:"cartodb_id";',
|
||||
' -torque-aggregation-function:"count(cartodb_id)";',
|
||||
' -torque-resolution:1;',
|
||||
' -torque-data-aggregation:linear;',
|
||||
'}',
|
||||
'#populated_places_simple_reduced{',
|
||||
' comp-op: multiply;',
|
||||
' marker-fill-opacity: 1;',
|
||||
' marker-line-color: #FFF;',
|
||||
' marker-line-width: 0;',
|
||||
' marker-line-opacity: 1;',
|
||||
' marker-type: rectangle;',
|
||||
' marker-width: 3;',
|
||||
' marker-fill: #FFCC00;',
|
||||
'}'
|
||||
].join(' '),
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var filteredLayersSuite = [
|
||||
[2, 2],
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
[1, 2],
|
||||
[2, 1], // ordering doesn't matter
|
||||
[0, 3],
|
||||
[1, 3],
|
||||
[1, 2, 5],
|
||||
[1, 2, 3, 4]
|
||||
];
|
||||
|
||||
function blendPngFixture(layers) {
|
||||
return './test/fixtures/blend/blend-filtering-layers-' + layers.join('.') + '-zxy-1.0.0.png';
|
||||
}
|
||||
|
||||
filteredLayersSuite.forEach(function(filteredLayers) {
|
||||
var layerFilter = filteredLayers.join(',');
|
||||
var tileRequest = {
|
||||
z: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
layer: layerFilter,
|
||||
format: 'png'
|
||||
};
|
||||
|
||||
it('should filter on ' + layerFilter + '/1/0/0.png', function (done) {
|
||||
testClient.getTileLayer(mapConfig, tileRequest, function(err, res) {
|
||||
assert.imageEqualsFile(res.body, blendPngFixture(filteredLayers), IMG_TOLERANCE_PER_MIL, function(err) {
|
||||
assert.ok(!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,140 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
|
||||
describe('blend http fallback', function() {
|
||||
|
||||
var IMG_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
var httpRendererResourcesServer;
|
||||
|
||||
before(function(done) {
|
||||
// Start a server to test external resources
|
||||
httpRendererResourcesServer = http.createServer( function(request, response) {
|
||||
if (request.url.match(/^\/error404\//)) {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
} else {
|
||||
var filename = __dirname + '/../../fixtures/http/light_nolabels-1-0-0.png';
|
||||
if (request.url.match(/^\/dark\//)) {
|
||||
filename = __dirname + '/../../fixtures/http/dark_nolabels-1-0-0.png';
|
||||
}
|
||||
fs.readFile(filename, {encoding: 'binary'}, function(err, file) {
|
||||
response.writeHead(200);
|
||||
response.write(file, "binary");
|
||||
response.end();
|
||||
});
|
||||
}
|
||||
});
|
||||
httpRendererResourcesServer.listen(8033, done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
httpRendererResourcesServer.close(done);
|
||||
});
|
||||
|
||||
var mapConfig = {
|
||||
version: '1.2.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'plain', // <- 0
|
||||
options: {
|
||||
color: '#fabada'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'http', // <- 1
|
||||
options: {
|
||||
urlTemplate: 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png',
|
||||
subdomains: ['light']
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'http', // <- 2
|
||||
options: {
|
||||
urlTemplate: 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png',
|
||||
subdomains: ['dark']
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'http', // <- 3
|
||||
options: {
|
||||
urlTemplate: 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png',
|
||||
subdomains: ['error404']
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'mapnik', // <- 4
|
||||
options: {
|
||||
sql: 'SELECT * FROM populated_places_simple_reduced',
|
||||
cartocss: '#layer { marker-fill:red; } #layer { marker-width: 2; }',
|
||||
cartocss_version: '2.3.0',
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var filteredLayersSuite = [
|
||||
['all'], // layers displayed: 2 + 4, skipping 3 as it fails
|
||||
[0, 4],
|
||||
[0, 3], // skips layer 3 as it fails
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[2, 3],
|
||||
[3, 4]
|
||||
];
|
||||
|
||||
function blendPngFixture(layers) {
|
||||
return './test/fixtures/blend/http_fallback/blend-layers-' + layers.join('.') + '-zxy-1.0.0.png';
|
||||
}
|
||||
|
||||
filteredLayersSuite.forEach(function(filteredLayers) {
|
||||
var layerFilter = filteredLayers.join(',');
|
||||
var tileRequest = {
|
||||
z: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
layer: layerFilter,
|
||||
format: 'png'
|
||||
};
|
||||
|
||||
it('should fallback on http error while blending layers ' + layerFilter + '/1/0/0.png', function (done) {
|
||||
testClient.getTileLayer(mapConfig, tileRequest, function(err, res) {
|
||||
assert.imageEqualsFile(res.body, blendPngFixture(filteredLayers), IMG_TOLERANCE_PER_MIL, function(err) {
|
||||
assert.ok(!err, err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep failing when http layer is requested individually', function(done) {
|
||||
var tileRequest = {
|
||||
z: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
layer: 3,
|
||||
format: 'png'
|
||||
};
|
||||
var expectedResponse = {
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
testClient.getTileLayer(mapConfig, tileRequest, expectedResponse, function(err, res) {
|
||||
assert.ok(!err);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.deepEqual(parsedBody, {
|
||||
errors: [
|
||||
"Unable to fetch http tile: http://127.0.0.1:8033/error404/1/0/0.png [404]"
|
||||
]
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
var serverOptions = require('./support/ported_server_options');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
|
||||
describe.skip('blend http client timeout', function() {
|
||||
|
||||
var mapConfig = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'http',
|
||||
options: {
|
||||
urlTemplate: 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png',
|
||||
subdomains: ['light']
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: 'SELECT * FROM populated_places_simple_reduced',
|
||||
cartocss: '#layer { marker-fill:red; } #layer { marker-width: 2; }',
|
||||
cartocss_version: '2.3.0',
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var oldHttpRendererTimeout;
|
||||
var httpRendererTimeout = 100;
|
||||
|
||||
var slowHttpRendererResourcesServer;
|
||||
|
||||
before(function(done) {
|
||||
oldHttpRendererTimeout = serverOptions.renderer.http.timeout;
|
||||
serverOptions.renderer.http.timeout = httpRendererTimeout;
|
||||
|
||||
// Start a server to test external resources
|
||||
slowHttpRendererResourcesServer = http.createServer( function(request, response) {
|
||||
setTimeout(function() {
|
||||
var filename = __dirname + '/../fixtures/http/light_nolabels-1-0-0.png';
|
||||
fs.readFile(filename, {encoding: 'binary'}, function(err, file) {
|
||||
response.writeHead(200);
|
||||
response.write(file, "binary");
|
||||
response.end();
|
||||
});
|
||||
}, httpRendererTimeout * 2);
|
||||
});
|
||||
slowHttpRendererResourcesServer.listen(8033, done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
serverOptions.renderer.http.timeout = oldHttpRendererTimeout;
|
||||
slowHttpRendererResourcesServer.close(done);
|
||||
});
|
||||
|
||||
it('should fail to render when http layer times out', function(done) {
|
||||
var options = {
|
||||
statusCode: 400,
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
serverOptions: serverOptions
|
||||
};
|
||||
testClient.withLayergroup(mapConfig, options, function(err, requestTile, finish) {
|
||||
var tileUrl = '/all/0/0/0.png';
|
||||
requestTile(tileUrl, options, function(err, res) {
|
||||
assert.ok(!err);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.ok(parsedBody.errors);
|
||||
assert.ok(parsedBody.errors.length);
|
||||
assert.equal(parsedBody.errors[0], 'Unable to fetch http tile: http://127.0.0.1:8033/light/0/0/0.png');
|
||||
finish(function(finishErr) {
|
||||
done(err || finishErr);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var fs = require('fs');
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var http = require('http');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
function rmdir_recursive_sync(dirname) {
|
||||
var files = fs.readdirSync(dirname);
|
||||
for (var i=0; i<files.length; ++i) {
|
||||
var f = dirname + "/" + files[i];
|
||||
var s = fs.lstatSync(f);
|
||||
if ( s.isFile() ) {
|
||||
fs.unlinkSync(f);
|
||||
}
|
||||
else {
|
||||
rmdir_recursive_sync(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('external resources', function() {
|
||||
|
||||
var res_serv; // resources server
|
||||
var res_serv_status = { numrequests:0 }; // status of resources server
|
||||
var res_serv_port = 8033; // FIXME: make configurable ?
|
||||
|
||||
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 25;
|
||||
|
||||
before(function(done) {
|
||||
// Start a server to test external resources
|
||||
res_serv = http.createServer( function(request, response) {
|
||||
++res_serv_status.numrequests;
|
||||
var filename = __dirname + '/../../fixtures/markers' + request.url;
|
||||
fs.readFile(filename, "binary", function(err, file) {
|
||||
if ( err ) {
|
||||
response.writeHead(404, {'Content-Type': 'text/plain'});
|
||||
response.write("404 Not Found\n");
|
||||
} else {
|
||||
response.writeHead(200);
|
||||
response.write(file, "binary");
|
||||
}
|
||||
response.end();
|
||||
});
|
||||
});
|
||||
res_serv.listen(res_serv_port, done);
|
||||
});
|
||||
|
||||
function imageCompareFn(fixture, done) {
|
||||
return function(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.imageEqualsFile(res.body, './test/fixtures/' + fixture, IMAGE_EQUALS_TOLERANCE_PER_MIL, done);
|
||||
};
|
||||
}
|
||||
|
||||
it("basic external resource", function(done) {
|
||||
|
||||
var circleStyle = "#test_table_3 { marker-file: url('http://localhost:" + res_serv_port +
|
||||
"/circle.svg'); marker-transform:'scale(0.2)'; }";
|
||||
|
||||
testClient.getTile(testClient.defaultTableMapConfig('test_table_3', circleStyle), 13, 4011, 3088,
|
||||
imageCompareFn('test_table_13_4011_3088_svg1.png', done));
|
||||
});
|
||||
|
||||
it("different external resource", function(done) {
|
||||
|
||||
var squareStyle = "#test_table_3 { marker-file: url('http://localhost:" + res_serv_port +
|
||||
"/square.svg'); marker-transform:'scale(0.2)'; }";
|
||||
|
||||
testClient.getTile(testClient.defaultTableMapConfig('test_table_3', squareStyle), 13, 4011, 3088,
|
||||
imageCompareFn('test_table_13_4011_3088_svg2.png', done));
|
||||
});
|
||||
|
||||
// See http://github.com/CartoDB/Windshaft/issues/107
|
||||
it("external resources get localized on renderer creation if not locally cached", function(done) {
|
||||
|
||||
var options = {
|
||||
serverOptions: PortedServerOptions
|
||||
};
|
||||
|
||||
var externalResourceStyle = "#test_table_3{marker-file: url('http://localhost:" + res_serv_port +
|
||||
"/square.svg'); marker-transform:'scale(0.2)'; }";
|
||||
|
||||
var externalResourceMapConfig = testClient.defaultTableMapConfig('test_table_3', externalResourceStyle);
|
||||
|
||||
testClient.createLayergroup(externalResourceMapConfig, options, function() {
|
||||
var externalResourceRequestsCount = res_serv_status.numrequests;
|
||||
|
||||
testClient.createLayergroup(externalResourceMapConfig, options, function() {
|
||||
assert.equal(res_serv_status.numrequests, externalResourceRequestsCount);
|
||||
|
||||
// reset resources cache
|
||||
rmdir_recursive_sync(global.environment.millstone.cache_basedir);
|
||||
|
||||
externalResourceMapConfig = testClient.defaultTableMapConfig('test_table_3 ', externalResourceStyle);
|
||||
|
||||
testClient.createLayergroup(externalResourceMapConfig, options, function() {
|
||||
assert.equal(res_serv_status.numrequests, externalResourceRequestsCount + 1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("referencing unexistant external resources returns an error", function(done) {
|
||||
var url = "http://localhost:" + res_serv_port + "/notfound.png";
|
||||
var style = "#test_table_3{marker-file: url('" + url + "'); marker-transform:'scale(0.2)'; }";
|
||||
|
||||
var mapConfig = testClient.defaultTableMapConfig('test_table_3', style);
|
||||
|
||||
testClient.createLayergroup(mapConfig, { statusCode: 400 }, function(err, res) {
|
||||
assert.deepEqual(JSON.parse(res.body), {
|
||||
errors: ["Unable to download '" + url + "' for 'style0' (server returned 404)"]
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
after(function(done) {
|
||||
rmdir_recursive_sync(global.environment.millstone.cache_basedir);
|
||||
|
||||
// Close the resources server
|
||||
res_serv.close(done);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"grid":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," !!! "," !!!!!! "," !!!!!!! "," !!!!!!!! "," !!!!!!!!! "," !!!!!!!! "," !!!!!!! "," !!!!!! "," !!! "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "],"keys":["","2"],"data":{"2":{"cartodb_id":2}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"grid":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," !! "," !!! "," !! "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "],"keys":["","2"],"data":{"2":{"cartodb_id":4}}}
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,89 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var fs = require('fs');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
var serverOptions = require('./support/ported_server_options');
|
||||
|
||||
describe.skip('render limits', function() {
|
||||
|
||||
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 25;
|
||||
|
||||
var limitsConfig;
|
||||
var onTileErrorStrategy;
|
||||
before(function() {
|
||||
limitsConfig = serverOptions.renderer.mapnik.limits;
|
||||
serverOptions.renderer.mapnik.limits = {
|
||||
render: 50,
|
||||
cacheOnTimeout: false
|
||||
};
|
||||
onTileErrorStrategy = serverOptions.renderer.onTileErrorStrategy;
|
||||
serverOptions.renderer.onTileErrorStrategy = function(err, tile, headers, stats, format, callback) {
|
||||
callback(err, tile, headers, stats);
|
||||
};
|
||||
});
|
||||
|
||||
after(function() {
|
||||
serverOptions.renderer.mapnik.limits = limitsConfig;
|
||||
serverOptions.renderer.onTileErrorStrategy = onTileErrorStrategy;
|
||||
});
|
||||
|
||||
var slowQuery = 'select pg_sleep(1), * from test_table limit 2';
|
||||
var slowQueryMapConfig = testClient.singleLayerMapConfig(slowQuery);
|
||||
|
||||
it('slow query/render returns with 400 status', function(done) {
|
||||
var options = {
|
||||
statusCode: 400,
|
||||
serverOptions: serverOptions
|
||||
};
|
||||
testClient.createLayergroup(slowQueryMapConfig, options, function(err, res) {
|
||||
assert.deepEqual(JSON.parse(res.body), { errors: ["Render timed out"] });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses onTileErrorStrategy to handle error and modify response', function(done) {
|
||||
serverOptions.renderer.onTileErrorStrategy = function(err, tile, headers, stats, format, callback) {
|
||||
var fixture = __dirname + '/../../fixtures/limits/fallback.png';
|
||||
fs.readFile(fixture, {encoding: 'binary'}, function(err, img) {
|
||||
callback(null, img, {'Content-Type': 'image/png'}, {});
|
||||
});
|
||||
};
|
||||
var options = {
|
||||
statusCode: 200,
|
||||
contentType: 'image/png',
|
||||
serverOptions: serverOptions
|
||||
};
|
||||
testClient.createLayergroup(slowQueryMapConfig, options, function(err, res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.layergroupid);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a fallback tile that was modified via onTileErrorStrategy', function(done) {
|
||||
var fixtureImage = './test/fixtures/limits/fallback.png';
|
||||
serverOptions.renderer.onTileErrorStrategy = function(err, tile, headers, stats, format, callback) {
|
||||
fs.readFile(fixtureImage, {encoding: null}, function(err, img) {
|
||||
callback(null, img, {'Content-Type': 'image/png'}, {});
|
||||
});
|
||||
};
|
||||
var options = {
|
||||
statusCode: 200,
|
||||
contentType: 'image/png',
|
||||
serverOptions: serverOptions
|
||||
};
|
||||
testClient.withLayergroup(slowQueryMapConfig, options, function(err, requestTile, finish) {
|
||||
var tileUrl = '/0/0/0.png';
|
||||
requestTile(tileUrl, options, function(err, res) {
|
||||
assert.imageEqualsFile(res.body, fixtureImage, IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err) {
|
||||
finish(function(finishErr) {
|
||||
done(err || finishErr);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,445 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var step = require('step');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
describe('multilayer error cases', function() {
|
||||
|
||||
var server = cartodbServer(ServerOptions);
|
||||
server.req2params = ServerOptions.req2params;
|
||||
server.setMaxListeners(0);
|
||||
|
||||
it("post layergroup with wrong Content-Type", function(done) {
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded' }
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.deepEqual(parsedBody, {"errors":["layergroup POST data must be of type application/json"]});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("post layergroup with no layers", function(done) {
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' }
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.deepEqual(parsedBody, {"errors":["Missing layers array from layergroup config"]});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("post layergroup jsonp errors are returned with 200 status", function(done) {
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup?callback=test',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' }
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.equal(res.body, 'test({"errors":["Missing layers array from layergroup config"]});');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft/issues/154
|
||||
it("mapnik tokens cannot be used with attributes service", function(done) {
|
||||
var layergroup = {
|
||||
version: '1.1.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: 'select cartodb_id, 1 as n, the_geom, !bbox! as b from test_table limit 1',
|
||||
cartocss: '#layer { marker-fill:red }',
|
||||
cartocss_version: '2.0.1',
|
||||
attributes: { id:'cartodb_id', columns:['n'] },
|
||||
geom_column: 'the_geom'
|
||||
} }
|
||||
]
|
||||
};
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json; charset=utf-8' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function do_check(err, res) {
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
var msg = parsed.errors[0];
|
||||
assert.ok(msg.match(/Attribute service cannot be activated/), msg);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("layergroup with no cartocss_version", function(done) {
|
||||
var layergroup = {
|
||||
version: '1.0.0',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: 'select cartodb_id, ST_Translate(the_geom, 50, 0) as the_geom from test_table limit 2',
|
||||
cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }',
|
||||
geom_column: 'the_geom'
|
||||
} }
|
||||
]
|
||||
};
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.deepEqual(parsedBody, {errors:["Missing cartocss_version for layer 0 options"]});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("sql/cartocss combination errors", function(done) {
|
||||
var layergroup = {
|
||||
version: '1.0.1',
|
||||
layers: [{ options: {
|
||||
sql: "select 1 as i, 'LINESTRING(0 0, 1 0)'::geometry as the_geom",
|
||||
cartocss_version: '2.0.2',
|
||||
cartocss: '#layer [missing=1] { line-width:16; }',
|
||||
geom_column: 'the_geom'
|
||||
}}]
|
||||
};
|
||||
ServerOptions.afterLayergroupCreateCalls = 0;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
try {
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
// See http://github.com/CartoDB/Windshaft/issues/159
|
||||
assert.equal(ServerOptions.afterLayergroupCreateCalls, 0);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
var error = parsed.errors[0];
|
||||
assert.ok(error.match(/column "missing" does not exist/m), error);
|
||||
// cannot check for error starting with style0 until a new enough mapnik
|
||||
// is used: https://github.com/mapnik/mapnik/issues/1924
|
||||
//assert.ok(error.match(/^style0/), "Error doesn't start with style0: " + error);
|
||||
// TODO: check which layer introduced the problem ?
|
||||
done();
|
||||
} catch (err) { done(err); }
|
||||
});
|
||||
});
|
||||
|
||||
it("sql/interactivity combination error", function(done) {
|
||||
var layergroup = {
|
||||
version: '1.0.1',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: "select 1 as i, st_setsrid('LINESTRING(0 0, 1 0)'::geometry, 4326) as the_geom",
|
||||
cartocss_version: '2.0.2',
|
||||
cartocss: '#layer { line-width:16; }',
|
||||
interactivity: 'i',
|
||||
geom_column: 'the_geom'
|
||||
}},
|
||||
{ options: {
|
||||
sql: "select 1 as i, st_setsrid('LINESTRING(0 0, 1 0)'::geometry, 4326) as the_geom",
|
||||
cartocss_version: '2.0.2',
|
||||
cartocss: '#layer { line-width:16; }',
|
||||
geom_column: 'the_geom'
|
||||
}},
|
||||
{ options: {
|
||||
sql: "select 1 as i, st_setsrid('LINESTRING(0 0, 1 0)'::geometry, 4326) as the_geom",
|
||||
cartocss_version: '2.0.2',
|
||||
cartocss: '#layer { line-width:16; }',
|
||||
interactivity: 'missing',
|
||||
geom_column: 'the_geom'
|
||||
}}
|
||||
]
|
||||
};
|
||||
ServerOptions.afterLayergroupCreateCalls = 0;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
try {
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
// See http://github.com/CartoDB/Windshaft/issues/159
|
||||
assert.equal(ServerOptions.afterLayergroupCreateCalls, 0);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
var error = parsed.errors[0];
|
||||
assert.ok(error.match(/column "missing" does not exist/m), error);
|
||||
// TODO: check which layer introduced the problem ?
|
||||
done();
|
||||
} catch (err) { done(err); }
|
||||
});
|
||||
});
|
||||
|
||||
it("blank CartoCSS error", function(done) {
|
||||
var layergroup = {
|
||||
version: '1.0.1',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: "select 1 as i, 'LINESTRING(0 0, 1 0)'::geometry as the_geom",
|
||||
cartocss_version: '2.0.2',
|
||||
cartocss: '#style { line-width:16 }',
|
||||
interactivity: 'i',
|
||||
geom_column: 'the_geom'
|
||||
}},
|
||||
{ options: {
|
||||
sql: "select 1 as i, 'LINESTRING(0 0, 1 0)'::geometry as the_geom",
|
||||
cartocss_version: '2.0.2',
|
||||
cartocss: '',
|
||||
interactivity: 'i',
|
||||
geom_column: 'the_geom'
|
||||
}}
|
||||
]
|
||||
};
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
try {
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
var error = parsed.errors[0];
|
||||
assert.ok(error.match(/^style1: CartoCSS is empty/), error);
|
||||
done();
|
||||
} catch (err) { done(err); }
|
||||
});
|
||||
});
|
||||
|
||||
it("Invalid mapnik-geometry-type CartoCSS error", function(done) {
|
||||
var layergroup = {
|
||||
version: '1.0.1',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: "select 1 as i, 'LINESTRING(0 0, 1 0)'::geometry as the_geom",
|
||||
cartocss_version: '2.0.2',
|
||||
cartocss: '#style [mapnik-geometry-type=bogus] { line-width:16 }',
|
||||
geom_column: 'the_geom'
|
||||
}},
|
||||
{ options: {
|
||||
sql: "select 1 as i, 'LINESTRING(0 0, 1 0)'::geometry as the_geom",
|
||||
cartocss_version: '2.0.2',
|
||||
cartocss: '#style [mapnik-geometry-type=bogus] { line-width:16 }',
|
||||
geom_column: 'the_geom'
|
||||
}}
|
||||
]
|
||||
};
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
try {
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
var error = parsed.errors[0];
|
||||
// carto-0.9.3 used to say "Failed to parse expression",
|
||||
// carto-0.9.5 says "not a valid keyword"
|
||||
assert.ok(error.match(/^style0:.*(Failed|not a valid)/), error);
|
||||
// TODO: check which layer introduced the problem ?
|
||||
done();
|
||||
} catch (err) { done(err); }
|
||||
});
|
||||
});
|
||||
|
||||
it("post'ing style with non existent column in filter returns 400 with error", function(done) {
|
||||
var layergroup = {
|
||||
version: '1.0.1',
|
||||
layers: [
|
||||
{ options: {
|
||||
sql: 'select * from test_table limit 1',
|
||||
cartocss: '#test_table::outline[address="one"], [address="two"] { marker-fill: red; }',
|
||||
cartocss_version: '2.0.2',
|
||||
interactivity: [ 'cartodb_id' ],
|
||||
geom_column: 'the_geom'
|
||||
} },
|
||||
{ options: {
|
||||
sql: 'select * from test_big_poly limit 1',
|
||||
cartocss: '#test_big_poly { marker-fill:blue }',
|
||||
cartocss_version: '2.0.2',
|
||||
interactivity: [ 'cartodb_id' ],
|
||||
geom_column: 'the_geom'
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) {
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
var error = parsed.errors[0];
|
||||
assert.ok(error.match(/column "address" does not exist/m), error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/Vizzuality/Windshaft/issues/31
|
||||
it('bogus sql raises 400 status code', function(done) {
|
||||
var bogusSqlMapConfig = testClient.singleLayerMapConfig('BOGUS FROM test_table');
|
||||
testClient.createLayergroup(bogusSqlMapConfig, { statusCode: 400 }, function(err, res) {
|
||||
assert.ok(/syntax error/.test(res.body), "Unexpected error: " + res.body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('bogus sql raises 200 status code for jsonp', function(done) {
|
||||
var bogusSqlMapConfig = testClient.singleLayerMapConfig('bogus');
|
||||
var options = {
|
||||
method: 'GET',
|
||||
callbackName: 'test',
|
||||
headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
}
|
||||
};
|
||||
testClient.createLayergroup(bogusSqlMapConfig, options, function(err, res) {
|
||||
assert.ok(/^test\(/.test(res.body), "Body start expected callback name: " + res.body);
|
||||
assert.ok(/syntax error/.test(res.body), "Unexpected error: " + res.body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('query not selecting the_geom raises 200 status code for jsonp instead of 404', function(done) {
|
||||
var noGeomMapConfig = testClient.singleLayerMapConfig('select null::geometry the_geom_wadus');
|
||||
var options = {
|
||||
method: 'GET',
|
||||
callbackName: 'test',
|
||||
headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
}
|
||||
};
|
||||
testClient.createLayergroup(noGeomMapConfig, options, function(err, res) {
|
||||
assert.ok(/^test\(/.test(res.body), "Body start expected callback name: " + res.body);
|
||||
assert.ok(/column.*does not exist/.test(res.body), "Unexpected error: " + res.body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("query with no geometry field returns 400 status", function(done){
|
||||
var noGeometrySqlMapConfig = testClient.singleLayerMapConfig('SELECT 1');
|
||||
testClient.createLayergroup(noGeometrySqlMapConfig, { statusCode: 400 }, function(err, res) {
|
||||
assert.ok(/column.*does not exist/.test(res.body), "Unexpected error: " + res.body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("bogus style should raise 400 status", function(done){
|
||||
var bogusStyleMapConfig = testClient.defaultTableMapConfig('test_table', '#test_table{xxxxx;}');
|
||||
testClient.createLayergroup(bogusStyleMapConfig, { method: 'GET', statusCode: 400 }, done);
|
||||
});
|
||||
|
||||
var defaultErrorExpectedResponse = {
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
it('should raise 400 error for out of bounds layer index', function(done){
|
||||
var mapConfig = testClient.singleLayerMapConfig('select * from test_table', null, null, 'name');
|
||||
|
||||
testClient.getGrid(mapConfig, 1, 13, 4011, 3088, defaultErrorExpectedResponse, function(err, res) {
|
||||
assert.deepEqual(JSON.parse(res.body), { errors: ["Layer '1' not found in layergroup"] });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// OPTIONS LAYERGROUP
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
it("nonexistent layergroup token error", function(done) {
|
||||
step(
|
||||
function do_get_tile(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/deadbeef/0/0/0/0.grid.json',
|
||||
method: 'GET',
|
||||
encoding: 'binary'
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function checkResponse(err, res) {
|
||||
assert.ifError(err);
|
||||
// FIXME: should be 404
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ':' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.deepEqual(parsed, {"errors": ["Invalid or nonexistent map configuration token 'deadbeef'"]});
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('error 400 on json syntax error', function(done) {
|
||||
var layergroup = {
|
||||
version: '1.0.1',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'select the_geom from test_table limit 1',
|
||||
cartocss: '#layer { marker-fill:red }'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json; charset=utf-8' },
|
||||
data: '{' + JSON.stringify(layergroup)
|
||||
},
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(res) {
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.deepEqual(parsedBody, { errors: ['SyntaxError: Unexpected token {'] });
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,373 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var _ = require('underscore');
|
||||
var redis = require('redis');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var getLayerTypeFn = require('windshaft').model.MapConfig.prototype.getType;
|
||||
var PortedServerOptions = require('./support/ported_server_options');
|
||||
var LayergroupToken = require('../../../lib/cartodb/models/layergroup_token');
|
||||
|
||||
describe('multilayer interactivity and layers order', function() {
|
||||
|
||||
var server = cartodbServer(PortedServerOptions);
|
||||
server.req2params = PortedServerOptions.req2params;
|
||||
server.setMaxListeners(0);
|
||||
var redisClient = redis.createClient(PortedServerOptions.redis.port);
|
||||
|
||||
function layerType(layer) {
|
||||
return layer.type || 'undefined';
|
||||
}
|
||||
|
||||
function testInteractivityLayersOrderScenario(testScenario) {
|
||||
it(testScenario.desc, function(done) {
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
layers: testScenario.layers
|
||||
};
|
||||
|
||||
PortedServerOptions.afterLayergroupCreateCalls = 0;
|
||||
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
},
|
||||
{
|
||||
//status: 200, don't use status here to have a more meaningful error message
|
||||
headers: {
|
||||
'content-type': 'application/json; charset=utf-8'
|
||||
}
|
||||
},
|
||||
function(response) {
|
||||
assert.equal(
|
||||
response.statusCode,
|
||||
200,
|
||||
'Expected status code 200, got ' + response.statusCode +
|
||||
'\n\tResponse body: ' + response.body +
|
||||
'\n\tLayer types: ' + layergroup.layers.map(layerType).join(', ')
|
||||
);
|
||||
|
||||
// assert.equal(PortedServerOptions.afterLayergroupCreateCalls, 1);
|
||||
|
||||
var layergroupResponse = JSON.parse(response.body);
|
||||
assert.ok(layergroupResponse);
|
||||
|
||||
var layergroupId = layergroupResponse.layergroupid;
|
||||
assert.ok(layergroupId);
|
||||
assert.equal(layergroupResponse.metadata.layers.length, layergroup.layers.length);
|
||||
|
||||
// check layers metadata at least match in number
|
||||
var layersMetadata = layergroupResponse.metadata.layers;
|
||||
assert.equal(layersMetadata.length, layergroup.layers.length);
|
||||
for (var i = 0, len = layersMetadata.length; i < len; i++) {
|
||||
assert.equal(
|
||||
getLayerTypeFn(layersMetadata[i].type),
|
||||
getLayerTypeFn(layergroup.layers[i].type)
|
||||
);
|
||||
}
|
||||
// check torque metadata at least match in number
|
||||
var torqueLayers = layergroup.layers.filter(function(layer) { return layer.type === 'torque'; });
|
||||
if (torqueLayers.length) {
|
||||
assert.equal(Object.keys(layergroupResponse.metadata.torque).length, torqueLayers.length);
|
||||
}
|
||||
|
||||
var token = LayergroupToken.parse(layergroupId).token;
|
||||
|
||||
redisClient.exists("map_cfg|" + token, function(err, exists) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.ok(exists, "Missing expected token " + token + " from redis");
|
||||
redisClient.del("map_cfg|" + token, function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
var cartocssVersion = '2.3.0';
|
||||
var cartocss = '#layer { line-width:16; }';
|
||||
var sql = "select 1 as i, st_setsrid('LINESTRING(0 0, 1 0)'::geometry, 4326) as the_geom";
|
||||
var sqlWadus = "select 1 as wadus, st_setsrid('LINESTRING(0 0, 1 0)'::geometry, 4326) as the_geom";
|
||||
|
||||
var httpLayer = {
|
||||
type: 'http',
|
||||
options: {
|
||||
urlTemplate: 'http://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png',
|
||||
subdomains: ['a','b','c']
|
||||
}
|
||||
};
|
||||
|
||||
var torqueLayer = {
|
||||
type: 'torque',
|
||||
options: {
|
||||
sql: "select 1 id, '1970-01-02'::date d, 'POINT(0 0)'::geometry the_geom_webmercator",
|
||||
cartocss: [
|
||||
"Map {",
|
||||
"-torque-frame-count:2;",
|
||||
"-torque-resolution:3;",
|
||||
"-torque-time-attribute:d;",
|
||||
"-torque-aggregation-function:'count(id)';",
|
||||
"}"
|
||||
].join(' '),
|
||||
cartocss_version: '2.0.1'
|
||||
}
|
||||
};
|
||||
|
||||
var mapnikLayer = {
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: sql,
|
||||
cartocss_version: cartocssVersion,
|
||||
cartocss: cartocss,
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
};
|
||||
|
||||
var mapnikInteractivityLayer = {
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: sql,
|
||||
cartocss_version: cartocssVersion,
|
||||
cartocss: cartocss,
|
||||
interactivity: 'i',
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
};
|
||||
|
||||
var cartodbLayer = {
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: sql,
|
||||
cartocss_version: cartocssVersion,
|
||||
cartocss: cartocss,
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
};
|
||||
|
||||
var cartodbInteractivityLayer = {
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: sql,
|
||||
cartocss_version: cartocssVersion,
|
||||
cartocss: cartocss,
|
||||
interactivity: 'i',
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
};
|
||||
|
||||
var cartodbWadusInteractivityLayer = {
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: sqlWadus,
|
||||
cartocss_version: cartocssVersion,
|
||||
cartocss: cartocss,
|
||||
interactivity: 'wadus',
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
};
|
||||
|
||||
var noTypeLayer = {
|
||||
options: {
|
||||
sql: sql,
|
||||
cartocss_version: cartocssVersion,
|
||||
cartocss: cartocss,
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
};
|
||||
|
||||
var noTypeInteractivityLayer = {
|
||||
options: {
|
||||
sql: sql,
|
||||
cartocss_version: cartocssVersion,
|
||||
cartocss: cartocss,
|
||||
interactivity: 'i',
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
};
|
||||
|
||||
var allLayers = [
|
||||
httpLayer,
|
||||
torqueLayer,
|
||||
mapnikLayer,
|
||||
mapnikInteractivityLayer,
|
||||
cartodbLayer,
|
||||
cartodbInteractivityLayer,
|
||||
cartodbWadusInteractivityLayer,
|
||||
noTypeLayer,
|
||||
noTypeInteractivityLayer
|
||||
];
|
||||
|
||||
var testScenarios = [
|
||||
{
|
||||
desc: 'one layer, no interactivity',
|
||||
layers: [cartodbLayer]
|
||||
},
|
||||
{
|
||||
desc: 'one layer with interactivity',
|
||||
layers: [cartodbInteractivityLayer]
|
||||
},
|
||||
{
|
||||
desc: 'two layers, interactivity mix',
|
||||
layers: [
|
||||
cartodbLayer,
|
||||
cartodbInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'two layers, different interactivity columns',
|
||||
layers: [
|
||||
cartodbWadusInteractivityLayer,
|
||||
cartodbInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'mix of no interactivity with interactivity',
|
||||
layers: [
|
||||
cartodbInteractivityLayer,
|
||||
cartodbLayer,
|
||||
cartodbWadusInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'interactivity layers + torque',
|
||||
layers: [
|
||||
cartodbWadusInteractivityLayer,
|
||||
cartodbInteractivityLayer,
|
||||
torqueLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'torque + interactivity layers',
|
||||
layers: [
|
||||
torqueLayer,
|
||||
cartodbWadusInteractivityLayer,
|
||||
cartodbInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'interactivity + torque + interactivity',
|
||||
layers: [
|
||||
cartodbInteractivityLayer,
|
||||
torqueLayer,
|
||||
cartodbInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'http + mix of [no] interactivity layers',
|
||||
layers: [
|
||||
httpLayer,
|
||||
cartodbInteractivityLayer,
|
||||
cartodbLayer,
|
||||
cartodbInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'http + interactivity + torque + interactivity',
|
||||
layers: [
|
||||
httpLayer,
|
||||
cartodbInteractivityLayer,
|
||||
torqueLayer,
|
||||
cartodbInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'http + interactivity + torque + no interactivity + torque + interactivity',
|
||||
layers: [
|
||||
httpLayer,
|
||||
cartodbInteractivityLayer,
|
||||
torqueLayer,
|
||||
cartodbLayer,
|
||||
torqueLayer,
|
||||
cartodbWadusInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'mapnik type – two layers, interactivity mix',
|
||||
layers: [
|
||||
mapnikLayer,
|
||||
mapnikInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'mapnik type – mix of no interactivity with interactivity',
|
||||
layers: [
|
||||
cartodbInteractivityLayer,
|
||||
cartodbLayer,
|
||||
mapnikInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'mapnik type – interactivity layers + torque',
|
||||
layers: [
|
||||
mapnikInteractivityLayer,
|
||||
cartodbInteractivityLayer,
|
||||
torqueLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'mapnik type – http + interactivity + torque + interactivity',
|
||||
layers: [
|
||||
httpLayer,
|
||||
mapnikInteractivityLayer,
|
||||
torqueLayer,
|
||||
cartodbInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'no type – two layers, interactivity mix',
|
||||
layers: [
|
||||
noTypeLayer,
|
||||
noTypeInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'no type – mix of no interactivity with interactivity',
|
||||
layers: [
|
||||
noTypeInteractivityLayer,
|
||||
noTypeLayer,
|
||||
mapnikInteractivityLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'no type – interactivity layers + torque',
|
||||
layers: [
|
||||
noTypeLayer,
|
||||
noTypeInteractivityLayer,
|
||||
torqueLayer
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: 'no type – http + interactivity + torque + interactivity',
|
||||
layers: [
|
||||
httpLayer,
|
||||
noTypeInteractivityLayer,
|
||||
torqueLayer,
|
||||
noTypeInteractivityLayer
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
var chaosScenariosSize = 25;
|
||||
var chaosScenarios = [];
|
||||
for (var i = 0; i < chaosScenariosSize; i++) {
|
||||
// Underscore.js' sample method uses Fisher-Yates shuffle internally, see http://bost.ocks.org/mike/shuffle/
|
||||
var randomLayers = _.sample(allLayers, _.random(1, allLayers.length));
|
||||
chaosScenarios.push({
|
||||
desc: 'chaos scenario – layer types: ' + randomLayers.map(layerType).join(', '),
|
||||
layers: randomLayers
|
||||
});
|
||||
}
|
||||
|
||||
testScenarios.forEach(testInteractivityLayersOrderScenario);
|
||||
chaosScenarios.forEach(testInteractivityLayersOrderScenario);
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var redis = require('redis');
|
||||
var step = require('step');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
|
||||
describe('raster', function() {
|
||||
|
||||
var server = cartodbServer(ServerOptions);
|
||||
server.req2params = ServerOptions.req2params;
|
||||
server.setMaxListeners(0);
|
||||
var redis_client = redis.createClient(ServerOptions.redis.port);
|
||||
|
||||
function checkCORSHeaders(res) {
|
||||
assert.equal(res.headers['access-control-allow-headers'], 'X-Requested-With, X-Prototype-Version, X-CSRF-Token');
|
||||
assert.equal(res.headers['access-control-allow-origin'], '*');
|
||||
}
|
||||
|
||||
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 2;
|
||||
|
||||
it("can render raster for valid mapconfig", function(done) {
|
||||
|
||||
var mapconfig = {
|
||||
version: '1.2.0',
|
||||
layers: [
|
||||
{ type: 'mapnik', options: {
|
||||
sql: "select ST_AsRaster(" +
|
||||
" ST_MakeEnvelope(-100,-40, 100, 40, 4326), " +
|
||||
" 1.0, -1.0, '8BUI', 127) as rst",
|
||||
geom_column: 'rst',
|
||||
geom_type: 'raster',
|
||||
cartocss: '#layer { raster-opacity:1.0 }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
var expected_token;
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(mapconfig)
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function checkPost(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
// CORS headers should be sent with response
|
||||
// from layergroup creation via POST
|
||||
checkCORSHeaders(res);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
if ( expected_token ) {
|
||||
assert.deepEqual(parsedBody, {layergroupid: expected_token, layercount: 2});
|
||||
} else {
|
||||
expected_token = parsedBody.layergroupid;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
function do_get_tile(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + expected_token + '/0/0/0.png',
|
||||
method: 'GET',
|
||||
encoding: 'binary'
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function check_response(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.deepEqual(res.headers['content-type'], "image/png");
|
||||
var next = this;
|
||||
assert.imageEqualsFile(res.body,
|
||||
'./test/fixtures/raster_gray_rect.png',
|
||||
IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err) {
|
||||
try {
|
||||
assert.ifError(err);
|
||||
next();
|
||||
} catch (err) { next(err); }
|
||||
});
|
||||
},
|
||||
function finish(err) {
|
||||
var errors = [];
|
||||
if ( err ) {
|
||||
errors.push(''+err);
|
||||
}
|
||||
redis_client.exists("map_cfg|" + expected_token, function(err/*, exists*/) {
|
||||
if ( err ) {
|
||||
errors.push(err.message);
|
||||
}
|
||||
//assert.ok(exists, "Missing expected token " + expected_token + " from redis");
|
||||
redis_client.del("map_cfg|" + expected_token, function(err) {
|
||||
if ( err ) {
|
||||
errors.push(err.message);
|
||||
}
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
} else {
|
||||
done(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("raster geom type does not allow interactivity", function(done) {
|
||||
|
||||
var mapconfig = {
|
||||
version: '1.2.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'cartodb',
|
||||
options: {
|
||||
sql: [
|
||||
"select 1 id,",
|
||||
"ST_AsRaster(ST_MakeEnvelope(-100, -40, 100, 40, 4326), 1.0, -1.0, '8BUI', 127) as rst"
|
||||
].join(' '),
|
||||
geom_column: 'rst',
|
||||
geom_type: 'raster',
|
||||
cartocss: '#layer { raster-opacity: 1.0 }',
|
||||
cartocss_version: '2.0.1',
|
||||
interactivity: 'id'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(mapconfig)
|
||||
},
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(res, err) {
|
||||
assert.ok(!err);
|
||||
checkCORSHeaders(res);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.deepEqual(parsedBody, { errors: [ 'Mapnik raster layers do not support interactivity' ] });
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var _ = require('underscore');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
function rmdir_recursive_sync(dirname) {
|
||||
var files = fs.readdirSync(dirname);
|
||||
for (var i=0; i<files.length; ++i) {
|
||||
var f = dirname + "/" + files[i];
|
||||
var s = fs.lstatSync(f);
|
||||
if ( s.isFile() ) {
|
||||
fs.unlinkSync(f);
|
||||
}
|
||||
else {
|
||||
rmdir_recursive_sync(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('regressions', function() {
|
||||
|
||||
var res_serv; // resources server
|
||||
var res_serv_status = { numrequests:0 }; // status of resources server
|
||||
var res_serv_port = 8033; // FIXME: make configurable ?
|
||||
|
||||
before(function(done) {
|
||||
// Start a server to test external resources
|
||||
res_serv = http.createServer( function(request, response) {
|
||||
++res_serv_status.numrequests;
|
||||
var filename = __dirname + '/../fixtures/markers' + request.url;
|
||||
fs.readFile(filename, "binary", function(err, file) {
|
||||
if ( err ) {
|
||||
response.writeHead(404, {'Content-Type': 'text/plain'});
|
||||
response.write("404 Not Found\n");
|
||||
} else {
|
||||
response.writeHead(200);
|
||||
response.write(file, "binary");
|
||||
}
|
||||
response.end();
|
||||
});
|
||||
});
|
||||
res_serv.listen(res_serv_port, done);
|
||||
});
|
||||
|
||||
|
||||
after(function(done) {
|
||||
rmdir_recursive_sync(global.environment.millstone.cache_basedir);
|
||||
|
||||
// Close the resources server
|
||||
res_serv.close(done);
|
||||
});
|
||||
|
||||
// See https://github.com/Vizzuality/Windshaft/issues/65
|
||||
it("#65 catching non-Error exception doesn't kill the backend", function(done) {
|
||||
var mapConfig = testClient.defaultTableMapConfig('test_table');
|
||||
testClient.withLayergroup(mapConfig, function(err, requestTile, finish) {
|
||||
var options = {
|
||||
statusCode: 400,
|
||||
contentType: 'application/json; charset=utf-8'
|
||||
};
|
||||
requestTile('/0/0/0.png?testUnexpectedError=1', options, function(err, res) {
|
||||
assert.deepEqual(JSON.parse(res.body), { "errors": ["test unexpected error"] });
|
||||
finish(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test that you cannot write to the database from a tile request
|
||||
//
|
||||
// See http://github.com/CartoDB/Windshaft/issues/130
|
||||
// [x] Needs a fix on the mapnik side: https://github.com/mapnik/mapnik/pull/2143
|
||||
//
|
||||
it("#130 database access is read-only", function(done) {
|
||||
|
||||
var writeSqlMapConfig = testClient.singleLayerMapConfig(
|
||||
'select st_point(0,0) as the_geom, * from test_table_inserter(st_setsrid(st_point(0,0),4326),\'write\')'
|
||||
);
|
||||
|
||||
var expectedResponse = {
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
testClient.getTile(writeSqlMapConfig, 0, 0, 0, expectedResponse, function(err, res) {
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.ok(parsedBody.errors);
|
||||
assert.equal(parsedBody.errors.length, 1);
|
||||
assert.ok(parsedBody.errors[0].match(/read-only transaction/), 'read-only error message expected');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft/issues/167
|
||||
it("#167 does not die on unexistent statsd host", function(done) {
|
||||
var CustomOptions = _.clone(ServerOptions);
|
||||
CustomOptions.statsd = _.clone(CustomOptions.statsd);
|
||||
CustomOptions.statsd.host = 'whoami.vizzuality.com';
|
||||
CustomOptions.statsd.cacheDns = false;
|
||||
var server = cartodbServer(CustomOptions);
|
||||
server.req2params = CustomOptions.req2params;
|
||||
server.setMaxListeners(0);
|
||||
|
||||
var errors = [];
|
||||
// We need multiple requests to make sure
|
||||
// statsd_client eventually tries to send
|
||||
// stats _and_ DNS lookup is given enough
|
||||
// time (an interval is used later for that)
|
||||
var numreq = 10;
|
||||
var pending = numreq;
|
||||
var completed = function(err) {
|
||||
if ( err ) {
|
||||
errors.push(err);
|
||||
}
|
||||
if ( ! --pending ) {
|
||||
setTimeout(function() {
|
||||
done(errors.length ? new Error(errors.join(',')) : null);
|
||||
}, 10);
|
||||
}
|
||||
};
|
||||
var mapConfig = testClient.defaultTableMapConfig('test_table');
|
||||
for (var i=0; i<numreq; ++i) {
|
||||
testClient.createLayergroup(mapConfig, { server: server }, completed);
|
||||
}
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/Windshaft/issues/173
|
||||
it.skip("#173 does not send db details in connection error response", function(done) {
|
||||
|
||||
var mapConfig = testClient.defaultTableMapConfig('test_table');
|
||||
|
||||
var CustomOptions = _.clone(ServerOptions);
|
||||
CustomOptions.grainstore = _.clone(CustomOptions.grainstore);
|
||||
CustomOptions.grainstore.datasource = _.clone(CustomOptions.grainstore.datasource);
|
||||
CustomOptions.grainstore.datasource.port = '666';
|
||||
|
||||
var options = {
|
||||
statusCode: 400,
|
||||
serverOptions: CustomOptions
|
||||
};
|
||||
|
||||
testClient.createLayergroup(mapConfig, options, function(err, res, parsedBody) {
|
||||
assert.ok(parsedBody.errors);
|
||||
var msg = parsedBody.errors[0];
|
||||
assert.ok(msg.match(/connect/), msg);
|
||||
assert.ok(!msg.match(/666/), msg);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var redis = require('redis');
|
||||
var mapnik = require('windshaft').mapnik;
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
|
||||
describe('retina support', function() {
|
||||
|
||||
var layergroupId = null;
|
||||
|
||||
var server = cartodbServer(ServerOptions);
|
||||
server.req2params = ServerOptions.req2params;
|
||||
server.setMaxListeners(0);
|
||||
var redis_client = redis.createClient(ServerOptions.redis.port);
|
||||
|
||||
beforeEach(function(done) {
|
||||
var retinaSampleMapConfig = {
|
||||
version: '1.2.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'SELECT * FROM populated_places_simple_reduced',
|
||||
cartocss: '#layer { marker-fill:red; } #layer { marker-width: 2; }',
|
||||
cartocss_version: '2.3.0',
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(retinaSampleMapConfig)
|
||||
},
|
||||
{
|
||||
|
||||
},
|
||||
function (res, err) {
|
||||
assert.ok(!err, 'Failed to create layergroup');
|
||||
|
||||
layergroupId = JSON.parse(res.body).layergroupid;
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
function testRetinaImage(scaleFactor, responseHead, assertFn) {
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/database/windshaft_test/layergroup/' + layergroupId + '/0/0/0' + scaleFactor + '.png',
|
||||
method: 'GET',
|
||||
encoding: 'binary'
|
||||
},
|
||||
responseHead,
|
||||
assertFn
|
||||
);
|
||||
}
|
||||
|
||||
function testValidImageDimmensions(scaleFactor, imageSize, done) {
|
||||
testRetinaImage(scaleFactor,
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'image/png'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
assert.ok(!err, 'Failed to request 0/0/0' + scaleFactor + '.png tile');
|
||||
|
||||
var image = new mapnik.Image.fromBytes(new Buffer(res.body, 'binary'));
|
||||
|
||||
assert.equal(image.width(), imageSize);
|
||||
assert.equal(image.height(), imageSize);
|
||||
done();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
it('image dimensions when scale factor is not defined', function(done) {
|
||||
testValidImageDimmensions('', 256, done);
|
||||
});
|
||||
|
||||
it('image dimensions when scale factor = @1x', function(done) {
|
||||
testValidImageDimmensions('@1x', 256, done);
|
||||
});
|
||||
|
||||
it('image dimensions when scale factor = @2x', function(done) {
|
||||
testValidImageDimmensions('@2x', 512, done);
|
||||
});
|
||||
|
||||
it('error when scale factor is not enabled', function(done) {
|
||||
|
||||
var scaleFactor = '@4x';
|
||||
|
||||
testRetinaImage(scaleFactor,
|
||||
{
|
||||
status: 404,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
},
|
||||
function(res, err) {
|
||||
assert.ok(!err, 'Failed to request 0/0/0' + scaleFactor + '.png tile');
|
||||
assert.deepEqual(JSON.parse(res.body), { errors: ["Tile with specified resolution not found"] } );
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
var redisKey = 'map_cfg|' + layergroupId;
|
||||
redis_client.del(redisKey, function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,197 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
function rmdir_recursive_sync(dirname) {
|
||||
var files = fs.readdirSync(dirname);
|
||||
for (var i=0; i<files.length; ++i) {
|
||||
var f = dirname + "/" + files[i];
|
||||
var s = fs.lstatSync(f);
|
||||
if ( s.isFile() ) {
|
||||
fs.unlinkSync(f);
|
||||
} else {
|
||||
rmdir_recursive_sync(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('server', function() {
|
||||
|
||||
var server = cartodbServer(ServerOptions);
|
||||
server.req2params = ServerOptions.req2params;
|
||||
server.setMaxListeners(0);
|
||||
var res_serv; // resources server
|
||||
var res_serv_status = { numrequests:0 }; // status of resources server
|
||||
var res_serv_port = 8033; // FIXME: make configurable ?
|
||||
|
||||
before(function(done) {
|
||||
|
||||
// Start a server to test external resources
|
||||
res_serv = http.createServer( function(request, response) {
|
||||
++res_serv_status.numrequests;
|
||||
var filename = __dirname + '/../fixtures/markers' + request.url;
|
||||
fs.readFile(filename, "binary", function(err, file) {
|
||||
if ( err ) {
|
||||
response.writeHead(404, {'Content-Type': 'text/plain'});
|
||||
response.write("404 Not Found\n");
|
||||
} else {
|
||||
response.writeHead(200);
|
||||
response.write(file, "binary");
|
||||
}
|
||||
response.end();
|
||||
});
|
||||
});
|
||||
res_serv.listen(res_serv_port, done);
|
||||
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
rmdir_recursive_sync(global.environment.millstone.cache_basedir);
|
||||
|
||||
// Close the resources server
|
||||
res_serv.close(done);
|
||||
});
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// GET INVALID
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
it("get call to server returns 200", function(done){
|
||||
assert.response(server, {
|
||||
url: '/',
|
||||
method: 'GET'
|
||||
},{
|
||||
// FIXME: shouldn't this be a 404 ?
|
||||
status: 200
|
||||
}, function() { done(); } );
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// GET VERSION
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
it("get /version returns versions", function(done){
|
||||
assert.response(server, {
|
||||
url: '/version',
|
||||
method: 'GET'
|
||||
},{
|
||||
status: 200
|
||||
}, function(res) {
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.hasOwnProperty('windshaft'), "No 'windshaft' version in " + parsed);
|
||||
assert.ok(parsed.hasOwnProperty('grainstore'), "No 'grainstore' version in " + parsed);
|
||||
assert.ok(parsed.hasOwnProperty('node_mapnik'), "No 'node_mapnik' version in " + parsed);
|
||||
assert.ok(parsed.hasOwnProperty('mapnik'), "No 'mapnik' version in " + parsed);
|
||||
// TODO: check actual versions ?
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// GET GRID
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
it("grid jsonp", function(done){
|
||||
var mapConfig = testClient.singleLayerMapConfig('select * from test_table', null, null, 'name');
|
||||
testClient.getGridJsonp(mapConfig, 0, 13, 4011, 3088, 'test', function(err, res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.deepEqual(res.headers['content-type'], 'application/json; charset=utf-8');
|
||||
var regexp = '^test\\((.*)\\);$';
|
||||
var matches = res.body.match(regexp);
|
||||
assert.equal(matches.length, 2, 'Unexpected JSONP response:' + res.body);
|
||||
assert.utfgridEqualsFile(matches[1], './test/fixtures/test_table_13_4011_3088.grid.json', 2, done);
|
||||
});
|
||||
});
|
||||
|
||||
it("get'ing a json with default style and single interactivity should return a grid", function(done){
|
||||
var mapConfig = testClient.singleLayerMapConfig('select * from test_table', null, null, 'name');
|
||||
testClient.getGrid(mapConfig, 0, 13, 4011, 3088, function(err, res) {
|
||||
var expected_json = {
|
||||
"1":{"name":"Hawai"},
|
||||
"2":{"name":"El Estocolmo"},
|
||||
"3":{"name":"El Rey del Tallarín"},
|
||||
"4":{"name":"El Lacón"},
|
||||
"5":{"name":"El Pico"}
|
||||
};
|
||||
assert.deepEqual(JSON.parse(res.body).data, expected_json);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("get'ing a json with default style and no interactivity should return an error", function(done){
|
||||
var mapConfig = testClient.singleLayerMapConfig('select * from test_table');
|
||||
var expectedResponse = {
|
||||
status: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
testClient.getGrid(mapConfig, 0, 13, 4011, 3088, expectedResponse, function(err, res) {
|
||||
console.log(res.body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("get grid jsonp error is returned with 200 status", function(done){
|
||||
var mapConfig = testClient.singleLayerMapConfig('select * from test_table');
|
||||
var expectedResponse = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'text/javascript; charset=utf-8'
|
||||
}
|
||||
};
|
||||
testClient.getGridJsonp(mapConfig, 0, 13, 4011, 3088, 'test', expectedResponse, function(err, res) {
|
||||
assert.ok(res.body.match(/"errors":/), 'missing error in response: ' + res.body);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// See http://github.com/Vizzuality/Windshaft/issues/50
|
||||
it("get'ing a json with no data should return an empty grid", function(done){
|
||||
var query = 'select * from test_table limit 0';
|
||||
var mapConfig = testClient.singleLayerMapConfig(query, null, null, 'name');
|
||||
testClient.getGrid(mapConfig, 0, 13, 4011, 3088, function(err, res) {
|
||||
assert.utfgridEqualsFile(res.body, './test/fixtures/test_table_13_4011_3088_empty.grid.json', 2, done);
|
||||
});
|
||||
});
|
||||
|
||||
// Another test for http://github.com/Vizzuality/Windshaft/issues/50
|
||||
it("get'ing a json with no data but interactivity should return an empty grid", function(done){
|
||||
var query = 'SELECT * FROM test_table limit 0';
|
||||
var mapConfig = testClient.singleLayerMapConfig(query, null, null, 'cartodb_id');
|
||||
testClient.getGrid(mapConfig, 0, 13, 4011, 3088, function(err, res) {
|
||||
assert.utfgridEqualsFile(res.body, './test/fixtures/test_table_13_4011_3088_empty.grid.json', 2, done);
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/67
|
||||
it("get'ing a solid grid while changing interactivity fields", function(done){
|
||||
var query = 'SELECT * FROM test_big_poly';
|
||||
var style211 = "#test_big_poly{polygon-fill:blue;}"; // for solid
|
||||
var mapConfigName = testClient.singleLayerMapConfig(query, style211, null, 'name');
|
||||
testClient.getGrid(mapConfigName, 0, 3, 2, 2, function(err, res) {
|
||||
var expected_data = { "1":{"name":"west"} };
|
||||
assert.deepEqual(JSON.parse(res.body).data, expected_data);
|
||||
|
||||
var mapConfigCartodbId = testClient.singleLayerMapConfig(query, style211, null, 'cartodb_id');
|
||||
testClient.getGrid(mapConfigCartodbId, 0, 3, 2, 2, function(err, res) {
|
||||
var expected_data = { "1":{"cartodb_id":"1"} };
|
||||
assert.deepEqual(JSON.parse(res.body).data, expected_data);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,295 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var fs = require('fs');
|
||||
var mapnik = require('windshaft').mapnik;
|
||||
var semver = require('semver');
|
||||
var http = require('http');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
function rmdir_recursive_sync(dirname) {
|
||||
var files = fs.readdirSync(dirname);
|
||||
for (var i=0; i<files.length; ++i) {
|
||||
var f = dirname + "/" + files[i];
|
||||
var s = fs.lstatSync(f);
|
||||
if ( s.isFile() ) {
|
||||
fs.unlinkSync(f);
|
||||
}
|
||||
else {
|
||||
rmdir_recursive_sync(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('server_gettile', function() {
|
||||
|
||||
var server = cartodbServer(ServerOptions);
|
||||
server.req2params = ServerOptions.req2params;
|
||||
server.setMaxListeners(0);
|
||||
var res_serv; // resources server
|
||||
var res_serv_status = { numrequests:0 }; // status of resources server
|
||||
var res_serv_port = 8033; // FIXME: make configurable ?
|
||||
|
||||
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 25;
|
||||
|
||||
before(function(done) {
|
||||
// Start a server to test external resources
|
||||
res_serv = http.createServer( function(request, response) {
|
||||
++res_serv_status.numrequests;
|
||||
var filename = __dirname + '/../fixtures/markers' + request.url;
|
||||
fs.readFile(filename, "binary", function(err, file) {
|
||||
if ( err ) {
|
||||
response.writeHead(404, {'Content-Type': 'text/plain'});
|
||||
response.write("404 Not Found\n");
|
||||
} else {
|
||||
response.writeHead(200);
|
||||
response.write(file, "binary");
|
||||
}
|
||||
response.end();
|
||||
});
|
||||
});
|
||||
res_serv.listen(res_serv_port, done);
|
||||
});
|
||||
|
||||
|
||||
after(function(done) {
|
||||
rmdir_recursive_sync(global.environment.millstone.cache_basedir);
|
||||
|
||||
// Close the resources server
|
||||
res_serv.close(done);
|
||||
});
|
||||
|
||||
function imageCompareFn(fixture, done) {
|
||||
return function(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.imageEqualsFile(res.body, './test/fixtures/' + fixture, IMAGE_EQUALS_TOLERANCE_PER_MIL, done);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// GET TILE
|
||||
// --{
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
it("get'ing a tile with default style should return an expected tile", function(done){
|
||||
testClient.getTile(testClient.defaultTableMapConfig('test_table'), 13, 4011, 3088,
|
||||
imageCompareFn('test_table_13_4011_3088.png', done)
|
||||
);
|
||||
});
|
||||
|
||||
it("response of get tile can be served by renderer cache", function(done) {
|
||||
var tileUrl = '/13/4011/3088.png';
|
||||
var lastXwc;
|
||||
var mapConfig = testClient.defaultTableMapConfig('test_table');
|
||||
testClient.withLayergroup(mapConfig, function (err, requestTile, finish) {
|
||||
requestTile(tileUrl, function (err, res) {
|
||||
var xwc = res.headers['x-windshaft-cache'];
|
||||
assert.ok(xwc);
|
||||
assert.ok(xwc > 0);
|
||||
lastXwc = xwc;
|
||||
|
||||
requestTile(tileUrl, function (err, res) {
|
||||
var xwc = res.headers['x-windshaft-cache'];
|
||||
assert.ok(xwc);
|
||||
assert.ok(xwc > 0);
|
||||
assert.ok(xwc >= lastXwc);
|
||||
|
||||
requestTile(tileUrl + '?cache_buster=wadus', function (err, res) {
|
||||
var xwc = res.headers['x-windshaft-cache'];
|
||||
assert.ok(!xwc);
|
||||
|
||||
finish(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should not choke when queries end with a semicolon", function(done){
|
||||
testClient.getTile(testClient.singleLayerMapConfig('SELECT * FROM test_table limit 2;'), 0, 0, 0, done);
|
||||
});
|
||||
|
||||
it("should not choke when sql ends with a semicolon and some blanks", function(done){
|
||||
testClient.getTile(testClient.singleLayerMapConfig('SELECT * FROM test_table limit 2; \t\n'), 0, 0, 0, done);
|
||||
});
|
||||
|
||||
it("should not strip quoted semicolons within an sql query", function(done){
|
||||
testClient.getTile(
|
||||
testClient.singleLayerMapConfig("SELECT * FROM test_table where name != ';\n'"), 0, 0, 0, done
|
||||
);
|
||||
});
|
||||
|
||||
it("getting two tiles with same configuration uses renderer cache", function(done) {
|
||||
|
||||
var imageFixture = './test/fixtures/test_table_13_4011_3088_styled.png';
|
||||
var tileUrl = '/13/4011/3088.png';
|
||||
var mapConfig = testClient.defaultTableMapConfig(
|
||||
'test_table',
|
||||
'#test_table{marker-fill: blue;marker-line-color: black;}'
|
||||
);
|
||||
|
||||
function validateLayergroup(res) {
|
||||
// cache is hit because we create a renderer to validate the map config
|
||||
assert.ok(!res.headers.hasOwnProperty('x-windshaft-cache'), "Did hit renderer cache on first time");
|
||||
}
|
||||
|
||||
testClient.withLayergroup(mapConfig, validateLayergroup, function(err, requestTile, finish) {
|
||||
|
||||
requestTile(tileUrl, function(err, res) {
|
||||
assert.ok(res.headers.hasOwnProperty('x-windshaft-cache'), "Did not hit renderer cache on second time");
|
||||
assert.ok(res.headers['x-windshaft-cache'] >= 0);
|
||||
|
||||
assert.imageEqualsFile(res.body, imageFixture, IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err) {
|
||||
|
||||
finish(function(finishErr) {
|
||||
done(err || finishErr);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var test_style_black_200 = "#test_table{marker-fill:black;marker-line-color:black;marker-width:5}";
|
||||
var test_style_black_210 = "#test_table{marker-fill:black;marker-line-color:black;marker-width:10}";
|
||||
|
||||
it("get'ing a tile with url specified 2.0.0 style should return an expected tile", function(done){
|
||||
testClient.getTile(testClient.defaultTableMapConfig('test_table', test_style_black_200, '2.0.0'),
|
||||
13, 4011, 3088, imageCompareFn('test_table_13_4011_3088_styled_black.png', done));
|
||||
});
|
||||
|
||||
it("get'ing a tile with url specified 2.1.0 style should return an expected tile", function(done){
|
||||
testClient.getTile(testClient.defaultTableMapConfig('test_table', test_style_black_210, '2.1.0'),
|
||||
13, 4011, 3088, imageCompareFn('test_table_13_4011_3088_styled_black.png', done));
|
||||
});
|
||||
|
||||
// See http://github.com/CartoDB/Windshaft/issues/99
|
||||
it("unused directives are tolerated", function(done){
|
||||
var style = "#test_table{point-transform: 'scale(100)';}";
|
||||
var sql = "SELECT 1 as cartodb_id, 'SRID=4326;POINT(0 0)'::geometry as the_geom";
|
||||
testClient.getTile(testClient.singleLayerMapConfig(sql, style), 0, 0, 0,
|
||||
imageCompareFn('test_default_mapnik_point.png', done));
|
||||
});
|
||||
|
||||
// See http://github.com/CartoDB/Windshaft/issues/100
|
||||
var test_strictness = function(done) {
|
||||
var nonStrictMapConfig = testClient.singleLayerMapConfig(
|
||||
"SELECT 1 as cartodb_id, 'SRID=3857;POINT(666 666)'::geometry as the_geom",
|
||||
"#test_table{point-transform: 'scale(100)';}"
|
||||
);
|
||||
testClient.withLayergroup(nonStrictMapConfig, function(err, requestTile, finish) {
|
||||
var options = {
|
||||
statusCode: 400,
|
||||
contentType: 'application/json; charset=utf-8'
|
||||
};
|
||||
requestTile('/0/0/0.png?strict=1', options, function() {
|
||||
finish(done);
|
||||
});
|
||||
});
|
||||
};
|
||||
var test_strict_lbl = "unused directives are not tolerated if strict";
|
||||
if ( semver.satisfies(mapnik.versions.mapnik, '2.3.x') ) {
|
||||
// Strictness handling changed in 2.3.x, possibly a bug:
|
||||
// see http://github.com/mapnik/mapnik/issues/2301
|
||||
console.warn("Strictness test skipped due to http://github.com/mapnik/mapnik/issues/2301");
|
||||
it.skip(test_strict_lbl, test_strictness);
|
||||
}
|
||||
else {
|
||||
it(test_strict_lbl, test_strictness);
|
||||
}
|
||||
|
||||
it('high cpu regression with mapnik <2.3.x', function(done) {
|
||||
var sql = [
|
||||
"SELECT 'my polygon name here' AS name,",
|
||||
"st_envelope(st_buffer(st_transform(",
|
||||
"st_setsrid(st_makepoint(-26.6592894004,49.7990296995),4326),3857),10000000)) AS the_geom",
|
||||
"FROM generate_series(-6,6) x",
|
||||
"UNION ALL",
|
||||
"SELECT 'my marker name here' AS name,",
|
||||
" st_transform(st_setsrid(st_makepoint(49.6042060319,-49.0522997372),4326),3857) AS the_geom",
|
||||
"FROM generate_series(-6,6) x"
|
||||
].join(' ');
|
||||
|
||||
var style = [
|
||||
'#test_table {marker-fill:#ff7;',
|
||||
' marker-max-error:0.447492761618;',
|
||||
' marker-line-opacity:0.659371340628;',
|
||||
' marker-allow-overlap:true;',
|
||||
' polygon-fill:green;',
|
||||
' marker-spacing:0.0;',
|
||||
' marker-width:4.0;',
|
||||
' marker-height:18.0;',
|
||||
' marker-opacity:0.942312062822;',
|
||||
' line-color:green;',
|
||||
' line-gamma:0.945973211092;',
|
||||
' line-cap:square;',
|
||||
' polygon-opacity:0.12576055992;',
|
||||
' marker-type:arrow;',
|
||||
' polygon-gamma:0.46354913107;',
|
||||
' line-dasharray:33,23;',
|
||||
' line-join:bevel;',
|
||||
' marker-placement:line;',
|
||||
' line-width:1.0;',
|
||||
' marker-line-color:#ff7;',
|
||||
' line-opacity:0.39403752154;',
|
||||
' marker-line-width:3.0;',
|
||||
'}'
|
||||
].join('');
|
||||
|
||||
testClient.getTile(testClient.singleLayerMapConfig(sql, style), 13, 4011, 3088, done);
|
||||
});
|
||||
|
||||
// https://github.com/CartoDB/Windshaft-cartodb/issues/316
|
||||
it('should return errors with better formatting', function(done) {
|
||||
var mapConfig = {
|
||||
"version": "1.0.1",
|
||||
"minzoom": 0,
|
||||
"maxzoom": 20,
|
||||
"layers": [
|
||||
{
|
||||
"type": 'mapnik',
|
||||
"options": {
|
||||
"cartocss_version": '2.1.1',
|
||||
"sql": "SELECT null::geometry AS the_geom",
|
||||
"cartocss": [
|
||||
'@water: #cdd2d4;',
|
||||
'Map {',
|
||||
'\tbackground-color: @water;',
|
||||
'\tbufferz-size: 256;',
|
||||
'}',
|
||||
'@landmass_fill: lighten(#e3e3dc, 8%);'
|
||||
].join('\n')
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": 'mapnik',
|
||||
"options": {
|
||||
"cartocss_version": '2.1.1',
|
||||
"sql": "SELECT the_geom FROM false_background_zoomed('!scale_denominator!', !bbox!) AS _",
|
||||
"cartocss": [
|
||||
'#false_background {',
|
||||
'\tpolygon-fill: @landmass_fill;',
|
||||
'}'
|
||||
].join('\n')
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var options = {
|
||||
statusCode: 400
|
||||
};
|
||||
|
||||
testClient.createLayergroup(mapConfig, options, function(err, res, parsedBody) {
|
||||
assert.ok(parsedBody.errors);
|
||||
// more assertions when errors is populated with better format
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,175 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var _ = require('underscore');
|
||||
var fs = require('fs');
|
||||
var redis = require('redis');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
|
||||
var IMAGE_EQUALS_TOLERANCE_PER_MIL = 85;
|
||||
|
||||
describe('server_png8_format', function() {
|
||||
|
||||
var serverOptionsPng32 = ServerOptions;
|
||||
serverOptionsPng32.grainstore = _.clone(ServerOptions.grainstore);
|
||||
serverOptionsPng32.grainstore.mapnik_tile_format = 'png32';
|
||||
var serverPng32 = new cartodbServer(serverOptionsPng32);
|
||||
serverPng32.req2params = ServerOptions.req2params;
|
||||
serverPng32.setMaxListeners(0);
|
||||
|
||||
var serverOptionsPng8 = ServerOptions;
|
||||
serverOptionsPng8.grainstore = _.clone(ServerOptions.grainstore);
|
||||
serverOptionsPng8.grainstore.mapnik_tile_format = 'png8:m=h';
|
||||
var serverPng8 = new cartodbServer(serverOptionsPng8);
|
||||
serverPng8.req2params = ServerOptions.req2params;
|
||||
serverPng8.setMaxListeners(0);
|
||||
|
||||
|
||||
var redisClient = redis.createClient(ServerOptions.redis.port);
|
||||
|
||||
var layergroupId;
|
||||
|
||||
before(function(done) {
|
||||
var testPngFilesDir = __dirname + '/../../results/png';
|
||||
fs.readdirSync(testPngFilesDir)
|
||||
.filter(function(fileName) {
|
||||
return /.*\.png$/.test(fileName);
|
||||
})
|
||||
.map(function(fileName) {
|
||||
return testPngFilesDir + '/' + fileName;
|
||||
})
|
||||
.forEach(fs.unlinkSync);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
function testOutputForPng32AndPng8(desc, tile, callback) {
|
||||
|
||||
var bufferPng32,
|
||||
bufferPng8;
|
||||
|
||||
it(desc + '; tile: ' + JSON.stringify(tile), function(done){
|
||||
assert.response(
|
||||
serverPng32,
|
||||
{
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroup)
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(res, err) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
layergroupId = JSON.parse(res.body).layergroupid;
|
||||
|
||||
var tilePartialUrl = _.template('/<%= z %>/<%= x %>/<%= y %>.png', tile);
|
||||
|
||||
var requestPayload = {
|
||||
url: '/database/windshaft_test/layergroup/' + layergroupId + tilePartialUrl,
|
||||
method: 'GET',
|
||||
encoding: 'binary'
|
||||
};
|
||||
|
||||
var requestHeaders = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'image/png'
|
||||
}
|
||||
};
|
||||
|
||||
assert.response(serverPng32, requestPayload, requestHeaders, function(responsePng32) {
|
||||
assert.equal(responsePng32.headers['content-type'], "image/png");
|
||||
bufferPng32 = responsePng32.body;
|
||||
assert.response(serverPng8, requestPayload, requestHeaders, function(responsePng8) {
|
||||
assert.equal(responsePng8.headers['content-type'], "image/png");
|
||||
bufferPng8 = responsePng8.body;
|
||||
assert.ok(bufferPng8.length < bufferPng32.length);
|
||||
assert.imageBuffersAreEqual(bufferPng32, bufferPng8, IMAGE_EQUALS_TOLERANCE_PER_MIL,
|
||||
function(err, imagePaths, similarity) {
|
||||
redisClient.del('map_cfg|' + layergroupId, function() {
|
||||
callback(err, imagePaths, similarity, done);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var currentLevel = 3,
|
||||
allLevelTiles = [],
|
||||
maxLevelTile = Math.pow(2, currentLevel);
|
||||
|
||||
for (var i = 0; i < maxLevelTile; i++) {
|
||||
for (var j = 0; j < maxLevelTile; j++) {
|
||||
allLevelTiles.push({
|
||||
z: currentLevel,
|
||||
x: i,
|
||||
y: j
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var layergroup = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
options: {
|
||||
sql: 'SELECT * FROM populated_places_simple_reduced',
|
||||
cartocss: [
|
||||
'#populated_places_simple_reduced {',
|
||||
'marker-fill: #FFCC00;',
|
||||
'marker-width: 10;',
|
||||
'marker-line-color: #FFF;',
|
||||
'marker-line-width: 1.5;',
|
||||
'marker-line-opacity: 1;',
|
||||
'marker-fill-opacity: 0.9;',
|
||||
'marker-comp-op: multiply;',
|
||||
'marker-type: ellipse;',
|
||||
'marker-placement: point;',
|
||||
'marker-allow-overlap: true;',
|
||||
'marker-clip: false;',
|
||||
'}'
|
||||
].join(' '),
|
||||
cartocss_version: '2.0.1'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var allImagePaths = [],
|
||||
similarities = [];
|
||||
allLevelTiles.forEach(function(tile) {
|
||||
testOutputForPng32AndPng8('intensity visualization', tile, function(err, imagePaths, similarity, done) {
|
||||
allImagePaths.push(imagePaths);
|
||||
similarities.push(similarity);
|
||||
var transformPaths = [];
|
||||
for (var i = 0, len = allImagePaths.length; i < len; i++) {
|
||||
if (similarities[i] > 0.075) {
|
||||
transformPaths.push({
|
||||
passive: allImagePaths[i][0],
|
||||
active: allImagePaths[i][1],
|
||||
similarity: similarities[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
var output = 'handleResults(' + JSON.stringify(transformPaths) + ');';
|
||||
fs.writeFileSync('test/results/png/results.js', output);
|
||||
assert.ifError(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
|
||||
describe('static_maps', function() {
|
||||
|
||||
var validUrlTemplate = 'http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png';
|
||||
var invalidUrlTemplate = 'http://127.0.0.1:8033/INVALID/{z}/{x}/{y}.png';
|
||||
|
||||
var httpRendererResourcesServer;
|
||||
|
||||
before(function(done) {
|
||||
// Start a server to test external resources
|
||||
httpRendererResourcesServer = http.createServer( function(request, response) {
|
||||
var filename = __dirname + '/../../fixtures/http/basemap.png';
|
||||
fs.readFile(filename, {encoding: 'binary'}, function(err, file) {
|
||||
response.writeHead(200);
|
||||
response.write(file, "binary");
|
||||
response.end();
|
||||
});
|
||||
});
|
||||
httpRendererResourcesServer.listen(8033, done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
httpRendererResourcesServer.close(done);
|
||||
});
|
||||
|
||||
function staticMapConfig(urlTemplate, cartocss) {
|
||||
return {
|
||||
version: '1.2.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'http',
|
||||
options: {
|
||||
urlTemplate: urlTemplate,
|
||||
subdomains: ['abcd']
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: 'SELECT * FROM populated_places_simple_reduced',
|
||||
cartocss: cartocss || '#layer { marker-fill:red; } #layer { marker-width: 2; }',
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
var zoom = 3,
|
||||
lat = 0,
|
||||
lon = 0,
|
||||
width = 400,
|
||||
height = 300;
|
||||
|
||||
it('center image', function (done) {
|
||||
var mapConfig = staticMapConfig(validUrlTemplate);
|
||||
testClient.getStaticCenter(mapConfig, zoom, lat, lon, width, height, function(err, res, image) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.equal(image.width(), width);
|
||||
assert.equal(image.height(), height);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('center image with invalid basemap', function (done) {
|
||||
var mapConfig = staticMapConfig(invalidUrlTemplate);
|
||||
testClient.getStaticCenter(mapConfig, zoom, lat, lon, width, height, function(err, res, image) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.equal(image.width(), width);
|
||||
assert.equal(image.height(), height);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var west = -90,
|
||||
south = -45,
|
||||
east = 90,
|
||||
north = 45,
|
||||
bbWidth = 640,
|
||||
bbHeight = 480;
|
||||
|
||||
it('bbox', function (done) {
|
||||
var mapConfig = staticMapConfig(validUrlTemplate);
|
||||
testClient.getStaticBbox(mapConfig, west, south, east, north, bbWidth, bbHeight, function(err, res, image) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.equal(image.width(), bbWidth);
|
||||
assert.equal(image.height(), bbHeight);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not fail for coordinates out of range', function (done) {
|
||||
var outOfRangeHeight = 3000;
|
||||
var mapConfig = staticMapConfig(validUrlTemplate);
|
||||
testClient.getStaticCenter(mapConfig, 1, lat, lon, width, outOfRangeHeight, function(err, res, image) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.equal(image.width(), width);
|
||||
assert.equal(image.height(), outOfRangeHeight);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should keep failing for other errors', function (done) {
|
||||
var invalidStyleForZoom = '#layer { marker-fill:red; } #layer[zoom='+zoom+'] { marker-width: [wadus] * 2; }';
|
||||
var mapConfig = staticMapConfig(validUrlTemplate, invalidStyleForZoom);
|
||||
var expectedResponse = {
|
||||
statusCode: 400,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
testClient.getStaticCenter(mapConfig, zoom, lat, lon, width, height, expectedResponse, function(err, res) {
|
||||
assert.ok(!err);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.ok(parsedBody.errors);
|
||||
assert.ok(parsedBody.errors.length);
|
||||
assert.ok(parsedBody.errors[0].match(/column \"wadus\" does not exist/));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
var _ = require('underscore');
|
||||
var serverOptions = require('../../../../lib/cartodb/server_options');
|
||||
var LayergroupToken = require('../../../../lib/cartodb/models/layergroup_token');
|
||||
var mapnik = require('windshaft').mapnik;
|
||||
|
||||
module.exports = _.extend({}, serverOptions, {
|
||||
base_url: '/database/:dbname/table/:table',
|
||||
base_url_mapconfig: '/database/:dbname/layergroup',
|
||||
grainstore: {
|
||||
datasource: {
|
||||
geometry_field: 'the_geom',
|
||||
srid: 4326
|
||||
},
|
||||
cachedir: global.environment.millstone.cache_basedir,
|
||||
mapnik_version: global.environment.mapnik_version || mapnik.versions.mapnik,
|
||||
gc_prob: 0 // run the garbage collector at each invocation
|
||||
},
|
||||
renderer: {
|
||||
mapnik: {
|
||||
poolSize: 4,//require('os').cpus().length,
|
||||
metatile: 1,
|
||||
bufferSize: 64,
|
||||
snapToGrid: false,
|
||||
clipByBox2d: false, // this requires postgis >=2.2 and geos >=3.5
|
||||
scale_factors: [1, 2],
|
||||
limits: {
|
||||
render: 0,
|
||||
cacheOnTimeout: true
|
||||
}
|
||||
},
|
||||
http: {
|
||||
timeout: 5000,
|
||||
whitelist: ['http://127.0.0.1:8033/{s}/{z}/{x}/{y}.png'],
|
||||
fallbackImage: {
|
||||
type: 'fs',
|
||||
src: __dirname + '/../../test/fixtures/http/basemap.png'
|
||||
}
|
||||
}
|
||||
},
|
||||
redis: global.environment.redis,
|
||||
enable_cors: global.environment.enable_cors,
|
||||
unbuffered_logging: true, // for smoother teardown from tests
|
||||
log_format: null, // do not log anything
|
||||
afterLayergroupCreateCalls: 0,
|
||||
useProfiler: true,
|
||||
req2params: function(req, callback){
|
||||
|
||||
if ( req.query.testUnexpectedError ) {
|
||||
return callback('test unexpected error');
|
||||
}
|
||||
|
||||
// this is in case you want to test sql parameters eg ...png?sql=select * from my_table limit 10
|
||||
req.params = _.extend({}, req.params);
|
||||
if (req.params.token) {
|
||||
req.params.token = LayergroupToken.parse(req.params.token).token;
|
||||
}
|
||||
|
||||
_.extend(req.params, req.query);
|
||||
req.params.user = 'localhost';
|
||||
req.params.dbuser = 'test_windshaft_publicuser';
|
||||
req.params.dbname = 'test_windshaft_cartodb_user_1_db';
|
||||
|
||||
|
||||
// increment number of calls counter
|
||||
// NOTE: "this" would likely point to the server instance
|
||||
this.req2params_calls = this.req2params_calls ? this.req2params_calls + 1 : 1;
|
||||
|
||||
// send the finished req object on
|
||||
callback(null,req);
|
||||
},
|
||||
afterLayergroupCreate: function(req, cfg, res, callback) {
|
||||
res.layercount = cfg.layers.length;
|
||||
// config.afterLayergroupCreateCalls++;
|
||||
callback(null);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,467 @@
|
||||
require('../../../support/test_helper');
|
||||
var step = require('step');
|
||||
var assert = require('../../../support/assert');
|
||||
var redis = require('redis');
|
||||
var _ = require('underscore');
|
||||
var querystring = require('querystring');
|
||||
var mapnik = require('windshaft').mapnik;
|
||||
var CartodbServer = require('../../../../lib/cartodb/server');
|
||||
var PortedServerOptions = require('./ported_server_options');
|
||||
|
||||
var DEFAULT_POINT_STYLE = [
|
||||
'#layer {',
|
||||
' marker-fill: #FF6600;',
|
||||
' marker-opacity: 1;',
|
||||
' marker-width: 16;',
|
||||
' marker-line-color: white;',
|
||||
' marker-line-width: 3;',
|
||||
' marker-line-opacity: 0.9;',
|
||||
' marker-placement: point;',
|
||||
' marker-type: ellipse;',
|
||||
' marker-allow-overlap: true;',
|
||||
'}'
|
||||
].join('');
|
||||
|
||||
module.exports = {
|
||||
createLayergroup: createLayergroup,
|
||||
withLayergroup: withLayergroup,
|
||||
|
||||
singleLayerMapConfig: singleLayerMapConfig,
|
||||
defaultTableMapConfig: defaultTableMapConfig,
|
||||
|
||||
getStaticBbox: getStaticBbox,
|
||||
getStaticCenter: getStaticCenter,
|
||||
getGrid: getGrid,
|
||||
getGridJsonp: getGridJsonp,
|
||||
getTorque: getTorque,
|
||||
getTile: getTile,
|
||||
getTileLayer: getTileLayer
|
||||
};
|
||||
|
||||
|
||||
var server = new CartodbServer(PortedServerOptions);
|
||||
server.req2params = PortedServerOptions.req2params;
|
||||
server.setMaxListeners(0);
|
||||
var redisClient = redis.createClient(global.environment.redis.port);
|
||||
|
||||
var jsonContentType = 'application/json; charset=utf-8';
|
||||
var pngContentType = 'image/png';
|
||||
|
||||
function createLayergroup(layergroupConfig, options, callback) {
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = {
|
||||
method: 'POST',
|
||||
statusCode: 200
|
||||
};
|
||||
}
|
||||
|
||||
var expectedResponse = {
|
||||
status: options.statusCode || 200,
|
||||
headers: options.headers || {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
step(
|
||||
function requestLayergroup() {
|
||||
var next = this;
|
||||
var request = layergroupRequest(layergroupConfig, options.method, options.callbackName, options.params);
|
||||
assert.response(serverInstance(options), request, expectedResponse, function (res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function validateLayergroup(err, res) {
|
||||
assert.ok(!err, 'Failed to request layergroup');
|
||||
|
||||
var parsedBody;
|
||||
var layergroupid;
|
||||
if (!options.callbackName) {
|
||||
parsedBody = JSON.parse(res.body);
|
||||
layergroupid = parsedBody.layergroupid;
|
||||
}
|
||||
|
||||
if (layergroupid) {
|
||||
var redisKey = 'map_cfg|' + layergroupid;
|
||||
redisClient.del(redisKey, function (/*delErr*/) {
|
||||
return callback(err, res, parsedBody);
|
||||
});
|
||||
} else {
|
||||
return callback(err, res, parsedBody);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function serverInstance(options) {
|
||||
if (options.server) {
|
||||
return options.server;
|
||||
}
|
||||
|
||||
if (options.serverOptions) {
|
||||
var otherServer = new CartodbServer(options.serverOptions);
|
||||
otherServer.req2params = options.serverOptions.req2params;
|
||||
otherServer.setMaxListeners(0);
|
||||
return otherServer;
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
function layergroupRequest(layergroupConfig, method, callbackName, extraParams) {
|
||||
method = method || 'POST';
|
||||
|
||||
var request = {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
var urlParams = _.extend({}, extraParams);
|
||||
if (callbackName) {
|
||||
urlParams.callback = callbackName;
|
||||
}
|
||||
|
||||
if (method.toUpperCase() === 'GET') {
|
||||
request.method = 'GET';
|
||||
urlParams.config = JSON.stringify(layergroupConfig);
|
||||
} else {
|
||||
request.method = 'POST';
|
||||
request.data = JSON.stringify(layergroupConfig);
|
||||
}
|
||||
|
||||
if (Object.keys(urlParams).length) {
|
||||
request.url += '?' + querystring.stringify(urlParams);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
function singleLayerMapConfig(sql, cartocss, cartocssVersion, interactivity) {
|
||||
return {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'mapnik',
|
||||
options: {
|
||||
sql: sql,
|
||||
cartocss: cartocss || DEFAULT_POINT_STYLE,
|
||||
cartocss_version: cartocssVersion || '2.3.0',
|
||||
interactivity: interactivity,
|
||||
geom_column: 'the_geom'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function defaultTableMapConfig(tableName, cartocss, cartocssVersion, interactivity) {
|
||||
return singleLayerMapConfig(defaultTableQuery(tableName), cartocss, cartocssVersion, interactivity);
|
||||
}
|
||||
|
||||
function defaultTableQuery(tableName) {
|
||||
return _.template('SELECT * FROM <%= tableName %>', {tableName: tableName});
|
||||
}
|
||||
|
||||
function getStaticBbox(layergroupConfig, west, south, east, north, width, height, expectedResponse, callback) {
|
||||
if (!callback) {
|
||||
callback = expectedResponse;
|
||||
expectedResponse = pngContentType;
|
||||
}
|
||||
|
||||
var url = [
|
||||
'static',
|
||||
'bbox',
|
||||
'<%= layergroupid %>',
|
||||
[west, south, east, north].join(','),
|
||||
width,
|
||||
height
|
||||
].join('/') + '.png';
|
||||
return getGeneric(layergroupConfig, url, expectedResponse, callback);
|
||||
}
|
||||
|
||||
function getStaticCenter(layergroupConfig, zoom, lat, lon, width, height, expectedResponse, callback) {
|
||||
if (!callback) {
|
||||
callback = expectedResponse;
|
||||
expectedResponse = pngContentType;
|
||||
}
|
||||
|
||||
var url = [
|
||||
'static',
|
||||
'center',
|
||||
'<%= layergroupid %>',
|
||||
zoom,
|
||||
lat,
|
||||
lon,
|
||||
width,
|
||||
height
|
||||
].join('/') + '.png';
|
||||
return getGeneric(layergroupConfig, url, expectedResponse, callback);
|
||||
}
|
||||
|
||||
function getGrid(layergroupConfig, layer, z, x, y, expectedResponse, callback) {
|
||||
if (!callback) {
|
||||
callback = expectedResponse;
|
||||
expectedResponse = jsonContentType;
|
||||
}
|
||||
|
||||
var options = {
|
||||
layer: layer,
|
||||
z: z,
|
||||
x: x,
|
||||
y: y,
|
||||
format: 'grid.json'
|
||||
};
|
||||
return getLayer(layergroupConfig, options, expectedResponse, callback);
|
||||
}
|
||||
|
||||
function getGridJsonp(layergroupConfig, layer, z, x, y, jsonpCallbackName, expectedResponse, callback) {
|
||||
if (!callback) {
|
||||
callback = expectedResponse;
|
||||
expectedResponse = jsonContentType;
|
||||
}
|
||||
|
||||
var options = {
|
||||
layer: layer,
|
||||
z: z,
|
||||
x: x,
|
||||
y: y,
|
||||
format: 'grid.json',
|
||||
jsonpCallbackName: jsonpCallbackName
|
||||
};
|
||||
return getLayer(layergroupConfig, options, expectedResponse, callback);
|
||||
}
|
||||
|
||||
function getTorque(layergroupConfig, layer, z, x, y, expectedResponse, callback) {
|
||||
if (!callback) {
|
||||
callback = expectedResponse;
|
||||
expectedResponse = jsonContentType;
|
||||
}
|
||||
|
||||
var options = {
|
||||
layer: layer,
|
||||
z: z,
|
||||
x: x,
|
||||
y: y,
|
||||
format: 'torque.json'
|
||||
};
|
||||
return getLayer(layergroupConfig, options, expectedResponse, callback);
|
||||
}
|
||||
|
||||
function getTile(layergroupConfig, z, x, y, expectedResponse, callback) {
|
||||
if (!callback) {
|
||||
callback = expectedResponse;
|
||||
expectedResponse = pngContentType;
|
||||
}
|
||||
|
||||
var options = {
|
||||
z: z,
|
||||
x: x,
|
||||
y: y,
|
||||
format: 'png'
|
||||
};
|
||||
return getLayer(layergroupConfig, options, expectedResponse, callback);
|
||||
}
|
||||
|
||||
function getTileLayer(layergroupConfig, options, expectedResponse, callback) {
|
||||
if (!callback) {
|
||||
callback = expectedResponse;
|
||||
expectedResponse = pngContentType;
|
||||
}
|
||||
|
||||
return getLayer(layergroupConfig, options, expectedResponse, callback);
|
||||
}
|
||||
|
||||
function getLayer(layergroupConfig, options, expectedResponse, callback) {
|
||||
return getGeneric(layergroupConfig, tileUrlStrategy(options), expectedResponse, callback);
|
||||
}
|
||||
|
||||
function tileUrlStrategy(options) {
|
||||
var urlLayerPattern = [
|
||||
'<%= layer %>',
|
||||
'<%= z %>',
|
||||
'<%= x %>',
|
||||
'<%= y %>'
|
||||
].join('/') + '.<%= format %>';
|
||||
|
||||
if (options.jsonpCallbackName) {
|
||||
urlLayerPattern += '?callback=<%= jsonpCallbackName %>';
|
||||
}
|
||||
|
||||
var urlNoLayerPattern = [
|
||||
'<%= z %>',
|
||||
'<%= x %>',
|
||||
'<%= y %>'
|
||||
].join('/') + '.<%= format %>';
|
||||
|
||||
var urlTemplate = _.template((options.layer === undefined) ? urlNoLayerPattern : urlLayerPattern);
|
||||
|
||||
options.format = options.format || 'png';
|
||||
|
||||
return '<%= layergroupid %>/' + urlTemplate(_.defaults(options, { z: 0, x: 0, y: 0, layer: 0 }));
|
||||
}
|
||||
|
||||
function getGeneric(layergroupConfig, url, expectedResponse, callback) {
|
||||
if (_.isString(expectedResponse)) {
|
||||
expectedResponse = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': expectedResponse
|
||||
}
|
||||
};
|
||||
}
|
||||
var contentType = expectedResponse.headers['Content-Type'];
|
||||
|
||||
var layergroupid = null;
|
||||
|
||||
step(
|
||||
function requestLayergroup() {
|
||||
var next = this;
|
||||
var request = {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: JSON.stringify(layergroupConfig)
|
||||
};
|
||||
var expectedResponse = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
assert.response(server, request, expectedResponse, function (res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function validateLayergroup(err, res) {
|
||||
assert.ok(!err, 'Failed to create layergroup');
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
layergroupid = parsedBody.layergroupid;
|
||||
|
||||
assert.ok(layergroupid);
|
||||
|
||||
return res;
|
||||
},
|
||||
function requestTile(err, res) {
|
||||
assert.ok(!err, 'Invalid layergroup response: ' + res.body);
|
||||
|
||||
var next = this;
|
||||
|
||||
var finalUrl = '/database/windshaft_test/layergroup/' + _.template(url, {
|
||||
layergroupid: layergroupid
|
||||
});
|
||||
|
||||
var request = {
|
||||
url: finalUrl,
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
if (contentType === pngContentType) {
|
||||
request.encoding = 'binary';
|
||||
}
|
||||
|
||||
assert.response(server, request, expectedResponse, function (res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function validateTile(err, res) {
|
||||
assert.ok(!err, 'Failed to get tile');
|
||||
var redisKey = 'map_cfg|' + layergroupid;
|
||||
|
||||
var img;
|
||||
if (contentType === pngContentType) {
|
||||
img = new mapnik.Image.fromBytesSync(new Buffer(res.body, 'binary'));
|
||||
}
|
||||
|
||||
redisClient.del(redisKey, function (/*delErr*/) {
|
||||
return callback(err, res, img);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function withLayergroup(layergroupConfig, options, callback) {
|
||||
var validationLayergroupFn = function() {};
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if (_.isFunction(options)) {
|
||||
validationLayergroupFn = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
var layergroupExpectedResponse = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
};
|
||||
|
||||
step(
|
||||
function requestLayergroup() {
|
||||
var next = this;
|
||||
var request = layergroupRequest(layergroupConfig, 'POST');
|
||||
assert.response(serverInstance(options), request, layergroupExpectedResponse, function (res, err) {
|
||||
next(err, res);
|
||||
});
|
||||
},
|
||||
function validateLayergroup(err, res) {
|
||||
assert.ok(!err, 'Failed to request layergroup');
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var layergroupid = parsedBody.layergroupid;
|
||||
|
||||
assert.ok(layergroupid, 'No layergroup was created');
|
||||
|
||||
validationLayergroupFn(res);
|
||||
|
||||
function requestTile(layergroupUrl, options, callback) {
|
||||
if (!callback) {
|
||||
callback = options;
|
||||
options = {
|
||||
statusCode: 200,
|
||||
contentType: pngContentType
|
||||
};
|
||||
}
|
||||
|
||||
var baseUrlTpl = '/database/windshaft_test/layergroup/<%= layergroupid %>';
|
||||
var finalUrl = _.template(baseUrlTpl, { layergroupid: layergroupid }) + layergroupUrl;
|
||||
|
||||
var request = {
|
||||
url: finalUrl,
|
||||
method: 'GET'
|
||||
};
|
||||
|
||||
if (options.contentType === pngContentType) {
|
||||
request.encoding = 'binary';
|
||||
}
|
||||
|
||||
var tileExpectedResponse = {
|
||||
status: options.statusCode || 200,
|
||||
headers: {
|
||||
'Content-Type': options.contentType || pngContentType
|
||||
}
|
||||
};
|
||||
|
||||
assert.response(serverInstance(options), request, tileExpectedResponse, function (res, err) {
|
||||
callback(err, res);
|
||||
});
|
||||
}
|
||||
|
||||
function finish(done) {
|
||||
var redisKey = 'map_cfg|' + layergroupid;
|
||||
redisClient.del(redisKey, function (delErr) {
|
||||
return done(delErr);
|
||||
});
|
||||
}
|
||||
|
||||
return callback(err, requestTile, finish);
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var _ = require('underscore');
|
||||
var redis = require('redis');
|
||||
var step = require('step');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
|
||||
describe('torque', function() {
|
||||
|
||||
var server = cartodbServer(ServerOptions);
|
||||
server.req2params = ServerOptions.req2params;
|
||||
server.setMaxListeners(0);
|
||||
var redis_client = redis.createClient(ServerOptions.redis.port);
|
||||
|
||||
function checkCORSHeaders(res) {
|
||||
assert.equal(res.headers['access-control-allow-headers'], 'X-Requested-With, X-Prototype-Version, X-CSRF-Token');
|
||||
assert.equal(res.headers['access-control-allow-origin'], '*');
|
||||
}
|
||||
|
||||
it("missing required property from torque layer", function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.1.0',
|
||||
layers: [
|
||||
{ type: 'torque', options: {
|
||||
sql: 'select cartodb_id, the_geom from test_table',
|
||||
geom_column: 'the_geom',
|
||||
srid: 4326,
|
||||
cartocss: 'Map { marker-fill:blue; }'
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
step(
|
||||
function do_post1()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) { next(null, res); });
|
||||
},
|
||||
function checkResponse(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors, parsed);
|
||||
var error = parsed.errors[0];
|
||||
assert.equal(error,
|
||||
"Missing required property '-torque-frame-count' in torque layer CartoCSS");
|
||||
return null;
|
||||
},
|
||||
function do_post2(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
var css = 'Map { -torque-frame-count: 2; }';
|
||||
layergroup.layers[0].options.cartocss = css;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) { next(null, res); });
|
||||
},
|
||||
function checkResponse2(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors, parsed);
|
||||
var error = parsed.errors[0];
|
||||
assert.equal(error,
|
||||
"Missing required property '-torque-resolution' in torque layer CartoCSS");
|
||||
return null;
|
||||
},
|
||||
function do_post3(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
var css = 'Map { -torque-frame-count: 2; -torque-resolution: 3; }';
|
||||
layergroup.layers[0].options.cartocss = css;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) { next(null, res); });
|
||||
},
|
||||
function checkResponse3(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors, parsed);
|
||||
var error = parsed.errors[0];
|
||||
assert.equal(error,
|
||||
"Missing required property '-torque-aggregation-function' in torque layer CartoCSS");
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// See http://github.com/CartoDB/Windshaft/issues/150
|
||||
it.skip("unquoted property in torque layer", function(done) {
|
||||
|
||||
var layergroup = {
|
||||
version: '1.1.0',
|
||||
layers: [
|
||||
{ type: 'torque', options: {
|
||||
sql: 'select updated_at as d, cartodb_id as id, the_geom from test_table',
|
||||
geom_column: 'the_geom',
|
||||
srid: 4326,
|
||||
cartocss: 'Map { -torque-frame-count:2; -torque-resolution:3; -torque-time-attribute:"d"; ' +
|
||||
'-torque-aggregation-function:count(id); }'
|
||||
} }
|
||||
]
|
||||
};
|
||||
step(
|
||||
function do_post1()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) { next(null, res); });
|
||||
},
|
||||
function checkResponse(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors, parsed);
|
||||
var error = parsed.errors[0];
|
||||
assert.equal(error, "Something meaningful here");
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("can render tile for valid mapconfig", function(done) {
|
||||
|
||||
var mapconfig = {
|
||||
version: '1.1.0',
|
||||
layers: [
|
||||
{ type: 'torque', options: {
|
||||
sql: "select 1 as id, '1970-01-02'::date as d, 'POINT(0 0)'::geometry as the_geom UNION select 2, " +
|
||||
"'1970-01-01'::date, 'POINT(1 1)'::geometry",
|
||||
geom_column: 'the_geom',
|
||||
cartocss: 'Map { -torque-frame-count:2; -torque-resolution:3; -torque-time-attribute:d; ' +
|
||||
'-torque-aggregation-function:\'count(id)\'; }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
var expected_token;
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(mapconfig)
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function checkPost(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
// CORS headers should be sent with response
|
||||
// from layergroup creation via POST
|
||||
checkCORSHeaders(res);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
if ( expected_token ) {
|
||||
assert.deepEqual(parsedBody, {layergroupid: expected_token, layercount: 2});
|
||||
} else {
|
||||
expected_token = parsedBody.layergroupid;
|
||||
}
|
||||
var meta = parsedBody.metadata;
|
||||
assert.ok(!_.isUndefined(meta),
|
||||
'No metadata in torque MapConfig creation response: ' + res.body);
|
||||
var tm = meta.torque;
|
||||
assert.ok(tm,
|
||||
'No "torque" in metadata:' + JSON.stringify(meta));
|
||||
var tm0 = tm[0];
|
||||
assert.ok(tm0,
|
||||
'No layer 0 in "torque" in metadata:' + JSON.stringify(tm));
|
||||
var expectedTorqueMetadata = {"start":0,"end":86400000,"data_steps":2,"column_type":"date"};
|
||||
assert.deepEqual(tm0, expectedTorqueMetadata);
|
||||
assert.deepEqual(meta.layers[0].meta, expectedTorqueMetadata);
|
||||
return null;
|
||||
},
|
||||
function do_get_tile(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + expected_token + '/0/0/0.png',
|
||||
method: 'GET',
|
||||
encoding: 'binary'
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function check_mapnik_error_1(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ( res.statusCode !== 200 ? (': ' + res.body) : '' ));
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
assert.equal(parsed.errors[0], "No 'mapnik' layers in MapConfig");
|
||||
return null;
|
||||
},
|
||||
function do_get_grid0(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + expected_token + '/0/0/0/0.grid.json',
|
||||
method: 'GET'
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function check_mapnik_error_2(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ( res.statusCode !== 200 ? (': ' + res.body) : '' ));
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
assert.equal(parsed.errors[0], "Unsupported format grid.json");
|
||||
return null;
|
||||
},
|
||||
function do_get_torque0(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + expected_token + '/0/0/0/0.json.torque',
|
||||
method: 'GET'
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function check_torque0_response(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
|
||||
var tile_content = [{"x__uint8":43,"y__uint8":43,"vals__uint8":[1,1],"dates__uint16":[0,1]}];
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.deepEqual(tile_content, parsed);
|
||||
return null;
|
||||
},
|
||||
function do_get_torque0_1(err)
|
||||
{
|
||||
assert.ifError(err);
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + expected_token + '/0/0/0/0.torque.json',
|
||||
method: 'GET'
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function check_torque0_response_1(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
|
||||
var tile_content = [{"x__uint8":43,"y__uint8":43,"vals__uint8":[1,1],"dates__uint16":[0,1]}];
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.deepEqual(tile_content, parsed);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
var errors = [];
|
||||
if ( err ) {
|
||||
errors.push(''+err);
|
||||
}
|
||||
redis_client.exists("map_cfg|" + expected_token, function(err/*, exists*/) {
|
||||
if ( err ) {
|
||||
errors.push(err.message);
|
||||
}
|
||||
//assert.ok(exists, "Missing expected token " + expected_token + " from redis");
|
||||
redis_client.del("map_cfg|" + expected_token, function(err) {
|
||||
if ( err ) {
|
||||
errors.push(err.message);
|
||||
}
|
||||
if ( errors.length ) {
|
||||
done(new Error(errors));
|
||||
} else {
|
||||
done(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Test that you cannot write to the database from a torque tile request
|
||||
//
|
||||
// Test for http://github.com/CartoDB/Windshaft/issues/130
|
||||
//
|
||||
it("database access is read-only", function(done) {
|
||||
var mapconfig = {
|
||||
version: '1.1.0',
|
||||
layers: [
|
||||
{ type: 'torque', options: {
|
||||
sql: "select 'SRID=3857;POINT(0 0)'::geometry as g, now() as d,* from " +
|
||||
"test_table_inserter(st_setsrid(st_point(0,0),4326),'write')",
|
||||
geom_column: 'g',
|
||||
cartocss: 'Map { -torque-frame-count:2; -torque-resolution:3; -torque-time-attribute:d; ' +
|
||||
'-torque-aggregation-function:\'count(*)\'; }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(mapconfig)
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function checkPost(err, res) {
|
||||
assert.ifError(err);
|
||||
// TODO: should be 403 Forbidden
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + (res.statusCode===200?'...':res.body));
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors);
|
||||
assert.equal(parsed.errors.length, 1);
|
||||
var msg = parsed.errors[0];
|
||||
assert.equal(msg, "TorqueRenderer: cannot execute INSERT in a read-only transaction");
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
// See http://github.com/CartoDB/Windshaft/issues/164
|
||||
it("gives a 500 on database connection refused", function(done) {
|
||||
|
||||
var mapconfig = {
|
||||
version: '1.1.0',
|
||||
layers: [
|
||||
{ type: 'torque', options: {
|
||||
sql: "select 1 as id, '1970-01-03'::date as d, 'POINT(0 0)'::geometry as the_geom UNION select 2, " +
|
||||
"'1970-01-01'::date, 'POINT(1 1)'::geometry",
|
||||
geom_column: 'the_geom',
|
||||
cartocss: 'Map { -torque-frame-count:2; -torque-resolution:3; -torque-time-attribute:d; ' +
|
||||
'-torque-aggregation-function:\'count(id)\'; }',
|
||||
cartocss_version: '2.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
step(
|
||||
function do_post()
|
||||
{
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup?dbport=1234567',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(mapconfig)
|
||||
}, {}, function(res, err) { next(err, res); });
|
||||
},
|
||||
function checkPost(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors, parsed);
|
||||
var error = parsed.errors[0];
|
||||
assert.equal(error, "TorqueRenderer: cannot connect to the database");
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("checks types for torque-specific styles", function(done) {
|
||||
var wrongStyle = ["Map {",
|
||||
"-torque-frame-count:512;",
|
||||
"-torque-animation-duration:30;",
|
||||
"-torque-time-attribute:'cartodb_id';",
|
||||
"-torque-aggregation-function:count(cartodb_id);", //unquoted aggregation function
|
||||
"-torque-resolution:4;",
|
||||
"-torque-data-aggregation:linear;",
|
||||
"}"].join(" ");
|
||||
var layergroup = {
|
||||
version: '1.1.0',
|
||||
layers: [
|
||||
{ type: 'torque', options: {
|
||||
sql: 'select cartodb_id, the_geom from test_table',
|
||||
geom_column: 'the_geom',
|
||||
srid: 4326,
|
||||
cartocss: wrongStyle
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
step(
|
||||
function request(){
|
||||
var next = this;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(layergroup)
|
||||
}, {}, function(res) { next(null, res); });
|
||||
},
|
||||
function checkResponse(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
var parsed = JSON.parse(res.body);
|
||||
assert.ok(parsed.errors, parsed);
|
||||
var error = parsed.errors[0];
|
||||
assert.equal(error,
|
||||
"Unexpected type for property '-torque-aggregation-function', expected string");
|
||||
done();
|
||||
return null;
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,446 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var redis = require('redis');
|
||||
var cartodbServer = require('../../../lib/cartodb/server');
|
||||
var ServerOptions = require('./support/ported_server_options');
|
||||
|
||||
describe('torque boundary points', function() {
|
||||
|
||||
var layergroupIdToDelete = null;
|
||||
|
||||
beforeEach(function() {
|
||||
layergroupIdToDelete = null;
|
||||
});
|
||||
|
||||
var server = cartodbServer(ServerOptions);
|
||||
server.req2params = ServerOptions.req2params;
|
||||
server.setMaxListeners(0);
|
||||
var redis_client = redis.createClient(ServerOptions.redis.port);
|
||||
|
||||
var boundaryPointsMapConfig = {
|
||||
version: '1.1.0',
|
||||
layers: [
|
||||
{ type: 'torque', options: {
|
||||
sql: "WITH p AS ( " +
|
||||
" SELECT '1970-01-02'::date as d, " +
|
||||
// r1 is pixel resolution at zoom level 1
|
||||
// s1 is tile size at zoom level 1
|
||||
" 1e-40 as o, 78271.517578125 as r1, 20037508.5 as s1 )" +
|
||||
" SELECT 1 as i, d, ST_MakePoint(0,0) as ug FROM p" +
|
||||
" UNION ALL " +
|
||||
"SELECT 2, d, ST_MakePoint(-o,-o) FROM p" +
|
||||
" UNION ALL " +
|
||||
"SELECT 3, d, ST_MakePoint(-o,o) FROM p" +
|
||||
" UNION ALL " +
|
||||
"SELECT 4, d, ST_MakePoint(o,-o) FROM p" +
|
||||
" UNION ALL " +
|
||||
"SELECT 5, d, ST_MakePoint(o,o) FROM p" +
|
||||
" UNION ALL " +
|
||||
"SELECT 6, d, ST_MakePoint(-r1,r1) FROM p" +
|
||||
" UNION ALL " +
|
||||
"SELECT 7, d, ST_MakePoint(-r1/2,r1) FROM p" +
|
||||
" UNION ALL " +
|
||||
"SELECT 8, d, ST_MakePoint(-r1,-r1) FROM p" +
|
||||
" UNION ALL " +
|
||||
// this is discarded, being on the right boundary
|
||||
"SELECT 9, d, ST_MakePoint(s1,0) FROM p" +
|
||||
" UNION ALL " +
|
||||
// this is discarded, being on the top boundary
|
||||
"SELECT 10, d, ST_MakePoint(0,s1) FROM p" +
|
||||
" UNION ALL " +
|
||||
// this is discarded, rounding to the right boundary
|
||||
"SELECT 11, d, ST_MakePoint(s1-r1/2,0) FROM p" +
|
||||
" UNION ALL " +
|
||||
// this is discarded, rounding to the top boundary
|
||||
"SELECT 12, d, ST_MakePoint(0,s1-r1/2) FROM p",
|
||||
geom_column: 'ug',
|
||||
cartocss: 'Map { ' +
|
||||
'-torque-frame-count:2; ' +
|
||||
'-torque-resolution:1; ' +
|
||||
'-torque-time-attribute:"d"; ' +
|
||||
'-torque-data-aggregation:linear; ' +
|
||||
'-torque-aggregation-function:"count(i)"; }',
|
||||
cartocss_version: '1.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
var tileRequests = [
|
||||
{
|
||||
desc: '0/0/0',
|
||||
repr: [
|
||||
' ------',
|
||||
'| |',
|
||||
'| |',
|
||||
' ------'
|
||||
],
|
||||
z: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
expects: [
|
||||
{
|
||||
x__uint8: 255,
|
||||
y__uint8: 128,
|
||||
vals__uint8: [{
|
||||
v: 2,
|
||||
d: 'i=9 and i=11'
|
||||
}],
|
||||
dates__uint16: [0]
|
||||
},
|
||||
{
|
||||
x__uint8: 128,
|
||||
y__uint8: 128,
|
||||
vals__uint8: [{
|
||||
v: 8,
|
||||
d: 'i=[1..8]'
|
||||
}],
|
||||
dates__uint16: [0]
|
||||
},
|
||||
{
|
||||
x__uint8: 128,
|
||||
y__uint8: 255,
|
||||
vals__uint8: [{
|
||||
v: 2,
|
||||
d: 'i=10 and i=12'
|
||||
}],
|
||||
dates__uint16: [0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: '1/0/0',
|
||||
repr: [
|
||||
'*00 | 10',
|
||||
'----+----',
|
||||
'01 | 11'
|
||||
],
|
||||
z: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
expects: [
|
||||
{
|
||||
x__uint8: 255,
|
||||
y__uint8: 0,
|
||||
vals__uint8: [{v: 2, d: 'i=6 (-r1,r1) and i=7(-r1/2,r1)'}],
|
||||
dates__uint16: [0]
|
||||
},
|
||||
{
|
||||
x__uint8: 255,
|
||||
y__uint8: 1,
|
||||
vals__uint8: [{v: 2, d: 'i=1 and i=3'}],
|
||||
dates__uint16: [0]
|
||||
},
|
||||
{
|
||||
x__uint8: 255,
|
||||
y__uint8: 255,
|
||||
vals__uint8: [{v: 2, d: 'i=10 and i=12'}],
|
||||
dates__uint16: [0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: '1/1/0',
|
||||
repr: [
|
||||
'00 | 10*',
|
||||
'----+----',
|
||||
'01 | 11'
|
||||
],
|
||||
z: 1,
|
||||
x: 1,
|
||||
y: 0,
|
||||
expects: [
|
||||
{
|
||||
x__uint8: 255,
|
||||
y__uint8: 0,
|
||||
vals__uint8: [{v: 2, d: 'i=9 and i=11'}],
|
||||
dates__uint16: [0]
|
||||
},
|
||||
{
|
||||
x__uint8: 0,
|
||||
y__uint8: 0,
|
||||
vals__uint8: [{v: 2, d: 'i=1 and i=5'}],
|
||||
dates__uint16: [0]
|
||||
},
|
||||
{
|
||||
x__uint8: 0,
|
||||
y__uint8: 255,
|
||||
vals__uint8: [{v: 2, d: 'i=10 and i=12'}],
|
||||
dates__uint16: [0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: '1/0/1',
|
||||
repr: [
|
||||
'00 | 10',
|
||||
'----+----',
|
||||
'*01 | 11'
|
||||
],
|
||||
z: 1,
|
||||
x: 0,
|
||||
y: 1,
|
||||
expects: [
|
||||
{
|
||||
x__uint8: 255,
|
||||
y__uint8: 255,
|
||||
vals__uint8: [{v: 3, d: 'i=1, i=2 and i=8'}],
|
||||
dates__uint16: [0]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
desc: '1/1/1',
|
||||
repr: [
|
||||
'00 | 10',
|
||||
'----+----',
|
||||
'01 | 11*'
|
||||
],
|
||||
z: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
expects: [
|
||||
{
|
||||
x__uint8: 255,
|
||||
y__uint8: 255,
|
||||
vals__uint8: [{
|
||||
v: 2,
|
||||
d: 'i=9 and i=11'
|
||||
}],
|
||||
dates__uint16: [0]
|
||||
},
|
||||
{
|
||||
x__uint8: 0,
|
||||
y__uint8: 255,
|
||||
vals__uint8: [{
|
||||
v: 2,
|
||||
d: 'i=1 and i=4'
|
||||
}],
|
||||
dates__uint16: [0]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
tileRequests.forEach(function(tileRequest) {
|
||||
// See https://github.com/CartoDB/Windshaft/issues/186
|
||||
var desc = 'handles ' + tileRequest.desc + '.json.torque\n\n\t' + tileRequest.repr.join('\n\t') + '\n\n';
|
||||
it(desc, function (done) {
|
||||
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(boundaryPointsMapConfig)
|
||||
}, {}, function (res, err) {
|
||||
|
||||
assert.ok(!err, 'Failed to create layergroup');
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var expected_token = parsedBody.layergroupid;
|
||||
layergroupIdToDelete = expected_token;
|
||||
|
||||
var partialUrl = tileRequest.z + '/' + tileRequest.x + '/' + tileRequest.y;
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + expected_token + '/0/' + partialUrl + '.json.torque',
|
||||
method: 'GET'
|
||||
}, {}, function (res, err) {
|
||||
assert.ok(!err, 'Failed to get json');
|
||||
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers['content-type'], "application/json; charset=utf-8");
|
||||
var parsed = JSON.parse(res.body);
|
||||
|
||||
var i = 0;
|
||||
tileRequest.expects.forEach(function(expected) {
|
||||
assert.equal(
|
||||
parsed[i].x__uint8,
|
||||
expected.x__uint8,
|
||||
parsed[i].x__uint8 + ' == ' + expected.x__uint8 +
|
||||
'\nRESULT\n------' +
|
||||
'\n' + JSON.stringify(parsed, null, 4) +
|
||||
'\nEXPECTED\n--------' +
|
||||
'\n' + JSON.stringify(tileRequest.expects, null, 4)
|
||||
);
|
||||
assert.equal(
|
||||
parsed[i].y__uint8,
|
||||
expected.y__uint8,
|
||||
parsed[i].y__uint8 + ' == ' + expected.y__uint8 +
|
||||
'\nRESULT\n------' +
|
||||
'\n' + JSON.stringify(parsed, null, 4) +
|
||||
'\nEXPECTED\n--------' +
|
||||
'\n' + JSON.stringify(tileRequest.expects, null, 4)
|
||||
);
|
||||
|
||||
var j = 0;
|
||||
expected.vals__uint8.forEach(function(val) {
|
||||
assert.equal(
|
||||
parsed[i].vals__uint8[j],
|
||||
val.v,
|
||||
'desc: ' + val.d +
|
||||
'Number of points got=' + parsed.length + '; ' +
|
||||
'expected=' + tileRequest.expects.length +
|
||||
'\n\tindex=' + i +
|
||||
'\n\tvals__uint8 index=' + j +
|
||||
'\n\tgot=' + parsed[i].vals__uint8[j] +
|
||||
'\n\texpected=' + val.v +
|
||||
'\nRESULT\n------' +
|
||||
'\n' + JSON.stringify(parsed, null, 4) +
|
||||
'\nEXPECTED\n--------' +
|
||||
'\n' + JSON.stringify(tileRequest.expects, null, 4));
|
||||
|
||||
j++;
|
||||
});
|
||||
|
||||
i++;
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
parsed.length,
|
||||
tileRequest.expects.length,
|
||||
'Number of points did not match ' +
|
||||
'got=' + parsed.length + '; ' +
|
||||
'expected=' + tileRequest.expects.length +
|
||||
'\nRESULT\n------' +
|
||||
'\n' + JSON.stringify(parsed, null, 4) +
|
||||
'\nEXPECTED\n--------' +
|
||||
'\n' + JSON.stringify(tileRequest.expects, null, 4));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('regression london point', function(done) {
|
||||
var londonPointMapConfig = {
|
||||
version: '1.1.0',
|
||||
layers: [
|
||||
{ type: 'torque', options: {
|
||||
sql: "SELECT " +
|
||||
"1 as i, " +
|
||||
"'1970-01-02'::date as d, " +
|
||||
"ST_MakePoint(-11309.9155492599,6715342.44989312) g",
|
||||
geom_column: 'g',
|
||||
cartocss: 'Map { ' +
|
||||
'-torque-frame-count:2; ' +
|
||||
'-torque-resolution:1; ' +
|
||||
'-torque-time-attribute:"d"; ' +
|
||||
'-torque-data-aggregation:linear; ' +
|
||||
'-torque-aggregation-function:"count(i)"; }',
|
||||
cartocss_version: '1.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(londonPointMapConfig)
|
||||
}, {}, function (res, err) {
|
||||
assert.ok(!err, 'Failed to create layergroup');
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var layergroupId = parsedBody.layergroupid;
|
||||
layergroupIdToDelete = layergroupId;
|
||||
|
||||
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + layergroupId + '/0/2/1/1.json.torque',
|
||||
method: 'GET'
|
||||
}, {}, function (res, err) {
|
||||
assert.ok(!err, 'Failed to request torque.json');
|
||||
|
||||
var parsed = JSON.parse(res.body);
|
||||
|
||||
assert.deepEqual(parsed, [{
|
||||
x__uint8: 255,
|
||||
y__uint8: 172,
|
||||
vals__uint8: [1],
|
||||
dates__uint16: [0]
|
||||
}]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should consider resolution for least value in query', function(done) {
|
||||
var londonPointMapConfig = {
|
||||
version: '1.1.0',
|
||||
layers: [
|
||||
{ type: 'torque', options: {
|
||||
sql: "" +
|
||||
"SELECT " +
|
||||
"0 as i, " +
|
||||
"st_transform('0101000020E6100000FABD3AB4B5031C400581A80ECC2F4940'::geometry, 3857) as g " +
|
||||
"UNION ALL " +
|
||||
"SELECT " +
|
||||
"2 as i, " +
|
||||
"st_transform('0101000020E61000006739E30EAE031C406625C0C3C72F4940'::geometry, 3857) as g " +
|
||||
"UNION ALL " +
|
||||
"SELECT " +
|
||||
"3 as i, " +
|
||||
"st_transform('0101000020E6100000E26DB8A2A7031C40C8BAA5C2C52F4940'::geometry, 3857) as g",
|
||||
geom_column: 'g',
|
||||
cartocss: 'Map { ' +
|
||||
'-torque-frame-count:1; ' +
|
||||
'-torque-animation-duration:30;' +
|
||||
'-torque-time-attribute:"i"; ' +
|
||||
'-torque-aggregation-function:"count(i)"; ' +
|
||||
'-torque-resolution:2; ' +
|
||||
'-torque-data-aggregation: cumulative; }',
|
||||
cartocss_version: '1.0.1'
|
||||
} }
|
||||
]
|
||||
};
|
||||
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup',
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json' },
|
||||
data: JSON.stringify(londonPointMapConfig)
|
||||
}, {}, function (res, err) {
|
||||
assert.ok(!err, 'Failed to create layergroup');
|
||||
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var layergroupId = parsedBody.layergroupid;
|
||||
layergroupIdToDelete = layergroupId;
|
||||
|
||||
|
||||
assert.response(server, {
|
||||
url: '/database/windshaft_test/layergroup/' + layergroupId + '/0/13/4255/2765.json.torque',
|
||||
method: 'GET'
|
||||
}, {}, function (res, err) {
|
||||
assert.ok(!err, 'Failed to request torque.json');
|
||||
|
||||
var parsed = JSON.parse(res.body);
|
||||
|
||||
assert.deepEqual(parsed, [
|
||||
{
|
||||
x__uint8: 47,
|
||||
y__uint8: 127,
|
||||
vals__uint8: [2],
|
||||
dates__uint16: [0]
|
||||
},
|
||||
{
|
||||
x__uint8: 48,
|
||||
y__uint8: 127,
|
||||
vals__uint8: [1],
|
||||
dates__uint16: [0]
|
||||
}
|
||||
]);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function(done) {
|
||||
var redisKey = 'map_cfg|' + layergroupIdToDelete;
|
||||
redis_client.del(redisKey, function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
describe('torque png renderer', function() {
|
||||
|
||||
var IMAGE_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
var torquePngPointsMapConfig = {
|
||||
version: '1.2.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'torque',
|
||||
options: {
|
||||
sql: "SELECT * FROM populated_places_simple_reduced where the_geom" +
|
||||
" && ST_MakeEnvelope(-90, 0, 90, 65)",
|
||||
cartocss: [
|
||||
'Map {',
|
||||
' buffer-size:0;',
|
||||
' -torque-frame-count:1;',
|
||||
' -torque-animation-duration:30;',
|
||||
' -torque-time-attribute:"cartodb_id";',
|
||||
' -torque-aggregation-function:"count(cartodb_id)";',
|
||||
' -torque-resolution:1;',
|
||||
' -torque-data-aggregation:linear;',
|
||||
'}',
|
||||
'#populated_places_simple_reduced{',
|
||||
' comp-op: multiply;',
|
||||
' marker-fill-opacity: 1;',
|
||||
' marker-line-color: #FFF;',
|
||||
' marker-line-width: 0;',
|
||||
' marker-line-opacity: 1;',
|
||||
' marker-type: rectangle;',
|
||||
' marker-width: 3;',
|
||||
' marker-fill: #FFCC00;',
|
||||
'}'
|
||||
].join(' '),
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var tileRequests = [
|
||||
{
|
||||
z: 2,
|
||||
x: 2,
|
||||
y: 1,
|
||||
layer: 0,
|
||||
format: 'torque.png'
|
||||
},
|
||||
{
|
||||
z: 2,
|
||||
x: 1,
|
||||
y: 1,
|
||||
layer: 0,
|
||||
format: 'torque.png'
|
||||
}
|
||||
];
|
||||
|
||||
function torquePngFixture(zxy) {
|
||||
return './test/fixtures/torque/populated_places_simple_reduced-' + zxy.join('.') + '.png';
|
||||
}
|
||||
|
||||
tileRequests.forEach(function(tileRequest) {
|
||||
var z = tileRequest.z,
|
||||
x = tileRequest.x,
|
||||
y = tileRequest.y;
|
||||
var zxy = [z, x, y];
|
||||
it('tile ' + zxy.join('/') + '.torque.png', function (done) {
|
||||
testClient.getTileLayer(torquePngPointsMapConfig, tileRequest, function(err, res) {
|
||||
assert.imageEqualsFile(res.body, torquePngFixture(zxy), IMAGE_TOLERANCE_PER_MIL, function(err) {
|
||||
assert.ok(!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var mapConfigTorqueOffset = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'torque',
|
||||
options: {
|
||||
sql: "SELECT * FROM populated_places_simple_reduced",
|
||||
cartocss: [
|
||||
'Map {',
|
||||
'buffer-size:0;',
|
||||
'-torque-frame-count:1;',
|
||||
'-torque-animation-duration:30;',
|
||||
'-torque-time-attribute:"cartodb_id";',
|
||||
'-torque-aggregation-function:"count(cartodb_id)";',
|
||||
'-torque-resolution:2;',
|
||||
'-torque-data-aggregation:linear;',
|
||||
'}',
|
||||
'',
|
||||
'#populated_places_simple_reduced{',
|
||||
' comp-op: lighter;',
|
||||
' marker-fill-opacity: 0.9;',
|
||||
' marker-line-color: #2167AB;',
|
||||
' marker-line-width: 5;',
|
||||
' marker-line-opacity: 1;',
|
||||
' marker-type: ellipse;',
|
||||
' marker-width: 6;',
|
||||
' marker-fill: #FF9900;',
|
||||
'}',
|
||||
'#populated_places_simple_reduced[frame-offset=1] {',
|
||||
' marker-width:8;',
|
||||
' marker-fill-opacity:0.45; ',
|
||||
'}',
|
||||
'#populated_places_simple_reduced[frame-offset=2] {',
|
||||
' marker-width:10;',
|
||||
' marker-fill-opacity:0.225; ',
|
||||
'}'
|
||||
].join(' '),
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
it('torque static map with offset', function(done) {
|
||||
var w = 600,
|
||||
h = 400;
|
||||
|
||||
testClient.getStaticBbox(mapConfigTorqueOffset, -170, -87, 170, 87, w, h, function(err, res, img) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
assert.equal(img.width(), w);
|
||||
assert.equal(img.height(), h);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
describe('torque tiles at 0,0 point', function() {
|
||||
|
||||
/*
|
||||
Tiles are represented as in:
|
||||
|
||||
---------
|
||||
| TL | TR |
|
||||
|--(0,0)--|
|
||||
| BL | BR |
|
||||
---------
|
||||
*/
|
||||
|
||||
var tiles = [
|
||||
{
|
||||
what: 'tl',
|
||||
x: 3,
|
||||
y: 3,
|
||||
expects: []
|
||||
},
|
||||
{
|
||||
what: 'tr',
|
||||
x: 4,
|
||||
y: 3,
|
||||
expects: []
|
||||
},
|
||||
{
|
||||
what: 'bl',
|
||||
x: 3,
|
||||
y: 4,
|
||||
expects: [{"x__uint8":1,"y__uint8":1,"vals__uint8":[1],"dates__uint16":[0]}]
|
||||
},
|
||||
{
|
||||
what: 'br',
|
||||
x: 4,
|
||||
y: 4,
|
||||
expects: [{"x__uint8":0,"y__uint8":1,"vals__uint8":[1],"dates__uint16":[0]}]
|
||||
}
|
||||
];
|
||||
|
||||
tiles.forEach(function(tile) {
|
||||
it(tile.what, function(done) {
|
||||
|
||||
var query = 'select 1 cartodb_id,' +
|
||||
' ST_Transform(ST_SetSRID(ST_MakePoint(0, 0), 4326), 3857) the_geom_webmercator';
|
||||
var mapConfig = {
|
||||
version: '1.3.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'torque',
|
||||
options: {
|
||||
sql: query,
|
||||
cartocss: [
|
||||
'Map {',
|
||||
' -torque-time-attribute: "cartodb_id";',
|
||||
' -torque-aggregation-function: "count(cartodb_id)";',
|
||||
' -torque-frame-count: 1;',
|
||||
' -torque-animation-duration: 15;',
|
||||
' -torque-resolution: 128',
|
||||
'}',
|
||||
'#layer {',
|
||||
' marker-fill: #fff;',
|
||||
' marker-fill-opacity: 0.4;',
|
||||
' marker-width: 1;',
|
||||
'}'
|
||||
].join(' '),
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
testClient.getTorque(mapConfig, 0, 3, tile.x, tile.y, function(err, res) {
|
||||
assert.deepEqual(JSON.parse(res.body), tile.expects);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
require('../../support/test_helper');
|
||||
|
||||
var assert = require('../../support/assert');
|
||||
var testClient = require('./support/test_client');
|
||||
|
||||
describe('wrap x coordinate', function() {
|
||||
|
||||
describe('renders correct tile', function() {
|
||||
var IMG_TOLERANCE_PER_MIL = 20;
|
||||
|
||||
function plainTorqueMapConfig(plainColor) {
|
||||
return {
|
||||
version: '1.2.0',
|
||||
layers: [
|
||||
{
|
||||
type: 'plain',
|
||||
options: {
|
||||
color: plainColor
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'torque',
|
||||
options: {
|
||||
sql: "SELECT * FROM populated_places_simple_reduced " +
|
||||
"where the_geom && ST_MakeEnvelope(-90, 0, 90, 65)",
|
||||
cartocss: [
|
||||
'Map {',
|
||||
' buffer-size:0;',
|
||||
' -torque-frame-count:1;',
|
||||
' -torque-animation-duration:30;',
|
||||
' -torque-time-attribute:"cartodb_id";',
|
||||
' -torque-aggregation-function:"count(cartodb_id)";',
|
||||
' -torque-resolution:1;',
|
||||
' -torque-data-aggregation:linear;',
|
||||
'}',
|
||||
'#populated_places_simple_reduced{',
|
||||
' comp-op: multiply;',
|
||||
' marker-fill-opacity: 1;',
|
||||
' marker-line-color: #FFF;',
|
||||
' marker-line-width: 0;',
|
||||
' marker-line-opacity: 1;',
|
||||
' marker-type: rectangle;',
|
||||
' marker-width: 3;',
|
||||
' marker-fill: #FFCC00;',
|
||||
'}'
|
||||
].join(' '),
|
||||
cartocss_version: '2.3.0'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
var testScenarios = [
|
||||
{
|
||||
tile: {
|
||||
z: 2,
|
||||
x: -2,
|
||||
y: 1,
|
||||
layer: 'all',
|
||||
format: 'png'
|
||||
},
|
||||
fixture: {
|
||||
z: 2,
|
||||
x: 2,
|
||||
y: 1
|
||||
},
|
||||
plainColor: 'white'
|
||||
},
|
||||
{
|
||||
tile: {
|
||||
z: 2,
|
||||
x: -3,
|
||||
y: 1,
|
||||
layer: 'all',
|
||||
format: 'png'
|
||||
},
|
||||
fixture: {
|
||||
z: 2,
|
||||
x: 1,
|
||||
y: 1
|
||||
},
|
||||
plainColor: '#339900'
|
||||
}
|
||||
];
|
||||
|
||||
function blendPngFixture(zxy) {
|
||||
return './test/fixtures/blend/blend-plain-torque-' + zxy.join('.') + '.png';
|
||||
}
|
||||
|
||||
testScenarios.forEach(function(testScenario) {
|
||||
var tileRequest = testScenario.tile;
|
||||
var zxy = [tileRequest.z, tileRequest.x, tileRequest.y];
|
||||
var fixtureZxy = [testScenario.fixture.z, testScenario.fixture.x, testScenario.fixture.y];
|
||||
it('tile all/' + zxy.join('/') + '.png', function (done) {
|
||||
testClient.getTileLayer(plainTorqueMapConfig(testScenario.plainColor), tileRequest, function(err, res) {
|
||||
assert.imageEqualsFile(res.body, blendPngFixture(fixtureZxy), IMG_TOLERANCE_PER_MIL, function(err) {
|
||||
assert.ok(!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapnik', function() {
|
||||
it("can get a tile with negative x coordinate", function(done){
|
||||
testClient.getTile(testClient.defaultTableMapConfig('test_table'), 2, -2, 1, function(err, res, img) {
|
||||
assert.ok(!err);
|
||||
assert.ok(img);
|
||||
assert.equal(img.width(), 256);
|
||||
assert.equal(img.height(), 256);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 857 B |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg viewBox="-101.255078125 -111.255078125 212.51015625 212.51015625" style="fill-opacity:0.5; stroke:black; stroke-width:0.205078125; fill:none" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<path d="M -61 -81 L -46 -93 -28 -102 -10 -108 10 -110 30 -108 48 -102 66 -93 81 -81 93 -66 102 -48 108 -30 110 -10 108 10 102 28 93 46 81 61 71 71 56 83 38 92 20 98 -0 100 -20 98 -38 92 -56 83 -71 71 -83 56 -92 38 -98 20 -100 0 -98 -20 -92 -38 -83 -56 -71 -71 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 624 B |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg viewBox="-100 -100 200 200" style="fill-opacity:0.5; stroke:black; stroke-width:0.205078125; fill:none" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<path d="M -100 -100 L 100 -100 100 100 -100 100 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 374 B |
|
After Width: | Height: | Size: 176 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 348 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 582 B |
|
After Width: | Height: | Size: 895 B |
@@ -0,0 +1 @@
|
||||
{"grid":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," !!!! "," !!!! "," ### !!!!! "," #### !!!! "," ##### !! "," ##### "," ### "," $$$$ "," $$$$$ "," $$$$$ "," $$$$ %%%% "," %%%%% "," %%%%% "," %%%% "," %% "," "," "," "," "," "," "," "," "," "," "," &&&& "," &&&& "," &&&&& "," &&&& "," && "],"keys":["","5","2","3","1","4"],"data":{}}
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,2 @@
|
||||
{"grid":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "],"keys":[""],"data":{}}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"grid":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," !!! "," !!!! "," !!!!! "," !!!!! "," !!! "," "," "," "," #### "," ##### "," ##### "," #### "," ## "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "],"keys":["","2","1"],"data":{}}
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 759 B |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 38 KiB |
@@ -116,3 +116,16 @@ fi
|
||||
|
||||
echo "Finished preparing data. Ready to run tests"
|
||||
|
||||
|
||||
############################ WINDSHAFT TESTS ############################
|
||||
|
||||
echo "...Configuring Windshaft test database"
|
||||
|
||||
cat sql/ported/populated_places_simple_reduced.sql |
|
||||
sed "s/:PUBLICUSER/${PUBLICUSER}/" |
|
||||
sed "s/:PUBLICPASS/${PUBLICPASS}/" |
|
||||
sed "s/:TESTUSER/${TESTUSER}/" |
|
||||
sed "s/:TESTPASS/${TESTPASS}/" |
|
||||
psql -v ON_ERROR_STOP=1 ${TEST_DB} || exit 1
|
||||
|
||||
echo "...Test database configuration complete"
|
||||
|
||||
@@ -217,3 +217,24 @@ GRANT ALL ON TABLE long_table_name_with_enough_chars_to_break_querytables_functi
|
||||
GRANT SELECT ON TABLE long_table_name_with_enough_chars_to_break_querytables_function TO :PUBLICUSER;
|
||||
|
||||
INSERT INTO CDB_TableMetadata (tabname, updated_at) VALUES ('long_table_name_with_enough_chars_to_break_querytables_function'::regclass, '2009-02-13T23:31:30.123Z');
|
||||
|
||||
CREATE FUNCTION test_table_inserter(geometry, text) returns int AS $$
|
||||
INSERT INTO test_table(name, the_geom, the_geom_webmercator)
|
||||
SELECT $2, $1, ST_Transform($1, 3857) RETURNING cartodb_id;
|
||||
$$ LANGUAGE 'sql' SECURITY DEFINER;
|
||||
|
||||
CREATE TABLE test_big_poly (
|
||||
updated_at timestamp without time zone DEFAULT now(),
|
||||
created_at timestamp without time zone DEFAULT now(),
|
||||
cartodb_id serial NOT NULL,
|
||||
name character varying,
|
||||
the_geom geometry(polygon) CHECK ( ST_Srid(the_geom) = 4326 ),
|
||||
the_geom_webmercator geometry(polygon) CHECK ( ST_Srid(the_geom_webmercator) = 3857 )
|
||||
);
|
||||
INSERT INTO test_big_poly (name, the_geom) VALUES ('west', 'SRID=4326;POLYGON((-180 -80, -180 80, 0 80, 0 -80, -180 -80))');
|
||||
UPDATE test_big_poly SET the_geom_webmercator = ST_Transform(the_geom, 3857);
|
||||
CREATE INDEX test_big_poly_the_geom_idx ON test_big_poly USING gist (the_geom);
|
||||
CREATE INDEX test_big_poly_the_geom_webmercator_idx ON test_big_poly USING gist (the_geom_webmercator);
|
||||
|
||||
GRANT ALL ON TABLE test_big_poly TO :TESTUSER;
|
||||
GRANT SELECT ON TABLE test_big_poly TO :PUBLICUSER;
|
||||
|
||||