Merge branch 'master' into node-v6

This commit is contained in:
Raul Ochoa 2016-12-16 15:24:26 +01:00
commit 32aaccb33e
16 changed files with 2309 additions and 1853 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ pids/*.pid
test/tmp/*
node_modules*
.idea/*
.vscode/
tools/munin/cdbsqlapi.conf
test/redis.pid
test/test.log

489
NEWS.md

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
SQL API for cartodb.com
SQL API for carto.com
========================
[![Build Status](https://travis-ci.org/CartoDB/CartoDB-SQL-API.png?branch=master)](https://travis-ci.org/CartoDB/CartoDB-SQL-API)

View File

@ -225,6 +225,7 @@ JobFallback.prototype.log = function(logger) {
username: this.data.user,
dbhost: this.data.host,
job: this.data.job_id,
status: query.status,
elapsed: elapsedTime(query.started_at, query.ended_at)
};

View File

@ -2,6 +2,8 @@
To make things easier for developers, we provide client libraries for different programming languages and caching functionalities.
**Note:** These libraries are externally developed and maintained. Use caution when using libraries in different languages, as some of these resources may be out-of-date.
- **R**
To help more researchers use CARTO to drive their geospatial data, we have released the R client library. [Fork it on GitHub!](https://github.com/Vizzuality/cartodb-r)
@ -25,3 +27,6 @@ To make things easier for developers, we provide client libraries for different
- **iOS**
Objective-C library for interacting with CARTO in native iOS applications. [Fork it on GitHub!](https://github.com/jmnavarro/cartodb-objectivec-client)
- **Golang**
A Go client for the CARTO SQL API that supports authentication using an API key. [Fork it on GitHub!](https://github.com/agonzalezro/cartodb_go)

2217
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"keywords": [
"cartodb"
],
"version": "1.42.4",
"version": "1.42.6",
"repository": {
"type": "git",
"url": "git://github.com/CartoDB/CartoDB-SQL-API.git"

View File

@ -0,0 +1,164 @@
require('../helper');
var server = require('../../app/server')();
var assert = require('../support/assert');
describe('app-configuration', function() {
var RESPONSE_OK = {
statusCode: 200
};
var expected_cache_control = 'no-cache,max-age=31536000,must-revalidate,public';
var expected_cache_control_persist = 'public,max-age=31536000';
it('GET /api/v1/version', function(done){
assert.response(server, {
url: '/api/v1/version',
method: 'GET'
}, RESPONSE_OK, function(err, res) {
var parsed = JSON.parse(res.body);
var sqlapi_version = require(__dirname + '/../../package.json').version;
assert.ok(parsed.hasOwnProperty('cartodb_sql_api'), "No 'cartodb_sql_api' version in " + parsed);
assert.equal(parsed.cartodb_sql_api, sqlapi_version);
done();
});
});
it('GET /api/v1/sql', function(done){
assert.response(server, {
url: '/api/v1/sql',
method: 'GET'
},{
status: 400
}, function(err, res) {
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":["You must indicate a sql query"]});
done();
});
});
// Test base_url setting
it('GET /api/whatever/sql', function(done){
assert.response(server, {
url: '/api/whatever/sql?q=SELECT%201',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, done);
});
// Test CORS headers with GET
it('GET /api/whatever/sql', function(done){
assert.response(server, {
url: '/api/whatever/sql?q=SELECT%201',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) {
assert.equal(
res.headers['access-control-allow-headers'], 'X-Requested-With, X-Prototype-Version, X-CSRF-Token'
);
assert.equal(res.headers['access-control-allow-origin'], '*');
done();
});
});
// Test that OPTIONS does not run queries
it('OPTIONS /api/x/sql', function(done){
assert.response(server, {
url: '/api/x/sql?q=syntax%20error',
headers: {host: 'vizzuality.cartodb.com'},
method: 'OPTIONS'
}, RESPONSE_OK, function(err, res) {
assert.equal(res.body, '');
assert.equal(
res.headers['access-control-allow-headers'], 'X-Requested-With, X-Prototype-Version, X-CSRF-Token'
);
assert.equal(res.headers['access-control-allow-origin'], '*');
done();
});
});
it('cache_policy=persist', function(done){
assert.response(server, {
url: '/api/v1/sql?q=' +
'SELECT%20*%20FROM%20untitle_table_4&database=cartodb_test_user_1_db&cache_policy=persist',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) {
// Check cache headers
assert.ok(res.headers.hasOwnProperty('x-cache-channel'));
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/105
assert.equal(res.headers['x-cache-channel'], 'cartodb_test_user_1_db:public.untitle_table_4');
assert.equal(res.headers['cache-control'], expected_cache_control_persist);
done();
});
});
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/121
it('SELECT from user-specific database', function(done){
var backupDBHost = global.settings.db_host;
global.settings.db_host = '6.6.6.6';
assert.response(server, {
url: '/api/v1/sql?q=SELECT+2+as+n',
headers: {host: 'cartodb250user.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) {
global.settings.db_host = backupDBHost;
try {
var parsed = JSON.parse(res.body);
assert.equal(parsed.rows.length, 1);
assert.equal(parsed.rows[0].n, 2);
} catch (e) {
return done(e);
}
done();
});
});
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/120
it('SELECT with user-specific password', function(done){
var backupDBUserPass = global.settings.db_user_pass;
global.settings.db_user_pass = '<%= user_password %>';
assert.response(server, {
url: '/api/v1/sql?q=SELECT+2+as+n&api_key=1234',
headers: {host: 'cartodb250user.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) {
global.settings.db_user_pass = backupDBUserPass;
try {
assert.equal(res.statusCode, 200, res.statusCode + ": " + res.body);
var parsed = JSON.parse(res.body);
assert.equal(parsed.rows.length, 1);
assert.equal(parsed.rows[0].n, 2);
} catch (e) {
return done(e);
}
return done();
});
});
/**
* CORS
*/
it('GET /api/v1/sql with SQL parameter on SELECT only should return CORS headers ', function(done){
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&database=cartodb_test_user_1_db',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) {
// Check cache headers
assert.equal(res.headers['x-cache-channel'], 'cartodb_test_user_1_db:public.untitle_table_4');
assert.equal(res.headers['cache-control'], expected_cache_control);
assert.equal(res.headers['access-control-allow-origin'], '*');
assert.equal(
res.headers['access-control-allow-headers'],
"X-Requested-With, X-Prototype-Version, X-CSRF-Token"
);
done();
});
});
});

View File

@ -29,84 +29,6 @@ describe('app.test', function() {
var expected_cache_control = 'no-cache,max-age=31536000,must-revalidate,public';
var expected_rw_cache_control = 'no-cache,max-age=0,must-revalidate,public';
var expected_cache_control_persist = 'public,max-age=31536000';
it('GET /api/v1/version', function(done){
assert.response(server, {
url: '/api/v1/version',
method: 'GET'
},{}, function(err, res) {
assert.equal(res.statusCode, 200);
var parsed = JSON.parse(res.body);
var sqlapi_version = require(__dirname + '/../../package.json').version;
assert.ok(parsed.hasOwnProperty('cartodb_sql_api'), "No 'cartodb_sql_api' version in " + parsed);
assert.equal(parsed.cartodb_sql_api, sqlapi_version);
done();
});
});
it('GET /api/v1/sql', function(done){
assert.response(server, {
url: '/api/v1/sql',
method: 'GET'
},{
status: 400
}, function(err, res) {
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":["You must indicate a sql query"]});
done();
});
});
// Test base_url setting
it('GET /api/whatever/sql', function(done){
assert.response(server, {
url: '/api/whatever/sql?q=SELECT%201',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{
}, function(err, res) {
assert.equal(res.statusCode, 200, res.body);
done();
});
});
// Test CORS headers with GET
it('GET /api/whatever/sql', function(done){
assert.response(server, {
url: '/api/whatever/sql?q=SELECT%201',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{
}, function(err, res) {
assert.equal(res.statusCode, 200, res.body);
assert.equal(
res.headers['access-control-allow-headers'], 'X-Requested-With, X-Prototype-Version, X-CSRF-Token'
);
assert.equal(res.headers['access-control-allow-origin'], '*');
done();
});
});
// Test that OPTIONS does not run queries
it('OPTIONS /api/x/sql', function(done){
assert.response(server, {
url: '/api/x/sql?q=syntax%20error',
headers: {host: 'vizzuality.cartodb.com'},
method: 'OPTIONS'
},{}, function(err, res) {
assert.equal(res.statusCode, 200, res.body);
assert.equal(res.body, '');
assert.equal(
res.headers['access-control-allow-headers'], 'X-Requested-With, X-Prototype-Version, X-CSRF-Token'
);
assert.equal(res.headers['access-control-allow-origin'], '*');
done();
});
});
it('GET /api/v1/sql with SQL parameter on SELECT only. No oAuth included ', function(done){
assert.response(server, {
@ -122,22 +44,6 @@ it('GET /api/v1/sql with SQL parameter on SELECT only. No oAuth included ', func
});
});
it('cache_policy=persist', function(done){
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&database=cartodb_test_user_1_db&cache_policy=persist',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{ }, function(err, res) {
assert.equal(res.statusCode, 200, res.body);
// Check cache headers
assert.ok(res.headers.hasOwnProperty('x-cache-channel'));
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/105
assert.equal(res.headers['x-cache-channel'], 'cartodb_test_user_1_db:public.untitle_table_4');
assert.equal(res.headers['cache-control'], expected_cache_control_persist);
done();
});
});
it('GET /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers', function(done){
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4',
@ -159,50 +65,6 @@ it('GET /user/vizzuality/api/v1/sql with SQL parameter on SELECT only', function
});
});
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/121
it('SELECT from user-specific database', function(done){
var backupDBHost = global.settings.db_host;
global.settings.db_host = '6.6.6.6';
assert.response(server, {
url: '/api/v1/sql?q=SELECT+2+as+n',
headers: {host: 'cartodb250user.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) {
global.settings.db_host = backupDBHost;
try {
var parsed = JSON.parse(res.body);
assert.equal(parsed.rows.length, 1);
assert.equal(parsed.rows[0].n, 2);
} catch (e) {
return done(e);
}
done();
});
});
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/120
it('SELECT with user-specific password', function(done){
var backupDBUserPass = global.settings.db_user_pass;
global.settings.db_user_pass = '<%= user_password %>';
assert.response(server, {
url: '/api/v1/sql?q=SELECT+2+as+n&api_key=1234',
headers: {host: 'cartodb250user.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) {
global.settings.db_user_pass = backupDBUserPass;
try {
assert.equal(res.statusCode, 200, res.statusCode + ": " + res.body);
var parsed = JSON.parse(res.body);
assert.equal(parsed.rows.length, 1);
assert.equal(parsed.rows[0].n, 2);
} catch (e) {
return done(e);
}
return done();
});
});
it('GET /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers. Authenticated.',
function(done){
assert.response(server, {
@ -218,121 +80,6 @@ function(done){
});
});
// Test for https://github.com/Vizzuality/CartoDB-SQL-API/issues/85
it("paging doesn't break x-cache-channel",
function(done){
assert.response(server, {
url: '/api/v1/sql?' + querystring.stringify({
// note: select casing intentionally mixed
q: 'selECT cartodb_id*3 FROM untitle_table_4',
api_key: '1234',
rows_per_page: 1,
page: 2
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{ }, function(err, res) {
assert.equal(res.statusCode, 200, res.body);
assert.equal(res.headers['x-cache-channel'], 'cartodb_test_user_1_db:public.untitle_table_4');
var parsed = JSON.parse(res.body);
assert.equal(parsed.rows.length, 1);
done();
});
});
// Test page and rows_per_page params
it("paging", function(done){
var sql = 'SELECT * FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(v)';
var pr = [ [2,3], [0,4] ]; // page and rows
var methods = [ 'GET', 'POST' ];
var authorized = 0;
var testing = 0;
var method = 0;
// jshint maxcomplexity:7
var testNext = function() {
if ( testing >= pr.length ) {
if ( method+1 >= methods.length ) {
if ( authorized ) {
done();
return;
} else {
authorized = 1;
method = 0;
testing = 0;
}
} else {
testing = 0;
++method;
}
}
var prcur = pr[testing++];
console.log("Test " + testing + "/" + pr.length + " method " + methods[method] + " " +
( authorized ? "authenticated" : "" ) );
var page = prcur[0];
var nrows = prcur[1];
var data_obj = {
q: sql,
rows_per_page: nrows,
page: page
};
if ( authorized ) {
data_obj.api_key = '1234';
}
var data = querystring.stringify(data_obj);
var req = {
url: '/api/v1/sql',
headers: {host: 'vizzuality.cartodb.com'}
};
if ( methods[method] === 'GET' ) {
req.method = 'GET';
req.url += '?' + data;
} else {
req.method = 'POST';
req.headers['Content-Type'] = 'application/x-www-form-urlencoded';
req.data = data;
}
assert.response(server, req, {}, function(err, res) {
assert.equal(res.statusCode, 200, res.body);
var parsed = JSON.parse(res.body);
assert.equal(parsed.rows.length, nrows);
for (var i=0; i<nrows; ++i) {
var obt = parsed.rows[i].v;
var exp = page * nrows + i + 1;
assert.equal(obt, exp, "Value " + i + " in page " + page + " is " + obt + ", expected " + exp);
}
testNext();
});
};
testNext();
});
// Test paging with WITH queries
it("paging starting with comment", function(done){
var sql = "-- this is a comment\n" +
"SELECT * FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(v)";
var nrows = 3;
var page = 2;
assert.response(server, {
url: '/api/v1/sql?' + querystring.stringify({
q: sql,
rows_per_page: nrows,
page: page
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, {}, function(err, res) {
assert.equal(res.statusCode, 200, res.body);
var parsed = JSON.parse(res.body);
assert.equal(parsed.rows.length, 3);
for (var i=0; i<nrows; ++i) {
var obt = parsed.rows[i].v;
var exp = page * nrows + i + 1;
assert.equal(obt, exp, "Value " + i + " in page " + page + " is " + obt + ", expected " + exp);
}
done();
});
});
it('POST /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers', function(done){
assert.response(server, {
url: '/api/v1/sql',
@ -390,152 +137,6 @@ it('GET /api/v1/sql with INSERT. header based db - should fail', function (done)
}, done);
});
// Check results from INSERT
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13
it('INSERT returns affected rows', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"INSERT INTO private_table(name) VALUES('noret1') UNION VALUES('noret2')"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
},{}, function(err, res) {
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 2);
assert.equal(out.rows.length, 0);
// Check cache headers
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/43
assert.ok(!res.hasOwnProperty('x-cache-channel'));
assert.equal(res.headers['cache-control'], expected_rw_cache_control);
done();
});
});
// Check results from UPDATE
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13
it('UPDATE returns affected rows', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"UPDATE private_table SET name = upper(name) WHERE name in ('noret1', 'noret2')"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
},{}, function(err, res) {
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 2);
assert.equal(out.rows.length, 0);
// Check cache headers
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/43
assert.ok(!res.hasOwnProperty('x-cache-channel'));
assert.equal(res.headers['cache-control'], expected_rw_cache_control);
done();
});
});
// Check results from DELETE
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13
it('DELETE returns affected rows', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"DELETE FROM private_table WHERE name in ('NORET1', 'NORET2')"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
},{}, function(err, res) {
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 2);
assert.equal(out.rows.length, 0);
// Check cache headers
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/43
assert.ok(!res.hasOwnProperty('x-cache-channel'));
assert.equal(res.headers['cache-control'], expected_rw_cache_control);
done();
});
});
// Check results from INSERT .. RETURNING
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50
it('INSERT with RETURNING returns all results', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"INSERT INTO private_table(name) VALUES('test') RETURNING upper(name), reverse(name)"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
},{}, function(err, res) {
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 1);
assert.equal(out.rows.length, 1);
assert.equal(_.keys(out.rows[0]).length, 2);
assert.equal(out.rows[0].upper, 'TEST');
assert.equal(out.rows[0].reverse, 'tset');
done();
});
});
// Check results from UPDATE .. RETURNING
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50
it('UPDATE with RETURNING returns all results', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"UPDATE private_table SET name = 'tost' WHERE name = 'test' RETURNING upper(name), reverse(name)"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
},{}, function(err, res) {
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 1);
assert.equal(out.rows.length, 1);
assert.equal(_.keys(out.rows[0]).length, 2);
assert.equal(out.rows[0].upper, 'TOST');
assert.equal(out.rows[0].reverse, 'tsot');
done();
});
});
// Check results from DELETE .. RETURNING
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50
it('DELETE with RETURNING returns all results', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"DELETE FROM private_table WHERE name = 'tost' RETURNING name"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
},{}, function(err, res) {
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 1);
assert.equal(out.rows.length, 1);
assert.equal(_.keys(out.rows[0]).length, 1);
assert.equal(out.rows[0].name, 'tost');
done();
});
});
it('GET /api/v1/sql with SQL parameter on DROP TABLE. should fail', function(done){
assert.response(server, {
url: "/api/v1/sql?q=DROP%20TABLE%20untitle_table_4",
@ -588,103 +189,6 @@ it('CREATE TABLE with GET and auth', function(done){
});
});
// See http://github.com/CartoDB/CartoDB-SQL-API/issues/127
it('SELECT INTO with paging ', function(done){
var esc_tabname = 'test ""select into""'; // escaped ident
step(
function select_into() {
var next = this;
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: 'SELECT generate_series(1,10) InTO "' + esc_tabname + '"',
rows_per_page: 1, page: 1,
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{}, function(err, res) { next(null, res); });
},
function check_res_test_fake_into_1(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var next = this;
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: 'SELECT \' INTO "c"\' FROM "' + esc_tabname + '"',
rows_per_page: 1, page: 1,
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{}, function(err, res) { next(null, res); });
},
function check_res_drop_table(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var out = JSON.parse(res.body);
assert.equal(out.total_rows, 1); // windowing works
var next = this;
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: 'DROP TABLE "' + esc_tabname + '"',
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, {}, function(err, res) { next(null, res); });
},
function check_drop(err, res) {
assert.ifError(err);
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
return null;
},
function finish(err) {
done(err);
}
);
});
// Test effects of COPY
// See https://github.com/Vizzuality/cartodb-management/issues/1502
it('COPY TABLE with GET and auth', function(done){
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: 'COPY test_table FROM stdin;',
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{}, function(err, res) {
// We expect a problem, actually
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":["COPY from stdin failed: No source stream defined"]});
done();
});
});
it('COPY TABLE with GET and auth', function(done){
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: "COPY test_table to '/tmp/x';",
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{}, function(err, res) {
// We expect a problem, actually
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: ["must be superuser to COPY to or from a file"],
hint: "Anyone can COPY to stdout or from stdin. psql's \\copy command also works for anyone."
});
done();
});
});
it('ALTER TABLE with GET and auth', function(done){
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
@ -890,67 +394,6 @@ it('GET /api/v1/sql with SQL parameter and no format, but a filename', function(
});
});
it('field named "the_geom_webmercator" is not skipped by default', function(done){
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{ }, function(err, res){
assert.equal(res.statusCode, 200, res.body);
var row0 = JSON.parse(res.body).rows[0];
var checkfields = {'name':1, 'cartodb_id':1, 'the_geom':1, 'the_geom_webmercator':1};
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 included fields', function(done){
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&skipfields=the_geom_webmercator,cartodb_id,unexistant',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{ }, function(err, res){
assert.equal(res.statusCode, 200, res.body);
var row0 = JSON.parse(res.body).rows[0];
var checkfields = {'name':1, 'cartodb_id':0, 'the_geom':1, '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('multiple skipfields parameter do not kill the backend', function(done){
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&skipfields=unexistent,the_geom_webmercator' +
'&skipfields=cartodb_id,unexistant',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{ }, function(err, res){
assert.equal(res.statusCode, 200, res.body);
var row0 = JSON.parse(res.body).rows[0];
var checkfields = {'name':1, 'cartodb_id':0, 'the_geom':1, '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 ensure cross domain set on errors', function(done){
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*gadfgadfg%20FROM%20untitle_table_4',
@ -967,68 +410,6 @@ it('GET /api/v1/sql ensure cross domain set on errors', function(done){
});
});
var systemQueriesSuitesToTest = [
{
desc: 'pg_ queries work with api_key and fail otherwise',
queries: [
'SELECT * FROM pg_attribute',
'SELECT * FROM PG_attribute',
'SELECT * FROM "pg_attribute"',
'SELECT a.* FROM untitle_table_4 a,pg_attribute',
'SELECT * FROM geometry_columns'
],
api_key_works: true,
no_api_key_works: false
},
{
desc: 'Possible false positive queries will work with api_key and without it',
queries: [
"SELECT 'pg_'",
'SELECT pg_attribute FROM ( select 1 as pg_attribute ) as f',
'SELECT * FROM cpg_test'
],
api_key_works: true,
no_api_key_works: true
},
{
desc: 'Set queries will FAIL for both api_key and no_api_key queries',
queries: [
' SET work_mem TO 80000',
' set statement_timeout TO 400'
],
api_key_works: false,
no_api_key_works: false
}
];
systemQueriesSuitesToTest.forEach(function(suiteToTest) {
var apiKeyStatusErrorCode = !!suiteToTest.api_key_works ? 200 : 403;
testSystemQueries(suiteToTest.desc + ' with api_key', suiteToTest.queries, apiKeyStatusErrorCode, '1234');
var noApiKeyStatusErrorCode = !!suiteToTest.no_api_key_works ? 200 : 403;
testSystemQueries(suiteToTest.desc, suiteToTest.queries, noApiKeyStatusErrorCode);
});
function testSystemQueries(description, queries, statusErrorCode, apiKey) {
queries.forEach(function(query) {
it('[' + description + '] query: ' + query, function(done) {
var queryStringParams = {q: query};
if (!!apiKey) {
queryStringParams.api_key = apiKey;
}
var request = {
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET',
url: '/api/v1/sql?' + querystring.stringify(queryStringParams)
};
assert.response(server, request, function(err, response) {
assert.equal(response.statusCode, statusErrorCode);
done();
});
});
});
}
it('GET decent error if domain is incorrect', function(done){
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
@ -1122,26 +503,6 @@ it('field names and types are exposed', function(done){
});
});
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/109
it('schema response takes skipfields into account', function(done){
assert.response(server, {
url: '/api/v1/sql?' + querystring.stringify({
q: "SELECT 1 as a, 2 as b, 3 as c ",
skipfields: 'b'
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{ }, function(err, res) {
assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body);
assert.equal(_.keys(parsedBody.fields).length, 2);
assert.ok(parsedBody.fields.hasOwnProperty('a'));
assert.ok(!parsedBody.fields.hasOwnProperty('b'));
assert.ok(parsedBody.fields.hasOwnProperty('c'));
done();
});
});
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/100
it('numeric fields are rendered as numbers in JSON', function(done){
assert.response(server, {
@ -1382,28 +743,6 @@ it('notice and warning info in JSON output', function(done){
);
});
/**
* CORS
*/
it('GET /api/v1/sql with SQL parameter on SELECT only should return CORS headers ', function(done){
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&database=cartodb_test_user_1_db',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{ }, function(err, res) {
assert.equal(res.statusCode, 200, res.body);
// Check cache headers
assert.equal(res.headers['x-cache-channel'], 'cartodb_test_user_1_db:public.untitle_table_4');
assert.equal(res.headers['cache-control'], expected_cache_control);
assert.equal(res.headers['access-control-allow-origin'], '*');
assert.equal(
res.headers['access-control-allow-headers'],
"X-Requested-With, X-Prototype-Version, X-CSRF-Token"
);
done();
});
});
it('GET with callback param returns wrapped result set with callback as jsonp', function(done) {
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&callback=foo_jsonp',

View File

@ -0,0 +1,78 @@
require('../helper');
var server = require('../../app/server')();
var assert = require('../support/assert');
var querystring = require('querystring');
describe('copy-statements', function() {
var RESPONSE_OK = {
statusCode: 200
};
before(function(done) {
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: 'CREATE TABLE copy_test_table(a int)',
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, done);
});
after(function(done) {
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: 'DROP TABLE IF EXISTS copy_test_table',
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, done);
});
// Test effects of COPY
// See https://github.com/Vizzuality/cartodb-management/issues/1502
it('COPY TABLE with GET and auth', function(done){
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: 'COPY copy_test_table FROM stdin;',
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{}, function(err, res) {
// We expect a problem, actually
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":["COPY from stdin failed: No source stream defined"]});
done();
});
});
it('COPY TABLE with GET and auth', function(done){
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: "COPY copy_test_table to '/tmp/x';",
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{}, function(err, res) {
// We expect a problem, actually
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: ["must be superuser to COPY to or from a file"],
hint: "Anyone can COPY to stdout or from stdin. psql's \\copy command also works for anyone."
});
done();
});
});
});

View File

@ -0,0 +1,173 @@
require('../helper');
var server = require('../../app/server')();
var assert = require('../support/assert');
var querystring = require('querystring');
var step = require('step');
describe('results-pagination', function() {
var RESPONSE_OK = {
statusCode: 200
};
// Test for https://github.com/Vizzuality/CartoDB-SQL-API/issues/85
it("paging doesn't break x-cache-channel", function(done) {
assert.response(server, {
url: '/api/v1/sql?' + querystring.stringify({
// note: select casing intentionally mixed
q: 'selECT cartodb_id*3 FROM untitle_table_4',
api_key: '1234',
rows_per_page: 1,
page: 2
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) {
assert.equal(res.headers['x-cache-channel'], 'cartodb_test_user_1_db:public.untitle_table_4');
var parsed = JSON.parse(res.body);
assert.equal(parsed.rows.length, 1);
done();
});
});
// Test page and rows_per_page params
it("paging", function(done){
var sql = 'SELECT * FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(v)';
var pr = [ [2,3], [0,4] ]; // page and rows
var methods = [ 'GET', 'POST' ];
var authorized = 0;
var testing = 0;
var method = 0;
// jshint maxcomplexity:7
var testNext = function() {
if ( testing >= pr.length ) {
if ( method+1 >= methods.length ) {
if ( authorized ) {
done();
return;
} else {
authorized = 1;
method = 0;
testing = 0;
}
} else {
testing = 0;
++method;
}
}
var prcur = pr[testing++];
console.log("Test " + testing + "/" + pr.length + " method " + methods[method] + " " +
( authorized ? "authenticated" : "" ) );
var page = prcur[0];
var nrows = prcur[1];
var data_obj = {
q: sql,
rows_per_page: nrows,
page: page
};
if ( authorized ) {
data_obj.api_key = '1234';
}
var data = querystring.stringify(data_obj);
var req = {
url: '/api/v1/sql',
headers: {host: 'vizzuality.cartodb.com'}
};
if ( methods[method] === 'GET' ) {
req.method = 'GET';
req.url += '?' + data;
} else {
req.method = 'POST';
req.headers['Content-Type'] = 'application/x-www-form-urlencoded';
req.data = data;
}
assert.response(server, req, RESPONSE_OK, function(err, res) {
var parsed = JSON.parse(res.body);
assert.equal(parsed.rows.length, nrows);
for (var i=0; i<nrows; ++i) {
var obt = parsed.rows[i].v;
var exp = page * nrows + i + 1;
assert.equal(obt, exp, "Value " + i + " in page " + page + " is " + obt + ", expected " + exp);
}
testNext();
});
};
testNext();
});
// Test paging with WITH queries
it("paging starting with comment", function(done){
var sql = "-- this is a comment\n" +
"SELECT * FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(v)";
var nrows = 3;
var page = 2;
assert.response(server, {
url: '/api/v1/sql?' + querystring.stringify({
q: sql,
rows_per_page: nrows,
page: page
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) {
var parsed = JSON.parse(res.body);
assert.equal(parsed.rows.length, 3);
for (var i=0; i<nrows; ++i) {
var obt = parsed.rows[i].v;
var exp = page * nrows + i + 1;
assert.equal(obt, exp, "Value " + i + " in page " + page + " is " + obt + ", expected " + exp);
}
done();
});
});
// See http://github.com/CartoDB/CartoDB-SQL-API/issues/127
it('SELECT INTO with paging', function(done){
var esc_tabname = 'test ""select into""'; // escaped ident
step(
function select_into() {
var next = this;
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: 'SELECT generate_series(1,10) InTO "' + esc_tabname + '"',
rows_per_page: 1, page: 1,
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},RESPONSE_OK, function(err, res) { next(null, res); });
},
function check_res_test_fake_into_1(err) {
assert.ifError(err);
var next = this;
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: 'SELECT \' INTO "c"\' FROM "' + esc_tabname + '"',
rows_per_page: 1, page: 1,
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) { next(null, res); });
},
function check_res_drop_table(err, res) {
assert.ifError(err);
var out = JSON.parse(res.body);
assert.equal(out.total_rows, 1); // windowing works
var next = this;
assert.response(server, {
url: "/api/v1/sql?" + querystring.stringify({
q: 'DROP TABLE "' + esc_tabname + '"',
api_key: 1234
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) { next(null, res); });
},
done
);
});
});

View File

@ -0,0 +1,157 @@
require('../helper');
var server = require('../../app/server')();
var assert = require('../support/assert');
var querystring = require('querystring');
var _ = require('underscore');
describe('query-returning', function() {
var RESPONSE_OK = {
statusCode: 200
};
var expected_rw_cache_control = 'no-cache,max-age=0,must-revalidate,public';
// Check results from INSERT
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13
it('INSERT returns affected rows', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"INSERT INTO private_table(name) VALUES('noret1') UNION VALUES('noret2')"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
}, RESPONSE_OK, function(err, res) {
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 2);
assert.equal(out.rows.length, 0);
// Check cache headers
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/43
assert.ok(!res.hasOwnProperty('x-cache-channel'));
assert.equal(res.headers['cache-control'], expected_rw_cache_control);
done();
});
});
// Check results from UPDATE
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13
it('UPDATE returns affected rows', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"UPDATE private_table SET name = upper(name) WHERE name in ('noret1', 'noret2')"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
}, RESPONSE_OK, function(err, res) {
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 2);
assert.equal(out.rows.length, 0);
// Check cache headers
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/43
assert.ok(!res.hasOwnProperty('x-cache-channel'));
assert.equal(res.headers['cache-control'], expected_rw_cache_control);
done();
});
});
// Check results from DELETE
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13
it('DELETE returns affected rows', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"DELETE FROM private_table WHERE name in ('NORET1', 'NORET2')"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
}, RESPONSE_OK, function(err, res) {
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 2);
assert.equal(out.rows.length, 0);
// Check cache headers
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/43
assert.ok(!res.hasOwnProperty('x-cache-channel'));
assert.equal(res.headers['cache-control'], expected_rw_cache_control);
done();
});
});
// Check results from INSERT .. RETURNING
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50
it('INSERT with RETURNING returns all results', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"INSERT INTO private_table(name) VALUES('test') RETURNING upper(name), reverse(name)"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
}, RESPONSE_OK, function(err, res) {
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 1);
assert.equal(out.rows.length, 1);
assert.equal(_.keys(out.rows[0]).length, 2);
assert.equal(out.rows[0].upper, 'TEST');
assert.equal(out.rows[0].reverse, 'tset');
done();
});
});
// Check results from UPDATE .. RETURNING
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50
it('UPDATE with RETURNING returns all results', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"UPDATE private_table SET name = 'tost' WHERE name = 'test' RETURNING upper(name), reverse(name)"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
}, RESPONSE_OK, function(err, res) {
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 1);
assert.equal(out.rows.length, 1);
assert.equal(_.keys(out.rows[0]).length, 2);
assert.equal(out.rows[0].upper, 'TOST');
assert.equal(out.rows[0].reverse, 'tsot');
done();
});
});
// Check results from DELETE .. RETURNING
//
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50
it('DELETE with RETURNING returns all results', function(done){
assert.response(server, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
"DELETE FROM private_table WHERE name = 'tost' RETURNING name"
}),
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
}, RESPONSE_OK, function(err, res) {
var out = JSON.parse(res.body);
assert.ok(out.hasOwnProperty('time'));
assert.equal(out.total_rows, 1);
assert.equal(out.rows.length, 1);
assert.equal(_.keys(out.rows[0]).length, 1);
assert.equal(out.rows[0].name, 'tost');
done();
});
});
});

View File

@ -0,0 +1,92 @@
require('../helper');
var server = require('../../app/server')();
var assert = require('../support/assert');
var querystring = require('querystring');
var _ = require('underscore');
describe('skipfields', function() {
var RESPONSE_OK = {
statusCode: 200
};
it('skipfields controls included fields', function(done){
assert.response(server, {
url: '/api/v1/sql?q=' +
'SELECT%20*%20FROM%20untitle_table_4&skipfields=the_geom_webmercator,cartodb_id,unexistant',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res){
var row0 = JSON.parse(res.body).rows[0];
var checkfields = {'name':1, 'cartodb_id':0, 'the_geom':1, '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('multiple skipfields parameter do not kill the backend', function(done){
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&skipfields=unexistent,the_geom_webmercator' +
'&skipfields=cartodb_id,unexistant',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res){
var row0 = JSON.parse(res.body).rows[0];
var checkfields = {'name':1, 'cartodb_id':0, 'the_geom':1, '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();
});
});
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/109
it('schema response takes skipfields into account', function(done){
assert.response(server, {
url: '/api/v1/sql?' + querystring.stringify({
q: "SELECT 1 as a, 2 as b, 3 as c ",
skipfields: 'b'
}),
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
}, RESPONSE_OK, function(err, res) {
var parsedBody = JSON.parse(res.body);
assert.equal(_.keys(parsedBody.fields).length, 2);
assert.ok(parsedBody.fields.hasOwnProperty('a'));
assert.ok(!parsedBody.fields.hasOwnProperty('b'));
assert.ok(parsedBody.fields.hasOwnProperty('c'));
done();
});
});
it('field named "the_geom_webmercator" is not skipped by default', function(done){
assert.response(server, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{ }, function(err, res){
assert.equal(res.statusCode, 200, res.body);
var row0 = JSON.parse(res.body).rows[0];
var checkfields = {'name':1, 'cartodb_id':1, 'the_geom':1, 'the_geom_webmercator':1};
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();
});
});
});

View File

@ -0,0 +1,71 @@
require('../helper');
var server = require('../../app/server')();
var assert = require('../support/assert');
var querystring = require('querystring');
describe('system-queries', function() {
var systemQueriesSuitesToTest = [
{
desc: 'pg_ queries work with api_key and fail otherwise',
queries: [
'SELECT * FROM pg_attribute',
'SELECT * FROM PG_attribute',
'SELECT * FROM "pg_attribute"',
'SELECT a.* FROM untitle_table_4 a,pg_attribute',
'SELECT * FROM geometry_columns'
],
api_key_works: true,
no_api_key_works: false
},
{
desc: 'Possible false positive queries will work with api_key and without it',
queries: [
"SELECT 'pg_'",
'SELECT pg_attribute FROM ( select 1 as pg_attribute ) as f',
'SELECT * FROM cpg_test'
],
api_key_works: true,
no_api_key_works: true
},
{
desc: 'Set queries will FAIL for both api_key and no_api_key queries',
queries: [
' SET work_mem TO 80000',
' set statement_timeout TO 400'
],
api_key_works: false,
no_api_key_works: false
}
];
systemQueriesSuitesToTest.forEach(function(suiteToTest) {
var apiKeyStatusErrorCode = !!suiteToTest.api_key_works ? 200 : 403;
testSystemQueries(suiteToTest.desc + ' with api_key', suiteToTest.queries, apiKeyStatusErrorCode, '1234');
var noApiKeyStatusErrorCode = !!suiteToTest.no_api_key_works ? 200 : 403;
testSystemQueries(suiteToTest.desc, suiteToTest.queries, noApiKeyStatusErrorCode);
});
function testSystemQueries(description, queries, statusErrorCode, apiKey) {
queries.forEach(function(query) {
it('[' + description + '] query: ' + query, function(done) {
var queryStringParams = {q: query};
if (!!apiKey) {
queryStringParams.api_key = apiKey;
}
var request = {
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET',
url: '/api/v1/sql?' + querystring.stringify(queryStringParams)
};
assert.response(server, request, function(err, response) {
assert.equal(response.statusCode, statusErrorCode);
done();
});
});
});
}
});

View File

@ -105,36 +105,37 @@ fi
if test x"$PREPARE_REDIS" = xyes; then
REDIS_HOST=`node -e "console.log(require('${TESTENV}').redis_host || '127.0.0.1')"`
REDIS_PORT=`node -e "console.log(require('${TESTENV}').redis_port || '6336')"`
echo "preparing redis..."
# delete previous publicuser
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
cat <<EOF | redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -n 5
HDEL rails:users:vizzuality database_host
HDEL rails:users:vizzuality database_publicuser
EOF
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
cat <<EOF | redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -n 5
HMSET rails:users:vizzuality \
id 1 \
database_name ${TEST_DB} \
database_host localhost \
database_host ${PGHOST} \
map_key 1234
SADD rails:users:vizzuality:map_key 1235
EOF
# A user configured as with cartodb-2.5.0+
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
cat <<EOF | redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -n 5
HMSET rails:users:cartodb250user \
id ${TESTUSERID} \
database_name ${TEST_DB} \
database_host localhost \
database_host ${PGHOST} \
database_password ${TESTPASS} \
map_key 1234
EOF
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 3
cat <<EOF | redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -n 3
HMSET rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR \
consumer_key fZeNGv5iYayvItgDYHUbot1Ukb5rVyX6QAg8GaY2 \
consumer_secret IBLCvPEefxbIiGZhGlakYV4eM8AbVSwsHxwEYpzx \
@ -145,17 +146,17 @@ HMSET rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR \
EOF
# delete previous jobs
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
cat <<EOF | redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -n 5
EVAL "return redis.call('del', unpack(redis.call('keys', ARGV[1])))" 0 batch:jobs:*
EOF
# delete job queue
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
cat <<EOF | redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -n 5
DEL batch:queues:localhost
EOF
# delete user index
cat <<EOF | redis-cli -p ${REDIS_PORT} -n 5
cat <<EOF | redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT} -n 5
DEL batch:users:vizzuality
EOF

View File

@ -40,18 +40,18 @@ CREATE TABLE untitle_table_4 (
CONSTRAINT enforce_srid_the_geom_webmercator CHECK ((st_srid(the_geom_webmercator) = 3857))
);
CREATE SEQUENCE test_table_cartodb_id_seq
CREATE SEQUENCE untitle_table_4_cartodb_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE test_table_cartodb_id_seq OWNED BY untitle_table_4.cartodb_id;
ALTER SEQUENCE untitle_table_4_cartodb_id_seq OWNED BY untitle_table_4.cartodb_id;
SELECT pg_catalog.setval('test_table_cartodb_id_seq', 60, true);
SELECT pg_catalog.setval('untitle_table_4_cartodb_id_seq', 60, true);
ALTER TABLE untitle_table_4 ALTER COLUMN cartodb_id SET DEFAULT nextval('test_table_cartodb_id_seq'::regclass);
ALTER TABLE untitle_table_4 ALTER COLUMN cartodb_id SET DEFAULT nextval('untitle_table_4_cartodb_id_seq'::regclass);
INSERT INTO untitle_table_4
(updated_at, created_at, cartodb_id, name, address, the_geom, the_geom_webmercator)
@ -63,10 +63,10 @@ VALUES
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.334931', 5, 'El Pico', 'Calle Divino Pastor 12, Madrid, Spain', '0101000020E61000003B6D8D08C6A10DC0371B2B31CF364440', '0101000020110F00005F716E91992A19C17DAAA4D6DACC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21', -1, 'Test', 'Fake for testing', 'SRID=4326;POINT(33 16)', 'SRID=3857;POINT(3673543.19617803 1804722.76625729)');
ALTER TABLE ONLY untitle_table_4 ADD CONSTRAINT test_table_pkey PRIMARY KEY (cartodb_id);
ALTER TABLE ONLY untitle_table_4 ADD CONSTRAINT untitle_table_4_pkey PRIMARY KEY (cartodb_id);
CREATE INDEX test_table_the_geom_idx ON untitle_table_4 USING gist (the_geom);
CREATE INDEX test_table_the_geom_webmercator_idx ON untitle_table_4 USING gist (the_geom_webmercator);
CREATE INDEX untitle_table_4_the_geom_idx ON untitle_table_4 USING gist (the_geom);
CREATE INDEX untitle_table_4_the_geom_webmercator_idx ON untitle_table_4 USING gist (the_geom_webmercator);
DROP TABLE IF EXISTS private_table;
CREATE TABLE private_table (
@ -85,18 +85,18 @@ CREATE TABLE private_table (
CONSTRAINT enforce_srid_the_geom_webmercator CHECK ((st_srid(the_geom_webmercator) = 3857))
);
CREATE SEQUENCE test_table_cartodb_id_seq_p
CREATE SEQUENCE untitle_table_4_cartodb_id_seq_p
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE test_table_cartodb_id_seq_p OWNED BY private_table.cartodb_id;
ALTER SEQUENCE untitle_table_4_cartodb_id_seq_p OWNED BY private_table.cartodb_id;
SELECT pg_catalog.setval('test_table_cartodb_id_seq_p', 60, true);
SELECT pg_catalog.setval('untitle_table_4_cartodb_id_seq_p', 60, true);
ALTER TABLE private_table ALTER COLUMN cartodb_id SET DEFAULT nextval('test_table_cartodb_id_seq_p'::regclass);
ALTER TABLE private_table ALTER COLUMN cartodb_id SET DEFAULT nextval('untitle_table_4_cartodb_id_seq_p'::regclass);
INSERT INTO private_table VALUES
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.314252', 1, 'Hawai', 'Calle de Pérez Galdós 9, Madrid, Spain', '0101000020E6100000A6B73F170D990DC064E8D84125364440', '0101000020110F000076491621312319C122D4663F1DCC5241'),
@ -105,10 +105,10 @@ INSERT INTO private_table VALUES
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.329509', 4, 'El Lacón', 'Manuel Fernández y González 8, Madrid, Spain', '0101000020E6100000BC5983F755990DC07D923B6C22354440', '0101000020110F00005DACDB056F2319C1EC41A980FCCA5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.334931', 5, 'El Pico', 'Calle Divino Pastor 12, Madrid, Spain', '0101000020E61000003B6D8D08C6A10DC0371B2B31CF364440', '0101000020110F00005F716E91992A19C17DAAA4D6DACC5241');
ALTER TABLE ONLY private_table ADD CONSTRAINT test_table_pkey_p PRIMARY KEY (cartodb_id);
ALTER TABLE ONLY private_table ADD CONSTRAINT untitle_table_4_pkey_p PRIMARY KEY (cartodb_id);
CREATE INDEX test_table_the_geom_idx_p ON private_table USING gist (the_geom);
CREATE INDEX test_table_the_geom_webmercator_idx_p ON private_table USING gist (the_geom_webmercator);
CREATE INDEX untitle_table_4_the_geom_idx_p ON private_table USING gist (the_geom);
CREATE INDEX untitle_table_4_the_geom_webmercator_idx_p ON private_table USING gist (the_geom_webmercator);
-- public user role
DROP USER IF EXISTS :PUBLICUSER;
@ -122,7 +122,7 @@ CREATE USER :TESTUSER WITH PASSWORD ':TESTPASS';
GRANT ALL ON TABLE untitle_table_4 TO :TESTUSER;
GRANT SELECT ON TABLE untitle_table_4 TO :PUBLICUSER;
GRANT ALL ON TABLE private_table TO :TESTUSER;
GRANT ALL ON SEQUENCE test_table_cartodb_id_seq_p TO :TESTUSER;
GRANT ALL ON SEQUENCE untitle_table_4_cartodb_id_seq_p TO :TESTUSER;
GRANT ALL ON TABLE spatial_ref_sys TO :TESTUSER, :PUBLICUSER;