0c92fcaf96
- Update `gc-stats` to version 1.4.0 - Replace `zipfile` -> `adm-zip` - Update `libxmljs` to version 0.19.7 - Update `sqlite` to version 4.2.0 - Adapted pool acquires to the new version of `cartodb-redis` - Adapted test to use `adm-zip`
369 lines
15 KiB
JavaScript
369 lines
15 KiB
JavaScript
'use strict';
|
|
|
|
require('../../helper');
|
|
|
|
var server = require('../../../lib/server')();
|
|
var assert = require('../../support/assert');
|
|
var querystring = require('querystring');
|
|
var shapefile = require('shapefile');
|
|
const AdmZip = require('adm-zip');
|
|
var fs = require('fs');
|
|
|
|
function assertZipfileContent (content, filename = 'cartodb-query') {
|
|
const tmpfile = '/tmp/myshape.zip';
|
|
const err = fs.writeFileSync(tmpfile, content, 'binary');
|
|
|
|
assert.ifError(err);
|
|
|
|
const names = new AdmZip(tmpfile).getEntries().map(entry => entry.name);
|
|
assert.ok(names.includes(`${filename}.shp`), `SHP zipfile does not contain .shp: ${names}`);
|
|
assert.ok(names.includes(`${filename}.shx`), `SHP zipfile does not contain .shx: ${names}`);
|
|
assert.ok(names.includes(`${filename}.dbf`), `SHP zipfile does not contain .dbf: ${names}`);
|
|
assert.ok(names.includes(`${filename}.prj`), `SHP zipfile does not contain .prj: ${names}`);
|
|
|
|
fs.unlinkSync(tmpfile);
|
|
}
|
|
|
|
describe('export.shapefile', function () {
|
|
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.ifError(err);
|
|
assert.strictEqual(res.statusCode, 200, res.body);
|
|
var cd = res.headers['content-disposition'];
|
|
assert.strictEqual(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
|
assert.strictEqual(true, /filename=cartodb-query.zip/gi.test(cd));
|
|
assertZipfileContent(res.body);
|
|
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.ifError(err);
|
|
assert.strictEqual(res.statusCode, 200, res.body);
|
|
var cd = res.headers['content-disposition'];
|
|
assert.strictEqual(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
|
assert.strictEqual(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.ifError(err);
|
|
assert.strictEqual(res.statusCode, 200, res.body);
|
|
var cd = res.headers['content-disposition'];
|
|
assert.strictEqual(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
|
assert.strictEqual(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.ifError(err);
|
|
assert.strictEqual(res.statusCode, 200, res.body);
|
|
var cd = res.headers['content-disposition'];
|
|
assert.strictEqual(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
|
assert.strictEqual(true, /filename=myshape.zip/gi.test(cd));
|
|
assertZipfileContent(res.body, 'myshape');
|
|
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.ifError(err);
|
|
assert.strictEqual(res.statusCode, 200, res.body);
|
|
var fname = 'b_______a';
|
|
var cd = res.headers['content-disposition'];
|
|
assert.strictEqual(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
|
assert.strictEqual(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);
|
|
}
|
|
const names = new AdmZip(tmpfile).getEntries().map(entry => entry.name);
|
|
assert.ok(names.includes(`${fname}.shp`), `SHP zipfile does not contain .shp: ${names}`);
|
|
assert.ok(names.includes(`${fname}.shx`), `SHP zipfile does not contain .shx: ${names}`);
|
|
assert.ok(names.includes(`${fname}.dbf`), `SHP zipfile does not contain .dbf: ${names}`);
|
|
assert.ok(names.includes(`${fname}.prj`), `SHP zipfile does not contain .prj: ${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.ifError(err);
|
|
assert.strictEqual(res.statusCode, 200, res.body);
|
|
var cd = res.headers['content-disposition'];
|
|
assert.strictEqual(true, /filename=cartodb-query.zip/gi.test(cd));
|
|
assertZipfileContent(res.body);
|
|
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.ifError(err);
|
|
assert.strictEqual(res.statusCode, 200, res.body);
|
|
var tmpfile = '/tmp/myshape.zip';
|
|
var writeErr = fs.writeFileSync(tmpfile, res.body, 'binary');
|
|
if (writeErr) {
|
|
return done(writeErr);
|
|
}
|
|
const buffer = new AdmZip(tmpfile).getEntry('myshape.dbf').getData();
|
|
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.ifError(err);
|
|
assert.deepStrictEqual(res.headers['content-type'], 'application/json; charset=utf-8');
|
|
assert.deepStrictEqual(res.headers['content-disposition'], 'inline');
|
|
assert.strictEqual(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.ifError(err);
|
|
assert.deepStrictEqual(res.headers['content-type'], 'application/json; charset=utf-8');
|
|
assert.deepStrictEqual(res.headers['content-disposition'], 'inline');
|
|
assert.strictEqual(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.ifError(err);
|
|
assert.strictEqual(res.statusCode, 200, res.body);
|
|
var tmpfile = '/tmp/myshape.zip';
|
|
var writeErr = fs.writeFileSync(tmpfile, res.body, 'binary');
|
|
if (writeErr) {
|
|
return done(writeErr);
|
|
}
|
|
const buffer = new AdmZip(tmpfile).getEntry('myshape.dbf').getData();
|
|
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) {
|
|
assert.ifError(err);
|
|
var cd = res.headers['content-disposition'];
|
|
assert.strictEqual(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
|
|
assert.strictEqual(true, /filename=cartodb-query.zip/gi.test(cd));
|
|
assertZipfileContent(res.body);
|
|
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.ifError(err);
|
|
assert.strictEqual(res.statusCode, 200, res.body);
|
|
var cd = res.headers['content-disposition'];
|
|
assert.strictEqual(true, /filename=cartodb-query.zip/gi.test(cd));
|
|
assertZipfileContent(res.body);
|
|
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);
|
|
}
|
|
|
|
const zf = new AdmZip(tmpShpPath);
|
|
zf.getEntries().forEach(entry => {
|
|
const buffer = entry.getData();
|
|
const tmpDbfPath = `/tmp/${entry.name}`;
|
|
const err = fs.writeFileSync(tmpDbfPath, buffer);
|
|
assert.ifError(err);
|
|
});
|
|
shapefile.read('/tmp/' + filename, function (err, collection) {
|
|
if (err) {
|
|
return done(err);
|
|
}
|
|
assert.strictEqual(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.ifError(err);
|
|
assert.strictEqual(res.statusCode, 400, res.body);
|
|
var parsedBody = JSON.parse(res.body);
|
|
var respBodyPattern = new RegExp('Error executing zip command, {2}Error: spawn(.*)ENOENT', 'i');
|
|
assert.strictEqual(respBodyPattern.test(parsedBody.error[0]), true);
|
|
done();
|
|
});
|
|
});
|
|
});
|