first commit
This commit is contained in:
45
test/acceptance/export/arraybuffer.js
Normal file
45
test/acceptance/export/arraybuffer.js
Normal file
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
require('../../helper');
|
||||
require('../../support/assert');
|
||||
|
||||
|
||||
var server = require('../../../app/server')();
|
||||
var assert = require('assert');
|
||||
var querystring = require('querystring');
|
||||
|
||||
describe('export.arraybuffer', function() {
|
||||
|
||||
it('GET /api/v1/sql as arraybuffer ', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: 'SELECT cartodb_id,name,1::integer,187.9 FROM untitle_table_4',
|
||||
format: 'arraybuffer'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers['content-type'], "application/octet-stream");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /api/v1/sql as arraybuffer does not support geometry types ', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: 'SELECT cartodb_id, the_geom FROM untitle_table_4',
|
||||
format: 'arraybuffer'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
var result = JSON.parse(res.body);
|
||||
assert.equal(result.error[0], "geometry types are not supported");
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
209
test/acceptance/export/csv.js
Normal file
209
test/acceptance/export/csv.js
Normal file
@@ -0,0 +1,209 @@
|
||||
'use strict';
|
||||
|
||||
require('../../helper');
|
||||
require('../../support/assert');
|
||||
|
||||
|
||||
var server = require('../../../app/server')();
|
||||
var assert = require('assert');
|
||||
var querystring = require('querystring');
|
||||
|
||||
describe('export.csv', function() {
|
||||
|
||||
it('CSV format', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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.headers['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[2], 'created_at');
|
||||
assert.equal(row1[2], '2011-09-21 14:02:21.314252');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('CSV format, bigger than 81920 bytes', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.ok(res.body.length > 81920, 'CSV smaller than expected: ' + res.body.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('CSV format from POST', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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.headers['content-type'];
|
||||
assert.equal(true, /header=present/.test(ct), "CSV doesn't advertise header presence: " + ct);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('CSV format, custom filename', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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.headers['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: true, cartodb_id: true, the_geom: true, the_geom_webmercator: true };
|
||||
Object.keys(checkFields).forEach(function(f) {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
it('skipfields controls fields included in CSV output', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var row0 = res.body.substring(0, res.body.search(/[\n\r]/)).split(',');
|
||||
var checkFields = { name: true, cartodb_id: false, the_geom: true, the_geom_webmercator: true };
|
||||
Object.keys(checkFields).forEach(function(f) {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /api/v1/sql as csv', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.ok(res.body.match(/cartodb_id,geom\r\n.?1.?,"SRID=4326;POINT(.*)"\r\n/));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/60
|
||||
it('GET /api/v1/sql as csv with no rows', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?q=SELECT%20true%20WHERE%20false&format=csv',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var obtained_lines = res.body.split('\r\n');
|
||||
assert.ok(obtained_lines.length <= 2, // may or may not have an header
|
||||
// See http://trac.osgeo.org/gdal/ticket/5234
|
||||
'Too many lines in output (' + obtained_lines.length + '): ' + obtained_lines.join('\n'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /api/v1/sql as csv, properly escaped', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.ok(res.body.match(/cartodb_id,address\r\n.?1.?,"Calle de Pérez Galdós 9, Madrid, Spain"\r\n/));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /api/v1/sql as csv, concurrently', function(done){
|
||||
|
||||
var concurrency = 4;
|
||||
var waiting = concurrency;
|
||||
function validate(err, res){
|
||||
assert.ok(res.body.match(/cartodb_id,address\r\n.?1.?,"Calle de Pérez Galdós 9, Madrid, Spain"\r\n/));
|
||||
if ( ! --waiting ) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
for (var i=0; i<concurrency; ++i) {
|
||||
assert.response(server,
|
||||
{
|
||||
url: '/api/v1/sql?q=SELECT%20cartodb_id,%20address%20FROM%20untitle_table_4%20LIMIT%201&format=csv',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
validate
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('expects 1200 rows in public table', function(done){
|
||||
var limit = 1200;
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: "SELECT * from populated_places_simple_reduced limit " + limit,
|
||||
format: 'csv'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(err, res) {
|
||||
var headersPlusExtraLine = 2;
|
||||
assert.equal(res.body.split('\n').length, limit + headersPlusExtraLine);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
35
test/acceptance/export/folder.js
Normal file
35
test/acceptance/export/folder.js
Normal file
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
require('../../helper');
|
||||
require('../../support/assert');
|
||||
|
||||
const fs = require('fs');
|
||||
let server = require('../../../app/server');
|
||||
const assert = require('assert');
|
||||
const querystring = require('querystring');
|
||||
|
||||
describe('export folder', function() {
|
||||
it('folder exists', function(done){
|
||||
const currentTmpDir = global.settings.tmpDir;
|
||||
|
||||
const dynamicTmpDir = `/tmp/${new Date().getTime()}/a/b/c`;
|
||||
global.settings.tmpDir = dynamicTmpDir;
|
||||
server = server();
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: 'SELECT 1',
|
||||
}),
|
||||
headers: { host: 'vizzuality.cartodb.com' },
|
||||
method: 'GET'
|
||||
}, {}, function(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.ok(res.statusCode === 200);
|
||||
assert.ok(fs.existsSync(dynamicTmpDir));
|
||||
|
||||
global.settings.tmpDir = currentTmpDir;
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
212
test/acceptance/export/geojson.js
Normal file
212
test/acceptance/export/geojson.js
Normal file
@@ -0,0 +1,212 @@
|
||||
'use strict';
|
||||
|
||||
require('../../helper');
|
||||
|
||||
var server = require('../../../app/server')();
|
||||
var assert = require('../../support/assert');
|
||||
var querystring = require('querystring');
|
||||
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
|
||||
describe('export.geojson', function() {
|
||||
|
||||
// GEOJSON tests
|
||||
|
||||
it('GET /api/v1/sql with SQL parameter, ensuring content-disposition set to geojson', function(done) {
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.equal(true, /^attachment/.test(cd), 'GEOJSON is not disposed as attachment: ' + cd);
|
||||
assert.equal(true, /filename=cartodb-query.geojson/gi.test(cd));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('POST /api/v1/sql with SQL parameter, ensuring content-disposition set to geojson', function(done) {
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql',
|
||||
data: querystring.stringify({q: "SELECT * FROM untitle_table_4", format: 'geojson' }),
|
||||
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
method: 'POST'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.equal(true, /^attachment/.test(cd), 'GEOJSON is not disposed as attachment: ' + cd);
|
||||
assert.equal(true, /filename=cartodb-query.geojson/gi.test(cd));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the last format parameter when multiple are used', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?format=csv&q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.equal(true, /filename=cartodb-query.geojson/gi.test(cd));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses custom filename', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson&filename=x',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.equal(true, /filename=x.geojson/gi.test(cd), cd);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not include the_geom and the_geom_webmercator properties by default', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsed_body = JSON.parse(res.body);
|
||||
var row0 = parsed_body.features[0].properties;
|
||||
var checkfields = {'name':1, 'cartodb_id':1, 'the_geom':0, 'the_geom_webmercator':0};
|
||||
for ( var f in checkfields ) {
|
||||
if ( checkfields[f] ) {
|
||||
assert.ok(row0.hasOwnProperty(f), "result does not include '" + f + "'");
|
||||
} else {
|
||||
assert.ok(!row0.hasOwnProperty(f), "result includes '" + f + "'");
|
||||
}
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('skipfields controls fields included in GeoJSON output', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson&skipfields=unexistant,cartodb_id',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var parsed_body = JSON.parse(res.body);
|
||||
var row0 = parsed_body.features[0].properties;
|
||||
var checkfields = {'name':1, 'cartodb_id':0, 'the_geom':0, 'the_geom_webmercator':0};
|
||||
for ( var f in checkfields ) {
|
||||
if ( checkfields[f] ) {
|
||||
assert.ok(row0.hasOwnProperty(f), "result does not include '" + f + "'");
|
||||
} else {
|
||||
assert.ok(!row0.hasOwnProperty(f), "result includes '" + f + "'");
|
||||
}
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('GET /api/v1/sql as geojson limiting decimal places', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: 'SELECT ST_MakePoint(0.123,2.3456) as the_geom',
|
||||
format: 'geojson',
|
||||
dp: '1'}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var result = JSON.parse(res.body);
|
||||
assert.equal(1, checkDecimals(result.features[0].geometry.coordinates[0], '.'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /api/v1/sql as geojson with default dp as 6', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: 'SELECT ST_MakePoint(0.12345678,2.3456787654) as the_geom',
|
||||
format: 'geojson'}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var result = JSON.parse(res.body);
|
||||
assert.equal(6, checkDecimals(result.features[0].geometry.coordinates[0], '.'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('null geometries in geojson output', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: "SELECT 1 as gid, 'U' as name, null::geometry as the_geom ",
|
||||
format: 'geojson'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.equal(true, /^attachment/.test(cd), 'GEOJSON is not disposed as attachment: ' + cd);
|
||||
assert.equal(true, /filename=cartodb-query.geojson/gi.test(cd));
|
||||
var gjson = JSON.parse(res.body);
|
||||
var expected = {
|
||||
type: 'FeatureCollection',
|
||||
features: [ { type: 'Feature',
|
||||
properties: { gid: 1, name: 'U' },
|
||||
geometry: null } ]
|
||||
};
|
||||
assert.deepEqual(gjson, expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('stream response handle errors', function(done) {
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: "SELECTT 1 as gid, null::geometry as the_geom ",
|
||||
format: 'geojson'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
var geoJson = JSON.parse(res.body);
|
||||
assert.ok(geoJson.error);
|
||||
assert.equal(geoJson.error.length, 1);
|
||||
assert.ok(geoJson.error[0].match(/^syntax error at or near.*/));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('stream response with empty result set has valid output', function(done) {
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: "SELECT 1 as gid, null::geometry as the_geom limit 0",
|
||||
format: 'geojson'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var geoJson = JSON.parse(res.body);
|
||||
var expectedGeoJson = {"type": "FeatureCollection", "features": []};
|
||||
assert.deepEqual(geoJson, expectedGeoJson);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
82
test/acceptance/export/geopackage.js
Normal file
82
test/acceptance/export/geopackage.js
Normal file
@@ -0,0 +1,82 @@
|
||||
'use strict';
|
||||
|
||||
require('../../helper');
|
||||
|
||||
var server = require('../../../app/server')();
|
||||
var assert = require('../../support/assert');
|
||||
var sqlite = require('sqlite3');
|
||||
var fs = require('fs');
|
||||
|
||||
describe('geopackage query', function(){
|
||||
// Default name, cartodb-query, fails because of the hyphen.
|
||||
var table_name = 'a_gpkg_table';
|
||||
var base_url = '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=gpkg&filename=' + table_name;
|
||||
|
||||
it('returns a valid geopackage database', function(done){
|
||||
assert.response(server, {
|
||||
url: base_url,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers["content-type"], "application/x-sqlite3; charset=utf-8");
|
||||
assert.notEqual(res.headers["content-disposition"].indexOf(table_name + ".gpkg"), -1);
|
||||
var db = new sqlite.Database(':memory:', res.body);
|
||||
var qr = db.get("PRAGMA database_list", function(err) {
|
||||
assert.equal(err, null);
|
||||
done();
|
||||
});
|
||||
assert.notEqual(qr, undefined);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('gets database and geopackage schema', function(done){
|
||||
assert.response(server, {
|
||||
url: base_url,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
encoding: 'binary',
|
||||
method: 'GET'
|
||||
},{ }, function(err, res) {
|
||||
var tmpfile = '/tmp/a_geopackage_file.gpkg';
|
||||
try {
|
||||
fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||
} catch(err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var db = new sqlite.Database(tmpfile, function(err) {
|
||||
if(!!err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
db.serialize(function() {
|
||||
var schemaQuery = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
|
||||
var sqr = db.get(schemaQuery, function(err, row) {
|
||||
assert.equal(err, null);
|
||||
assert.equal(row.name, table_name);
|
||||
});
|
||||
assert.notEqual(sqr, undefined);
|
||||
|
||||
var gpkgQuery = "SELECT table_name FROM gpkg_contents";
|
||||
var gqr = db.get(gpkgQuery, function(err, row) {
|
||||
assert.equal(row.table_name, table_name);
|
||||
assert.equal(err, null);
|
||||
});
|
||||
assert.notEqual(gqr, undefined);
|
||||
|
||||
var dataQuery = "SELECT * FROM " + table_name + " order by cartodb_id";
|
||||
var dqr = db.get(dataQuery, function(err, row) {
|
||||
assert.equal(err, null);
|
||||
assert.equal(row.cartodb_id, 1);
|
||||
assert.equal(row.name, 'Hawai');
|
||||
assert.equal(row.fid, undefined);
|
||||
done();
|
||||
});
|
||||
assert.notEqual(dqr, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
403
test/acceptance/export/kml.js
Normal file
403
test/acceptance/export/kml.js
Normal file
@@ -0,0 +1,403 @@
|
||||
'use strict';
|
||||
|
||||
require('../../helper');
|
||||
|
||||
var server = require('../../../app/server')();
|
||||
var assert = require('../../support/assert');
|
||||
var querystring = require('querystring');
|
||||
var libxmljs = require('libxmljs');
|
||||
|
||||
describe('export.kml', function() {
|
||||
|
||||
// Check if an attribute is in the KML output
|
||||
//
|
||||
// NOTE: "name" and "description" attributes are threated specially
|
||||
// in that they are matched in case-insensitive way
|
||||
//
|
||||
var hasAttribute = function(kml, att) {
|
||||
|
||||
// Strip namespace:
|
||||
//https://github.com/polotek/libxmljs/issues/212
|
||||
kml = kml.replace(/ xmlns=[^>]*>/, '>');
|
||||
|
||||
var doc = libxmljs.parseXmlString(kml);
|
||||
//console.log("doc: " + doc);
|
||||
var xpath;
|
||||
|
||||
xpath = "//SimpleField[@name='" + att + "']";
|
||||
if ( doc.get(xpath) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
xpath = "//Placemark/" + att;
|
||||
if ( doc.get(xpath) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var lcatt = att.toLowerCase();
|
||||
if ( lcatt === 'name' || lcatt === 'description' ) {
|
||||
xpath = "//Placemark/" + lcatt;
|
||||
if ( doc.get(xpath) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//if ( lowerkml.indexOf('simplefield name="'+ loweratt + '"') != -1 ) return true;
|
||||
//if ( lowerkml.indexOf('<'+loweratt+'>') != -1 ) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
// Return the first coordinate array found in KML
|
||||
var extractCoordinates = function(kml) {
|
||||
|
||||
// Strip namespace:
|
||||
//https://github.com/polotek/libxmljs/issues/212
|
||||
kml = kml.replace(/ xmlns=[^>]*>/, '>');
|
||||
|
||||
var doc = libxmljs.parseXmlString(kml);
|
||||
//console.log("doc: " + doc);
|
||||
if ( ! doc ) {
|
||||
return;
|
||||
}
|
||||
var coo = doc.get("//coordinates");
|
||||
//console.log("coo: " + coo);
|
||||
if ( ! coo ) {
|
||||
return;
|
||||
}
|
||||
coo = coo.text();
|
||||
//console.log("coo: " + coo);
|
||||
if ( ! coo ) {
|
||||
return;
|
||||
}
|
||||
coo = coo.split(' ');
|
||||
//console.log("coo: " + coo);
|
||||
for (var i=0; i<coo.length; ++i) {
|
||||
coo[i] = coo[i].split(',');
|
||||
}
|
||||
|
||||
return coo;
|
||||
};
|
||||
|
||||
// Return the first folder name in KML
|
||||
var extractFolderName = function(kml) {
|
||||
|
||||
// Strip namespace:
|
||||
//https://github.com/polotek/libxmljs/issues/212
|
||||
kml = kml.replace(/ xmlns=[^>]*>/, '>');
|
||||
|
||||
var doc = libxmljs.parseXmlString(kml);
|
||||
//console.log("doc: " + doc);
|
||||
if ( ! doc ) {
|
||||
return;
|
||||
}
|
||||
var coo = doc.get("//Document/Folder/name");
|
||||
//console.log("coo: " + coo);
|
||||
if ( ! coo ) {
|
||||
return;
|
||||
}
|
||||
coo = coo.text();
|
||||
//console.log("coo: " + coo);
|
||||
if ( ! coo ) {
|
||||
return;
|
||||
}
|
||||
return coo;
|
||||
};
|
||||
|
||||
// KML tests
|
||||
|
||||
it('KML format, unauthenticated', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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};
|
||||
Object.keys(checkfields).forEach(function(f) {
|
||||
if ( checkfields[f] ) {
|
||||
assert.ok(hasAttribute(row0, f), "result does not include '" + f + "': " + row0);
|
||||
} else {
|
||||
assert.ok(!hasAttribute(row0, f), "result includes '" + f + "'");
|
||||
}
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('KML format, unauthenticated, POST', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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();
|
||||
});
|
||||
});
|
||||
|
||||
it('KML format, bigger than 81920 bytes', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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();
|
||||
});
|
||||
});
|
||||
|
||||
it('KML format, skipfields', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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};
|
||||
Object.keys(checkFields).forEach(function(f) {
|
||||
if ( checkFields[f] ) {
|
||||
assert.ok(hasAttribute(row0, f), "result does not include '" + f + "': " + row0);
|
||||
} else {
|
||||
assert.ok(!hasAttribute(row0, f), "result includes '" + f + "'");
|
||||
}
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('KML format, unauthenticated, custom filename', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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);
|
||||
var name = extractFolderName(res.body);
|
||||
assert.equal(name, "kmltest");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('KML format, authenticated', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.equal(true, /filename=cartodb-query.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('KML format, unauthenticated, concurrent requests', function(done){
|
||||
var query = querystring.stringify({
|
||||
q: "SELECT 'val', x, y, st_setsrid(st_makepoint(x,y),4326) as the_geom " +
|
||||
"FROM generate_series(-180, 180) as x, generate_series(-90,90) y",
|
||||
format: 'kml',
|
||||
filename: 'multi'
|
||||
});
|
||||
|
||||
var concurrency = 4;
|
||||
var waiting = concurrency;
|
||||
|
||||
function validate(err, res) {
|
||||
//console.log("Response ended");
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.ok(res.body);
|
||||
var snippet = res.body.substr(0, 5);
|
||||
assert.equal(snippet, "<?xml");
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.equal(true, /^attachment/.test(cd), 'KML is not disposed as attachment: ' + cd);
|
||||
assert.equal(true, /filename=multi.kml/gi.test(cd), 'Unexpected KML filename: ' + cd);
|
||||
if ( ! --waiting ) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
var request = {
|
||||
method: 'GET',
|
||||
headers: { host: 'vizzuality.cartodb.com' },
|
||||
url: '/api/v1/sql?' + query
|
||||
};
|
||||
|
||||
for (var i=0; i<concurrency; ++i) {
|
||||
//console.log("Sending request");
|
||||
assert.response(server, request, { status: 200 }, validate);
|
||||
}
|
||||
});
|
||||
|
||||
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/60
|
||||
it('GET /api/v1/sql as kml with no rows', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?q=SELECT%20true%20WHERE%20false&format=kml',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
// NOTE: GDAL-1.11+ added 'id="root_doc"' attribute to the output
|
||||
var pat = new RegExp('^<\\?xml version="1.0" encoding="utf-8" \\?>' +
|
||||
'<kml xmlns="http://www.opengis.net/kml/2.2">' +
|
||||
'<Document( id="root_doc")?><Folder><name>cartodb_query</name></Folder></Document>' +
|
||||
'</kml>$');
|
||||
var body = res.body.replace(/\n/g,'');
|
||||
assert.ok(body.match(pat),
|
||||
"Response:\n" + body + '\ndoes not match pattern:\n' + pat);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/90
|
||||
it('GET /api/v1/sql as kml with ending semicolon', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: 'SELECT true WHERE false;',
|
||||
format: 'kml'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
// NOTE: GDAL-1.11+ added 'id="root_doc"' attribute to the output
|
||||
var pat = new RegExp('^<\\?xml version="1.0" encoding="utf-8" \\?>' +
|
||||
'<kml xmlns="http://www.opengis.net/kml/2.2">' +
|
||||
'<Document( id="root_doc")?><Folder><name>cartodb_query</name></Folder></Document>' +
|
||||
'</kml>$');
|
||||
var body = res.body.replace(/\n/g,'');
|
||||
assert.ok(body.match(pat),
|
||||
"Response:\n" + body + '\ndoes not match pattern:\n' + pat);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/cartodb/issues/276
|
||||
it('check point coordinates, unauthenticated', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: 'SELECT * from untitle_table_4 WHERE cartodb_id = -1',
|
||||
format: 'kml'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var coords = extractCoordinates(res.body);
|
||||
assert(coords, 'No coordinates in ' + res.body);
|
||||
assert.deepEqual(coords, [[33,16]]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/cartodb/issues/276
|
||||
it('check point coordinates, authenticated', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: 'SELECT * from untitle_table_4 WHERE cartodb_id = -1',
|
||||
api_key: 1234,
|
||||
format: 'kml'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var coords = extractCoordinates(res.body);
|
||||
assert(coords, 'No coordinates in ' + res.body);
|
||||
assert.deepEqual(coords, [[33,16]]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var limit = 1200;
|
||||
|
||||
it('expects ' + limit + ' placemarks in public table', function(done){
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql',
|
||||
data: querystring.stringify({
|
||||
q: "SELECT * from populated_places_simple_reduced limit " + limit,
|
||||
format: 'kml'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
method: 'POST'
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(err, res) {
|
||||
assert.equal(res.body.match(/<Placemark>/g).length, limit);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('expects ' + limit + ' placemarks in private table using the API KEY', function(done){
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: "SELECT * from populated_places_simple_reduced limit " + limit,
|
||||
api_key: 1234,
|
||||
format: 'kml'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(err, res) {
|
||||
assert.equal(res.body.match(/<Placemark>/g).length, limit);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should work with queries returning no results', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: "/api/v1/sql?" + querystring.stringify({
|
||||
q: "SELECT * FROM populated_places_simple_reduced LIMIT 0",
|
||||
format: 'kml'
|
||||
}),
|
||||
headers: {
|
||||
host: 'vizzuality.cartodb.com'
|
||||
},
|
||||
encoding: 'binary',
|
||||
method: 'GET'
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(err, res) {
|
||||
assert.equal(res.body.match(/<Placemark>/g), null);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
405
test/acceptance/export/shapefile.js
Normal file
405
test/acceptance/export/shapefile.js
Normal file
@@ -0,0 +1,405 @@
|
||||
'use strict';
|
||||
|
||||
require('../../helper');
|
||||
|
||||
var server = require('../../../app/server')();
|
||||
var assert = require('../../support/assert');
|
||||
var querystring = require('querystring');
|
||||
var shapefile = require('shapefile');
|
||||
var _ = require('underscore');
|
||||
var zipfile = require('zipfile');
|
||||
var fs = require('fs');
|
||||
|
||||
describe('export.shapefile', function() {
|
||||
|
||||
// SHP tests
|
||||
|
||||
it('SHP format, unauthenticated', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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 writeErr = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||
if (writeErr) {
|
||||
return done(writeErr);
|
||||
}
|
||||
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);
|
||||
assert.ok(_.contains(zf.names, 'cartodb-query.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
||||
// TODO: check DBF contents
|
||||
fs.unlinkSync(tmpfile);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('SHP format, unauthenticated, POST', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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();
|
||||
});
|
||||
});
|
||||
|
||||
it('SHP format, big size, POST', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql',
|
||||
data: querystring.stringify({
|
||||
q: 'SELECT 0 as fname, st_makepoint(i,i) FROM generate_series(0,81920) i',
|
||||
format: 'shp'
|
||||
}),
|
||||
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
method: 'POST'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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();
|
||||
});
|
||||
});
|
||||
|
||||
it('SHP format, unauthenticated, with custom filename', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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 writeErr = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||
if (writeErr) {
|
||||
return done(writeErr);
|
||||
}
|
||||
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);
|
||||
assert.ok(_.contains(zf.names, 'myshape.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
||||
fs.unlinkSync(tmpfile);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('SHP format, unauthenticated, with custom, dangerous filename', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var fname = "b_______a";
|
||||
var cd = res.headers['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 writeErr = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||
if (writeErr) {
|
||||
return done(writeErr);
|
||||
}
|
||||
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);
|
||||
assert.ok(_.contains(zf.names, fname+ '.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
||||
fs.unlinkSync(tmpfile);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('SHP format, authenticated', function(done){
|
||||
assert.response(server, {
|
||||
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(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd));
|
||||
var tmpfile = '/tmp/myshape.zip';
|
||||
var writeErr = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||
if (writeErr) {
|
||||
return done(writeErr);
|
||||
}
|
||||
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);
|
||||
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
|
||||
it('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(server, {
|
||||
url: '/api/v1/sql?' + query,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
encoding: 'binary',
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var tmpfile = '/tmp/myshape.zip';
|
||||
var writeErr = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||
if (writeErr) {
|
||||
return done(writeErr);
|
||||
}
|
||||
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
|
||||
it('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(server, {
|
||||
url: '/api/v1/sql?' + query,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
encoding: 'binary',
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.deepEqual(res.headers['content-type'], 'application/json; charset=utf-8');
|
||||
assert.deepEqual(res.headers['content-disposition'], 'inline');
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' +res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var error = parsedBody.error[0];
|
||||
var expectedError = /Attempt to write non-point \(LINESTRING\) geometry to point shapefile/g;
|
||||
assert.ok(expectedError.test(error), error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/87
|
||||
it('errors are not confused with warnings', function(done){
|
||||
var query = querystring.stringify({
|
||||
q: [
|
||||
"SELECT 'POINT(0 0)'::geometry as g, 1 as a_very_very_very_long_field_name",
|
||||
"SELECT 'LINESTRING(0 0, 1 0)'::geometry, 2"
|
||||
].join(" UNION ALL "),
|
||||
format: 'shp'
|
||||
});
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + query,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
encoding: 'binary',
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.deepEqual(res.headers['content-type'], 'application/json; charset=utf-8');
|
||||
assert.deepEqual(res.headers['content-disposition'], 'inline');
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' +res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var error = parsedBody.error[0];
|
||||
var expectedError = /Attempt to write non-point \(LINESTRING\) geometry to point shapefile/g;
|
||||
assert.ok(expectedError.test(error), error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('skipfields controls fields included in SHP output', function(done){
|
||||
var query = querystring.stringify({
|
||||
q: "SELECT 111 as skipme, 222 as keepme, 'POINT(0 0)'::geometry as g",
|
||||
format: 'shp',
|
||||
skipfields: 'skipme',
|
||||
filename: 'myshape'
|
||||
});
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + query,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
encoding: 'binary',
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var tmpfile = '/tmp/myshape.zip';
|
||||
var writeErr = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||
if (writeErr) {
|
||||
return done(writeErr);
|
||||
}
|
||||
var zf = new zipfile.ZipFile(tmpfile);
|
||||
var buffer = zf.readFileSync('myshape.dbf');
|
||||
fs.unlinkSync(tmpfile);
|
||||
var strings = buffer.toString();
|
||||
assert.ok(!/skipme/.exec(strings), "Could not skip 'skipme' field:\n" + strings);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('SHP format, concurrently', function(done){
|
||||
var concurrency = 1;
|
||||
var waiting = concurrency;
|
||||
function validate(err, res){
|
||||
var cd = res.headers['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 writeErr = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||
if (writeErr) {
|
||||
return done(writeErr);
|
||||
}
|
||||
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);
|
||||
assert.ok(_.contains(zf.names, 'cartodb-query.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
|
||||
// TODO: check DBF contents
|
||||
fs.unlinkSync(tmpfile);
|
||||
if ( ! --waiting ) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
for (var i=0; i<concurrency; ++i) {
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
encoding: 'binary',
|
||||
method: 'GET'
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
validate
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/111
|
||||
it('point with null first', function(done){
|
||||
var query = querystring.stringify({
|
||||
q: "SELECT null::geometry as g UNION ALL SELECT 'SRID=4326;POINT(0 0)'::geometry",
|
||||
format: 'shp'
|
||||
});
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + query,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
encoding: 'binary',
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd));
|
||||
var tmpfile = '/tmp/myshape.zip';
|
||||
var writeErr = fs.writeFileSync(tmpfile, res.body, 'binary');
|
||||
if (writeErr) {
|
||||
return done(writeErr);
|
||||
}
|
||||
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);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
var limit = 1200;
|
||||
|
||||
it('expects ' + limit + ' rows in public table', function(done){
|
||||
|
||||
var filename = 'test_1200';
|
||||
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify({
|
||||
q: "SELECT * from populated_places_simple_reduced limit " + limit,
|
||||
format: 'shp',
|
||||
filename: filename
|
||||
}),
|
||||
headers: { host: 'vizzuality.cartodb.com' },
|
||||
encoding: 'binary',
|
||||
method: 'GET'
|
||||
},
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var tmpShpPath = '/tmp/'+filename+'.zip';
|
||||
err = fs.writeFileSync(tmpShpPath, res.body, 'binary');
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var zf = new zipfile.ZipFile(tmpShpPath);
|
||||
zf.names.forEach(function(name) {
|
||||
var buffer = zf.readFileSync(name);
|
||||
var tmpDbfPath = '/tmp/' + name;
|
||||
err = fs.writeFileSync(tmpDbfPath, buffer);
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
});
|
||||
shapefile.read('/tmp/'+filename, function(err, collection) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.equal(collection.features.length, limit);
|
||||
done();
|
||||
});
|
||||
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('SHP zip, wrong path for zip command should return error', function(done){
|
||||
global.settings.zipCommand = '/wrong/path';
|
||||
var query = querystring.stringify({
|
||||
q: "SELECT st_makepoint(0,0,4326) as the_geom",
|
||||
format: 'shp',
|
||||
filename: 'myshape'
|
||||
});
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?' + query,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
encoding: 'binary',
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 400, res.body);
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
var respBodyPattern = new RegExp('Error executing zip command, Error: spawn(.*)ENOENT', 'i');
|
||||
assert.equal(respBodyPattern.test(parsedBody.error[0]), true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
56
test/acceptance/export/spatialite.js
Normal file
56
test/acceptance/export/spatialite.js
Normal file
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
require('../../helper');
|
||||
|
||||
var server = require('../../../app/server')();
|
||||
var assert = require('../../support/assert');
|
||||
var sqlite = require('sqlite3');
|
||||
|
||||
describe('spatialite query', function(){
|
||||
|
||||
it('returns a valid sqlite database', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=spatialite',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
assert.equal(res.headers["content-type"], "application/x-sqlite3; charset=utf-8");
|
||||
var db = new sqlite.Database(':memory:', res.body);
|
||||
var qr = db.get("PRAGMA database_list", function(err){
|
||||
assert.equal(err, null);
|
||||
done();
|
||||
});
|
||||
assert.notEqual(qr, undefined);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('different file name', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=spatialite&filename=manolo',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res) {
|
||||
assert.equal(res.headers["content-type"], "application/x-sqlite3; charset=utf-8");
|
||||
assert.notEqual(res.headers["content-disposition"].indexOf("manolo.sqlite"), -1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('gets database schema', function(done){
|
||||
assert.response(server, {
|
||||
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=spatialite',
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res) {
|
||||
var db = new sqlite.Database(':memory:', res.body);
|
||||
var schemaQuery = "SELECT name, sql FROM sqlite_master WHERE type='table' ORDER BY name";
|
||||
var qr = db.get(schemaQuery, function(err){
|
||||
assert.equal(err, null);
|
||||
done();
|
||||
});
|
||||
assert.notEqual(qr, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
199
test/acceptance/export/svg.js
Normal file
199
test/acceptance/export/svg.js
Normal file
@@ -0,0 +1,199 @@
|
||||
'use strict';
|
||||
|
||||
require('../../helper');
|
||||
|
||||
var server = require('../../../app/server')();
|
||||
var assert = require('../../support/assert');
|
||||
var querystring = require('querystring');
|
||||
|
||||
describe('export.svg', function() {
|
||||
|
||||
it('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(server, {
|
||||
url: '/api/v1/sql?' + query,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
||||
assert.equal(res.headers['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();
|
||||
});
|
||||
});
|
||||
|
||||
it('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(server, {
|
||||
url: '/api/v1/sql',
|
||||
data: query,
|
||||
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
method: 'POST'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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.headers['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();
|
||||
});
|
||||
});
|
||||
|
||||
it('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(server, {
|
||||
url: '/api/v1/sql?' + query,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.ok(/filename=mysvg.svg/gi.test(cd), cd);
|
||||
assert.equal(res.headers['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();
|
||||
});
|
||||
});
|
||||
|
||||
it('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(server, {
|
||||
url: '/api/v1/sql?' + query,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
||||
assert.equal(res.headers['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();
|
||||
});
|
||||
});
|
||||
|
||||
it('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(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify(queryobj),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['content-disposition'];
|
||||
assert.ok(/filename=cartodb-query.svg/gi.test(cd), cd);
|
||||
assert.equal(res.headers['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(server, {
|
||||
url: '/api/v1/sql?' + querystring.stringify(queryobj),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{}, function(err, res) {
|
||||
assert.equal(res.statusCode, 200, res.body);
|
||||
var cd = res.headers['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.headers['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
|
||||
it('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(server, {
|
||||
url: '/api/v1/sql?' + query,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
assert.deepEqual(res.headers['content-type'], 'application/json; charset=utf-8');
|
||||
assert.deepEqual(res.headers['content-disposition'], 'inline');
|
||||
assert.deepEqual(JSON.parse(res.body), {
|
||||
error:['column "the_geom" does not exist']
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('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(server, {
|
||||
url: '/api/v1/sql?' + query,
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
},{ }, function(err, res){
|
||||
assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body);
|
||||
assert.deepEqual(JSON.parse(res.body), {
|
||||
error:['column "the_geom" does not exist']
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should close on error and error must be the only key in the body', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: "/api/v1/sql?" + querystring.stringify({
|
||||
q: "SELECT the_geom, 100/(cartodb_id - 3) cdb_ratio FROM untitle_table_4",
|
||||
format: 'svg'
|
||||
}),
|
||||
headers: {
|
||||
host: 'vizzuality.cartodb.com'
|
||||
},
|
||||
method: 'GET'
|
||||
},
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(err, res) {
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.deepEqual(Object.keys(parsedBody), ['error']);
|
||||
assert.deepEqual(parsedBody.error, ["division by zero"]);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
202
test/acceptance/export/timeout.js
Normal file
202
test/acceptance/export/timeout.js
Normal file
@@ -0,0 +1,202 @@
|
||||
'use strict';
|
||||
|
||||
const TestClient = require('../../support/test-client');
|
||||
|
||||
require('../../support/assert');
|
||||
|
||||
var assert = require('assert');
|
||||
var querystring = require('querystring');
|
||||
const db_utils = require('../../support/db_utils');
|
||||
|
||||
describe('timeout', function () {
|
||||
describe('export database', function () {
|
||||
before(db_utils.resetPgBouncerConnections);
|
||||
after(db_utils.resetPgBouncerConnections);
|
||||
|
||||
const databaseTimeoutQuery = `
|
||||
select
|
||||
ST_SetSRID(ST_Point(0, 0), 4326) as the_geom,
|
||||
pg_sleep(0.2) as sleep,
|
||||
1 as value
|
||||
`;
|
||||
|
||||
const scenarios = [
|
||||
{
|
||||
desc: 'CSV',
|
||||
format: 'csv',
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
parser: querystring.stringify,
|
||||
// only: true,
|
||||
skip: true
|
||||
},
|
||||
{
|
||||
query: databaseTimeoutQuery,
|
||||
desc: 'Geopackage',
|
||||
format: 'gpkg'
|
||||
},
|
||||
{
|
||||
query: databaseTimeoutQuery,
|
||||
desc: 'KML',
|
||||
format: 'kml'
|
||||
},
|
||||
{
|
||||
query: databaseTimeoutQuery,
|
||||
desc: 'Shapefile',
|
||||
format: 'shp'
|
||||
},
|
||||
{
|
||||
query: databaseTimeoutQuery,
|
||||
desc: 'Spatialite',
|
||||
format: 'spatialite'
|
||||
},
|
||||
{
|
||||
query: databaseTimeoutQuery,
|
||||
desc: 'Array Buffer',
|
||||
format: 'arraybuffer'
|
||||
},
|
||||
{
|
||||
query: databaseTimeoutQuery,
|
||||
desc: 'GeoJSON',
|
||||
format: 'geojson'
|
||||
},
|
||||
{
|
||||
query: databaseTimeoutQuery,
|
||||
desc: 'JSON',
|
||||
format: 'json'
|
||||
},
|
||||
{
|
||||
query: databaseTimeoutQuery,
|
||||
desc: 'SVG',
|
||||
format: 'svg'
|
||||
},
|
||||
{
|
||||
query: databaseTimeoutQuery,
|
||||
desc: 'TopoJSON',
|
||||
format: 'topojson'
|
||||
}
|
||||
];
|
||||
|
||||
beforeEach(function (done) {
|
||||
this.testClient = new TestClient();
|
||||
this.testClient.setUserDatabaseTimeoutLimit('localhost', 100, done);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
this.testClient.setUserDatabaseTimeoutLimit('localhost', 2000, done);
|
||||
});
|
||||
|
||||
scenarios.forEach((scenario) => {
|
||||
const test = scenario.only ? it.only : scenario.skip ? it.skip : it;
|
||||
|
||||
test(`${scenario.desc} export exceeding statement timeout responds 429 Over Limits`, function (done) {
|
||||
const override = {
|
||||
'Content-Type': scenario.contentType,
|
||||
parser: scenario.parser,
|
||||
anonymous: true,
|
||||
format: scenario.format,
|
||||
response: {
|
||||
status: 429
|
||||
}
|
||||
};
|
||||
|
||||
this.testClient.getResult(scenario.query, override, (err, res) => {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.deepEqual(res, {
|
||||
error: [
|
||||
'You are over platform\'s limits: SQL query timeout error.' +
|
||||
' Refactor your query before running again or contact CARTO support for more details.',
|
||||
],
|
||||
context: 'limit',
|
||||
detail: 'datasource'
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('export ogr command timeout', function () {
|
||||
const ogrCommandTimeoutQuery = `
|
||||
select
|
||||
ST_SetSRID(ST_Point(0, 0), 4326) as the_geom,
|
||||
pg_sleep(0.2) as sleep,
|
||||
1 as value
|
||||
`;
|
||||
|
||||
const scenarios = [
|
||||
{
|
||||
query: ogrCommandTimeoutQuery,
|
||||
desc: 'CSV',
|
||||
format: 'csv',
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
parser: querystring.stringify,
|
||||
// only: true,
|
||||
// skip: true
|
||||
},
|
||||
{
|
||||
query: ogrCommandTimeoutQuery,
|
||||
filename: 'wadus_gpkg_filename',
|
||||
desc: 'Geopackage',
|
||||
format: 'gpkg'
|
||||
},
|
||||
{
|
||||
query: ogrCommandTimeoutQuery,
|
||||
desc: 'KML',
|
||||
format: 'kml'
|
||||
},
|
||||
{
|
||||
query: ogrCommandTimeoutQuery,
|
||||
desc: 'Shapefile',
|
||||
format: 'shp'
|
||||
},
|
||||
{
|
||||
query: ogrCommandTimeoutQuery,
|
||||
desc: 'Spatialite',
|
||||
format: 'spatialite'
|
||||
}
|
||||
];
|
||||
|
||||
beforeEach(function (done) {
|
||||
this.testClient = new TestClient();
|
||||
this.testClient.setUserRenderTimeoutLimit('vizzuality', 100, done);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
this.testClient.setUserRenderTimeoutLimit('vizzuality', 0, done);
|
||||
});
|
||||
|
||||
scenarios.forEach((scenario) => {
|
||||
const test = scenario.only ? it.only : scenario.skip ? it.skip : it;
|
||||
|
||||
test(`${scenario.desc} export exceeding statement timeout responds 429 Over Limits`, function (done) {
|
||||
const override = {
|
||||
'Content-Type': scenario.contentType,
|
||||
parser: scenario.parser,
|
||||
anonymous: true,
|
||||
format: scenario.format,
|
||||
filename: scenario.filename,
|
||||
response: {
|
||||
status: 429
|
||||
}
|
||||
};
|
||||
|
||||
this.testClient.getResult(scenario.query, override, (err, res) => {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.deepEqual(res, {
|
||||
error: [
|
||||
'You are over platform\'s limits: SQL query timeout error.' +
|
||||
' Refactor your query before running again or contact CARTO support for more details.',
|
||||
],
|
||||
context: 'limit',
|
||||
detail: 'datasource'
|
||||
});
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
267
test/acceptance/export/topojson.js
Normal file
267
test/acceptance/export/topojson.js
Normal file
@@ -0,0 +1,267 @@
|
||||
'use strict';
|
||||
|
||||
require('../../helper');
|
||||
|
||||
var server = require('../../../app/server')();
|
||||
var assert = require('../../support/assert');
|
||||
var querystring = require('querystring');
|
||||
var _ = require('underscore');
|
||||
|
||||
describe('export.topojson', function() {
|
||||
|
||||
// TOPOJSON tests
|
||||
|
||||
function getRequest(query, extraParams) {
|
||||
var params = {
|
||||
q: query,
|
||||
format: 'topojson'
|
||||
};
|
||||
|
||||
params = _.extend(params, extraParams || {});
|
||||
|
||||
return {
|
||||
url: '/api/v1/sql?' + querystring.stringify(params),
|
||||
headers: {host: 'vizzuality.cartodb.com'},
|
||||
method: 'GET'
|
||||
};
|
||||
}
|
||||
|
||||
it('GET two polygons sharing an edge as topojson', function(done){
|
||||
assert.response(server,
|
||||
getRequest(
|
||||
"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 "
|
||||
),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(err, res) {
|
||||
var cd = res.headers['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();
|
||||
});
|
||||
});
|
||||
|
||||
it('null geometries', function(done){
|
||||
assert.response(server, getRequest(
|
||||
"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 "
|
||||
),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(err, res) {
|
||||
var cd = res.headers['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();
|
||||
});
|
||||
});
|
||||
|
||||
it('skipped fields are not returned', function(done) {
|
||||
assert.response(server,
|
||||
getRequest(
|
||||
"SELECT 1 as gid, 'U' as name, 'POLYGON((-5 0,5 0,0 5,-5 0))'::geometry as the_geom",
|
||||
{
|
||||
skipfields: 'name'
|
||||
}
|
||||
),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(err, res) {
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.equal(parsedBody.objects[0].properties.gid, 1, 'gid was expected property');
|
||||
assert.ok(!parsedBody.objects[0].properties.name);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('jsonp callback is invoked', function(done){
|
||||
assert.response(
|
||||
server,
|
||||
getRequest(
|
||||
"SELECT 1 as gid, 'U' as name, 'POLYGON((-5 0,5 0,0 5,-5 0))'::geometry as the_geom",
|
||||
{
|
||||
callback: 'foo_jsonp'
|
||||
}
|
||||
),
|
||||
{
|
||||
status: 200
|
||||
},
|
||||
function(err, res) {
|
||||
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
|
||||
var didRunJsonCallback = false;
|
||||
// jshint ignore:start
|
||||
function foo_jsonp(body) {
|
||||
didRunJsonCallback = true;
|
||||
}
|
||||
eval(res.body);
|
||||
// jshint ignore:end
|
||||
assert.ok(didRunJsonCallback);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should close on error and error must be the only key in the body', function(done) {
|
||||
assert.response(
|
||||
server,
|
||||
{
|
||||
url: "/api/v1/sql?" + querystring.stringify({
|
||||
q: "SELECT the_geom, 100/(cartodb_id - 3) cdb_ratio FROM untitle_table_4",
|
||||
format: 'topojson'
|
||||
}),
|
||||
headers: {
|
||||
host: 'vizzuality.cartodb.com'
|
||||
},
|
||||
method: 'GET'
|
||||
},
|
||||
{
|
||||
status: 400
|
||||
},
|
||||
function(err, res) {
|
||||
var parsedBody = JSON.parse(res.body);
|
||||
assert.deepEqual(Object.keys(parsedBody), ['error']);
|
||||
assert.deepEqual(parsedBody.error, ["division by zero"]);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user