Merge branch 'release/staging'
This commit is contained in:
commit
e95bba851f
15
NEWS.md
15
NEWS.md
@ -1,3 +1,18 @@
|
|||||||
|
1.3.5 (DD/MM/YY)
|
||||||
|
-----
|
||||||
|
|
||||||
|
1.3.4 (21/01/13)
|
||||||
|
-----
|
||||||
|
* Improve mixed-geometry export error message (#78)
|
||||||
|
* Remove NULL the_geom features from topojson output (#80)
|
||||||
|
* Fix crash when issuing SQL "COPY" command
|
||||||
|
* Return an error when "the_geom" is in skipfield for SVG output (#73)
|
||||||
|
|
||||||
|
1.3.3 (11/01/13)
|
||||||
|
-----
|
||||||
|
* Fix Date format in CSV output (#77)
|
||||||
|
* Add TopoJSON output format (#79)
|
||||||
|
|
||||||
1.3.2 (30/11/12)
|
1.3.2 (30/11/12)
|
||||||
-----
|
-----
|
||||||
* Fix KML export truncation (#70)
|
* Fix KML export truncation (#70)
|
||||||
|
@ -28,6 +28,7 @@ var express = require('express')
|
|||||||
, crypto = require('crypto')
|
, crypto = require('crypto')
|
||||||
, fs = require('fs')
|
, fs = require('fs')
|
||||||
, zlib = require('zlib')
|
, zlib = require('zlib')
|
||||||
|
, strftime = require('strftime')
|
||||||
, util = require('util')
|
, util = require('util')
|
||||||
, spawn = require('child_process').spawn
|
, spawn = require('child_process').spawn
|
||||||
, Meta = require(global.settings.app_root + '/app/models/metadata')
|
, Meta = require(global.settings.app_root + '/app/models/metadata')
|
||||||
@ -78,7 +79,7 @@ function sanitize_filename(filename) {
|
|||||||
// request handlers
|
// request handlers
|
||||||
function handleQuery(req, res) {
|
function handleQuery(req, res) {
|
||||||
|
|
||||||
var supportedFormats = ['json', 'geojson', 'csv', 'svg', 'shp', 'kml'];
|
var supportedFormats = ['json', 'geojson', 'topojson', 'csv', 'svg', 'shp', 'kml'];
|
||||||
var svg_width = 1024.0;
|
var svg_width = 1024.0;
|
||||||
var svg_height = 768.0;
|
var svg_height = 768.0;
|
||||||
|
|
||||||
@ -186,8 +187,13 @@ function handleQuery(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: refactor formats to external object
|
// TODO: refactor formats to external object
|
||||||
if (format === 'geojson'){
|
if (format === 'geojson' || format === 'topojson' ){
|
||||||
sql = ['SELECT *, ST_AsGeoJSON(the_geom,',dp,') as the_geom FROM (', sql, ') as foo'].join("");
|
sql = 'SELECT *, ST_AsGeoJSON(' + gn + ',' + dp
|
||||||
|
+ ') as the_geom FROM (' + sql + ') as foo';
|
||||||
|
if ( format === 'topojson' ) {
|
||||||
|
// see https://github.com/Vizzuality/CartoDB-SQL-API/issues/80
|
||||||
|
sql += ' where ' + gn + ' is not null';
|
||||||
|
}
|
||||||
} else if (format === 'shp') {
|
} else if (format === 'shp') {
|
||||||
return null;
|
return null;
|
||||||
} else if (format === 'svg') {
|
} else if (format === 'svg') {
|
||||||
@ -254,7 +260,9 @@ function handleQuery(req, res) {
|
|||||||
|
|
||||||
// TODO: refactor formats to external object
|
// TODO: refactor formats to external object
|
||||||
if (format === 'geojson'){
|
if (format === 'geojson'){
|
||||||
toGeoJSON(result, res, this);
|
toGeoJSON(result, gn, this);
|
||||||
|
} else if (format === 'topojson'){
|
||||||
|
toTopoJSON(result, gn, skipfields, this);
|
||||||
} else if (format === 'svg'){
|
} else if (format === 'svg'){
|
||||||
toSVG(result.rows, gn, this);
|
toSVG(result.rows, gn, this);
|
||||||
} else if (format === 'csv'){
|
} else if (format === 'csv'){
|
||||||
@ -298,7 +306,7 @@ function handleCacheStatus(req, res){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// helper functions
|
// helper functions
|
||||||
function toGeoJSON(data, res, callback){
|
function toGeoJSON(data, gn, callback){
|
||||||
try{
|
try{
|
||||||
var out = {
|
var out = {
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
@ -311,9 +319,9 @@ function toGeoJSON(data, res, callback){
|
|||||||
properties: { },
|
properties: { },
|
||||||
geometry: { }
|
geometry: { }
|
||||||
};
|
};
|
||||||
geojson.geometry = JSON.parse(ele["the_geom"]);
|
geojson.geometry = JSON.parse(ele[gn]);
|
||||||
delete ele["the_geom"];
|
delete ele[gn];
|
||||||
delete ele["the_geom_webmercator"];
|
delete ele["the_geom_webmercator"]; // TODO: use skipfields
|
||||||
geojson.properties = ele;
|
geojson.properties = ele;
|
||||||
out.features.push(geojson);
|
out.features.push(geojson);
|
||||||
});
|
});
|
||||||
@ -325,6 +333,31 @@ function toGeoJSON(data, res, callback){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toTopoJSON(data, gn, skipfields, callback){
|
||||||
|
toGeoJSON(data, gn, function(err, geojson) {
|
||||||
|
if ( err ) {
|
||||||
|
callback(err, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var TopoJSON = require('topojson');
|
||||||
|
var topology = TopoJSON.topology(geojson.features, {
|
||||||
|
/* TODO: expose option to API for requesting an identifier
|
||||||
|
"id": function(o) {
|
||||||
|
console.log("id called with obj: "); console.dir(o);
|
||||||
|
return o;
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
"quantization": 1e4, // TODO: expose option to API (use existing "dp" for this ?)
|
||||||
|
"force-clockwise": true,
|
||||||
|
"property-filter": function(d) {
|
||||||
|
// TODO: delegate skipfields handling to toGeoJSON
|
||||||
|
return skipfields.indexOf(d) != -1 ? null : d;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
callback(err, topology);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function toSVG(rows, gn, callback){
|
function toSVG(rows, gn, callback){
|
||||||
|
|
||||||
var radius = 5; // in pixels (based on svg_width and svg_height)
|
var radius = 5; // in pixels (based on svg_width and svg_height)
|
||||||
@ -340,6 +373,9 @@ function toSVG(rows, gn, callback){
|
|||||||
var lines = [];
|
var lines = [];
|
||||||
var points = [];
|
var points = [];
|
||||||
_.each(rows, function(ele){
|
_.each(rows, function(ele){
|
||||||
|
if ( ! ele.hasOwnProperty(gn) ) {
|
||||||
|
throw new Error('column "' + gn + '" does not exist');
|
||||||
|
}
|
||||||
var g = ele[gn];
|
var g = ele[gn];
|
||||||
if ( ! g ) return; // null or empty
|
if ( ! g ) return; // null or empty
|
||||||
var gdims = ele[gn + '_dimension'];
|
var gdims = ele[gn + '_dimension'];
|
||||||
@ -425,7 +461,21 @@ function toCSV(data, res, callback){
|
|||||||
// stream the csv out over http
|
// stream the csv out over http
|
||||||
csv()
|
csv()
|
||||||
.from(data.rows)
|
.from(data.rows)
|
||||||
.toStream(res, {end: true, columns: columns, header: true});
|
.toStream(res, {end: true, columns: columns, header: true})
|
||||||
|
.transform( function(data) {
|
||||||
|
for (var i in data) {
|
||||||
|
// convert dates to string
|
||||||
|
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/77
|
||||||
|
// TODO: take a format string as a parameter
|
||||||
|
if ( data[i] instanceof Date ) {
|
||||||
|
// ISO time
|
||||||
|
data[i] = strftime('%F %H:%M:%S', data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
callback(err,null);
|
callback(err,null);
|
||||||
@ -509,7 +559,10 @@ console.log(['ogr2ogr',
|
|||||||
|
|
||||||
child.on('exit', function(code) {
|
child.on('exit', function(code) {
|
||||||
if ( code ) {
|
if ( code ) {
|
||||||
next(new Error("ogr2ogr returned an error (error code " + code + ")\n" + stderr));
|
var emsg = stderr.split('\n')[0];
|
||||||
|
// TODO: add more info about this error ?
|
||||||
|
//if ( RegExp(/attempt to write non-.*geometry.*to.*type shapefile/i).exec(emsg) )
|
||||||
|
next(new Error(emsg));
|
||||||
} else {
|
} else {
|
||||||
next(null);
|
next(null);
|
||||||
}
|
}
|
||||||
@ -719,6 +772,9 @@ function getContentDisposition(format, filename, inline) {
|
|||||||
if (format === 'geojson'){
|
if (format === 'geojson'){
|
||||||
ext = 'geojson';
|
ext = 'geojson';
|
||||||
}
|
}
|
||||||
|
else if (format === 'topojson'){
|
||||||
|
ext = 'topojson';
|
||||||
|
}
|
||||||
else if (format === 'csv'){
|
else if (format === 'csv'){
|
||||||
ext = 'csv';
|
ext = 'csv';
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ var cluster = new Cluster({
|
|||||||
host: global.settings.node_host,
|
host: global.settings.node_host,
|
||||||
monHost: global.settings.node_host,
|
monHost: global.settings.node_host,
|
||||||
monPort: global.settings.node_port+1,
|
monPort: global.settings.node_port+1,
|
||||||
timeout: 600000
|
timeout: global.settings.node_socket_timeout
|
||||||
});
|
});
|
||||||
|
|
||||||
cluster.listen(function(cb) {
|
cluster.listen(function(cb) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
module.exports.node_port = 8080;
|
module.exports.node_port = 8080;
|
||||||
module.exports.node_host = '127.0.0.1';
|
module.exports.node_host = '127.0.0.1';
|
||||||
|
// idle socket timeout, in miliseconds
|
||||||
|
module.exports.node_socket_timeout = 600000;
|
||||||
module.exports.environment = 'development';
|
module.exports.environment = 'development';
|
||||||
module.exports.db_base_name = 'cartodb_dev_user_<%= user_id %>_db';
|
module.exports.db_base_name = 'cartodb_dev_user_<%= user_id %>_db';
|
||||||
module.exports.db_user = 'development_cartodb_user_<%= user_id %>';
|
module.exports.db_user = 'development_cartodb_user_<%= user_id %>';
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
module.exports.node_port = 8080;
|
module.exports.node_port = 8080;
|
||||||
module.exports.node_host = '127.0.0.1';
|
module.exports.node_host = '127.0.0.1';
|
||||||
|
// idle socket timeout, in miliseconds
|
||||||
|
module.exports.node_socket_timeout = 600000;
|
||||||
module.exports.environment = 'production';
|
module.exports.environment = 'production';
|
||||||
module.exports.db_base_name = 'cartodb_user_<%= user_id %>_db';
|
module.exports.db_base_name = 'cartodb_user_<%= user_id %>_db';
|
||||||
module.exports.db_user = 'cartodb_user_<%= user_id %>';
|
module.exports.db_user = 'cartodb_user_<%= user_id %>';
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
module.exports.node_port = 8080;
|
module.exports.node_port = 8080;
|
||||||
module.exports.node_host = '127.0.0.1';
|
module.exports.node_host = '127.0.0.1';
|
||||||
|
// idle socket timeout, in miliseconds
|
||||||
|
module.exports.node_socket_timeout = 600000;
|
||||||
module.exports.environment = 'staging';
|
module.exports.environment = 'staging';
|
||||||
module.exports.db_base_name = 'cartodb_staging_user_<%= user_id %>_db';
|
module.exports.db_base_name = 'cartodb_staging_user_<%= user_id %>_db';
|
||||||
module.exports.db_user = 'cartodb_staging_user_<%= user_id %>';
|
module.exports.db_user = 'cartodb_staging_user_<%= user_id %>';
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
module.exports.node_port = 8080;
|
module.exports.node_port = 8080;
|
||||||
module.exports.node_host = '127.0.0.1';
|
module.exports.node_host = '127.0.0.1';
|
||||||
|
// idle socket timeout, in miliseconds
|
||||||
|
module.exports.node_socket_timeout = 600000;
|
||||||
module.exports.environment = 'test';
|
module.exports.environment = 'test';
|
||||||
module.exports.db_base_name = 'cartodb_test_user_<%= user_id %>_db';
|
module.exports.db_base_name = 'cartodb_test_user_<%= user_id %>_db';
|
||||||
module.exports.db_user = 'test_cartodb_user_<%= user_id %>';
|
module.exports.db_user = 'test_cartodb_user_<%= user_id %>';
|
||||||
|
@ -12,7 +12,7 @@ Supported query string parameters:
|
|||||||
|
|
||||||
'format': Specifies which format to use for the response.
|
'format': Specifies which format to use for the response.
|
||||||
Supported formats: JSON (the default), GeoJSON,
|
Supported formats: JSON (the default), GeoJSON,
|
||||||
CSV, SVG, SHP
|
TopoJSON, CSV, SVG, SHP
|
||||||
|
|
||||||
'filename': Sets the filename to use for the query result
|
'filename': Sets the filename to use for the query result
|
||||||
file attachment
|
file attachment
|
||||||
@ -22,7 +22,7 @@ Supported query string parameters:
|
|||||||
in output. Only useful with "SELECT *" queries.
|
in output. Only useful with "SELECT *" queries.
|
||||||
|
|
||||||
'dp': Number of digits after the decimal point.
|
'dp': Number of digits after the decimal point.
|
||||||
Only affects format=GeoJSON.
|
Only affects format GeoJSON, TopoJSON, SVG.
|
||||||
By default this is 6.
|
By default this is 6.
|
||||||
|
|
||||||
'api_key': Needed to authenticate in order to modify the database.
|
'api_key': Needed to authenticate in order to modify the database.
|
||||||
@ -88,7 +88,7 @@ The GeoJSON response is follows:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
TODO: csv, kml responses
|
TODO: csv, kml, svg, topojson responses
|
||||||
|
|
||||||
Response errors
|
Response errors
|
||||||
---------------
|
---------------
|
||||||
|
22
npm-shrinkwrap.json
generated
22
npm-shrinkwrap.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cartodb_api",
|
"name": "cartodb_api",
|
||||||
"version": "1.3.0",
|
"version": "1.3.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cluster2": {
|
"cluster2": {
|
||||||
"version": "0.3.5-cdb02",
|
"version": "0.3.5-cdb02",
|
||||||
@ -185,10 +185,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pg": {
|
"pg": {
|
||||||
"version": "0.6.14",
|
"version": "0.11.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"generic-pool": {
|
"generic-pool": {
|
||||||
"version": "1.0.9"
|
"version": "2.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -204,6 +204,19 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"version": "0.0.5"
|
"version": "0.0.5"
|
||||||
},
|
},
|
||||||
|
"topojson": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"dependencies": {
|
||||||
|
"optimist": {
|
||||||
|
"version": "0.3.5",
|
||||||
|
"dependencies": {
|
||||||
|
"wordwrap": {
|
||||||
|
"version": "0.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"oauth-client": {
|
"oauth-client": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -215,6 +228,9 @@
|
|||||||
"node-uuid": {
|
"node-uuid": {
|
||||||
"version": "1.3.3"
|
"version": "1.3.3"
|
||||||
},
|
},
|
||||||
|
"strftime": {
|
||||||
|
"version": "0.4.7"
|
||||||
|
},
|
||||||
"csv": {
|
"csv": {
|
||||||
"version": "0.0.13"
|
"version": "0.0.13"
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"name": "cartodb_api",
|
"name": "cartodb_api",
|
||||||
"description": "high speed SQL api for cartodb",
|
"description": "high speed SQL api for cartodb",
|
||||||
"version": "1.3.2",
|
"version": "1.3.5",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Simon Tokumine, Sandro Santilli, Vizzuality",
|
"name": "Simon Tokumine, Sandro Santilli, Vizzuality",
|
||||||
"url": "http://vizzuality.com",
|
"url": "http://vizzuality.com",
|
||||||
@ -13,13 +13,15 @@
|
|||||||
"express": "~2.5.11",
|
"express": "~2.5.11",
|
||||||
"underscore" : "1.1.x",
|
"underscore" : "1.1.x",
|
||||||
"underscore.string": "1.1.5",
|
"underscore.string": "1.1.5",
|
||||||
"pg": "0.6.14",
|
"pg": "~0.11.2",
|
||||||
"generic-pool": "1.0.x",
|
"generic-pool": "1.0.x",
|
||||||
"redis": "0.7.1",
|
"redis": "0.7.1",
|
||||||
"hiredis": "*",
|
"hiredis": "*",
|
||||||
"step": "0.0.x",
|
"step": "0.0.x",
|
||||||
|
"topojson": "~0.0.8",
|
||||||
"oauth-client": "0.2.0",
|
"oauth-client": "0.2.0",
|
||||||
"node-uuid":"1.3.3",
|
"node-uuid":"1.3.3",
|
||||||
|
"strftime":"~0.4.7",
|
||||||
"csv":"0.0.13"
|
"csv":"0.0.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -28,7 +30,7 @@
|
|||||||
"libxmljs": "~0.6.1"
|
"libxmljs": "~0.6.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "test/run_tests.sh"
|
"test": "test/run_tests.sh ${RUNTESTFLAGS} test/unit/redis_pool.test.js test/unit/metadata.test.js test/unit/oauth.test.js test/unit/psql.test.js test/acceptance/app.test.js test/acceptance/app.auth.test.js test/acceptance/export/*.js"
|
||||||
},
|
},
|
||||||
"engines": { "node": ">= 0.4.1 < 0.9" }
|
"engines": { "node": ">= 0.4.1 < 0.9" }
|
||||||
}
|
}
|
||||||
|
@ -330,24 +330,39 @@ test('CREATE TABLE with GET and auth', function(done){
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: test COPY
|
// Test effects of COPY
|
||||||
//test('COPY TABLE with GET and auth', function(done){
|
// See https://github.com/Vizzuality/cartodb-management/issues/1502
|
||||||
// assert.response(app, {
|
test('COPY TABLE with GET and auth', function(done){
|
||||||
// url: "/api/v1/sql?" + querystring.stringify({
|
assert.response(app, {
|
||||||
// q: 'COPY TABLE test_table FROM stdin; 1\n\\.\n',
|
url: "/api/v1/sql?" + querystring.stringify({
|
||||||
// api_key: 1234
|
q: 'COPY test_table FROM stdin;',
|
||||||
// }),
|
api_key: 1234
|
||||||
// headers: {host: 'vizzuality.cartodb.com'},
|
}),
|
||||||
// method: 'GET'
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
// },{}, function(res) {
|
method: 'GET'
|
||||||
// assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
},{}, function(res) {
|
||||||
// // Check cache headers
|
// We expect a problem, actually
|
||||||
// // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/43
|
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||||
// assert.equal(res.headers['x-cache-channel'], 'NONE');
|
assert.deepEqual(JSON.parse(res.body), {"error":["COPY from stdin failed: No source stream defined"]});
|
||||||
// assert.equal(res.headers['cache-control'], expected_cache_control);
|
done();
|
||||||
// done();
|
});
|
||||||
// });
|
});
|
||||||
//});
|
|
||||||
|
test('COPY TABLE with GET and auth', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: "/api/v1/sql?" + querystring.stringify({
|
||||||
|
q: "COPY test_table to '/tmp/x';",
|
||||||
|
api_key: 1234
|
||||||
|
}),
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{}, function(res) {
|
||||||
|
// We expect a problem, actually
|
||||||
|
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||||
|
assert.deepEqual(JSON.parse(res.body), {"error":["must be superuser to COPY to or from a file"]});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('ALTER TABLE with GET and auth', function(done){
|
test('ALTER TABLE with GET and auth', function(done){
|
||||||
assert.response(app, {
|
assert.response(app, {
|
||||||
@ -707,542 +722,29 @@ test('GET /api/v1/sql as geojson with default dp as 6', function(done){
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('null geometries in geojson output', function(done){
|
||||||
// CSV tests
|
|
||||||
|
|
||||||
test('CSV format', function(done){
|
|
||||||
assert.response(app, {
|
assert.response(app, {
|
||||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=csv',
|
url: '/api/v1/sql?' + querystring.stringify({
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
q: "SELECT 1 as gid, 'U' as name, null::geometry as the_geom ",
|
||||||
method: 'GET'
|
format: 'geojson'
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'CSV is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=cartodb-query.csv/gi.test(cd));
|
|
||||||
var ct = res.header('Content-Type');
|
|
||||||
assert.equal(true, /header=present/.test(ct), "CSV doesn't advertise header presence: " + ct);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('CSV format, bigger than 81920 bytes', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql',
|
|
||||||
data: querystring.stringify({
|
|
||||||
q: 'SELECT 0 as fname FROM generate_series(0,81920)',
|
|
||||||
format: 'csv'
|
|
||||||
}),
|
}),
|
||||||
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
method: 'POST'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.ok(res.body.length > 81920, 'CSV smaller than expected: ' + res.body.length);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test('CSV format from POST', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql',
|
|
||||||
data: querystring.stringify({q: "SELECT * FROM untitle_table_4 LIMIT 1", format: 'csv'}),
|
|
||||||
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
method: 'POST'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'CSV is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=cartodb-query.csv/gi.test(cd));
|
|
||||||
var ct = res.header('Content-Type');
|
|
||||||
assert.equal(true, /header=present/.test(ct), "CSV doesn't advertise header presence: " + ct);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('CSV format, custom filename', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=csv&filename=mycsv.csv',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
},{ }, function(res){
|
},{ }, function(res){
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
var cd = res.header('Content-Disposition');
|
var cd = res.header('Content-Disposition');
|
||||||
assert.equal(true, /^attachment/.test(cd), 'CSV is not disposed as attachment: ' + cd);
|
assert.equal(true, /^attachment/.test(cd), 'GEOJSON is not disposed as attachment: ' + cd);
|
||||||
assert.equal(true, /filename=mycsv.csv/gi.test(cd), cd);
|
assert.equal(true, /filename=cartodb-query.geojson/gi.test(cd));
|
||||||
var ct = res.header('Content-Type');
|
var gjson = JSON.parse(res.body);
|
||||||
assert.equal(true, /header=present/.test(ct), "CSV doesn't advertise header presence: " + ct);
|
var expected = {
|
||||||
var row0 = res.body.substring(0, res.body.search(/[\n\r]/)).split(',');
|
type: 'FeatureCollection',
|
||||||
var checkfields = {'name':1, 'cartodb_id':1, 'the_geom':1, 'the_geom_webmercator':1};
|
features: [ { type: 'Feature',
|
||||||
for ( var f in checkfields ) {
|
properties: { gid: 1, name: 'U' },
|
||||||
var idx = row0.indexOf(f);
|
geometry: null } ]
|
||||||
if ( checkfields[f] ) {
|
};
|
||||||
assert.ok(idx != -1, "result does not include '" + f + "'");
|
assert.deepEqual(gjson, expected);
|
||||||
} else {
|
|
||||||
assert.ok(idx == -1, "result includes '" + f + "' ("+idx+")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
done();
|
done();
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('skipfields controls fields included in CSV output', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=csv&skipfields=unexistant,cartodb_id',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var row0 = res.body.substring(0, res.body.search(/[\n\r]/)).split(',');
|
|
||||||
var checkfields = {'name':1, 'cartodb_id':0, 'the_geom':1, 'the_geom_webmercator':1};
|
|
||||||
for ( var f in checkfields ) {
|
|
||||||
var idx = row0.indexOf(f);
|
|
||||||
if ( checkfields[f] ) {
|
|
||||||
assert.ok(idx != -1, "result does not include '" + f + "'");
|
|
||||||
} else {
|
|
||||||
assert.ok(idx == -1, "result includes '" + f + "' ("+idx+")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /api/v1/sql as csv', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20cartodb_id,ST_AsEWKT(the_geom)%20as%20geom%20FROM%20untitle_table_4%20LIMIT%201&format=csv',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var body = "cartodb_id,geom\r\n1,SRID=4326;POINT(-3.699732 40.423012)";
|
|
||||||
assert.equal(body, res.body);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/60
|
|
||||||
test('GET /api/v1/sql as csv with no rows', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20true%20WHERE%20false&format=csv',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var body = "";
|
|
||||||
assert.equal(body, res.body);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /api/v1/sql as csv, properly escaped', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20cartodb_id,%20address%20FROM%20untitle_table_4%20LIMIT%201&format=csv',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var body = 'cartodb_id,address\r\n1,"Calle de Pérez Galdós 9, Madrid, Spain"';
|
|
||||||
assert.equal(body, res.body);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// SVG tests
|
|
||||||
|
|
||||||
test('GET /api/v1/sql with SVG format', function(done){
|
|
||||||
var query = querystring.stringify({
|
|
||||||
q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ",
|
|
||||||
format: "svg"
|
|
||||||
});
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?' + query,
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
|
||||||
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
|
||||||
assert.ok( res.body.indexOf('<path d="M 0 768 L 1024 0" />') > 0, res.body );
|
|
||||||
// TODO: test viewBox
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /api/v1/sql with SVG format', function(done){
|
|
||||||
var query = querystring.stringify({
|
|
||||||
q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ",
|
|
||||||
format: "svg"
|
|
||||||
});
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql',
|
|
||||||
data: query,
|
|
||||||
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
method: 'POST'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'SVG is not disposed as attachment: ' + cd);
|
|
||||||
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
|
||||||
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
|
||||||
assert.ok( res.body.indexOf('<path d="M 0 768 L 1024 0" />') > 0, res.body );
|
|
||||||
// TODO: test viewBox
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /api/v1/sql with SVG format and custom filename', function(done){
|
|
||||||
var query = querystring.stringify({
|
|
||||||
q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ",
|
|
||||||
format: "svg",
|
|
||||||
filename: 'mysvg'
|
|
||||||
});
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?' + query,
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.ok(/filename=mysvg.svg/gi.test(cd), cd);
|
|
||||||
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
|
||||||
assert.ok( res.body.indexOf('<path d="M 0 768 L 1024 0" />') > 0, res.body );
|
|
||||||
// TODO: test viewBox
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /api/v1/sql with SVG format and centered point', function(done){
|
|
||||||
var query = querystring.stringify({
|
|
||||||
q: "SELECT 1 as cartodb_id, ST_MakePoint(5000, -54) AS the_geom ",
|
|
||||||
format: "svg"
|
|
||||||
});
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?' + query,
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
|
||||||
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
|
||||||
assert.ok( res.body.indexOf('cx="0" cy="0"') > 0, res.body );
|
|
||||||
// TODO: test viewBox
|
|
||||||
// TODO: test radius
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /api/v1/sql with SVG format and trimmed decimals', function(done){
|
|
||||||
var queryobj = {
|
|
||||||
q: "SELECT 1 as cartodb_id, 'LINESTRING(0 0, 1024 768, 500.123456 600.98765432)'::geometry AS the_geom ",
|
|
||||||
format: "svg",
|
|
||||||
dp: 2
|
|
||||||
};
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?' + querystring.stringify(queryobj),
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
|
||||||
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
|
||||||
assert.ok( res.body.indexOf('<path d="M 0 768 L 1024 0 500.12 167.01" />') > 0, res.body );
|
|
||||||
// TODO: test viewBox
|
|
||||||
|
|
||||||
queryobj.dp = 3;
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?' + querystring.stringify(queryobj),
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{}, function(res) {
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'SVG is not disposed as attachment: ' + cd);
|
|
||||||
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
|
||||||
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
|
||||||
assert.ok( res.body.indexOf('<path d="M 0 768 L 1024 0 500.123 167.012" />') > 0, res.body );
|
|
||||||
// TODO: test viewBox
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// SHP tests
|
|
||||||
|
|
||||||
test('SHP format, unauthenticated', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
encoding: 'binary',
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd));
|
|
||||||
var tmpfile = '/tmp/myshape.zip';
|
|
||||||
var err = fs.writeFileSync(tmpfile, res.body, 'binary');
|
|
||||||
if (err) { done(err); return }
|
|
||||||
var zf = new zipfile.ZipFile(tmpfile);
|
|
||||||
assert.ok(_.contains(zf.names, 'cartodb-query.shp'), 'SHP zipfile does not contain .shp: ' + zf.names);
|
|
||||||
assert.ok(_.contains(zf.names, 'cartodb-query.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
|
|
||||||
assert.ok(_.contains(zf.names, 'cartodb-query.dbf'), 'SHP zipfile does not contain .dbf: ' + zf.names);
|
|
||||||
// missing SRID, so no PRJ (TODO: add ?)
|
|
||||||
//assert.ok(_.contains(zf.names, 'cartodb-query.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
|
||||||
// TODO: check DBF contents
|
|
||||||
fs.unlinkSync(tmpfile);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('SHP format, unauthenticated, POST', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql',
|
|
||||||
data: 'q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
method: 'POST'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd), 'Unexpected SHP filename: ' + cd);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('SHP format, big size, POST', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql',
|
|
||||||
data: querystring.stringify({
|
|
||||||
q: 'SELECT 0 as fname FROM generate_series(0,81920)',
|
|
||||||
format: 'shp'
|
|
||||||
}),
|
|
||||||
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
method: 'POST'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd), 'Unexpected SHP filename: ' + cd);
|
|
||||||
assert.ok(res.body.length > 81920, 'SHP smaller than expected: ' + res.body.length);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('SHP format, unauthenticated, with custom filename', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&filename=myshape',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
encoding: 'binary',
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=myshape.zip/gi.test(cd));
|
|
||||||
var tmpfile = '/tmp/myshape.zip';
|
|
||||||
var err = fs.writeFileSync(tmpfile, res.body, 'binary');
|
|
||||||
if (err) { done(err); return }
|
|
||||||
var zf = new zipfile.ZipFile(tmpfile);
|
|
||||||
assert.ok(_.contains(zf.names, 'myshape.shp'), 'SHP zipfile does not contain .shp: ' + zf.names);
|
|
||||||
assert.ok(_.contains(zf.names, 'myshape.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
|
|
||||||
assert.ok(_.contains(zf.names, 'myshape.dbf'), 'SHP zipfile does not contain .dbf: ' + zf.names);
|
|
||||||
// missing SRID, so no PRJ (TODO: add ?)
|
|
||||||
//assert.ok(_.contains(zf.names, 'myshape.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
|
||||||
fs.unlinkSync(tmpfile);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('SHP format, unauthenticated, with custom, dangerous filename', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&filename=b;"%20()[]a',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
encoding: 'binary',
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var fname = "b_______a";
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=b_______a.zip/gi.test(cd), 'Unexpected SHP filename: ' + cd);
|
|
||||||
var tmpfile = '/tmp/myshape.zip';
|
|
||||||
var err = fs.writeFileSync(tmpfile, res.body, 'binary');
|
|
||||||
if (err) { done(err); return }
|
|
||||||
var zf = new zipfile.ZipFile(tmpfile);
|
|
||||||
assert.ok(_.contains(zf.names, fname + '.shp'), 'SHP zipfile does not contain .shp: ' + zf.names);
|
|
||||||
assert.ok(_.contains(zf.names, fname + '.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
|
|
||||||
assert.ok(_.contains(zf.names, fname + '.dbf'), 'SHP zipfile does not contain .dbf: ' + zf.names);
|
|
||||||
// missing SRID, so no PRJ (TODO: add ?)
|
|
||||||
//assert.ok(_.contains(zf.names, fname+ '.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
|
||||||
fs.unlinkSync(tmpfile);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('SHP format, authenticated', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&api_key=1234',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
encoding: 'binary',
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd));
|
|
||||||
var tmpfile = '/tmp/myshape.zip';
|
|
||||||
var err = fs.writeFileSync(tmpfile, res.body, 'binary');
|
|
||||||
if (err) { done(err); return }
|
|
||||||
var zf = new zipfile.ZipFile(tmpfile);
|
|
||||||
assert.ok(_.contains(zf.names, 'cartodb-query.shp'), 'SHP zipfile does not contain .shp: ' + zf.names);
|
|
||||||
assert.ok(_.contains(zf.names, 'cartodb-query.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
|
|
||||||
assert.ok(_.contains(zf.names, 'cartodb-query.dbf'), 'SHP zipfile does not contain .dbf: ' + zf.names);
|
|
||||||
// missing SRID, so no PRJ (TODO: add ?)
|
|
||||||
//assert.ok(_.contains(zf.names, 'cartodb-query.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
|
||||||
// TODO: check contents of the DBF
|
|
||||||
fs.unlinkSync(tmpfile);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/66
|
|
||||||
test('SHP format, unauthenticated, with utf8 data', function(done){
|
|
||||||
var query = querystring.stringify({
|
|
||||||
q: "SELECT '♥♦♣♠' as f, st_makepoint(0,0,4326) as the_geom",
|
|
||||||
format: 'shp',
|
|
||||||
filename: 'myshape'
|
|
||||||
});
|
});
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?' + query,
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
encoding: 'binary',
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var tmpfile = '/tmp/myshape.zip';
|
|
||||||
var err = fs.writeFileSync(tmpfile, res.body, 'binary');
|
|
||||||
if (err) { done(err); return }
|
|
||||||
var zf = new zipfile.ZipFile(tmpfile);
|
|
||||||
var buffer = zf.readFileSync('myshape.dbf');
|
|
||||||
fs.unlinkSync(tmpfile);
|
|
||||||
var strings = buffer.toString();
|
|
||||||
assert.ok(/♥♦♣♠/.exec(strings), "Cannot find '♥♦♣♠' in here:\n" + strings);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// KML tests
|
|
||||||
|
|
||||||
test('KML format, unauthenticated', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'KML is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=cartodb-query.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
|
||||||
var row0 = res.body;
|
|
||||||
var checkfields = {'Name':1, 'address':1, 'cartodb_id':1, 'the_geom':0, 'the_geom_webmercator':0};
|
|
||||||
for ( var f in checkfields ) {
|
|
||||||
if ( checkfields[f] ) {
|
|
||||||
assert.ok(row0.indexOf('SimpleData name="'+ f + '"') != -1, "result does not include '" + f + "'");
|
|
||||||
} else {
|
|
||||||
assert.ok(row0.indexOf('SimpleData name="'+ f + '"') == -1, "result includes '" + f + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KML format, unauthenticated, POST', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql',
|
|
||||||
data: 'q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
method: 'POST'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'KML is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=cartodb-query.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KML format, bigger than 81920 bytes', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql',
|
|
||||||
data: querystring.stringify({
|
|
||||||
q: 'SELECT 0 as fname FROM generate_series(0,81920)',
|
|
||||||
format: 'kml'
|
|
||||||
}),
|
|
||||||
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
method: 'POST'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'KML is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=cartodb-query.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
|
||||||
assert.ok(res.body.length > 81920, 'KML smaller than expected: ' + res.body.length);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KML format, skipfields', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&skipfields=address,cartodb_id',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'KML is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=cartodb-query.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
|
||||||
var row0 = res.body;
|
|
||||||
var checkfields = {'Name':1, 'address':0, 'cartodb_id':0, 'the_geom':0, 'the_geom_webmercator':0};
|
|
||||||
for ( var f in checkfields ) {
|
|
||||||
if ( checkfields[f] ) {
|
|
||||||
assert.ok(row0.indexOf('SimpleData name="'+ f + '"') != -1, "result does not include '" + f + "'");
|
|
||||||
} else {
|
|
||||||
assert.ok(row0.indexOf('SimpleData name="'+ f + '"') == -1, "result includes '" + f + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KML format, unauthenticated, custom filename', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&filename=kmltest',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /^attachment/.test(cd), 'KML is not disposed as attachment: ' + cd);
|
|
||||||
assert.equal(true, /filename=kmltest.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
|
||||||
// TODO: check for actual content, at least try to uncompress..
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('KML format, authenticated', function(done){
|
|
||||||
assert.response(app, {
|
|
||||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&api_key=1234',
|
|
||||||
headers: {host: 'vizzuality.cartodb.com'},
|
|
||||||
method: 'GET'
|
|
||||||
},{ }, function(res){
|
|
||||||
assert.equal(res.statusCode, 200, res.body);
|
|
||||||
var cd = res.header('Content-Disposition');
|
|
||||||
assert.equal(true, /filename=cartodb-query.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
|
||||||
// TODO: check for actual content, at least try to uncompress..
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
166
test/acceptance/export/csv.js
Normal file
166
test/acceptance/export/csv.js
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
require('../../helper');
|
||||||
|
require('../../support/assert');
|
||||||
|
|
||||||
|
|
||||||
|
var app = require(global.settings.app_root + '/app/controllers/app')
|
||||||
|
, assert = require('assert')
|
||||||
|
, querystring = require('querystring')
|
||||||
|
, _ = require('underscore')
|
||||||
|
, zipfile = require('zipfile')
|
||||||
|
, fs = require('fs')
|
||||||
|
, libxmljs = require('libxmljs')
|
||||||
|
;
|
||||||
|
|
||||||
|
// allow lots of emitters to be set to silence warning
|
||||||
|
app.setMaxListeners(0);
|
||||||
|
|
||||||
|
suite('export.csv', function() {
|
||||||
|
|
||||||
|
test('CSV format', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + querystring.stringify({
|
||||||
|
q: 'SELECT * FROM untitle_table_4 WHERE cartodb_id = 1',
|
||||||
|
format: 'csv'
|
||||||
|
}),
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'CSV is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=cartodb-query.csv/gi.test(cd));
|
||||||
|
var ct = res.header('Content-Type');
|
||||||
|
assert.equal(true, /header=present/.test(ct), "CSV doesn't advertise header presence: " + ct);
|
||||||
|
|
||||||
|
var rows = res.body.split(/\r\n/);
|
||||||
|
var row0 = rows[0].split(',');
|
||||||
|
var row1 = rows[1].split(',');
|
||||||
|
|
||||||
|
assert.equal(row0[1], 'created_at');
|
||||||
|
assert.equal(row1[1], '2011-09-21 14:02:21');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CSV format, bigger than 81920 bytes', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql',
|
||||||
|
data: querystring.stringify({
|
||||||
|
q: 'SELECT 0 as fname FROM generate_series(0,81920)',
|
||||||
|
format: 'csv'
|
||||||
|
}),
|
||||||
|
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
method: 'POST'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.ok(res.body.length > 81920, 'CSV smaller than expected: ' + res.body.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('CSV format from POST', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql',
|
||||||
|
data: querystring.stringify({q: "SELECT * FROM untitle_table_4 LIMIT 1", format: 'csv'}),
|
||||||
|
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
method: 'POST'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'CSV is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=cartodb-query.csv/gi.test(cd));
|
||||||
|
var ct = res.header('Content-Type');
|
||||||
|
assert.equal(true, /header=present/.test(ct), "CSV doesn't advertise header presence: " + ct);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CSV format, custom filename', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=csv&filename=mycsv.csv',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'CSV is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=mycsv.csv/gi.test(cd), cd);
|
||||||
|
var ct = res.header('Content-Type');
|
||||||
|
assert.equal(true, /header=present/.test(ct), "CSV doesn't advertise header presence: " + ct);
|
||||||
|
var row0 = res.body.substring(0, res.body.search(/[\n\r]/)).split(',');
|
||||||
|
var checkfields = {'name':1, 'cartodb_id':1, 'the_geom':1, 'the_geom_webmercator':1};
|
||||||
|
for ( var f in checkfields ) {
|
||||||
|
var idx = row0.indexOf(f);
|
||||||
|
if ( checkfields[f] ) {
|
||||||
|
assert.ok(idx != -1, "result does not include '" + f + "'");
|
||||||
|
} else {
|
||||||
|
assert.ok(idx == -1, "result includes '" + f + "' ("+idx+")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('skipfields controls fields included in CSV output', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=csv&skipfields=unexistant,cartodb_id',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var row0 = res.body.substring(0, res.body.search(/[\n\r]/)).split(',');
|
||||||
|
var checkfields = {'name':1, 'cartodb_id':0, 'the_geom':1, 'the_geom_webmercator':1};
|
||||||
|
for ( var f in checkfields ) {
|
||||||
|
var idx = row0.indexOf(f);
|
||||||
|
if ( checkfields[f] ) {
|
||||||
|
assert.ok(idx != -1, "result does not include '" + f + "'");
|
||||||
|
} else {
|
||||||
|
assert.ok(idx == -1, "result includes '" + f + "' ("+idx+")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /api/v1/sql as csv', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20cartodb_id,ST_AsEWKT(the_geom)%20as%20geom%20FROM%20untitle_table_4%20LIMIT%201&format=csv',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var body = "cartodb_id,geom\r\n1,SRID=4326;POINT(-3.699732 40.423012)";
|
||||||
|
assert.equal(body, res.body);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/60
|
||||||
|
test('GET /api/v1/sql as csv with no rows', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20true%20WHERE%20false&format=csv',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var body = "";
|
||||||
|
assert.equal(body, res.body);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /api/v1/sql as csv, properly escaped', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20cartodb_id,%20address%20FROM%20untitle_table_4%20LIMIT%201&format=csv',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var body = 'cartodb_id,address\r\n1,"Calle de Pérez Galdós 9, Madrid, Spain"';
|
||||||
|
assert.equal(body, res.body);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
142
test/acceptance/export/kml.js
Normal file
142
test/acceptance/export/kml.js
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
require('../../helper');
|
||||||
|
require('../../support/assert');
|
||||||
|
|
||||||
|
|
||||||
|
var app = require(global.settings.app_root + '/app/controllers/app')
|
||||||
|
, assert = require('assert')
|
||||||
|
, querystring = require('querystring')
|
||||||
|
, _ = require('underscore')
|
||||||
|
, zipfile = require('zipfile')
|
||||||
|
, fs = require('fs')
|
||||||
|
, libxmljs = require('libxmljs')
|
||||||
|
;
|
||||||
|
|
||||||
|
// allow lots of emitters to be set to silence warning
|
||||||
|
app.setMaxListeners(0);
|
||||||
|
|
||||||
|
suite('export.kml', function() {
|
||||||
|
|
||||||
|
var expected_cache_control = 'no-cache,max-age=3600,must-revalidate,public';
|
||||||
|
var expected_cache_control_persist = 'public,max-age=31536000';
|
||||||
|
|
||||||
|
// use dec_sep for internationalization
|
||||||
|
var checkDecimals = function(x, dec_sep){
|
||||||
|
var tmp='' + x;
|
||||||
|
if (tmp.indexOf(dec_sep)>-1)
|
||||||
|
return tmp.length-tmp.indexOf(dec_sep)-1;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// KML tests
|
||||||
|
|
||||||
|
test('KML format, unauthenticated', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'KML is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=cartodb-query.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
||||||
|
var row0 = res.body;
|
||||||
|
var checkfields = {'Name':1, 'address':1, 'cartodb_id':1, 'the_geom':0, 'the_geom_webmercator':0};
|
||||||
|
for ( var f in checkfields ) {
|
||||||
|
if ( checkfields[f] ) {
|
||||||
|
assert.ok(row0.indexOf('SimpleData name="'+ f + '"') != -1, "result does not include '" + f + "'");
|
||||||
|
} else {
|
||||||
|
assert.ok(row0.indexOf('SimpleData name="'+ f + '"') == -1, "result includes '" + f + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('KML format, unauthenticated, POST', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql',
|
||||||
|
data: 'q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
method: 'POST'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'KML is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=cartodb-query.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('KML format, bigger than 81920 bytes', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql',
|
||||||
|
data: querystring.stringify({
|
||||||
|
q: 'SELECT 0 as fname FROM generate_series(0,81920)',
|
||||||
|
format: 'kml'
|
||||||
|
}),
|
||||||
|
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
method: 'POST'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'KML is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=cartodb-query.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
||||||
|
assert.ok(res.body.length > 81920, 'KML smaller than expected: ' + res.body.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('KML format, skipfields', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&skipfields=address,cartodb_id',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'KML is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=cartodb-query.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
||||||
|
var row0 = res.body;
|
||||||
|
var checkfields = {'Name':1, 'address':0, 'cartodb_id':0, 'the_geom':0, 'the_geom_webmercator':0};
|
||||||
|
for ( var f in checkfields ) {
|
||||||
|
if ( checkfields[f] ) {
|
||||||
|
assert.ok(row0.indexOf('SimpleData name="'+ f + '"') != -1, "result does not include '" + f + "'");
|
||||||
|
} else {
|
||||||
|
assert.ok(row0.indexOf('SimpleData name="'+ f + '"') == -1, "result includes '" + f + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('KML format, unauthenticated, custom filename', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&filename=kmltest',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'KML is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=kmltest.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
||||||
|
// TODO: check for actual content, at least try to uncompress..
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('KML format, authenticated', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&api_key=1234',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /filename=cartodb-query.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
||||||
|
// TODO: check for actual content, at least try to uncompress..
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
206
test/acceptance/export/shapefile.js
Normal file
206
test/acceptance/export/shapefile.js
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
require('../../helper');
|
||||||
|
require('../../support/assert');
|
||||||
|
|
||||||
|
|
||||||
|
var app = require(global.settings.app_root + '/app/controllers/app')
|
||||||
|
, assert = require('assert')
|
||||||
|
, querystring = require('querystring')
|
||||||
|
, _ = require('underscore')
|
||||||
|
, zipfile = require('zipfile')
|
||||||
|
, fs = require('fs')
|
||||||
|
, libxmljs = require('libxmljs')
|
||||||
|
;
|
||||||
|
|
||||||
|
// allow lots of emitters to be set to silence warning
|
||||||
|
app.setMaxListeners(0);
|
||||||
|
|
||||||
|
suite('export.shapefile', function() {
|
||||||
|
|
||||||
|
// SHP tests
|
||||||
|
|
||||||
|
test('SHP format, unauthenticated', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
encoding: 'binary',
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd));
|
||||||
|
var tmpfile = '/tmp/myshape.zip';
|
||||||
|
var err = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||||
|
if (err) { done(err); return }
|
||||||
|
var zf = new zipfile.ZipFile(tmpfile);
|
||||||
|
assert.ok(_.contains(zf.names, 'cartodb-query.shp'), 'SHP zipfile does not contain .shp: ' + zf.names);
|
||||||
|
assert.ok(_.contains(zf.names, 'cartodb-query.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
|
||||||
|
assert.ok(_.contains(zf.names, 'cartodb-query.dbf'), 'SHP zipfile does not contain .dbf: ' + zf.names);
|
||||||
|
// missing SRID, so no PRJ (TODO: add ?)
|
||||||
|
//assert.ok(_.contains(zf.names, 'cartodb-query.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
||||||
|
// TODO: check DBF contents
|
||||||
|
fs.unlinkSync(tmpfile);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHP format, unauthenticated, POST', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql',
|
||||||
|
data: 'q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
method: 'POST'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd), 'Unexpected SHP filename: ' + cd);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHP format, big size, POST', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql',
|
||||||
|
data: querystring.stringify({
|
||||||
|
q: 'SELECT 0 as fname FROM generate_series(0,81920)',
|
||||||
|
format: 'shp'
|
||||||
|
}),
|
||||||
|
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
method: 'POST'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd), 'Unexpected SHP filename: ' + cd);
|
||||||
|
assert.ok(res.body.length > 81920, 'SHP smaller than expected: ' + res.body.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHP format, unauthenticated, with custom filename', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&filename=myshape',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
encoding: 'binary',
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=myshape.zip/gi.test(cd));
|
||||||
|
var tmpfile = '/tmp/myshape.zip';
|
||||||
|
var err = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||||
|
if (err) { done(err); return }
|
||||||
|
var zf = new zipfile.ZipFile(tmpfile);
|
||||||
|
assert.ok(_.contains(zf.names, 'myshape.shp'), 'SHP zipfile does not contain .shp: ' + zf.names);
|
||||||
|
assert.ok(_.contains(zf.names, 'myshape.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
|
||||||
|
assert.ok(_.contains(zf.names, 'myshape.dbf'), 'SHP zipfile does not contain .dbf: ' + zf.names);
|
||||||
|
// missing SRID, so no PRJ (TODO: add ?)
|
||||||
|
//assert.ok(_.contains(zf.names, 'myshape.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
||||||
|
fs.unlinkSync(tmpfile);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHP format, unauthenticated, with custom, dangerous filename', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&filename=b;"%20()[]a',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
encoding: 'binary',
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var fname = "b_______a";
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=b_______a.zip/gi.test(cd), 'Unexpected SHP filename: ' + cd);
|
||||||
|
var tmpfile = '/tmp/myshape.zip';
|
||||||
|
var err = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||||
|
if (err) { done(err); return }
|
||||||
|
var zf = new zipfile.ZipFile(tmpfile);
|
||||||
|
assert.ok(_.contains(zf.names, fname + '.shp'), 'SHP zipfile does not contain .shp: ' + zf.names);
|
||||||
|
assert.ok(_.contains(zf.names, fname + '.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
|
||||||
|
assert.ok(_.contains(zf.names, fname + '.dbf'), 'SHP zipfile does not contain .dbf: ' + zf.names);
|
||||||
|
// missing SRID, so no PRJ (TODO: add ?)
|
||||||
|
//assert.ok(_.contains(zf.names, fname+ '.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
||||||
|
fs.unlinkSync(tmpfile);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHP format, authenticated', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&api_key=1234',
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
encoding: 'binary',
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd));
|
||||||
|
var tmpfile = '/tmp/myshape.zip';
|
||||||
|
var err = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||||
|
if (err) { done(err); return }
|
||||||
|
var zf = new zipfile.ZipFile(tmpfile);
|
||||||
|
assert.ok(_.contains(zf.names, 'cartodb-query.shp'), 'SHP zipfile does not contain .shp: ' + zf.names);
|
||||||
|
assert.ok(_.contains(zf.names, 'cartodb-query.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
|
||||||
|
assert.ok(_.contains(zf.names, 'cartodb-query.dbf'), 'SHP zipfile does not contain .dbf: ' + zf.names);
|
||||||
|
// missing SRID, so no PRJ (TODO: add ?)
|
||||||
|
//assert.ok(_.contains(zf.names, 'cartodb-query.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
||||||
|
// TODO: check contents of the DBF
|
||||||
|
fs.unlinkSync(tmpfile);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/66
|
||||||
|
test('SHP format, unauthenticated, with utf8 data', function(done){
|
||||||
|
var query = querystring.stringify({
|
||||||
|
q: "SELECT '♥♦♣♠' as f, st_makepoint(0,0,4326) as the_geom",
|
||||||
|
format: 'shp',
|
||||||
|
filename: 'myshape'
|
||||||
|
});
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + query,
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
encoding: 'binary',
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var tmpfile = '/tmp/myshape.zip';
|
||||||
|
var err = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||||
|
if (err) { done(err); return }
|
||||||
|
var zf = new zipfile.ZipFile(tmpfile);
|
||||||
|
var buffer = zf.readFileSync('myshape.dbf');
|
||||||
|
fs.unlinkSync(tmpfile);
|
||||||
|
var strings = buffer.toString();
|
||||||
|
assert.ok(/♥♦♣♠/.exec(strings), "Cannot find '♥♦♣♠' in here:\n" + strings);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/66
|
||||||
|
test('mixed type geometry', function(done){
|
||||||
|
var query = querystring.stringify({
|
||||||
|
q: "SELECT 'POINT(0 0)'::geometry as g UNION ALL "
|
||||||
|
+ "SELECT 'LINESTRING(0 0, 1 0)'::geometry",
|
||||||
|
format: 'shp'
|
||||||
|
});
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + query,
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
encoding: 'binary',
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 400, res.statusCode + ': ' +res.body);
|
||||||
|
var parsedBody = JSON.parse(res.body);
|
||||||
|
var expectedBody = {"error":["ERROR 1: Attempt to write non-point (LINESTRING) geometry to point shapefile."]}
|
||||||
|
assert.deepEqual(parsedBody, expectedBody);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
181
test/acceptance/export/svg.js
Normal file
181
test/acceptance/export/svg.js
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
require('../../helper');
|
||||||
|
require('../../support/assert');
|
||||||
|
|
||||||
|
|
||||||
|
var app = require(global.settings.app_root + '/app/controllers/app')
|
||||||
|
, assert = require('assert')
|
||||||
|
, querystring = require('querystring')
|
||||||
|
, _ = require('underscore')
|
||||||
|
, zipfile = require('zipfile')
|
||||||
|
, fs = require('fs')
|
||||||
|
, libxmljs = require('libxmljs')
|
||||||
|
;
|
||||||
|
|
||||||
|
// allow lots of emitters to be set to silence warning
|
||||||
|
app.setMaxListeners(0);
|
||||||
|
|
||||||
|
suite('export.svg', function() {
|
||||||
|
|
||||||
|
test('GET /api/v1/sql with SVG format', function(done){
|
||||||
|
var query = querystring.stringify({
|
||||||
|
q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ",
|
||||||
|
format: "svg"
|
||||||
|
});
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + query,
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
||||||
|
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
||||||
|
assert.ok( res.body.indexOf('<path d="M 0 768 L 1024 0" />') > 0, res.body );
|
||||||
|
// TODO: test viewBox
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('POST /api/v1/sql with SVG format', function(done){
|
||||||
|
var query = querystring.stringify({
|
||||||
|
q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ",
|
||||||
|
format: "svg"
|
||||||
|
});
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql',
|
||||||
|
data: query,
|
||||||
|
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
method: 'POST'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'SVG is not disposed as attachment: ' + cd);
|
||||||
|
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
||||||
|
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
||||||
|
assert.ok( res.body.indexOf('<path d="M 0 768 L 1024 0" />') > 0, res.body );
|
||||||
|
// TODO: test viewBox
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /api/v1/sql with SVG format and custom filename', function(done){
|
||||||
|
var query = querystring.stringify({
|
||||||
|
q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ",
|
||||||
|
format: "svg",
|
||||||
|
filename: 'mysvg'
|
||||||
|
});
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + query,
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.ok(/filename=mysvg.svg/gi.test(cd), cd);
|
||||||
|
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
||||||
|
assert.ok( res.body.indexOf('<path d="M 0 768 L 1024 0" />') > 0, res.body );
|
||||||
|
// TODO: test viewBox
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /api/v1/sql with SVG format and centered point', function(done){
|
||||||
|
var query = querystring.stringify({
|
||||||
|
q: "SELECT 1 as cartodb_id, ST_MakePoint(5000, -54) AS the_geom ",
|
||||||
|
format: "svg"
|
||||||
|
});
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + query,
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
||||||
|
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
||||||
|
assert.ok( res.body.indexOf('cx="0" cy="0"') > 0, res.body );
|
||||||
|
// TODO: test viewBox
|
||||||
|
// TODO: test radius
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET /api/v1/sql with SVG format and trimmed decimals', function(done){
|
||||||
|
var queryobj = {
|
||||||
|
q: "SELECT 1 as cartodb_id, 'LINESTRING(0 0, 1024 768, 500.123456 600.98765432)'::geometry AS the_geom ",
|
||||||
|
format: "svg",
|
||||||
|
dp: 2
|
||||||
|
};
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + querystring.stringify(queryobj),
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
||||||
|
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
||||||
|
assert.ok( res.body.indexOf('<path d="M 0 768 L 1024 0 500.12 167.01" />') > 0, res.body );
|
||||||
|
// TODO: test viewBox
|
||||||
|
|
||||||
|
queryobj.dp = 3;
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + querystring.stringify(queryobj),
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{}, function(res) {
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'SVG is not disposed as attachment: ' + cd);
|
||||||
|
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
||||||
|
assert.equal(res.header('Content-Type'), 'image/svg+xml; charset=utf-8');
|
||||||
|
assert.ok( res.body.indexOf('<path d="M 0 768 L 1024 0 500.123 167.012" />') > 0, res.body );
|
||||||
|
// TODO: test viewBox
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test adding "the_geom" to skipfields
|
||||||
|
// See http://github.com/Vizzuality/CartoDB-SQL-API/issues/73
|
||||||
|
test('SVG format with "the_geom" in skipfields', function(done){
|
||||||
|
var query = querystring.stringify({
|
||||||
|
q: "SELECT 1 as cartodb_id, ST_MakePoint(5000, -54) AS the_geom ",
|
||||||
|
format: "svg",
|
||||||
|
skipfields: "the_geom"
|
||||||
|
});
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + query,
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||||
|
|
||||||
|
assert.deepEqual(JSON.parse(res.body), {
|
||||||
|
error:['column "the_geom" does not exist']
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SVG format with missing "the_geom" field', function(done){
|
||||||
|
var query = querystring.stringify({
|
||||||
|
q: "SELECT 1 as cartodb_id, ST_MakePoint(5000, -54) AS something_else ",
|
||||||
|
format: "svg"
|
||||||
|
});
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + query,
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||||
|
assert.deepEqual(JSON.parse(res.body), {
|
||||||
|
error:['column "the_geom" does not exist']
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
189
test/acceptance/export/topojson.js
Normal file
189
test/acceptance/export/topojson.js
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
require('../../helper');
|
||||||
|
require('../../support/assert');
|
||||||
|
|
||||||
|
|
||||||
|
var app = require(global.settings.app_root + '/app/controllers/app')
|
||||||
|
, assert = require('assert')
|
||||||
|
, querystring = require('querystring')
|
||||||
|
, _ = require('underscore')
|
||||||
|
, zipfile = require('zipfile')
|
||||||
|
, fs = require('fs')
|
||||||
|
, libxmljs = require('libxmljs')
|
||||||
|
;
|
||||||
|
|
||||||
|
// allow lots of emitters to be set to silence warning
|
||||||
|
app.setMaxListeners(0);
|
||||||
|
|
||||||
|
|
||||||
|
suite('export.topojson', function() {
|
||||||
|
|
||||||
|
// TOPOJSON tests
|
||||||
|
|
||||||
|
test('GET two polygons sharing an edge as topojson', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + querystring.stringify({
|
||||||
|
q: "SELECT 1 as gid, 'U' as name, 'POLYGON((-5 0,5 0,0 5,-5 0))'::geometry as the_geom " +
|
||||||
|
" UNION ALL " +
|
||||||
|
"SELECT 2, 'D', 'POLYGON((0 -5,0 5,-5 0,0 -5))'::geometry as the_geom ",
|
||||||
|
format: 'topojson'
|
||||||
|
}),
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'TOPOJSON is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=cartodb-query.topojson/gi.test(cd));
|
||||||
|
var topojson = JSON.parse(res.body);
|
||||||
|
assert.equal(topojson.type, 'Topology');
|
||||||
|
|
||||||
|
// Check transform
|
||||||
|
assert.ok(topojson.hasOwnProperty('transform'));
|
||||||
|
var trans = topojson.transform;
|
||||||
|
assert.equal(_.keys(trans).length, 2); // only scale and translate
|
||||||
|
assert.equal(trans.scale.length, 2); // scalex, scaley
|
||||||
|
assert.equal(Math.round(trans.scale[0]*1e6), 1000);
|
||||||
|
assert.equal(Math.round(trans.scale[1]*1e6), 1000);
|
||||||
|
assert.equal(trans.translate.length, 2); // translatex, translatey
|
||||||
|
assert.equal(trans.translate[0], -5);
|
||||||
|
assert.equal(trans.translate[1], -5);
|
||||||
|
|
||||||
|
// Check objects
|
||||||
|
assert.ok(topojson.hasOwnProperty('objects'));
|
||||||
|
assert.equal(_.keys(topojson.objects).length, 2);
|
||||||
|
|
||||||
|
var obj = topojson.objects[0];
|
||||||
|
//console.dir(obj);
|
||||||
|
// Expected:
|
||||||
|
// { type: 'Polygon',
|
||||||
|
// arcs: [ [ 0, 1 ] ],
|
||||||
|
// properties: { gid: 1, nam: 'U' } }
|
||||||
|
assert.equal(_.keys(obj).length, 3); // type, arcs, properties
|
||||||
|
assert.equal(obj.type, 'Polygon');
|
||||||
|
assert.equal(obj.arcs.length, 1); /* only shell, no holes */
|
||||||
|
var shell = obj.arcs[0];
|
||||||
|
assert.equal(shell.length, 2); /* one shared arc, one non-shared */
|
||||||
|
assert.equal(shell[0], 0); /* shared arc */
|
||||||
|
assert.equal(shell[1], 1); /* non-shared arc */
|
||||||
|
var props = obj.properties;
|
||||||
|
assert.equal(_.keys(props).length, 2); // gid, name
|
||||||
|
assert.equal(props['gid'], 1);
|
||||||
|
assert.equal(props['name'], 'U');
|
||||||
|
|
||||||
|
obj = topojson.objects[1];
|
||||||
|
//console.dir(obj);
|
||||||
|
// Expected:
|
||||||
|
// { type: 'Polygon',
|
||||||
|
// arcs: [ [ 0, 2 ] ],
|
||||||
|
// properties: { gid: 2, nam: 'D' } }
|
||||||
|
assert.equal(_.keys(obj).length, 3); // type, arcs, properties
|
||||||
|
assert.equal(obj.type, 'Polygon');
|
||||||
|
assert.equal(obj.arcs.length, 1); /* only shell, no holes */
|
||||||
|
shell = obj.arcs[0];
|
||||||
|
assert.equal(shell.length, 2); /* one shared arc, one non-shared */
|
||||||
|
assert.equal(shell[0], 0); /* shared arc */
|
||||||
|
assert.equal(shell[1], 2); /* non-shared arc */
|
||||||
|
props = obj.properties;
|
||||||
|
assert.equal(_.keys(props).length, 2); // gid, name
|
||||||
|
assert.equal(props['gid'], 2);
|
||||||
|
assert.equal(props['name'], 'D');
|
||||||
|
|
||||||
|
// Check arcs
|
||||||
|
assert.ok(topojson.hasOwnProperty('arcs'));
|
||||||
|
assert.equal(topojson.arcs.length, 3); // one shared, two non-shared
|
||||||
|
var arc = topojson.arcs[0]; // shared arc
|
||||||
|
assert.equal(arc.length, 2); // shared arc has two vertices
|
||||||
|
var p = arc[0];
|
||||||
|
assert.equal(Math.round(p[0]*trans.scale[0]), 0);
|
||||||
|
assert.equal(Math.round(p[1]*trans.scale[1]), 5);
|
||||||
|
p = arc[1];
|
||||||
|
assert.equal(Math.round(p[0]*trans.scale[0]), 5);
|
||||||
|
assert.equal(Math.round(p[1]*trans.scale[1]), 5);
|
||||||
|
arc = topojson.arcs[1]; // non shared arc
|
||||||
|
assert.equal(arc.length, 3); // non shared arcs have three vertices
|
||||||
|
p = arc[0];
|
||||||
|
assert.equal(Math.round(p[0]*trans.scale[0]), 5);
|
||||||
|
assert.equal(Math.round(p[1]*trans.scale[1]), 10);
|
||||||
|
p = arc[1];
|
||||||
|
assert.equal(Math.round(p[0]*trans.scale[0]), 5);
|
||||||
|
assert.equal(Math.round(p[1]*trans.scale[1]), -5);
|
||||||
|
p = arc[2];
|
||||||
|
assert.equal(Math.round(p[0]*trans.scale[0]), -10);
|
||||||
|
assert.equal(Math.round(p[1]*trans.scale[1]), 0);
|
||||||
|
arc = topojson.arcs[2]; // non shared arc
|
||||||
|
assert.equal(arc.length, 3); // non shared arcs have three vertices
|
||||||
|
p = arc[0];
|
||||||
|
assert.equal(Math.round(p[0]*trans.scale[0]), 5);
|
||||||
|
assert.equal(Math.round(p[1]*trans.scale[1]), 10);
|
||||||
|
p = arc[1];
|
||||||
|
assert.equal(Math.round(p[0]*trans.scale[0]), 0);
|
||||||
|
assert.equal(Math.round(p[1]*trans.scale[1]), -10);
|
||||||
|
p = arc[2];
|
||||||
|
assert.equal(Math.round(p[0]*trans.scale[0]), -5);
|
||||||
|
assert.equal(Math.round(p[1]*trans.scale[1]), 5);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('null geometries', function(done){
|
||||||
|
assert.response(app, {
|
||||||
|
url: '/api/v1/sql?' + querystring.stringify({
|
||||||
|
q: "SELECT 1 as gid, 'U' as name, 'POLYGON((-5 0,5 0,0 5,-5 0))'::geometry as the_geom " +
|
||||||
|
" UNION ALL " +
|
||||||
|
"SELECT 2, 'D', null::geometry as the_geom ",
|
||||||
|
format: 'topojson'
|
||||||
|
}),
|
||||||
|
headers: {host: 'vizzuality.cartodb.com'},
|
||||||
|
method: 'GET'
|
||||||
|
},{ }, function(res){
|
||||||
|
assert.equal(res.statusCode, 200, res.body);
|
||||||
|
var cd = res.header('Content-Disposition');
|
||||||
|
assert.equal(true, /^attachment/.test(cd), 'TOPOJSON is not disposed as attachment: ' + cd);
|
||||||
|
assert.equal(true, /filename=cartodb-query.topojson/gi.test(cd));
|
||||||
|
var topojson = JSON.parse(res.body);
|
||||||
|
assert.equal(topojson.type, 'Topology');
|
||||||
|
|
||||||
|
// Check transform
|
||||||
|
assert.ok(topojson.hasOwnProperty('transform'));
|
||||||
|
var trans = topojson.transform;
|
||||||
|
assert.equal(_.keys(trans).length, 2); // only scale and translate
|
||||||
|
assert.equal(trans.scale.length, 2); // scalex, scaley
|
||||||
|
assert.equal(Math.round(trans.scale[0]*1e6), 1000);
|
||||||
|
assert.equal(Math.round(trans.scale[1]*1e6), 500);
|
||||||
|
assert.equal(trans.translate.length, 2); // translatex, translatey
|
||||||
|
assert.equal(trans.translate[0], -5);
|
||||||
|
assert.equal(trans.translate[1], 0);
|
||||||
|
|
||||||
|
// Check objects
|
||||||
|
assert.ok(topojson.hasOwnProperty('objects'));
|
||||||
|
assert.equal(_.keys(topojson.objects).length, 1);
|
||||||
|
|
||||||
|
var obj = topojson.objects[0];
|
||||||
|
//console.dir(obj);
|
||||||
|
// Expected:
|
||||||
|
// { type: 'Polygon',
|
||||||
|
// arcs: [ [ 0, 1 ] ],
|
||||||
|
// properties: { gid: 1, nam: 'U' } }
|
||||||
|
assert.equal(_.keys(obj).length, 3); // type, arcs, properties
|
||||||
|
assert.equal(obj.type, 'Polygon');
|
||||||
|
assert.equal(obj.arcs.length, 1); /* only shell, no holes */
|
||||||
|
var shell = obj.arcs[0];
|
||||||
|
assert.equal(shell.length, 1); /* one non shared arc */
|
||||||
|
assert.equal(shell[0], 0); /* non-shared arc */
|
||||||
|
var props = obj.properties;
|
||||||
|
assert.equal(_.keys(props).length, 2); // gid, name
|
||||||
|
assert.equal(props['gid'], 1);
|
||||||
|
assert.equal(props['name'], 'U');
|
||||||
|
|
||||||
|
// Check arcs
|
||||||
|
assert.ok(topojson.hasOwnProperty('arcs'));
|
||||||
|
assert.equal(topojson.arcs.length, 1);
|
||||||
|
var arc = topojson.arcs[0];
|
||||||
|
assert.deepEqual(arc, [ [ 0, 0 ], [ 4999, 9999 ], [ 5000, -9999 ], [ -9999, 0 ] ]);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -4,9 +4,25 @@
|
|||||||
# TODO: read from there
|
# TODO: read from there
|
||||||
REDIS_PORT=6333
|
REDIS_PORT=6333
|
||||||
|
|
||||||
|
OPT_CREATE=yes # create the test environment
|
||||||
|
OPT_DROP=yes # drop the test environment
|
||||||
|
|
||||||
|
cd $(dirname $0)
|
||||||
|
BASEDIR=$(pwd)
|
||||||
|
cd -
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
echo "Cleaning up"
|
if test x"$OPT_DROP" = xyes; then
|
||||||
kill ${PID_REDIS}
|
if test x"$PID_REDIS" = x; then
|
||||||
|
PID_REDIS=$(cat ${BASEDIR}/redis.pid)
|
||||||
|
if test x"$PID_REDIS" = x; then
|
||||||
|
echo "Could not find a test redis pid to kill it"
|
||||||
|
return;
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "Cleaning up"
|
||||||
|
kill ${PID_REDIS}
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup_and_exit() {
|
cleanup_and_exit() {
|
||||||
@ -23,23 +39,43 @@ die() {
|
|||||||
|
|
||||||
trap 'cleanup_and_exit' 1 2 3 5 9 13
|
trap 'cleanup_and_exit' 1 2 3 5 9 13
|
||||||
|
|
||||||
echo "Starting redis on port ${REDIS_PORT}"
|
while [ -n "$1" ]; do
|
||||||
echo "port ${REDIS_PORT}" | redis-server - > test/test.log &
|
if test "$1" = "--nodrop"; then
|
||||||
PID_REDIS=$!
|
OPT_DROP=no
|
||||||
|
shift
|
||||||
|
continue
|
||||||
|
elif test "$1" = "--nocreate"; then
|
||||||
|
OPT_CREATE=no
|
||||||
|
shift
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
echo "Preparing the environment"
|
if [ -z "$1" ]; then
|
||||||
cd test; sh prepare_db.sh || die "database preparation failure"; cd -;
|
echo "Usage: $0 [<options>] <test> [<test>]" >&2
|
||||||
|
echo "Options:" >&2
|
||||||
|
echo " --nocreate do not create the test environment on start" >&2
|
||||||
|
echo " --nodrop do not drop the test environment on exit" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TESTS=$@
|
||||||
|
|
||||||
|
if test x"$OPT_CREATE" = xyes; then
|
||||||
|
echo "Starting redis on port ${REDIS_PORT}"
|
||||||
|
echo "port ${REDIS_PORT}" | redis-server - > ${BASEDIR}/test.log &
|
||||||
|
PID_REDIS=$!
|
||||||
|
echo ${PID_REDIS} > ${BASEDIR}/redis.pid
|
||||||
|
|
||||||
|
echo "Preparing the environment"
|
||||||
|
cd ${BASEDIR}; sh prepare_db.sh || die "database preparation failure"; cd -
|
||||||
|
fi
|
||||||
|
|
||||||
PATH=node_modules/.bin/:$PATH
|
PATH=node_modules/.bin/:$PATH
|
||||||
|
|
||||||
echo "Running tests"
|
echo "Running tests"
|
||||||
mocha -u tdd \
|
mocha -u tdd ${TESTS}
|
||||||
test/unit/redis_pool.test.js \
|
|
||||||
test/unit/metadata.test.js \
|
|
||||||
test/unit/oauth.test.js \
|
|
||||||
test/unit/psql.test.js \
|
|
||||||
test/acceptance/app.test.js \
|
|
||||||
test/acceptance/app.auth.test.js
|
|
||||||
|
|
||||||
|
|
||||||
cleanup
|
cleanup
|
||||||
|
Loading…
Reference in New Issue
Block a user