Ports acceptance tests from windshaft

This commit is contained in:
Raul Ochoa
2015-07-07 23:46:58 +02:00
parent b63e697934
commit d52b65470e
72 changed files with 13778 additions and 1 deletions
+6 -1
View File
@@ -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
+29
View File
@@ -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;
+311
View File
@@ -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);
}
);
});
});
+92
View File
@@ -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();
});
});
});
});
});
+158
View File
@@ -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}}}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

+89
View File
@@ -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);
});
});
});
});
});
});
File diff suppressed because it is too large Load Diff
@@ -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);
});
+159
View File
@@ -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();
}
);
});
});
+159
View File
@@ -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();
});
});
});
+126
View File
@@ -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();
});
});
});
+197
View File
@@ -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();
});
});
});
});
+295
View File
@@ -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();
});
});
});
+146
View File
@@ -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);
}
);
}
+434
View File
@@ -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;
}
);
});
});
+446
View File
@@ -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();
});
});
});
+142
View File
@@ -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();
});
});
});
});
+118
View File
@@ -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();
});
});
});
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

+5
View File
@@ -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

+5
View File
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

+1
View File
@@ -0,0 +1 @@
{"grid":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," !!!! "," !!!! "," ### !!!!! "," #### !!!! "," ##### !! "," ##### "," ### "," $$$$ "," $$$$$ "," $$$$$ "," $$$$ %%%% "," %%%%% "," %%%%% "," %%%% "," %% "," "," "," "," "," "," "," "," "," "," "," &&&& "," &&&& "," &&&&& "," &&&& "," && "],"keys":["","5","2","3","1","4"],"data":{}}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

+2
View File
@@ -0,0 +1,2 @@
{"grid":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "],"keys":[""],"data":{}}
@@ -0,0 +1 @@
{"grid":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," !!! "," !!!! "," !!!!! "," !!!!! "," !!! "," "," "," "," #### "," ##### "," ##### "," #### "," ## "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "],"keys":["","2","1"],"data":{}}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+13
View File
@@ -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"
File diff suppressed because it is too large Load Diff
+21
View File
@@ -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;