2015-09-25 22:16:33 +08:00
|
|
|
var testHelper = require('../../support/test_helper');
|
2015-07-08 05:46:58 +08:00
|
|
|
|
|
|
|
var assert = require('../../support/assert');
|
|
|
|
var _ = require('underscore');
|
|
|
|
var step = require('step');
|
|
|
|
var cartodbServer = require('../../../lib/cartodb/server');
|
|
|
|
var ServerOptions = require('./support/ported_server_options');
|
|
|
|
|
2015-09-17 00:09:39 +08:00
|
|
|
var BaseController = require('../../../lib/cartodb/controllers/base');
|
2015-09-25 22:16:33 +08:00
|
|
|
var LayergroupToken = require('../../../lib/cartodb/models/layergroup_token');
|
2015-09-17 00:09:39 +08:00
|
|
|
|
2015-07-08 05:46:58 +08:00
|
|
|
describe('torque', function() {
|
|
|
|
|
|
|
|
var server = cartodbServer(ServerOptions);
|
|
|
|
server.setMaxListeners(0);
|
|
|
|
|
2015-09-17 00:09:39 +08:00
|
|
|
var req2paramsFn;
|
|
|
|
before(function() {
|
|
|
|
req2paramsFn = BaseController.prototype.req2params;
|
|
|
|
BaseController.prototype.req2params = ServerOptions.req2params;
|
|
|
|
});
|
|
|
|
|
|
|
|
after(function() {
|
|
|
|
BaseController.prototype.req2params = req2paramsFn;
|
|
|
|
});
|
|
|
|
|
2015-09-25 22:16:33 +08:00
|
|
|
var keysToDelete;
|
|
|
|
beforeEach(function() {
|
2015-09-26 01:16:57 +08:00
|
|
|
keysToDelete = {};
|
2015-09-25 22:16:33 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(function(done) {
|
|
|
|
testHelper.deleteRedisKeys(keysToDelete, done);
|
|
|
|
});
|
|
|
|
|
2015-07-08 05:46:58 +08:00
|
|
|
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) {
|
2015-09-25 22:16:33 +08:00
|
|
|
keysToDelete['map_cfg|' + LayergroupToken.parse(expected_token).token] = 0;
|
2015-09-26 01:16:57 +08:00
|
|
|
keysToDelete['user:localhost:mapviews:global'] = 5;
|
2015-09-25 22:16:33 +08:00
|
|
|
done(err);
|
2015-07-08 05:46:58 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
});
|