Merge pull request #220 from CartoDB/jshint

Add jshint linting as part of the test suite
This commit is contained in:
Raul Ochoa 2015-05-13 14:55:06 +02:00
commit 80e485d475
41 changed files with 1041 additions and 824 deletions

3
.jshintignore Normal file
View File

@ -0,0 +1,3 @@
test/support/
test/websocket_test/
app/models/formats/topojson.js

98
.jshintrc Normal file
View File

@ -0,0 +1,98 @@
{
// // JSHint Default Configuration File (as on JSHint website)
// // See http://jshint.com/docs/ for more details
//
// "maxerr" : 50, // {int} Maximum error before stopping
//
// // Enforcing
// "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
// "camelcase" : false, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
"freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
"immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
// "indent" : 4, // {int} Number of spaces to use for indentation
// "latedef" : false, // true: Require variables/functions to be defined before being used
"newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
// "noempty" : true, // true: Prohibit use of empty blocks
"nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
"nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment)
// "plusplus" : false, // true: Prohibit use of `++` & `--`
// "quotmark" : false, // Quotation mark consistency:
// // false : do nothing (default)
// // true : ensure whatever is used is consistent
// // "single" : require single quotes
// // "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : true, // true: Require all defined variables be used
// "strict" : true, // true: Requires all functions run in ES5 Strict Mode
// "maxparams" : false, // {int} Max number of formal params allowed per function
// "maxdepth" : false, // {int} Max depth of nested blocks (within functions)
// "maxstatements" : false, // {int} Max number statements per function
"maxcomplexity" : 6, // {int} Max cyclomatic complexity per function
"maxlen" : 120, // {int} Max number of characters per line
//
// // Relaxing
// "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
// "boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
// "eqnull" : false, // true: Tolerate use of `== null`
// "es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
// "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
// "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// // (ex: `for each`, multiple try/catch, function expression…)
// "evil" : false, // true: Tolerate use of `eval` and `new Function()`
// "expr" : false, // true: Tolerate `ExpressionStatement` as Programs
// "funcscope" : false, // true: Tolerate defining variables inside control statements
// "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
// "iterator" : false, // true: Tolerate using the `__iterator__` property
// "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
// "laxbreak" : false, // true: Tolerate possibly unsafe line breakings
// "laxcomma" : false, // true: Tolerate comma-first style coding
// "loopfunc" : false, // true: Tolerate functions being defined in loops
// "multistr" : false, // true: Tolerate multi-line strings
// "noyield" : false, // true: Tolerate generator functions with no yield statement in them.
// "notypeof" : false, // true: Tolerate invalid typeof operator values
// "proto" : false, // true: Tolerate using the `__proto__` property
// "scripturl" : false, // true: Tolerate script-targeted URLs
// "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
// "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
// "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
// "validthis" : false, // true: Tolerate using this in a non-constructor function
//
// // Environments
// "browser" : true, // Web Browser (window, document, etc)
// "browserify" : false, // Browserify (node.js code in the browser)
// "couch" : false, // CouchDB
// "devel" : true, // Development/debugging (alert, confirm, etc)
// "dojo" : false, // Dojo Toolkit
// "jasmine" : false, // Jasmine
// "jquery" : false, // jQuery
"mocha" : true, // Mocha
// "mootools" : false, // MooTools
"node" : true, // Node.js
// "nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
// "prototypejs" : false, // Prototype and Scriptaculous
// "qunit" : false, // QUnit
// "rhino" : false, // Rhino
// "shelljs" : false, // ShellJS
// "worker" : false, // Web Workers
// "wsh" : false, // Windows Scripting Host
// "yui" : false, // Yahoo User Interface
// Custom Globals
"globals" : { // additional predefined global variables
"suite": true,
"suiteSetup": true,
"test": true,
"suiteTeardown": true,
"beforeEach": true,
"afterEach": true,
"before": true,
"after": true,
"describe": true,
"it": true
}
}

View File

@ -22,5 +22,4 @@ env:
language: node_js language: node_js
node_js: node_js:
- "0.8"
- "0.10" - "0.10"

View File

@ -6,3 +6,23 @@ clean:
check: check:
npm test npm test
jshint:
@echo "***jshint***"
@./node_modules/.bin/jshint app/ test/ app.js
test:
@echo "***tests***"
test/run_tests.sh ${RUNTESTFLAGS} test/unit/*.js test/unit/model/*.js test/acceptance/*.js test/acceptance/export/*.js
test-unit:
@echo "***unit tests***"
test/run_tests.sh ${RUNTESTFLAGS} test/unit/*.js test/unit/model/*.js
test-acceptance:
@echo "***acceptance tests***"
test/run_tests.sh ${RUNTESTFLAGS} test/acceptance/*.js test/acceptance/export/*.js
test-all: jshint test
.PHONY: test

46
app.js
View File

@ -9,20 +9,24 @@
* environments: [development, test, production] * environments: [development, test, production]
* *
*/ */
var _ = require('underscore'), var _ = require('underscore');
fs = require('fs'), var fs = require('fs');
path = require('path'); var path = require('path');
if ( process.argv[2] ) ENV = process.argv[2]; var ENV = process.env.NODE_ENV || 'development';
else if ( process.env['NODE_ENV'] ) ENV = process.env['NODE_ENV'];
else ENV = 'development';
process.env['NODE_ENV'] = ENV; if (process.argv[2]) {
ENV = process.argv[2];
}
process.env.NODE_ENV = ENV;
var availableEnvironments = ['development', 'production', 'test', 'staging'];
// sanity check arguments // sanity check arguments
if (ENV != 'development' && ENV != 'production' && ENV != 'test' && ENV != 'staging' ) { if (availableEnvironments.indexOf(ENV) === -1) {
console.error("\nnode app.js [environment]"); console.error("\nnode app.js [environment]");
console.error("environments: development, staging, production, test"); console.error("environments: " + availableEnvironments.join(', '));
process.exit(1); process.exit(1);
} }
@ -32,8 +36,8 @@ var env = require(__dirname + '/config/environments/' + ENV);
env.api_hostname = require('os').hostname().split('.')[0]; env.api_hostname = require('os').hostname().split('.')[0];
_.extend(global.settings, env); _.extend(global.settings, env);
global.log4js = require('log4js') global.log4js = require('log4js');
log4js_config = { var log4js_config = {
appenders: [], appenders: [],
replaceConsole:true replaceConsole:true
}; };
@ -63,31 +67,33 @@ if ( global.settings.rollbar ) {
}); });
} }
log4js.configure(log4js_config, { cwd: __dirname }); global.log4js.configure(log4js_config, { cwd: __dirname });
global.logger = log4js.getLogger(); global.logger = global.log4js.getLogger();
// kick off controller // kick off controller
if ( ! global.settings.base_url ) global.settings.base_url = '/api/*'; if ( ! global.settings.base_url ) {
global.settings.base_url = '/api/*';
}
var version = require("./package").version; var version = require("./package").version;
var app = require(global.settings.app_root + '/app/controllers/app')(); var app = require(global.settings.app_root + '/app/controllers/app')();
app.listen(global.settings.node_port, global.settings.node_host, function() { app.listen(global.settings.node_port, global.settings.node_host, function() {
console.log("CartoDB SQL API " + version + " listening on " + console.log(
global.settings.node_host + ":" + global.settings.node_port + "CartoDB SQL API %s listening on %s:%s with base_url %s (%s)",
" with base_url " + global.settings.base_url version, global.settings.node_host, global.settings.node_port, global.settings.base_url, ENV
+ " (" + ENV + ")"); );
}); });
process.on('uncaughtException', function(err) { process.on('uncaughtException', function(err) {
logger.error('Uncaught exception: ' + err.stack); global.logger.error('Uncaught exception: ' + err.stack);
}); });
process.on('SIGHUP', function() { process.on('SIGHUP', function() {
global.log4js.clearAndShutdownAppenders(function() { global.log4js.clearAndShutdownAppenders(function() {
global.log4js.configure(log4js_config); global.log4js.configure(log4js_config);
global.logger = log4js.getLogger(); global.logger = global.log4js.getLogger();
console.log('Log files reloaded'); console.log('Log files reloaded');
}); });
}); });

View File

@ -12,10 +12,8 @@ ApikeyAuth.prototype.verifyCredentials = function(options, callback) {
}; };
ApikeyAuth.prototype.hasCredentials = function() { ApikeyAuth.prototype.hasCredentials = function() {
return !!(this.req.query.api_key return !!(this.req.query.api_key || this.req.query.map_key ||
|| this.req.query.map_key (this.req.body && this.req.body.api_key) || (this.req.body && this.req.body.map_key));
|| (this.req.body && this.req.body.api_key)
|| (this.req.body && this.req.body.map_key));
}; };
/** /**
@ -30,14 +28,14 @@ function verifyRequest(req, requiredApi, callback) {
var valid = false; var valid = false;
if ( requiredApi ) { if ( requiredApi ) {
if ( requiredApi == req.query.map_key ) { if ( requiredApi === req.query.map_key ) {
valid = true; valid = true;
} else if ( requiredApi == req.query.api_key ) { } else if ( requiredApi === req.query.api_key ) {
valid = true; valid = true;
// check also in request body // check also in request body
} else if ( req.body && req.body.map_key && requiredApi == req.body.map_key ) { } else if ( req.body && req.body.map_key && requiredApi === req.body.map_key ) {
valid = true; valid = true;
} else if ( req.body && req.body.api_key && requiredApi == req.body.api_key ) { } else if ( req.body && req.body.api_key && requiredApi === req.body.api_key ) {
valid = true; valid = true;
} }
} }

View File

@ -1,10 +1,10 @@
// too bound to the request object, but ok for now // too bound to the request object, but ok for now
var _ = require('underscore') var _ = require('underscore');
, OAuthUtil = require('oauth-client') var OAuthUtil = require('oauth-client');
, url = require('url') var step = require('step');
, Step = require('step'); var assert = require('assert');
var oAuth = function(){ var oAuth = (function(){
var me = { var me = {
oauth_database: 3, oauth_database: 3,
oauth_user_key: "rails:oauth_access_tokens:<%= oauth_access_key %>", oauth_user_key: "rails:oauth_access_tokens:<%= oauth_access_key %>",
@ -15,7 +15,7 @@ var oAuth = function(){
// * in GET request // * in GET request
// * in header // * in header
me.parseTokens = function(req){ me.parseTokens = function(req){
var query_oauth = _.clone(req.method == "POST" ? req.body: req.query); var query_oauth = _.clone(req.method === "POST" ? req.body: req.query);
var header_oauth = {}; var header_oauth = {};
var oauth_variables = ['oauth_body_hash', var oauth_variables = ['oauth_body_hash',
'oauth_consumer_key', 'oauth_consumer_key',
@ -34,9 +34,10 @@ var oAuth = function(){
var header_string = req.headers.authorization; var header_string = req.headers.authorization;
if (!_.isUndefined(header_string)) { if (!_.isUndefined(header_string)) {
_.each(oauth_variables, function(oauth_key){ _.each(oauth_variables, function(oauth_key){
var matched_string = header_string.match(new RegExp(oauth_key + '=\"([^\"]+)\"')) var matched_string = header_string.match(new RegExp(oauth_key + '=\"([^\"]+)\"'));
if (!_.isNull(matched_string)) if (!_.isNull(matched_string)) {
header_oauth[oauth_key] = decodeURIComponent(matched_string[1]); header_oauth[oauth_key] = decodeURIComponent(matched_string[1]);
}
}); });
} }
@ -69,12 +70,12 @@ var oAuth = function(){
var ohash; var ohash;
var signature; var signature;
Step( step(
function getTokensFromURL(){ function getTokensFromURL(){
return oAuth.parseTokens(req); return oAuth.parseTokens(req);
}, },
function getOAuthHash(err, data){ function getOAuthHash(err, data){
if (err) throw err; assert.ifError(err);
// this is oauth request only if oauth headers are present // this is oauth request only if oauth headers are present
this.is_oauth_request = !_.isEmpty(data); this.is_oauth_request = !_.isEmpty(data);
@ -87,8 +88,10 @@ var oAuth = function(){
} }
}, },
function regenerateSignature(err, data){ function regenerateSignature(err, data){
if (err) throw err; assert.ifError(err);
if (!this.is_oauth_request) return null; if (!this.is_oauth_request) {
return null;
}
ohash = data; ohash = data;
var consumer = OAuthUtil.createConsumer(ohash.consumer_key, ohash.consumer_secret); var consumer = OAuthUtil.createConsumer(ohash.consumer_key, ohash.consumer_secret);
@ -98,7 +101,7 @@ var oAuth = function(){
var method = req.method; var method = req.method;
var host = req.headers.host; var host = req.headers.host;
if(!httpProto || (httpProto != 'http' && httpProto != 'https')) { if(!httpProto || (httpProto !== 'http' && httpProto !== 'https')) {
var msg = "Unknown HTTP protocol " + httpProto + "."; var msg = "Unknown HTTP protocol " + httpProto + ".";
err = new Error(msg); err = new Error(msg);
err.http_status = 500; err.http_status = 500;
@ -111,13 +114,13 @@ var oAuth = function(){
// remove signature from passed_tokens // remove signature from passed_tokens
signature = passed_tokens.oauth_signature; signature = passed_tokens.oauth_signature;
delete passed_tokens['oauth_signature']; delete passed_tokens.oauth_signature;
var joined = {}; var joined = {};
// remove oauth_signature from body // remove oauth_signature from body
if(req.body) { if(req.body) {
delete req.body['oauth_signature']; delete req.body.oauth_signature;
} }
_.extend(joined, req.body ? req.body : null); _.extend(joined, req.body ? req.body : null);
_.extend(joined, passed_tokens); _.extend(joined, passed_tokens);
@ -126,7 +129,7 @@ var oAuth = function(){
return signer.sign(method, path, joined); return signer.sign(method, path, joined);
}, },
function checkSignature(err, data){ function checkSignature(err, data){
if (err) throw err; assert.ifError(err);
//console.log(data + " should equal the provided signature: " + signature); //console.log(data + " should equal the provided signature: " + signature);
callback(err, (signature === data && !_.isUndefined(data)) ? true : null); callback(err, (signature === data && !_.isUndefined(data)) ? true : null);
@ -139,7 +142,7 @@ var oAuth = function(){
}; };
return me; return me;
}(); })();
function OAuthAuth(req) { function OAuthAuth(req) {
this.req = req; this.req = req;

View File

@ -14,36 +14,47 @@
// eg. vizzuality.cartodb.com/api/v1/?sql=SELECT * from my_table // eg. vizzuality.cartodb.com/api/v1/?sql=SELECT * from my_table
// //
// //
var express = require('express');
var path = require('path');
var step = require('step');
var crypto = require('crypto');
var os = require('os');
var Profiler = require('step-profiler');
var StatsD = require('node-statsd').StatsD;
var PSQL = require('cartodb-psql');
var _ = require('underscore');
var LRU = require('lru-cache');
var assert = require('assert');
if ( ! process.env['PGAPPNAME'] ) var CdbRequest = require('../models/cartodb_request');
process.env['PGAPPNAME']='cartodb_sqlapi'; var AuthApi = require('../auth/auth_api');
var formats = require('../models/formats');
var HealthCheck = require('../monitoring/health_check');
var PgErrorHandler = require('../postgresql/error_handler');
process.env.PGAPPNAME = process.env.PGAPPNAME || 'cartodb_sqlapi';
// jshint ignore:start
function pad(n) { return n < 10 ? '0' + n : n; }
Date.prototype.toJSON = function() {
var s = this.getFullYear() + '-' + pad(this.getMonth() + 1) + '-' + pad(this.getDate()) + 'T' +
pad(this.getHours()) + ':' + pad(this.getMinutes()) + ':' + pad(this.getSeconds());
var offset = this.getTimezoneOffset();
if (offset === 0) {
s += 'Z';
} else {
s += ( offset < 0 ? '+' : '-' ) + pad(Math.abs(offset / 60)) + pad(Math.abs(offset % 60));
}
return s;
};
// jshint ignore:end
// jshint maxcomplexity:21
function App() { function App() {
var path = require('path'); var app = express.createServer();
var express = require('express') var metadataBackend = require('cartodb-redis')({
, app = express.createServer()
, Step = require('step')
, crypto = require('crypto')
, fs = require('fs')
, os = require('os')
, zlib = require('zlib')
, util = require('util')
, Profiler = require('step-profiler')
, StatsD = require('node-statsd').StatsD
, MetadataDB = require('cartodb-redis')
, PSQL = require('cartodb-psql')
, CdbRequest = require(global.settings.app_root + '/app/models/cartodb_request')
, AuthApi = require(global.settings.app_root + '/app/auth/auth_api')
, _ = require('underscore')
, LRU = require('lru-cache')
, formats = require(global.settings.app_root + '/app/models/formats')
, HealthCheck = require(global.settings.app_root + '/app/monitoring/health_check')
, PgErrorHandler = require(global.settings.app_root + '/app/postgresql/error_handler')
;
var metadataBackend = MetadataDB({
host: global.settings.redis_host, host: global.settings.redis_host,
port: global.settings.redis_port, port: global.settings.redis_port,
max: global.settings.redisPool, max: global.settings.redisPool,
@ -63,25 +74,6 @@ var tableCache = LRU({
maxAge: global.settings.tableCacheMaxAge || 1000*60*10 maxAge: global.settings.tableCacheMaxAge || 1000*60*10
}); });
function pad(n) { return n < 10 ? '0' + n : n }
Date.prototype.toJSON = function() {
var s = this.getFullYear() + '-'
+ pad(this.getMonth() + 1) + '-'
+ pad(this.getDate()) + 'T'
+ pad(this.getHours()) + ':'
+ pad(this.getMinutes()) + ':'
+ pad(this.getSeconds());
var offset = this.getTimezoneOffset();
if (offset == 0) s += 'Z';
else {
s += ( offset < 0 ? '+' : '-' )
+ pad(Math.abs(offset / 60))
+ pad(Math.abs(offset % 60))
}
return s;
};
var loggerOpts = { var loggerOpts = {
buffer: true, buffer: true,
format: global.settings.log_format || format: global.settings.log_format ||
@ -89,7 +81,7 @@ var loggerOpts = {
}; };
if ( global.log4js ) { if ( global.log4js ) {
app.use(log4js.connectLogger(log4js.getLogger(), _.defaults(loggerOpts, {level:'info'}))); app.use(global.log4js.connectLogger(global.log4js.getLogger(), _.defaults(loggerOpts, {level:'info'})));
} else { } else {
app.use(express.logger(loggerOpts)); app.use(express.logger(loggerOpts));
} }
@ -110,7 +102,7 @@ if ( global.settings.statsd ) {
var last_err = statsd_client.last_error; var last_err = statsd_client.last_error;
var last_msg = last_err.msg; var last_msg = last_err.msg;
var this_msg = ''+err; var this_msg = ''+err;
if ( this_msg != last_msg ) { if ( this_msg !== last_msg ) {
console.error("statsd client socket error: " + err); console.error("statsd client socket error: " + err);
statsd_client.last_error.count = 1; statsd_client.last_error.count = 1;
statsd_client.last_error.msg = this_msg; statsd_client.last_error.msg = this_msg;
@ -147,7 +139,7 @@ if ( global.settings.hasOwnProperty('node_socket_timeout') ) {
var timeout = parseInt(global.settings.node_socket_timeout); var timeout = parseInt(global.settings.node_socket_timeout);
app.use(function(req, res, next) { app.use(function(req, res, next) {
req.connection.setTimeout(timeout); req.connection.setTimeout(timeout);
next() next();
}); });
} }
@ -216,7 +208,9 @@ function handleQuery(req, res) {
var gn = "the_geom"; // TODO: read from configuration file var gn = "the_geom"; // TODO: read from configuration file
var tableCacheItem; var tableCacheItem;
if ( req.profiler ) req.profiler.start('sqlapi.query'); if ( req.profiler ) {
req.profiler.start('sqlapi.query');
}
req.aborted = false; req.aborted = false;
req.on("close", function() { req.on("close", function() {
@ -248,8 +242,9 @@ function handleQuery(req, res) {
// Accept both comma-separated string or array of comma-separated strings // Accept both comma-separated string or array of comma-separated strings
if ( requestedSkipfields ) { if ( requestedSkipfields ) {
if ( _.isString(requestedSkipfields) ) skipfields = requestedSkipfields.split(','); if ( _.isString(requestedSkipfields) ) {
else if ( _.isArray(requestedSkipfields) ) { skipfields = requestedSkipfields.split(',');
} else if ( _.isArray(requestedSkipfields) ) {
skipfields = []; skipfields = [];
_.each(requestedSkipfields, function(ele) { _.each(requestedSkipfields, function(ele) {
skipfields = skipfields.concat(ele.split(',')); skipfields = skipfields.concat(ele.split(','));
@ -287,14 +282,16 @@ function handleQuery(req, res) {
var authApi = new AuthApi(req, params), var authApi = new AuthApi(req, params),
dbParams; dbParams;
if ( req.profiler ) req.profiler.done('init'); if ( req.profiler ) {
req.profiler.done('init');
}
// 1. Get database from redis via the username stored in the host header subdomain // 1. Get database from redis via the username stored in the host header subdomain
// 2. Run the request through OAuth to get R/W user id if signed // 2. Run the request through OAuth to get R/W user id if signed
// 3. Get the list of tables affected by the query // 3. Get the list of tables affected by the query
// 4. Setup headers // 4. Setup headers
// 5. Send formatted results back // 5. Send formatted results back
Step( step(
function getDatabaseConnectionParams() { function getDatabaseConnectionParams() {
checkAborted('getDatabaseConnectionParams'); checkAborted('getDatabaseConnectionParams');
// If the request is providing credentials it may require every DB parameters // If the request is providing credentials it may require every DB parameters
@ -307,12 +304,14 @@ function handleQuery(req, res) {
function authenticate(err, userDBParams) { function authenticate(err, userDBParams) {
if (err) { if (err) {
err.http_status = 404; err.http_status = 404;
err.message = "Sorry, we can't find CartoDB user '" + cdbUsername err.message = "Sorry, we can't find CartoDB user '" + cdbUsername + "'. " +
+ "'. Please check that you have entered the correct domain."; "Please check that you have entered the correct domain.";
throw err; throw err;
} }
if ( req.profiler ) req.profiler.done('getDBParams'); if ( req.profiler ) {
req.profiler.done('getDBParams');
}
dbParams = userDBParams; dbParams = userDBParams;
@ -330,7 +329,9 @@ function handleQuery(req, res) {
throw err; throw err;
} }
if ( req.profiler ) req.profiler.done('authenticate'); if ( req.profiler ) {
req.profiler.done('authenticate');
}
if (_.isBoolean(isAuthenticated) && isAuthenticated) { if (_.isBoolean(isAuthenticated) && isAuthenticated) {
authenticated = isAuthenticated; authenticated = isAuthenticated;
@ -349,9 +350,11 @@ function handleQuery(req, res) {
function queryExplain(err){ function queryExplain(err){
var self = this; var self = this;
if (err) throw err; assert.ifError(err);
if ( req.profiler ) req.profiler.done('setDBAuth'); if ( req.profiler ) {
req.profiler.done('setDBAuth');
}
checkAborted('queryExplain'); checkAborted('queryExplain');
@ -373,16 +376,20 @@ function handleQuery(req, res) {
var tables = raw_tables.split(/^\{(.*)\}$/)[1].split(','); var tables = raw_tables.split(/^\{(.*)\}$/)[1].split(',');
self(null, tables); self(null, tables);
} else { } else {
console.error("Unexpected result from CDB_QueryTables($quotesql$" + sql + "$quotesql$): " + result); console.error(
"Unexpected result from CDB_QueryTables($quotesql$" + sql + "$quotesql$): " + result
);
self(null, []); self(null, []);
} }
}); });
} }
}, },
function setHeaders(err, tables){ function setHeaders(err, tables){
if (err) throw err; assert.ifError(err);
if ( req.profiler ) req.profiler.done('queryExplain'); if ( req.profiler ) {
req.profiler.done('queryExplain');
}
checkAborted('setHeaders'); checkAborted('setHeaders');
@ -410,8 +417,8 @@ function handleQuery(req, res) {
} }
} }
var fClass = formats[format]; var FormatClass = formats[format];
formatter = new fClass(); formatter = new FormatClass();
req.formatter = formatter; req.formatter = formatter;
@ -454,7 +461,7 @@ function handleQuery(req, res) {
return null; return null;
}, },
function generateFormat(err){ function generateFormat(err){
if (err) throw err; assert.ifError(err);
checkAborted('generateFormat'); checkAborted('generateFormat');
// TODO: drop this, fix UI! // TODO: drop this, fix UI!
@ -493,25 +500,32 @@ function handleQuery(req, res) {
function errorHandle(err){ function errorHandle(err){
formatter = null; formatter = null;
if ( err ) handleException(err, res); if ( err ) {
handleException(err, res);
}
if ( req.profiler ) { if ( req.profiler ) {
req.profiler.sendStats(); // TODO: do on nextTick ? req.profiler.sendStats(); // TODO: do on nextTick ?
} }
if (statsd_client) { if (statsd_client) {
if ( err ) statsd_client.increment('sqlapi.query.error'); if ( err ) {
else statsd_client.increment('sqlapi.query.success'); statsd_client.increment('sqlapi.query.error');
} else {
statsd_client.increment('sqlapi.query.success');
}
} }
} }
); );
} catch (err) { } catch (err) {
handleException(err, res); handleException(err, res);
if (statsd_client) statsd_client.increment('sqlapi.query.error'); if (statsd_client) {
statsd_client.increment('sqlapi.query.error');
}
} }
} }
function handleCacheStatus(req, res){ function handleCacheStatus(req, res){
var tableCacheValues = tableCache.values(); var tableCacheValues = tableCache.values();
var totalExplainHits = _.reduce(tableCacheValues, function(memo, res) { return memo + res.hits}, 0); var totalExplainHits = _.reduce(tableCacheValues, function(memo, res) { return memo + res.hits; }, 0);
var totalExplainKeys = tableCacheValues.length; var totalExplainKeys = tableCacheValues.length;
res.send({explain: {pid: process.pid, hits: totalExplainHits, keys : totalExplainKeys }}); res.send({explain: {pid: process.pid, hits: totalExplainHits, keys : totalExplainKeys }});
} }
@ -543,7 +557,8 @@ function handleHealthCheck(req, res) {
function getContentDisposition(formatter, filename, inline) { function getContentDisposition(formatter, filename, inline) {
var ext = formatter.getFileExtension(); var ext = formatter.getFileExtension();
var time = new Date().toUTCString(); var time = new Date().toUTCString();
return ( inline ? 'inline' : 'attachment' ) +'; filename=' + filename + '.' + ext + '; modification-date="' + time + '";'; return ( inline ? 'inline' : 'attachment' ) + '; filename=' + filename + '.' + ext + '; ' +
'modification-date="' + time + '";';
} }
function setCrossDomain(res){ function setCrossDomain(res){
@ -575,13 +590,13 @@ function handleException(err, res) {
_.defaults(msg, pgErrorHandler.getFields()); _.defaults(msg, pgErrorHandler.getFields());
if (global.settings.environment == 'development') { if (global.settings.environment === 'development') {
msg.stack = err.stack; msg.stack = err.stack;
} }
if (global.settings.environment !== 'test'){ if (global.settings.environment !== 'test'){
// TODO: email this Exception report // TODO: email this Exception report
console.error("EXCEPTION REPORT: " + err.stack) console.error("EXCEPTION REPORT: " + err.stack);
} }
// allow cross site post // allow cross site post

View File

@ -1,6 +1,8 @@
function ArrayBufferSer(type, data, options) { function ArrayBufferSer(type, data, options) {
if(type === undefined) throw "ArrayBufferSer should be created with a type"; if(type === undefined) {
throw "ArrayBufferSer should be created with a type";
}
this.options = options || {}; this.options = options || {};
this._initFunctions(); this._initFunctions();
this.headerSize = 8; this.headerSize = 8;
@ -14,13 +16,14 @@ function ArrayBufferSer(type, data, options) {
var w = this.writeFn[type]; var w = this.writeFn[type];
var i;
if(!this.options.delta) { if(!this.options.delta) {
for(var i = 0; i < data.length; ++i) { for(i = 0; i < data.length; ++i) {
this[w](data[i]); this[w](data[i]);
} }
} else { } else {
this[w](data[0]); this[w](data[0]);
for(var i = 1; i < data.length; ++i) { for(i = 1; i < data.length; ++i) {
this[w](data[i] - data[i - 1]); this[w](data[i] - data[i - 1]);
} }
} }
@ -66,7 +69,7 @@ ArrayBufferSer.prototype = {
var s = this.sizes[type]; var s = this.sizes[type];
if(s) { if(s) {
var r = off % s; var r = off % s;
return r == 0 ? 0 : s - r; return r === 0 ? 0 : s - r;
} }
return 0; return 0;
}, },
@ -78,7 +81,7 @@ ArrayBufferSer.prototype = {
return s*t.length; return s*t.length;
} }
s = 0; s = 0;
if(this.type == ArrayBufferSer.STRING) { if(this.type === ArrayBufferSer.STRING) {
// calculate size with padding // calculate size with padding
t.forEach(function(arr) { t.forEach(function(arr) {
var pad = self._paddingFor(offset, ArrayBufferSer.MAX_PADDING); var pad = self._paddingFor(offset, ArrayBufferSer.MAX_PADDING);
@ -108,15 +111,29 @@ ArrayBufferSer.prototype = {
return this.headerSize + this._sizeFor(this.headerSize, this.data); return this.headerSize + this._sizeFor(this.headerSize, this.data);
}, },
writeFn: ['', 'writeInt8', 'writeUInt8','writeUInt8Clamp', 'writeInt16LE', 'writeUInt16LE', 'writeUInt32LE', 'writeUInt32LE', 'writeFloatLE', 'writeDoubleLE', 'writeString', 'writteBuffer'], writeFn: [
'',
'writeInt8',
'writeUInt8',
'writeUInt8Clamp',
'writeInt16LE',
'writeUInt16LE',
'writeUInt32LE',
'writeUInt32LE',
'writeFloatLE',
'writeDoubleLE',
'writeString',
'writteBuffer'
],
_initFunctions: function() { _initFunctions: function() {
var self = this; var self = this;
this.writeFn.forEach(function(fn) { this.writeFn.forEach(function(fn) {
if(self[fn] === undefined) if(self[fn] === undefined) {
self[fn] = function(d) { self[fn] = function(d) {
self.buffer[fn](d, self.offset); self.buffer[fn](d, self.offset);
self.offset += self.sizes[self.type]; self.offset += self.sizes[self.type];
};
} }
}); });
}, },

View File

@ -30,7 +30,9 @@ function userByHostName(host) {
} }
if (mat.length !== 2) { if (mat.length !== 2) {
console.error("ERROR: pattern '" + re_userFromHost + "' gave unexpected matches against '" + host + "': " + mat); console.error(
"ERROR: pattern '" + re_userFromHost + "' gave unexpected matches against '" + host + "': " + mat
);
return; return;
} }
return mat[1]; return mat[1];

View File

@ -1,6 +1,7 @@
var pg = require('./pg'), var _ = require('underscore');
ArrayBufferSer = require("../bin_encoder"),
_ = require('underscore'); var pg = require('./pg');
var ArrayBufferSer = require("../bin_encoder");
function BinaryFormat() {} function BinaryFormat() {}
@ -9,13 +10,14 @@ BinaryFormat.prototype = new pg('arraybuffer');
BinaryFormat.prototype._contentType = "application/octet-stream"; BinaryFormat.prototype._contentType = "application/octet-stream";
BinaryFormat.prototype._extractTypeFromName = function(name) { BinaryFormat.prototype._extractTypeFromName = function(name) {
var g = name.match(new RegExp(/.*__(uintclamp|uint|int|float)(8|16|32)/i)) var g = name.match(/.*__(uintclamp|uint|int|float)(8|16|32)/i);
if(g && g.length == 3) { if(g && g.length === 3) {
var typeName = g[1] + g[2]; var typeName = g[1] + g[2];
return ArrayBufferSer.typeNames[typeName]; return ArrayBufferSer.typeNames[typeName];
} }
}; };
// jshint maxcomplexity:12
BinaryFormat.prototype.transform = function(result, options, callback) { BinaryFormat.prototype.transform = function(result, options, callback) {
var total_rows = result.rowCount; var total_rows = result.rowCount;
var rows = result.rows; var rows = result.rows;
@ -35,27 +37,28 @@ BinaryFormat.prototype.transform = function(result, options, callback) {
} }
try { try {
var i;
var t;
// get header types (and guess from name) // get header types (and guess from name)
for(var i = 0; i < headersNames.length; ++i) { for(i = 0; i < headersNames.length; ++i) {
var r = rows[0]; r = rows[0];
var n = headersNames[i]; n = headersNames[i];
if(typeof(r[n]) == 'string') { if(typeof(r[n]) === 'string') {
headerTypes.push(ArrayBufferSer.STRING); headerTypes.push(ArrayBufferSer.STRING);
} else if(typeof(r[n]) == 'object') { } else if(typeof(r[n]) === 'object') {
var t = this._extractTypeFromName(n); t = this._extractTypeFromName(n);
t = t == undefined ? ArrayBufferSer.FLOAT32: t; t = t || ArrayBufferSer.FLOAT32;
headerTypes.push(ArrayBufferSer.BUFFER + t); headerTypes.push(ArrayBufferSer.BUFFER + t);
} else { } else {
var t = this._extractTypeFromName(n); t = this._extractTypeFromName(n);
headerTypes.push(t == undefined ? ArrayBufferSer.FLOAT32: t); headerTypes.push(t || ArrayBufferSer.FLOAT32);
} }
} }
// pack the data // pack the data
var header = new ArrayBufferSer(ArrayBufferSer.STRING, headersNames); var header = new ArrayBufferSer(ArrayBufferSer.STRING, headersNames);
var data = [header]; var data = [header];
for(var i = 0; i < headersNames.length; ++i) { for(i = 0; i < headersNames.length; ++i) {
var d = []; var d = [];
var n = headersNames[i]; var n = headersNames[i];
for(var r = 0; r < total_rows; ++r) { for(var r = 0; r < total_rows; ++r) {

View File

@ -1,5 +1,6 @@
var _ = require('underscore'), var _ = require('underscore');
pg = require('./pg');
var pg = require('./pg');
function GeoJsonFormat() { function GeoJsonFormat() {
this.buffer = ''; this.buffer = '';
@ -40,7 +41,7 @@ GeoJsonFormat.prototype.handleQueryRow = function(row) {
'"properties":' '"properties":'
]; ];
delete row[this.opts.gn]; delete row[this.opts.gn];
delete row['the_geom_webmercator']; delete row.the_geom_webmercator;
geojson.push(JSON.stringify(row)); geojson.push(JSON.stringify(row));
geojson.push('}'); geojson.push('}');
@ -52,13 +53,15 @@ GeoJsonFormat.prototype.handleQueryRow = function(row) {
} }
}; };
GeoJsonFormat.prototype.handleQueryEnd = function(result) { GeoJsonFormat.prototype.handleQueryEnd = function(/*result*/) {
if (this.error) { if (this.error) {
this.callback(this.error); this.callback(this.error);
return; return;
} }
if ( this.opts.profiler ) this.opts.profiler.done('gotRows'); if ( this.opts.profiler ) {
this.opts.profiler.done('gotRows');
}
if ( ! this._streamingStarted ) { if ( ! this._streamingStarted ) {
this.startStreaming(); this.startStreaming();
@ -91,7 +94,7 @@ function _toGeoJSON(data, gn, callback){
}; };
_geojson.geometry = JSON.parse(ele[gn]); _geojson.geometry = JSON.parse(ele[gn]);
delete ele[gn]; delete ele[gn];
delete ele["the_geom_webmercator"]; // TODO: use skipfields delete ele.the_geom_webmercator; // TODO: use skipfields
_geojson.properties = ele; _geojson.properties = ele;
out.features.push(_geojson); out.features.push(_geojson);
}); });

View File

@ -2,9 +2,9 @@
// load all the formats // load all the formats
// //
var formats = {}, var formats = {};
path = require('path'), var path = require('path');
folder = __dirname + "/"; var folder = __dirname + "/";
require("fs").readdirSync(folder).forEach(function(file) { require("fs").readdirSync(folder).forEach(function(file) {
if (path.extname(file) === '.js' && file !== 'index.js' && file !== 'ogr.js' && file !== 'pg.js' ) { if (path.extname(file) === '.js' && file !== 'index.js' && file !== 'ogr.js' && file !== 'pg.js' ) {

View File

@ -1,7 +1,7 @@
var pg = require('./pg'), var _ = require('underscore');
util = require('util'),
PgErrorHandler = require(global.settings.app_root + '/app/postgresql/error_handler'), var pg = require('./pg');
_ = require('underscore'); var PgErrorHandler = require('../../postgresql/error_handler');
function JsonFormat() { function JsonFormat() {
this.buffer = ''; this.buffer = '';
@ -12,6 +12,7 @@ JsonFormat.prototype = new pg('json');
JsonFormat.prototype._contentType = "application/json; charset=utf-8"; JsonFormat.prototype._contentType = "application/json; charset=utf-8";
// jshint maxcomplexity:9
JsonFormat.prototype.formatResultFields = function(flds) { JsonFormat.prototype.formatResultFields = function(flds) {
flds = flds || []; flds = flds || [];
var nfields = {}; var nfields = {};
@ -73,13 +74,16 @@ JsonFormat.prototype.handleQueryRow = function(row, result) {
} }
}; };
// jshint maxcomplexity:13
JsonFormat.prototype.handleQueryEnd = function(result) { JsonFormat.prototype.handleQueryEnd = function(result) {
if (this.error && !this._streamingStarted) { if (this.error && !this._streamingStarted) {
this.callback(this.error); this.callback(this.error);
return; return;
} }
if ( this.opts.profiler ) this.opts.profiler.done('gotRows'); if ( this.opts.profiler ) {
this.opts.profiler.done('gotRows');
}
if ( ! this._streamingStarted ) { if ( ! this._streamingStarted ) {
this.startStreaming(); this.startStreaming();
@ -95,7 +99,9 @@ JsonFormat.prototype.handleQueryEnd = function(result) {
var sf = this.opts.skipfields; var sf = this.opts.skipfields;
for (var i = 0; i < result.fields.length; i++) { for (var i = 0; i < result.fields.length; i++) {
var f = result.fields[i]; var f = result.fields[i];
if ( sf.indexOf(f.name) == -1 ) newfields.push(f); if ( sf.indexOf(f.name) === -1 ) {
newfields.push(f);
}
} }
result.fields = newfields; result.fields = newfields;
} }
@ -124,7 +130,7 @@ JsonFormat.prototype.handleQueryEnd = function(result) {
severities.push(severity); severities.push(severity);
notices[severity] = []; notices[severity] = [];
} }
notices[severity].push(notice.message) notices[severity].push(notice.message);
}); });
_.each(severities, function(severity) { _.each(severities, function(severity) {
out.push(','); out.push(',');

View File

@ -1,9 +1,10 @@
var crypto = require('crypto'), var crypto = require('crypto');
Step = require('step'), var step = require('step');
fs = require('fs'), var fs = require('fs');
_ = require('underscore'), var _ = require('underscore');
PSQL = require('cartodb-psql'), var PSQL = require('cartodb-psql');
spawn = require('child_process').spawn; var spawn = require('child_process').spawn;
var assert = require('assert');
// Keeps track of what's waiting baking for export // Keeps track of what's waiting baking for export
var bakingExports = {}; var bakingExports = {};
@ -18,12 +19,12 @@ OgrFormat.prototype = {
is_file: true, is_file: true,
getQuery: function(sql, options) { getQuery: function(/*sql, options*/) {
return null; // dont execute the query return null; // dont execute the query
}, },
transform: function(result, options, callback) { transform: function(/*result, options, callback*/) {
throw "should not be called for file formats" throw "should not be called for file formats";
}, },
getContentType: function(){ return this._contentType; }, getContentType: function(){ return this._contentType; },
@ -50,7 +51,7 @@ OgrFormat.prototype = {
// Internal function usable by all OGR-driven outputs // Internal function usable by all OGR-driven outputs
OgrFormat.prototype.toOGR = function(options, out_format, out_filename, callback) { OgrFormat.prototype.toOGR = function(options, out_format, out_filename, callback) {
var gcol = options.gn; //var gcol = options.gn;
var sql = options.sql; var sql = options.sql;
var skipfields = options.skipfields; var skipfields = options.skipfields;
var out_layername = options.filename; var out_layername = options.filename;
@ -73,15 +74,16 @@ OgrFormat.prototype.toOGR = function(options, out_format, out_filename, callback
// Drop ending semicolon (ogr doens't like it) // Drop ending semicolon (ogr doens't like it)
sql = sql.replace(/;\s*$/, ''); sql = sql.replace(/;\s*$/, '');
Step ( step (
function fetchColumns() { function fetchColumns() {
var colsql = 'SELECT * FROM (' + sql + ') as _cartodbsqlapi LIMIT 0'; var colsql = 'SELECT * FROM (' + sql + ') as _cartodbsqlapi LIMIT 0';
pg = new PSQL(dbopts); pg = new PSQL(dbopts);
pg.query(colsql, this); pg.query(colsql, this);
}, },
// jshint maxcomplexity:9
function findSRS(err, result) { function findSRS(err, result) {
if (err) throw err; assert.ifError(err);
//if ( ! result.rows.length ) throw new Error("Query returns no rows"); //if ( ! result.rows.length ) throw new Error("Query returns no rows");
@ -91,20 +93,29 @@ OgrFormat.prototype.toOGR = function(options, out_format, out_filename, callback
for (var i=0; i<result.fields.length; ++i) { for (var i=0; i<result.fields.length; ++i) {
var field = result.fields[i]; var field = result.fields[i];
var k = field.name; var k = field.name;
if ( skipfields.indexOf(k) != -1 ) continue; if ( skipfields.indexOf(k) !== -1 ) {
if ( out_format != 'CSV' && k == "the_geom_webmercator" ) continue; // TODO: drop ? continue;
if ( out_format == 'CSV' ) columns.push(pg.quoteIdentifier(k)+'::text'); }
else columns.push(pg.quoteIdentifier(k)); if ( out_format !== 'CSV' && k === "the_geom_webmercator" ) {
continue;
} // TODO: drop ?
if ( out_format === 'CSV' ) {
columns.push(pg.quoteIdentifier(k)+'::text');
} else {
columns.push(pg.quoteIdentifier(k));
}
if ( needSRS ) { if ( needSRS ) {
if ( ! geocol && pg.typeName(field.dataTypeID) == 'geometry' ) { if ( ! geocol && pg.typeName(field.dataTypeID) === 'geometry' ) {
geocol = k geocol = k;
} }
} }
} }
//console.log(columns.join(',')); //console.log(columns.join(','));
if ( ! needSRS || ! geocol ) return null; if ( ! needSRS || ! geocol ) {
return null;
}
var next = this; var next = this;
@ -124,23 +135,18 @@ OgrFormat.prototype.toOGR = function(options, out_format, out_filename, callback
}, },
function spawnDumper(err, srid, type) { function spawnDumper(err, srid, type) {
if (err) throw err; assert.ifError(err);
var next = this; var next = this;
var ogrsql = 'SELECT ' + columns.join(',') var ogrsql = 'SELECT ' + columns.join(',') + ' FROM (' + sql + ') as _cartodbsqlapi';
+ ' FROM (' + sql + ') as _cartodbsqlapi';
var ogrargs = [ var ogrargs = [
'-f', out_format, '-f', out_format,
'-lco', 'ENCODING=UTF-8', '-lco', 'ENCODING=UTF-8',
'-lco', 'LINEFORMAT=CRLF', '-lco', 'LINEFORMAT=CRLF',
out_filename, out_filename,
"PG:host=" + dbhost "PG:host=" + dbhost + " port=" + dbport + " user=" + dbuser + " dbname=" + dbname + " password=" + dbpass,
+ " port=" + dbport
+ " user=" + dbuser
+ " dbname=" + dbname
+ " password=" + dbpass,
'-sql', ogrsql '-sql', ogrsql
]; ];
@ -171,7 +177,9 @@ console.log('ogr2ogr ' + _.map(ogrargs, function(x) { return "'" + x + "'"; }).j
child.stderr.on('data', function(data) { child.stderr.on('data', function(data) {
data = data.toString(); // know of a faster way ? data = data.toString(); // know of a faster way ?
// Store only the first ERROR line // Store only the first ERROR line
if ( ! stderr && data.match(logErrPat) ) stderr = data; if ( ! stderr && data.match(logErrPat) ) {
stderr = data;
}
console.log('ogr2ogr stderr: ' + data); console.log('ogr2ogr stderr: ' + data);
}); });
@ -204,7 +212,14 @@ OgrFormat.prototype.toOGR_SingleFile = function(options, fmt, callback) {
var layername = options.filename; var layername = options.filename;
var tmpdir = global.settings.tmpDir || '/tmp'; var tmpdir = global.settings.tmpDir || '/tmp';
var reqKey = [ fmt, dbname, user_id, gcol, this.generateMD5(layername), this.generateMD5(sql) ].concat(skipfields).join(':'); var reqKey = [
fmt,
dbname,
user_id,
gcol,
this.generateMD5(layername),
this.generateMD5(sql)
].concat(skipfields).join(':');
var outdirpath = tmpdir + '/sqlapi-' + process.pid + '-' + reqKey; var outdirpath = tmpdir + '/sqlapi-' + process.pid + '-' + reqKey;
var dumpfile = outdirpath + ':cartodb-query.' + ext; var dumpfile = outdirpath + ':cartodb-query.' + ext;
@ -214,7 +229,7 @@ OgrFormat.prototype.toOGR_SingleFile = function(options, fmt, callback) {
}; };
OgrFormat.prototype.sendResponse = function(opts, callback) { OgrFormat.prototype.sendResponse = function(opts, callback) {
var next = callback; //var next = callback;
var reqKey = this.getKey(opts); var reqKey = this.getKey(opts);
var qElem = new ExportRequest(opts.sink, callback, opts.beforeSink); var qElem = new ExportRequest(opts.sink, callback, opts.beforeSink);
var baking = bakingExports[reqKey]; var baking = bakingExports[reqKey];
@ -223,8 +238,10 @@ OgrFormat.prototype.sendResponse = function(opts, callback) {
} else { } else {
baking = bakingExports[reqKey] = { req: [ qElem ] }; baking = bakingExports[reqKey] = { req: [ qElem ] };
this.generate(opts, function(err, dumpfile) { this.generate(opts, function(err, dumpfile) {
if ( opts.profiler ) opts.profiler.done('generate'); if ( opts.profiler ) {
Step ( opts.profiler.done('generate');
}
step (
function sendResults() { function sendResults() {
var nextPipe = function(finish) { var nextPipe = function(finish) {
var r = baking.req.shift(); var r = baking.req.shift();
@ -234,28 +251,29 @@ OgrFormat.prototype.sendResponse = function(opts, callback) {
}); });
}; };
if ( ! err ) nextPipe(this); if ( ! err ) {
else { nextPipe(this);
} else {
_.each(baking.req, function(r) { _.each(baking.req, function(r) {
r.cb(err); r.cb(err);
}); });
return true; return true;
} }
}, },
function cleanup(err) { function cleanup(/*err*/) {
delete bakingExports[reqKey]; delete bakingExports[reqKey];
// unlink dump file (sync to avoid race condition) // unlink dump file (sync to avoid race condition)
console.log("removing", dumpfile); console.log("removing", dumpfile);
try { fs.unlinkSync(dumpfile); } try { fs.unlinkSync(dumpfile); }
catch (e) { catch (e) {
if ( e.code != 'ENOENT' ) { if ( e.code !== 'ENOENT' ) {
console.log("Could not unlink dumpfile " + dumpfile + ": " + e); console.log("Could not unlink dumpfile " + dumpfile + ": " + e);
} }
} }
} }
); );
}) });
} }
}; };
@ -284,8 +302,10 @@ ExportRequest.prototype.sendFile = function (err, filename, callback) {
if ( ! this.canceled ) { if ( ! this.canceled ) {
//console.log("Creating readable stream out of dumpfile"); //console.log("Creating readable stream out of dumpfile");
this.istream = fs.createReadStream(filename) this.istream = fs.createReadStream(filename)
.on('open', function(fd) { .on('open', function(/*fd*/) {
if ( that.beforeSink ) that.beforeSink(); if ( that.beforeSink ) {
that.beforeSink();
}
that.istream.pipe(that.ostream); that.istream.pipe(that.ostream);
callback(); callback();
}) })

View File

@ -1,5 +1,6 @@
var Step = require('step'), var step = require('step');
PSQL = require('cartodb-psql'); var PSQL = require('cartodb-psql');
var assert = require('assert');
function PostgresFormat(id) { function PostgresFormat(id) {
this.id = id; this.id = id;
@ -7,7 +8,7 @@ function PostgresFormat(id) {
PostgresFormat.prototype = { PostgresFormat.prototype = {
getQuery: function(sql, options) { getQuery: function(sql/*, options*/) {
return sql; return sql;
}, },
@ -34,7 +35,9 @@ PostgresFormat.prototype.handleQueryRowWithSkipFields = function(row, result) {
}; };
PostgresFormat.prototype.handleNotice = function(msg, result) { PostgresFormat.prototype.handleNotice = function(msg, result) {
if ( ! result.notices ) result.notices = []; if ( ! result.notices ) {
result.notices = [];
}
for (var i=0; i<msg.length; i++) { for (var i=0; i<msg.length; i++) {
result.notices.push(msg[i]); result.notices.push(msg[i]);
} }
@ -48,7 +51,9 @@ PostgresFormat.prototype.handleQueryEnd = function(result) {
return; return;
} }
if ( this.opts.profiler ) this.opts.profiler.done('gotRows'); if ( this.opts.profiler ) {
this.opts.profiler.done('gotRows');
}
this.opts.total_time = (Date.now() - this.start_time)/1000; this.opts.total_time = (Date.now() - this.start_time)/1000;
@ -58,14 +63,16 @@ PostgresFormat.prototype.handleQueryEnd = function(result) {
var newfields = []; var newfields = [];
for ( var j=0; j<result.fields.length; ++j ) { for ( var j=0; j<result.fields.length; ++j ) {
var f = result.fields[j]; var f = result.fields[j];
if ( sf.indexOf(f.name) == -1 ) newfields.push(f); if ( sf.indexOf(f.name) === -1 ) {
newfields.push(f);
}
} }
result.fields = newfields; result.fields = newfields;
} }
var that = this; var that = this;
Step ( step (
function packageResult() { function packageResult() {
if ( that.opts.abortChecker ) { if ( that.opts.abortChecker ) {
that.opts.abortChecker('packageResult'); that.opts.abortChecker('packageResult');
@ -74,11 +81,13 @@ PostgresFormat.prototype.handleQueryEnd = function(result) {
}, },
function sendResults(err, out){ function sendResults(err, out){
if (err) throw err; assert.ifError(err);
// return to browser // return to browser
if ( out ) { if ( out ) {
if ( that.opts.beforeSink ) that.opts.beforeSink(); if ( that.opts.beforeSink ) {
that.opts.beforeSink();
}
that.opts.sink.send(out); that.opts.sink.send(out);
} else { } else {
console.error("No output from transform, doing nothing ?!"); console.error("No output from transform, doing nothing ?!");
@ -117,7 +126,9 @@ PostgresFormat.prototype.sendResponse = function(opts, callback) {
callback(err); callback(err);
return; return;
} }
if ( that.opts.profiler ) that.opts.profiler.done('eventedQuery'); if ( that.opts.profiler ) {
that.opts.profiler.done('eventedQuery');
}
if (that.hasSkipFields) { if (that.hasSkipFields) {
query.on('row', that.handleQueryRowWithSkipFields.bind(that)); query.on('row', that.handleQueryRowWithSkipFields.bind(that));

View File

@ -1,8 +1,9 @@
var crypto = require('crypto'), var step = require('step');
Step = require('step'), var fs = require('fs');
fs = require('fs'), var spawn = require('child_process').spawn;
spawn = require('child_process').spawn, var assert = require('assert');
ogr = require('./ogr');
var ogr = require('./ogr');
function ShpFormat() { function ShpFormat() {
} }
@ -42,16 +43,17 @@ ShpFormat.prototype.toSHP = function (options, callback) {
// TODO: following tests: // TODO: following tests:
// - fetch query with no "the_geom" column // - fetch query with no "the_geom" column
Step ( step (
function createOutDir() { function createOutDir() {
fs.mkdir(outdirpath, 0777, this); fs.mkdir(outdirpath, 0777, this);
}, },
function spawnDumper(err) { function spawnDumper(err) {
if ( err ) throw err; assert.ifError(err);
fmtObj.toOGR(options, 'ESRI Shapefile', shapefile, this); fmtObj.toOGR(options, 'ESRI Shapefile', shapefile, this);
}, },
function doZip(err) { function doZip(err) {
if ( err ) throw err; assert.ifError(err);
var next = this; var next = this;
@ -82,21 +84,24 @@ ShpFormat.prototype.toSHP = function (options, callback) {
if ( err ) { if ( err ) {
console.log("Unlinking " + fn + ": " + err); console.log("Unlinking " + fn + ": " + err);
finish(err); finish(err);
} else {
unlinkall(dir, files, finish);
} }
else unlinkall(dir, files, finish)
}); });
}; };
fs.readdir(outdirpath, function(err, files) { fs.readdir(outdirpath, function(err, files) {
if ( err ) { if ( err ) {
if ( err.code != 'ENOENT' ) { if ( err.code !== 'ENOENT' ) {
next(new Error([topError, err].join('\n'))); next(new Error([topError, err].join('\n')));
} else { } else {
next(topError); next(topError);
} }
} else { } else {
unlinkall(outdirpath, files, function(err) { unlinkall(outdirpath, files, function(/*err*/) {
fs.rmdir(outdirpath, function(err) { fs.rmdir(outdirpath, function(err) {
if ( err ) console.log("Removing dir " + path + ": " + err); if ( err ) {
console.log("Removing dir " + outdirpath + ": " + err);
}
next(topError, zipfile); next(topError, zipfile);
}); });
}); });

View File

@ -1,5 +1,4 @@
var pg = require('./pg'), var pg = require('./pg');
_ = require('underscore');
var svg_width = 1024.0; var svg_width = 1024.0;
var svg_height = 768.0; var svg_height = 768.0;
@ -29,26 +28,26 @@ SvgFormat.prototype._contentType = "image/svg+xml; charset=utf-8";
SvgFormat.prototype.getQuery = function(sql, options) { SvgFormat.prototype.getQuery = function(sql, options) {
var gn = options.gn; var gn = options.gn;
var dp = options.dp; var dp = options.dp;
return 'WITH source AS ( ' + sql + '), extent AS ( ' return 'WITH source AS ( ' + sql + '), extent AS ( ' +
+ ' SELECT ST_Extent(' + gn + ') AS e FROM source ' ' SELECT ST_Extent(' + gn + ') AS e FROM source ' +
+ '), extent_info AS ( SELECT e, ' '), extent_info AS ( SELECT e, ' +
+ 'st_xmin(e) as ex0, st_ymax(e) as ey0, ' 'st_xmin(e) as ex0, st_ymax(e) as ey0, ' +
+ 'st_xmax(e)-st_xmin(e) as ew, ' 'st_xmax(e)-st_xmin(e) as ew, ' +
+ 'st_ymax(e)-st_ymin(e) as eh FROM extent )' 'st_ymax(e)-st_ymin(e) as eh FROM extent )' +
+ ', trans AS ( SELECT CASE WHEN ' ', trans AS ( SELECT CASE WHEN ' +
+ 'eh = 0 THEN ' + svg_width 'eh = 0 THEN ' + svg_width +
+ '/ COALESCE(NULLIF(ew,0),' + svg_width +') WHEN ' '/ COALESCE(NULLIF(ew,0),' + svg_width +') WHEN ' +
+ svg_ratio + ' <= (ew / eh) THEN (' svg_ratio + ' <= (ew / eh) THEN (' +
+ svg_width + '/ew ) ELSE (' svg_width + '/ew ) ELSE (' +
+ svg_height + '/eh ) END as s ' svg_height + '/eh ) END as s ' +
+ ', ex0 as x0, ey0 as y0 FROM extent_info ) ' ', ex0 as x0, ey0 as y0 FROM extent_info ) ' +
+ 'SELECT st_TransScale(e, -x0, -y0, s, s)::box2d as ' 'SELECT st_TransScale(e, -x0, -y0, s, s)::box2d as ' +
+ gn + '_box, ST_Dimension(' + gn + ') as ' + gn gn + '_box, ST_Dimension(' + gn + ') as ' + gn +
+ '_dimension, ST_AsSVG(ST_TransScale(' + gn + ', ' '_dimension, ST_AsSVG(ST_TransScale(' + gn + ', ' +
+ '-x0, -y0, s, s), 0, ' + dp + ') as ' + gn '-x0, -y0, s, s), 0, ' + dp + ') as ' + gn +
//+ ', ex0, ey0, ew, eh, s ' // DEBUG ONLY //+ ', ex0, ey0, ew, eh, s ' // DEBUG ONLY +
+ ' FROM trans, extent_info, source' ' FROM trans, extent_info, source' +
+ ' ORDER BY the_geom_dimension ASC'; ' ORDER BY the_geom_dimension ASC';
}; };
SvgFormat.prototype.startStreaming = function() { SvgFormat.prototype.startStreaming = function() {
@ -72,14 +71,11 @@ SvgFormat.prototype.startStreaming = function() {
this.bbox.ymax += growby; this.bbox.ymax += growby;
this.bbox.width = this.bbox.xmax - this.bbox.xmin; this.bbox.width = this.bbox.xmax - this.bbox.xmin;
this.bbox.height = this.bbox.ymax - this.bbox.ymin; this.bbox.height = this.bbox.ymax - this.bbox.ymin;
rootTag += 'viewBox="' + this.bbox.xmin + ' ' + (-this.bbox.ymax) + ' ' rootTag += 'viewBox="' + this.bbox.xmin + ' ' + (-this.bbox.ymax) + ' ' +
+ this.bbox.width + ' ' + this.bbox.height + '" '; this.bbox.width + ' ' + this.bbox.height + '" ';
} }
rootTag += 'style="fill-opacity:' + fill_opacity rootTag += 'style="fill-opacity:' + fill_opacity + '; stroke:' + stroke_color + '; ' +
+ '; stroke:' + stroke_color 'stroke-width:' + stroke_width + '; fill:' + fill_color + '" ';
+ '; stroke-width:' + stroke_width
+ '; fill:' + fill_color
+ '" ';
rootTag += 'xmlns="http://www.w3.org/2000/svg" version="1.1">\n'; rootTag += 'xmlns="http://www.w3.org/2000/svg" version="1.1">\n';
header.push(rootTag); header.push(rootTag);
@ -89,6 +85,7 @@ SvgFormat.prototype.startStreaming = function() {
this._streamingStarted = true; this._streamingStarted = true;
}; };
// jshint maxcomplexity:11
SvgFormat.prototype.handleQueryRow = function(row) { SvgFormat.prototype.handleQueryRow = function(row) {
this.totalRows++; this.totalRows++;
@ -97,8 +94,11 @@ SvgFormat.prototype.handleQueryRow = function(row) {
} }
var g = row[this.opts.gn]; var g = row[this.opts.gn];
if ( ! g ) return; // null or empty if ( ! g ) {
return;
} // null or empty
// jshint ignore:start
var gdims = row[this.opts.gn + '_dimension']; var gdims = row[this.opts.gn + '_dimension'];
// TODO: add an identifier, if any of "cartodb_id", "oid", "id", "gid" are found // TODO: add an identifier, if any of "cartodb_id", "oid", "id", "gid" are found
// TODO: add "class" attribute to help with styling ? // TODO: add "class" attribute to help with styling ?
@ -106,10 +106,11 @@ SvgFormat.prototype.handleQueryRow = function(row) {
this.buffer += '<circle r="' + radius + '" ' + g + ' />\n'; this.buffer += '<circle r="' + radius + '" ' + g + ' />\n';
} else if ( gdims == '1' ) { } else if ( gdims == '1' ) {
// Avoid filling closed linestrings // Avoid filling closed linestrings
this.buffer += '<path ' + ( fill_color != 'none' ? 'fill="none" ' : '' ) + 'd="' + g + '" />\n'; this.buffer += '<path ' + ( fill_color !== 'none' ? 'fill="none" ' : '' ) + 'd="' + g + '" />\n';
} else if ( gdims == '2' ) { } else if ( gdims == '2' ) {
this.buffer += '<path d="' + g + '" />\n'; this.buffer += '<path d="' + g + '" />\n';
} }
// jshint ignore:end
if ( ! this.bbox ) { if ( ! this.bbox ) {
// Parse layer extent: "BOX(x y, X Y)" // Parse layer extent: "BOX(x y, X Y)"

View File

@ -1,7 +1,7 @@
var pg = require('./pg'), var pg = require('./pg');
_ = require('underscore'), var _ = require('underscore');
geojson = require('./geojson'), var geojson = require('./geojson');
TopoJSON = require('topojson'); var TopoJSON = require('topojson');
function TopoJsonFormat() { function TopoJsonFormat() {
this.features = []; this.features = [];
@ -19,7 +19,7 @@ TopoJsonFormat.prototype.handleQueryRow = function(row) {
}; };
_geojson.geometry = JSON.parse(row[this.opts.gn]); _geojson.geometry = JSON.parse(row[this.opts.gn]);
delete row[this.opts.gn]; delete row[this.opts.gn];
delete row["the_geom_webmercator"]; delete row.the_geom_webmercator;
_geojson.properties = row; _geojson.properties = row;
this.features.push(_geojson); this.features.push(_geojson);
}; };
@ -30,7 +30,9 @@ TopoJsonFormat.prototype.handleQueryEnd = function() {
return; return;
} }
if ( this.opts.profiler ) this.opts.profiler.done('gotRows'); if ( this.opts.profiler ) {
this.opts.profiler.done('gotRows');
}
var topology = TopoJSON.topology(this.features, { var topology = TopoJSON.topology(this.features, {
"quantization": 1e4, "quantization": 1e4,

View File

@ -34,7 +34,9 @@ For logger.trace('one','two','three'):
// //
// We only log error and higher errors // We only log error and higher errors
// //
if ( loggingEvent.level.level < 40000 ) return; if ( loggingEvent.level.level < 40000 ) {
return;
}
rollbar.reportMessage(loggingEvent.data); rollbar.reportMessage(loggingEvent.data);
}; };

View File

@ -1,23 +1,19 @@
var Step = require('step'), var step = require('step'),
_ = require('underscore'),
fs = require('fs'); fs = require('fs');
function HealthCheck(metadataBackend, psqlClass) { function HealthCheck(metadataBackend) {
this.metadataBackend = metadataBackend; this.metadataBackend = metadataBackend;
this.psqlClass = psqlClass;
} }
module.exports = HealthCheck; module.exports = HealthCheck;
HealthCheck.prototype.check = function(username, query, callback) { HealthCheck.prototype.check = function(username, query, callback) {
var self = this, var result = {
startTime,
result = {
redis: {}, redis: {},
postgresql: {} postgresql: {}
}; };
Step( step(
function getManualDisable() { function getManualDisable() {
fs.readFile(global.settings.disabled_file, this); fs.readFile(global.settings.disabled_file, this);
}, },

11
npm-shrinkwrap.json generated
View File

@ -143,8 +143,7 @@
}, },
"inherits": { "inherits": {
"version": "2.0.1", "version": "2.0.1",
"from": "inherits@~2.0.1", "from": "inherits@~2.0.1"
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
} }
} }
}, },
@ -156,9 +155,9 @@
} }
}, },
"lru-cache": { "lru-cache": {
"version": "2.5.0", "version": "2.5.2",
"from": "lru-cache@~2.5.0", "from": "lru-cache@~2.5.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.0.tgz" "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz"
}, },
"node-statsd": { "node-statsd": {
"version": "0.0.7", "version": "0.0.7",
@ -220,9 +219,9 @@
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.5.tgz", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.5.tgz",
"dependencies": { "dependencies": {
"wordwrap": { "wordwrap": {
"version": "0.0.2", "version": "0.0.3",
"from": "wordwrap@~0.0.2", "from": "wordwrap@~0.0.2",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
} }
} }
} }

View File

@ -32,11 +32,12 @@
"devDependencies": { "devDependencies": {
"redis": "0.7.1", "redis": "0.7.1",
"mocha": "~1.21.4", "mocha": "~1.21.4",
"jshint": "~2.6.0",
"zipfile": "~0.5.0", "zipfile": "~0.5.0",
"libxmljs": "~0.8.1" "libxmljs": "~0.8.1"
}, },
"scripts": { "scripts": {
"test": "test/run_tests.sh ${RUNTESTFLAGS} test/unit/*.js test/unit/model/*.js test/acceptance/*.js test/acceptance/export/*.js" "test": "make test-all"
}, },
"engines": { "engines": {
"node": ">=0.8 <0.11", "node": ">=0.8 <0.11",

View File

@ -1,40 +1,37 @@
require('../helper'); require('../helper');
require('../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')() var app = require(global.settings.app_root + '/app/controllers/app')();
, assert = require('assert') var assert = require('../support/assert');
, tests = module.exports = {}
, querystring = require('querystring');
suite('app.auth', function() { describe('app.auth', function() {
var scenarios = [ var scenarios = [
{ {
desc: 'valid api key should allow insert in protected tables', desc: 'valid api key should allow insert in protected tables',
url: "/api/v1/sql?api_key=1234&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('app_auth_test1')", url: "/api/v1/sql?api_key=1234&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('app_auth_test1')",
statusCode: 200 statusCode: 200
} },
,{ {
desc: 'valid api key should allow delete in protected tables', desc: 'valid api key should allow delete in protected tables',
url: "/api/v1/sql?api_key=1234&q=DELETE%20FROM%20private_table%20WHERE%20name%3d'app_auth_test1'", url: "/api/v1/sql?api_key=1234&q=DELETE%20FROM%20private_table%20WHERE%20name%3d'app_auth_test1'",
statusCode: 200 statusCode: 200
} },
,{ {
desc: 'invalid api key should NOT allow insert in protected tables', desc: 'invalid api key should NOT allow insert in protected tables',
url: "/api/v1/sql?api_key=RAMBO&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('RAMBO')", url: "/api/v1/sql?api_key=RAMBO&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('RAMBO')",
statusCode: 401 statusCode: 401
} },
,{ {
desc: 'invalid api key (old redis location) should NOT allow insert in protected tables', desc: 'invalid api key (old redis location) should NOT allow insert in protected tables',
url: "/api/v1/sql?api_key=1235&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('RAMBO')", url: "/api/v1/sql?api_key=1235&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('RAMBO')",
statusCode: 401 statusCode: 401
} },
,{ {
desc: 'no api key should NOT allow insert in protected tables', desc: 'no api key should NOT allow insert in protected tables',
url: "/api/v1/sql?q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('RAMBO')", url: "/api/v1/sql?q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('RAMBO')",
statusCode: 401 statusCode: 401
} },
,{ {
desc: 'no api key should NOT allow insert in public tables', desc: 'no api key should NOT allow insert in public tables',
url: "/api/v1/sql?q=INSERT%20INTO%20untitle_table_4%20(name)%20VALUES%20('RAMBO')", url: "/api/v1/sql?q=INSERT%20INTO%20untitle_table_4%20(name)%20VALUES%20('RAMBO')",
statusCode: 401 statusCode: 401
@ -42,7 +39,7 @@ suite('app.auth', function() {
]; ];
scenarios.forEach(function(scenario) { scenarios.forEach(function(scenario) {
test(scenario.desc, function(done) { it(scenario.desc, function(done) {
assert.response(app, { assert.response(app, {
// view prepare_db.sh to find public table name and structure // view prepare_db.sh to find public table name and structure
url: scenario.url, url: scenario.url,

View File

@ -13,27 +13,21 @@
* *
*/ */
require('../helper'); require('../helper');
require('../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')();
var assert = require('../support/assert');
var querystring = require('querystring');
var _ = require('underscore');
var step = require('step');
var app = require(global.settings.app_root + '/app/controllers/app')() describe('app.test', function() {
, assert = require('assert')
, querystring = require('querystring')
, _ = require('underscore')
, zipfile = require('zipfile')
, fs = require('fs')
, libxmljs = require('libxmljs')
, Step = require('step')
;
suite('app.test', function() {
var expected_cache_control = 'no-cache,max-age=31536000,must-revalidate,public'; 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_rw_cache_control = 'no-cache,max-age=0,must-revalidate,public';
var expected_cache_control_persist = 'public,max-age=31536000'; var expected_cache_control_persist = 'public,max-age=31536000';
test('GET /api/v1/version', function(done){ it('GET /api/v1/version', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/version', url: '/api/v1/version',
method: 'GET' method: 'GET'
@ -47,7 +41,7 @@ test('GET /api/v1/version', function(done){
}); });
}); });
test('GET /api/v1/sql', function(done){ it('GET /api/v1/sql', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql', url: '/api/v1/sql',
method: 'GET' method: 'GET'
@ -62,7 +56,7 @@ test('GET /api/v1/sql', function(done){
}); });
// Test base_url setting // Test base_url setting
test('GET /api/whatever/sql', function(done){ it('GET /api/whatever/sql', function(done){
assert.response(app, { assert.response(app, {
url: '/api/whatever/sql?q=SELECT%201', url: '/api/whatever/sql?q=SELECT%201',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -75,7 +69,7 @@ test('GET /api/whatever/sql', function(done){
}); });
// Test CORS headers with GET // Test CORS headers with GET
test('GET /api/whatever/sql', function(done){ it('GET /api/whatever/sql', function(done){
assert.response(app, { assert.response(app, {
url: '/api/whatever/sql?q=SELECT%201', url: '/api/whatever/sql?q=SELECT%201',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -83,14 +77,16 @@ test('GET /api/whatever/sql', function(done){
},{ },{
}, function(res) { }, function(res) {
assert.equal(res.statusCode, 200, res.body); 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-headers'], 'X-Requested-With, X-Prototype-Version, X-CSRF-Token'
);
assert.equal(res.headers['access-control-allow-origin'], '*'); assert.equal(res.headers['access-control-allow-origin'], '*');
done(); done();
}); });
}); });
// Test that OPTIONS does not run queries // Test that OPTIONS does not run queries
test('OPTIONS /api/x/sql', function(done){ it('OPTIONS /api/x/sql', function(done){
assert.response(app, { assert.response(app, {
url: '/api/x/sql?q=syntax%20error', url: '/api/x/sql?q=syntax%20error',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -98,7 +94,9 @@ test('OPTIONS /api/x/sql', function(done){
},{}, function(res) { },{}, function(res) {
assert.equal(res.statusCode, 200, res.body); assert.equal(res.statusCode, 200, res.body);
assert.equal(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-headers'], 'X-Requested-With, X-Prototype-Version, X-CSRF-Token'
);
assert.equal(res.headers['access-control-allow-origin'], '*'); assert.equal(res.headers['access-control-allow-origin'], '*');
done(); done();
}); });
@ -106,7 +104,7 @@ test('OPTIONS /api/x/sql', function(done){
test('GET /api/v1/sql with SQL parameter on SELECT only. No oAuth included ', function(done){ it('GET /api/v1/sql with SQL parameter on SELECT only. No oAuth included ', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&database=cartodb_test_user_1_db', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&database=cartodb_test_user_1_db',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -120,7 +118,7 @@ test('GET /api/v1/sql with SQL parameter on SELECT only. No oAuth included ', fu
}); });
}); });
test('cache_policy=persist', function(done){ it('cache_policy=persist', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&database=cartodb_test_user_1_db&cache_policy=persist', 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'}, headers: {host: 'vizzuality.cartodb.com'},
@ -136,7 +134,7 @@ test('cache_policy=persist', function(done){
}); });
}); });
test('GET /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers', function(done){ it('GET /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -147,7 +145,7 @@ test('GET /api/v1/sql with SQL parameter on SELECT only. no database param, just
}); });
}); });
test('GET /user/vizzuality/api/v1/sql with SQL parameter on SELECT only', function(done){ it('GET /user/vizzuality/api/v1/sql with SQL parameter on SELECT only', function(done){
assert.response(app, { assert.response(app, {
url: '/user/vizzuality/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4', url: '/user/vizzuality/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4',
method: 'GET' method: 'GET'
@ -159,7 +157,7 @@ test('GET /user/vizzuality/api/v1/sql with SQL parameter on SELECT only', functi
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/121 // See https://github.com/CartoDB/CartoDB-SQL-API/issues/121
test('SELECT from user-specific database', function(done){ it('SELECT from user-specific database', function(done){
var backupDBHost = global.settings.db_host; var backupDBHost = global.settings.db_host;
global.settings.db_host = '6.6.6.6'; global.settings.db_host = '6.6.6.6';
assert.response(app, { assert.response(app, {
@ -182,7 +180,7 @@ test('SELECT from user-specific database', function(done){
}); });
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/120 // See https://github.com/CartoDB/CartoDB-SQL-API/issues/120
test('SELECT with user-specific password', function(done){ it('SELECT with user-specific password', function(done){
var backupDBUserPass = global.settings.db_user_pass; var backupDBUserPass = global.settings.db_user_pass;
global.settings.db_user_pass = '<%= user_password %>'; global.settings.db_user_pass = '<%= user_password %>';
assert.response(app, { assert.response(app, {
@ -204,7 +202,7 @@ test('SELECT with user-specific password', function(done){
}); });
}); });
test('GET /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers. Authenticated.', it('GET /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers. Authenticated.',
function(done){ function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20cartodb_id*2%20FROM%20untitle_table_4&api_key=1234', url: '/api/v1/sql?q=SELECT%20cartodb_id*2%20FROM%20untitle_table_4&api_key=1234',
@ -220,7 +218,7 @@ function(done){
}); });
// Test for https://github.com/Vizzuality/CartoDB-SQL-API/issues/85 // Test for https://github.com/Vizzuality/CartoDB-SQL-API/issues/85
test("paging doesn't break x-cache-channel", it("paging doesn't break x-cache-channel",
function(done){ function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
@ -242,13 +240,14 @@ function(done){
}); });
// Test page and rows_per_page params // Test page and rows_per_page params
test("paging", function(done){ it("paging", function(done){
var sql = 'SELECT * FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(v)'; 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 pr = [ [2,3], [0,4] ]; // page and rows
var methods = [ 'GET', 'POST' ]; var methods = [ 'GET', 'POST' ];
var authorized = 0; var authorized = 0;
var testing = 0; var testing = 0;
var method = 0; var method = 0;
// jshint maxcomplexity:7
var testNext = function() { var testNext = function() {
if ( testing >= pr.length ) { if ( testing >= pr.length ) {
if ( method+1 >= methods.length ) { if ( method+1 >= methods.length ) {
@ -266,9 +265,8 @@ test("paging", function(done){
} }
} }
var prcur = pr[testing++]; var prcur = pr[testing++];
console.log("Test " + testing + "/" + pr.length console.log("Test " + testing + "/" + pr.length + " method " + methods[method] + " " +
+ " method " + methods[method] + " " ( authorized ? "authenticated" : "" ) );
+ ( authorized ? "authenticated" : "" ) );
var page = prcur[0]; var page = prcur[0];
var nrows = prcur[1]; var nrows = prcur[1];
var data_obj = { var data_obj = {
@ -276,13 +274,15 @@ test("paging", function(done){
rows_per_page: nrows, rows_per_page: nrows,
page: page page: page
}; };
if ( authorized ) data_obj['api_key'] = '1234'; if ( authorized ) {
data_obj.api_key = '1234';
}
var data = querystring.stringify(data_obj); var data = querystring.stringify(data_obj);
var req = { var req = {
url: '/api/v1/sql', url: '/api/v1/sql',
headers: {host: 'vizzuality.cartodb.com'} headers: {host: 'vizzuality.cartodb.com'}
}; };
if ( methods[method] == 'GET' ) { if ( methods[method] === 'GET' ) {
req.method = 'GET'; req.method = 'GET';
req.url += '?' + data; req.url += '?' + data;
} else { } else {
@ -306,7 +306,7 @@ test("paging", function(done){
}); });
// Test paging with WITH queries // Test paging with WITH queries
test("paging starting with comment", function(done){ it("paging starting with comment", function(done){
var sql = "-- this is a comment\n" + var sql = "-- this is a comment\n" +
"SELECT * FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(v)"; "SELECT * FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(v)";
var nrows = 3; var nrows = 3;
@ -332,7 +332,7 @@ test("paging starting with comment", function(done){
}); });
}); });
test('POST /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers', function(done){ it('POST /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql', url: '/api/v1/sql',
data: querystring.stringify({q: "SELECT * FROM untitle_table_4"}), data: querystring.stringify({q: "SELECT * FROM untitle_table_4"}),
@ -344,9 +344,10 @@ test('POST /api/v1/sql with SQL parameter on SELECT only. no database param, jus
}); });
}); });
test('GET /api/v1/sql with INSERT. oAuth not used, so public user - should fail', function(done){ it('GET /api/v1/sql with INSERT. oAuth not used, so public user - should fail', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?q=INSERT%20INTO%20untitle_table_4%20(cartodb_id)%20VALUES%20(1e4)&database=cartodb_test_user_1_db", url: "/api/v1/sql?q=INSERT%20INTO%20untitle_table_4%20(cartodb_id)%20VALUES%20(1e4)" +
"&database=cartodb_test_user_1_db",
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
method: 'GET' method: 'GET'
},{ },{
@ -361,7 +362,7 @@ test('GET /api/v1/sql with INSERT. oAuth not used, so public user - should fail'
}); });
}); });
test('GET /api/v1/sql with DROP TABLE. oAuth not used, so public user - should fail', function(done){ it('GET /api/v1/sql with DROP TABLE. oAuth not used, so public user - should fail', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?q=DROP%20TABLE%20untitle_table_4&database=cartodb_test_user_1_db", url: "/api/v1/sql?q=DROP%20TABLE%20untitle_table_4&database=cartodb_test_user_1_db",
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -378,7 +379,7 @@ test('GET /api/v1/sql with DROP TABLE. oAuth not used, so public user - should f
}); });
}); });
test('GET /api/v1/sql with INSERT. header based db - should fail', function(){ it('GET /api/v1/sql with INSERT. header based db - should fail', function(){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?q=INSERT%20INTO%20untitle_table_4%20(id)%20VALUES%20(1)", url: "/api/v1/sql?q=INSERT%20INTO%20untitle_table_4%20(id)%20VALUES%20(1)",
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -391,11 +392,10 @@ test('GET /api/v1/sql with INSERT. header based db - should fail', function(){
// Check results from INSERT // Check results from INSERT
// //
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13
test('INSERT returns affected rows', function(done){ it('INSERT returns affected rows', function(done){
assert.response(app, { assert.response(app, {
// view prepare_db.sh to see where to set api_key // view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
+ querystring.stringify({q:
"INSERT INTO private_table(name) VALUES('noret1') UNION VALUES('noret2')" "INSERT INTO private_table(name) VALUES('noret1') UNION VALUES('noret2')"
}), }),
headers: {host: 'vizzuality.localhost.lan:8080' }, headers: {host: 'vizzuality.localhost.lan:8080' },
@ -417,11 +417,10 @@ test('INSERT returns affected rows', function(done){
// Check results from UPDATE // Check results from UPDATE
// //
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13
test('UPDATE returns affected rows', function(done){ it('UPDATE returns affected rows', function(done){
assert.response(app, { assert.response(app, {
// view prepare_db.sh to see where to set api_key // view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
+ querystring.stringify({q:
"UPDATE private_table SET name = upper(name) WHERE name in ('noret1', 'noret2')" "UPDATE private_table SET name = upper(name) WHERE name in ('noret1', 'noret2')"
}), }),
headers: {host: 'vizzuality.localhost.lan:8080' }, headers: {host: 'vizzuality.localhost.lan:8080' },
@ -443,11 +442,10 @@ test('UPDATE returns affected rows', function(done){
// Check results from DELETE // Check results from DELETE
// //
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/13
test('DELETE returns affected rows', function(done){ it('DELETE returns affected rows', function(done){
assert.response(app, { assert.response(app, {
// view prepare_db.sh to see where to set api_key // view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
+ querystring.stringify({q:
"DELETE FROM private_table WHERE name in ('NORET1', 'NORET2')" "DELETE FROM private_table WHERE name in ('NORET1', 'NORET2')"
}), }),
headers: {host: 'vizzuality.localhost.lan:8080' }, headers: {host: 'vizzuality.localhost.lan:8080' },
@ -469,11 +467,10 @@ test('DELETE returns affected rows', function(done){
// Check results from INSERT .. RETURNING // Check results from INSERT .. RETURNING
// //
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50
test('INSERT with RETURNING returns all results', function(done){ it('INSERT with RETURNING returns all results', function(done){
assert.response(app, { assert.response(app, {
// view prepare_db.sh to see where to set api_key // view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
+ querystring.stringify({q:
"INSERT INTO private_table(name) VALUES('test') RETURNING upper(name), reverse(name)" "INSERT INTO private_table(name) VALUES('test') RETURNING upper(name), reverse(name)"
}), }),
headers: {host: 'vizzuality.localhost.lan:8080' }, headers: {host: 'vizzuality.localhost.lan:8080' },
@ -494,11 +491,10 @@ test('INSERT with RETURNING returns all results', function(done){
// Check results from UPDATE .. RETURNING // Check results from UPDATE .. RETURNING
// //
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50
test('UPDATE with RETURNING returns all results', function(done){ it('UPDATE with RETURNING returns all results', function(done){
assert.response(app, { assert.response(app, {
// view prepare_db.sh to see where to set api_key // view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
+ querystring.stringify({q:
"UPDATE private_table SET name = 'tost' WHERE name = 'test' RETURNING upper(name), reverse(name)" "UPDATE private_table SET name = 'tost' WHERE name = 'test' RETURNING upper(name), reverse(name)"
}), }),
headers: {host: 'vizzuality.localhost.lan:8080' }, headers: {host: 'vizzuality.localhost.lan:8080' },
@ -519,11 +515,10 @@ test('UPDATE with RETURNING returns all results', function(done){
// Check results from DELETE .. RETURNING // Check results from DELETE .. RETURNING
// //
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/50
test('DELETE with RETURNING returns all results', function(done){ it('DELETE with RETURNING returns all results', function(done){
assert.response(app, { assert.response(app, {
// view prepare_db.sh to see where to set api_key // view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
+ querystring.stringify({q:
"DELETE FROM private_table WHERE name = 'tost' RETURNING name" "DELETE FROM private_table WHERE name = 'tost' RETURNING name"
}), }),
headers: {host: 'vizzuality.localhost.lan:8080' }, headers: {host: 'vizzuality.localhost.lan:8080' },
@ -540,7 +535,7 @@ test('DELETE with RETURNING returns all results', function(done){
}); });
}); });
test('GET /api/v1/sql with SQL parameter on DROP TABLE. should fail', function(done){ it('GET /api/v1/sql with SQL parameter on DROP TABLE. should fail', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?q=DROP%20TABLE%20untitle_table_4", url: "/api/v1/sql?q=DROP%20TABLE%20untitle_table_4",
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -559,11 +554,10 @@ test('GET /api/v1/sql with SQL parameter on DROP TABLE. should fail', function(d
// Check X-Cache-Channel when querying "updated_at" fields // Check X-Cache-Channel when querying "updated_at" fields
// //
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/99 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/99
test('Field name is not confused with UPDATE operation', function(done){ it('Field name is not confused with UPDATE operation', function(done){
assert.response(app, { assert.response(app, {
// view prepare_db.sh to see where to set api_key // view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&" url: "/api/v1/sql?api_key=1234&" + querystring.stringify({q:
+ querystring.stringify({q:
"SELECT min(updated_at) FROM private_table" "SELECT min(updated_at) FROM private_table"
}), }),
headers: {host: 'vizzuality.localhost.lan:8080' }, headers: {host: 'vizzuality.localhost.lan:8080' },
@ -575,7 +569,7 @@ test('Field name is not confused with UPDATE operation', function(done){
}); });
}); });
test('CREATE TABLE with GET and auth', function(done){ it('CREATE TABLE with GET and auth', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?" + querystring.stringify({ url: "/api/v1/sql?" + querystring.stringify({
q: 'CREATE TABLE test_table(a int)', q: 'CREATE TABLE test_table(a int)',
@ -594,9 +588,9 @@ test('CREATE TABLE with GET and auth', function(done){
}); });
// See http://github.com/CartoDB/CartoDB-SQL-API/issues/127 // See http://github.com/CartoDB/CartoDB-SQL-API/issues/127
test('SELECT INTO with paging ', function(done){ it('SELECT INTO with paging ', function(done){
var esc_tabname = 'test ""select into""'; // escaped ident var esc_tabname = 'test ""select into""'; // escaped ident
Step( step(
function select_into() { function select_into() {
var next = this; var next = this;
assert.response(app, { assert.response(app, {
@ -610,7 +604,7 @@ test('SELECT INTO with paging ', function(done){
},{}, function(res) { next(null, res); }); },{}, function(res) { next(null, res); });
}, },
function check_res_test_fake_into_1(err, res) { function check_res_test_fake_into_1(err, res) {
if ( err ) throw err; assert.ifError(err);
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var next = this; var next = this;
assert.response(app, { assert.response(app, {
@ -624,7 +618,7 @@ test('SELECT INTO with paging ', function(done){
},{}, function(res) { next(null, res); }); },{}, function(res) { next(null, res); });
}, },
function check_res_drop_table(err, res) { function check_res_drop_table(err, res) {
if ( err ) throw err; assert.ifError(err);
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var out = JSON.parse(res.body); var out = JSON.parse(res.body);
assert.equal(out.total_rows, 1); // windowing works assert.equal(out.total_rows, 1); // windowing works
@ -639,19 +633,19 @@ test('SELECT INTO with paging ', function(done){
},{}, function(res) { next(null, res); }); },{}, function(res) { next(null, res); });
}, },
function check_drop(err, res) { function check_drop(err, res) {
if ( err ) throw err; assert.ifError(err);
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
return null; return null;
}, },
function finish(err) { function finish(err) {
done(err) done(err);
} }
); );
}); });
// Test effects of COPY // Test effects of COPY
// See https://github.com/Vizzuality/cartodb-management/issues/1502 // See https://github.com/Vizzuality/cartodb-management/issues/1502
test('COPY TABLE with GET and auth', function(done){ it('COPY TABLE with GET and auth', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?" + querystring.stringify({ url: "/api/v1/sql?" + querystring.stringify({
q: 'COPY test_table FROM stdin;', q: 'COPY test_table FROM stdin;',
@ -669,7 +663,7 @@ test('COPY TABLE with GET and auth', function(done){
}); });
}); });
test('COPY TABLE with GET and auth', function(done){ it('COPY TABLE with GET and auth', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?" + querystring.stringify({ url: "/api/v1/sql?" + querystring.stringify({
q: "COPY test_table to '/tmp/x';", q: "COPY test_table to '/tmp/x';",
@ -690,7 +684,7 @@ test('COPY TABLE with GET and auth', function(done){
}); });
}); });
test('ALTER TABLE with GET and auth', function(done){ it('ALTER TABLE with GET and auth', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?" + querystring.stringify({ url: "/api/v1/sql?" + querystring.stringify({
q: 'ALTER TABLE test_table ADD b int', q: 'ALTER TABLE test_table ADD b int',
@ -708,10 +702,11 @@ test('ALTER TABLE with GET and auth', function(done){
}); });
}); });
test('multistatement insert, alter, select, begin, commit', function(done){ it('multistatement insert, alter, select, begin, commit', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?" + querystring.stringify({ url: "/api/v1/sql?" + querystring.stringify({
q: 'BEGIN; DELETE FROM test_table; COMMIT; BEGIN; INSERT INTO test_table(b) values (5); COMMIT; ALTER TABLE test_table ALTER b TYPE float USING b::float/2; SELECT b FROM test_table;', q: 'BEGIN; DELETE FROM test_table; COMMIT; BEGIN; INSERT INTO test_table(b) values (5); COMMIT; ' +
'ALTER TABLE test_table ALTER b TYPE float USING b::float/2; SELECT b FROM test_table;',
api_key: 1234 api_key: 1234
}), }),
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -725,7 +720,7 @@ test('multistatement insert, alter, select, begin, commit', function(done){
}); });
}); });
test('TRUNCATE TABLE with GET and auth', function(done){ it('TRUNCATE TABLE with GET and auth', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?" + querystring.stringify({ url: "/api/v1/sql?" + querystring.stringify({
q: 'TRUNCATE TABLE test_table', q: 'TRUNCATE TABLE test_table',
@ -752,13 +747,13 @@ test('TRUNCATE TABLE with GET and auth', function(done){
assert.equal(res.headers['cache-control'], expected_cache_control); assert.equal(res.headers['cache-control'], expected_cache_control);
var pbody = JSON.parse(res.body); var pbody = JSON.parse(res.body);
assert.equal(pbody.total_rows, 1); assert.equal(pbody.total_rows, 1);
assert.equal(pbody.rows[0]['count'], 0); assert.equal(pbody.rows[0].count, 0);
done(); done();
}); });
}); });
}); });
test('REINDEX TABLE with GET and auth', function(done){ it('REINDEX TABLE with GET and auth', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?" + querystring.stringify({ url: "/api/v1/sql?" + querystring.stringify({
q: ' ReINdEX TABLE test_table', q: ' ReINdEX TABLE test_table',
@ -776,7 +771,7 @@ test('REINDEX TABLE with GET and auth', function(done){
}); });
}); });
test('DROP TABLE with GET and auth', function(done){ it('DROP TABLE with GET and auth', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?" + querystring.stringify({ url: "/api/v1/sql?" + querystring.stringify({
q: 'DROP TABLE test_table', q: 'DROP TABLE test_table',
@ -812,7 +807,7 @@ test('CREATE FUNCTION with GET and auth', function(done){
}); });
}); });
test('DROP FUNCTION with GET and auth', function(done){ it('DROP FUNCTION with GET and auth', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?" + querystring.stringify({ url: "/api/v1/sql?" + querystring.stringify({
q: 'DROP FUNCTION create_func_test(a int)', q: 'DROP FUNCTION create_func_test(a int)',
@ -830,7 +825,7 @@ test('DROP FUNCTION with GET and auth', function(done){
}); });
}); });
test('sends a 400 when an unsupported format is requested', function(done){ it('sends a 400 when an unsupported format is requested', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=unknown', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=unknown',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -844,7 +839,7 @@ test('sends a 400 when an unsupported format is requested', function(done){
}); });
}); });
test('GET /api/v1/sql with SQL parameter and no format, ensuring content-disposition set to json', function(done){ it('GET /api/v1/sql with SQL parameter and no format, ensuring content-disposition set to json', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -860,7 +855,7 @@ test('GET /api/v1/sql with SQL parameter and no format, ensuring content-disposi
}); });
}); });
test('POST /api/v1/sql with SQL parameter and no format, ensuring content-disposition set to json', function(done){ it('POST /api/v1/sql with SQL parameter and no format, ensuring content-disposition set to json', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql', url: '/api/v1/sql',
data: querystring.stringify({q: "SELECT * FROM untitle_table_4" }), data: querystring.stringify({q: "SELECT * FROM untitle_table_4" }),
@ -877,7 +872,7 @@ test('POST /api/v1/sql with SQL parameter and no format, ensuring content-dispos
}); });
}); });
test('GET /api/v1/sql with SQL parameter and no format, but a filename', function(done){ it('GET /api/v1/sql with SQL parameter and no format, but a filename', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&filename=x', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&filename=x',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -893,7 +888,7 @@ test('GET /api/v1/sql with SQL parameter and no format, but a filename', functio
}); });
}); });
test('field named "the_geom_webmercator" is not skipped by default', function(done){ it('field named "the_geom_webmercator" is not skipped by default', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -913,7 +908,7 @@ test('field named "the_geom_webmercator" is not skipped by default', function(do
}); });
}); });
test('skipfields controls included fields', function(done){ it('skipfields controls included fields', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&skipfields=the_geom_webmercator,cartodb_id,unexistant', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&skipfields=the_geom_webmercator,cartodb_id,unexistant',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -933,9 +928,10 @@ test('skipfields controls included fields', function(done){
}); });
}); });
test('multiple skipfields parameter do not kill the backend', function(done){ it('multiple skipfields parameter do not kill the backend', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&skipfields=unexistent,the_geom_webmercator&skipfields=cartodb_id,unexistant', 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'}, headers: {host: 'vizzuality.cartodb.com'},
method: 'GET' method: 'GET'
},{ }, function(res){ },{ }, function(res){
@ -953,7 +949,7 @@ test('multiple skipfields parameter do not kill the backend', function(done){
}); });
}); });
test('GET /api/v1/sql ensure cross domain set on errors', function(done){ it('GET /api/v1/sql ensure cross domain set on errors', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*gadfgadfg%20FROM%20untitle_table_4', url: '/api/v1/sql?q=SELECT%20*gadfgadfg%20FROM%20untitle_table_4',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -1013,7 +1009,7 @@ var systemQueriesSuitesToTest = [
function testSystemQueries(description, queries, statusErrorCode, apiKey) { function testSystemQueries(description, queries, statusErrorCode, apiKey) {
queries.forEach(function(query) { queries.forEach(function(query) {
test('[' + description + '] query: ' + query, function(done) { it('[' + description + '] query: ' + query, function(done) {
var queryStringParams = {q: query}; var queryStringParams = {q: query};
if (!!apiKey) { if (!!apiKey) {
queryStringParams.api_key = apiKey; queryStringParams.api_key = apiKey;
@ -1031,23 +1027,26 @@ function testSystemQueries(description, queries, statusErrorCode, apiKey) {
}); });
} }
test('GET decent error if domain is incorrect', function(done){ it('GET decent error if domain is incorrect', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
headers: {host: 'vizzualinot.cartodb.com'}, headers: {host: 'vizzualinot.cartodb.com'},
method: 'GET' method: 'GET'
}, {}, function(res){ }, {}, function(res){
assert.equal(res.statusCode, 404, res.statusCode + ( res.statusCode != 200 ? ( ': ' + res.body ) : '')); assert.equal(res.statusCode, 404, res.statusCode + ( res.statusCode !== 200 ? ( ': ' + res.body ) : ''));
assert.deepEqual(res.headers['content-type'], 'application/json; charset=utf-8'); assert.deepEqual(res.headers['content-type'], 'application/json; charset=utf-8');
assert.deepEqual(res.headers['content-disposition'], 'inline'); assert.deepEqual(res.headers['content-disposition'], 'inline');
var result = JSON.parse(res.body); var result = JSON.parse(res.body);
assert.equal(result.error[0],"Sorry, we can't find CartoDB user 'vizzualinot'. Please check that you have entered the correct domain."); assert.equal(
result.error[0],
"Sorry, we can't find CartoDB user 'vizzualinot'. Please check that you have entered the correct domain."
);
done(); done();
}); });
}); });
// this test does not make sense with the current CDB_QueryTables implementation // this test does not make sense with the current CDB_QueryTables implementation
test('GET decent error if SQL is broken', function(done){ it('GET decent error if SQL is broken', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({q: url: '/api/v1/sql?' + querystring.stringify({q:
'SELECT star FROM this and that' 'SELECT star FROM this and that'
@ -1066,10 +1065,9 @@ test('GET decent error if SQL is broken', function(done){
}); });
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/88 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/88
test('numeric arrays are rendered as such', function(done){ it('numeric arrays are rendered as such', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?" url: "/api/v1/sql?" + querystring.stringify({q:
+ querystring.stringify({q:
"SELECT ARRAY[8.7,4.3]::numeric[] as x" "SELECT ARRAY[8.7,4.3]::numeric[] as x"
}), }),
headers: {host: 'vizzuality.localhost.lan:8080' }, headers: {host: 'vizzuality.localhost.lan:8080' },
@ -1090,7 +1088,7 @@ test('numeric arrays are rendered as such', function(done){
}); });
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/97 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/97
test('field names and types are exposed', function(done){ it('field names and types are exposed', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: "SELECT 1::int as a, 2::float8 as b, 3::varchar as c, " + q: "SELECT 1::int as a, 2::float8 as b, 3::varchar as c, " +
@ -1123,7 +1121,7 @@ test('field names and types are exposed', function(done){
}); });
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/109 // See https://github.com/CartoDB/CartoDB-SQL-API/issues/109
test('schema response takes skipfields into account', function(done){ it('schema response takes skipfields into account', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: "SELECT 1 as a, 2 as b, 3 as c ", q: "SELECT 1 as a, 2 as b, 3 as c ",
@ -1143,7 +1141,7 @@ test('schema response takes skipfields into account', function(done){
}); });
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/100 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/100
test('numeric fields are rendered as numbers in JSON', function(done){ it('numeric fields are rendered as numbers in JSON', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: "WITH inp AS ( SELECT 1::int2 as a, 2::int4 as b, " + q: "WITH inp AS ( SELECT 1::int2 as a, 2::int4 as b, " +
@ -1191,8 +1189,8 @@ test('numeric fields are rendered as numbers in JSON', function(done){
// numbers, but it'd currently take running the // numbers, but it'd currently take running the
// test again (new mocha run) with a different TZ // test again (new mocha run) with a different TZ
// //
test('timezone info in JSON output', function(done){ it('timezone info in JSON output', function(done){
Step( step(
function testEuropeRomeExplicit() { function testEuropeRomeExplicit() {
var next = this; var next = this;
assert.response(app, { assert.response(app, {
@ -1213,7 +1211,7 @@ test('timezone info in JSON output', function(done){
}); });
}, },
function testEuropeRomeImplicit(err) { function testEuropeRomeImplicit(err) {
if ( err ) throw err; assert.ifError(err);
var next = this; var next = this;
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
@ -1233,7 +1231,7 @@ test('timezone info in JSON output', function(done){
}); });
}, },
function testUTCExplicit(err) { function testUTCExplicit(err) {
if ( err ) throw err; assert.ifError(err);
var next = this; var next = this;
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
@ -1253,7 +1251,7 @@ test('timezone info in JSON output', function(done){
}); });
}, },
function testUTCImplicit(err) { function testUTCImplicit(err) {
if ( err ) throw err; assert.ifError(err);
var next = this; var next = this;
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
@ -1280,13 +1278,15 @@ test('timezone info in JSON output', function(done){
// WARNING and NOTICE in JSON output // WARNING and NOTICE in JSON output
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/104 // See https://github.com/CartoDB/CartoDB-SQL-API/issues/104
test('notice and warning info in JSON output', function(done){ it('notice and warning info in JSON output', function(done){
Step( step(
function addRaiseFunction() { function addRaiseFunction() {
var next = this; var next = this;
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: "create or replace function raise(lvl text, msg text) returns void as $$ begin if lvl = 'notice' then raise notice '%', msg; elsif lvl = 'warning' then raise warning '%', msg; else raise exception '%', msg; end if; end; $$ language plpgsql;", q: "create or replace function raise(lvl text, msg text) returns void as $$ begin if lvl = 'notice' " +
"then raise notice '%', msg; elsif lvl = 'warning' then raise warning '%', msg; " +
"else raise exception '%', msg; end if; end; $$ language plpgsql;",
api_key: '1234' api_key: '1234'
}), }),
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -1300,7 +1300,7 @@ test('notice and warning info in JSON output', function(done){
}); });
}, },
function raiseNotice(err) { function raiseNotice(err) {
if ( err ) throw err; assert.ifError(err);
var next = this; var next = this;
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
@ -1321,7 +1321,7 @@ test('notice and warning info in JSON output', function(done){
}); });
}, },
function raiseWarning(err) { function raiseWarning(err) {
if ( err ) throw err; assert.ifError(err);
var next = this; var next = this;
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
@ -1342,11 +1342,12 @@ test('notice and warning info in JSON output', function(done){
}); });
}, },
function raiseBothWarningAndNotice(err) { function raiseBothWarningAndNotice(err) {
if ( err ) throw err; assert.ifError(err);
var next = this; var next = this;
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: "SET client_min_messages TO 'notice'; select raise('warning', 'hello again warning'), raise('notice', 'hello again notice');" q: "SET client_min_messages TO 'notice'; select raise('warning', 'hello again warning'), " +
"raise('notice', 'hello again notice');"
}), }),
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
method: 'GET' method: 'GET'
@ -1377,7 +1378,7 @@ test('notice and warning info in JSON output', function(done){
},{ }, function(res) { },{ }, function(res) {
try { try {
assert.equal(res.statusCode, 200, res.body); assert.equal(res.statusCode, 200, res.body);
var parsedBody = JSON.parse(res.body); JSON.parse(res.body);
} catch (e) { err = new Error(err + ',' + e); } } catch (e) { err = new Error(err + ',' + e); }
next(err); next(err);
}); });
@ -1391,7 +1392,7 @@ test('notice and warning info in JSON output', function(done){
/** /**
* CORS * CORS
*/ */
test('GET /api/v1/sql with SQL parameter on SELECT only should return CORS headers ', function(done){ it('GET /api/v1/sql with SQL parameter on SELECT only should return CORS headers ', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&database=cartodb_test_user_1_db', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&database=cartodb_test_user_1_db',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -1402,12 +1403,15 @@ test('GET /api/v1/sql with SQL parameter on SELECT only should return CORS heade
assert.equal(res.headers['x-cache-channel'], 'cartodb_test_user_1_db:public.untitle_table_4'); 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['cache-control'], expected_cache_control);
assert.equal(res.headers['access-control-allow-origin'], '*'); 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"); assert.equal(
res.headers['access-control-allow-headers'],
"X-Requested-With, X-Prototype-Version, X-CSRF-Token"
);
done(); done();
}); });
}); });
test('GET with callback param returns wrapped result set with callback as jsonp', function(done) { it('GET with callback param returns wrapped result set with callback as jsonp', function(done) {
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&callback=foo_jsonp', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&callback=foo_jsonp',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -1419,7 +1423,7 @@ test('GET with callback param returns wrapped result set with callback as jsonp'
}); });
}); });
test('GET with callback must return 200 status error even if it is an error', function(done){ it('GET with callback must return 200 status error even if it is an error', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?q=DROP%20TABLE%20untitle_table_4&callback=foo_jsonp", url: "/api/v1/sql?q=DROP%20TABLE%20untitle_table_4&callback=foo_jsonp",
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -1427,17 +1431,19 @@ test('GET with callback must return 200 status error even if it is an error', fu
},{}, function(res) { },{}, function(res) {
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var didRunJsonCallback = false; var didRunJsonCallback = false;
// jshint ignore:start
function foo_jsonp(body) { function foo_jsonp(body) {
assert.deepEqual(body, {"error":["must be owner of relation untitle_table_4"]}); assert.deepEqual(body, {"error":["must be owner of relation untitle_table_4"]});
didRunJsonCallback = true; didRunJsonCallback = true;
} }
eval(res.body); eval(res.body);
// jshint ignore:end
assert.ok(didRunJsonCallback); assert.ok(didRunJsonCallback);
done(); done();
}); });
}); });
test('GET with slow query exceeding statement timeout returns proper error message', function(done){ it('GET with slow query exceeding statement timeout returns proper error message', function(done){
assert.response(app, { assert.response(app, {
url: "/api/v1/sql?q=select%20pg_sleep(2.1)%20as%20sleep", url: "/api/v1/sql?q=select%20pg_sleep(2.1)%20as%20sleep",
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -1452,7 +1458,7 @@ test('GET with callback must return 200 status error even if it is an error', fu
}); });
}); });
test('stream response is closed on error and error message is part of the response', function(done){ it('stream response is closed on error and error message is part of the response', function(done){
assert.response( assert.response(
app, app,
{ {
@ -1475,7 +1481,7 @@ test('GET with callback must return 200 status error even if it is an error', fu
); );
}); });
test('too large rows get into error log', function(done){ it('too large rows get into error log', function(done){
var dbMaxRowSize = global.settings.db_max_row_size; var dbMaxRowSize = global.settings.db_max_row_size;
global.settings.db_max_row_size = 4; global.settings.db_max_row_size = 4;

View File

@ -1,15 +1,10 @@
require('../helper'); require('../helper');
require('../support/assert');
var assert = require('assert') var assert = require('../support/assert');
, App = require(global.settings.app_root + '/app/controllers/app') var step = require('step');
, querystring = require('querystring') var net = require('net');
, _ = require('underscore')
, Step = require('step')
, net = require('net')
;
var sql_server_port = 5556; var sql_server_port = 5540;
var sql_server = net.createServer(function(c) { var sql_server = net.createServer(function(c) {
console.log('server connected'); console.log('server connected');
c.destroy(); c.destroy();
@ -19,21 +14,21 @@ var sql_server = net.createServer(function(c) {
}); });
}); });
suite('backend crash', function() { describe('backend crash', function() {
suiteSetup(function(done){ before(function(done){
sql_server.listen(sql_server_port, done); sql_server.listen(sql_server_port, done);
}); });
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/135 // See https://github.com/CartoDB/CartoDB-SQL-API/issues/135
test('does not hang server', function(done){ it('does not hang server', function(done){
//console.log("settings:"); console.dir(global.settings); //console.log("settings:"); console.dir(global.settings);
var db_host_backup = global.settings.db_host; var db_host_backup = global.settings.db_host;
var db_port_backup = global.settings.db_port; var db_port_backup = global.settings.db_port;
global.settings.db_host = 'localhost'; global.settings.db_host = 'localhost';
global.settings.db_port = sql_server_port; global.settings.db_port = sql_server_port;
var app = App(); var app = require(global.settings.app_root + '/app/controllers/app')();
Step( step(
function sendQuery() { function sendQuery() {
var next = this; var next = this;
assert.response(app, { assert.response(app, {
@ -45,7 +40,7 @@ test('does not hang server', function(done){
}); });
}, },
function checkResponse(err, res) { function checkResponse(err, res) {
if ( err ) throw err; assert.ifError(err);
assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body); assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body); var parsed = JSON.parse(res.body);
assert.ok(parsed.error); assert.ok(parsed.error);
@ -64,7 +59,7 @@ test('does not hang server', function(done){
}); });
}, },
function checkResponse(err, res) { function checkResponse(err, res) {
if ( err ) throw err; assert.ifError(err);
assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body); assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body);
var parsed = JSON.parse(res.body); var parsed = JSON.parse(res.body);
assert.ok(parsed.error); assert.ok(parsed.error);
@ -80,7 +75,7 @@ test('does not hang server', function(done){
); );
}); });
suiteTeardown(function(done) { after(function(done) {
try { try {
sql_server.close(done); sql_server.close(done);
} catch (er) { } catch (er) {

View File

@ -2,34 +2,16 @@ require('../../helper');
require('../../support/assert'); require('../../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')() var app = require(global.settings.app_root + '/app/controllers/app')();
, assert = require('assert') var assert = require('assert');
, querystring = require('querystring') var querystring = require('querystring');
, _ = require('underscore')
, zipfile = require('zipfile')
, fs = require('fs')
, libxmljs = require('libxmljs')
, Step = require('step')
;
// allow lots of emitters to be set to silence warning // allow lots of emitters to be set to silence warning
app.setMaxListeners(0); app.setMaxListeners(0);
suite('export.arraybuffer', function() { describe('export.arraybuffer', function() {
var expected_cache_control = 'no-cache,max-age=3600,must-revalidate,public'; it('GET /api/v1/sql as arraybuffer ', function(done){
var expected_cache_control_persist = 'public,max-age=31536000';
// use dec_sep for internationalization
var checkDecimals = function(x, dec_sep){
var tmp='' + x;
if (tmp.indexOf(dec_sep)>-1)
return tmp.length-tmp.indexOf(dec_sep)-1;
else
return 0;
}
test('GET /api/v1/sql as arraybuffer ', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: 'SELECT cartodb_id,name,1::integer,187.9 FROM untitle_table_4', q: 'SELECT cartodb_id,name,1::integer,187.9 FROM untitle_table_4',
@ -39,12 +21,12 @@ test('GET /api/v1/sql as arraybuffer ', function(done){
method: 'GET' method: 'GET'
},{ }, function(res){ },{ }, function(res){
assert.equal(res.statusCode, 200, res.body); assert.equal(res.statusCode, 200, res.body);
assert.equal(res.headers['content-type'], "application/octet-stream") assert.equal(res.headers['content-type'], "application/octet-stream");
done(); done();
}); });
}); });
test('GET /api/v1/sql as arraybuffer does not support geometry types ', function(done){ it('GET /api/v1/sql as arraybuffer does not support geometry types ', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: 'SELECT cartodb_id, the_geom FROM untitle_table_4', q: 'SELECT cartodb_id, the_geom FROM untitle_table_4',

View File

@ -2,21 +2,16 @@ require('../../helper');
require('../../support/assert'); require('../../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')() var app = require(global.settings.app_root + '/app/controllers/app')();
, assert = require('assert') var assert = require('assert');
, querystring = require('querystring') var querystring = require('querystring');
, _ = require('underscore')
, zipfile = require('zipfile')
, fs = require('fs')
, libxmljs = require('libxmljs')
;
// allow lots of emitters to be set to silence warning // allow lots of emitters to be set to silence warning
app.setMaxListeners(0); app.setMaxListeners(0);
suite('export.csv', function() { describe('export.csv', function() {
test('CSV format', function(done){ it('CSV format', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: 'SELECT * FROM untitle_table_4 WHERE cartodb_id = 1', q: 'SELECT * FROM untitle_table_4 WHERE cartodb_id = 1',
@ -43,7 +38,7 @@ test('CSV format', function(done){
}); });
}); });
test('CSV format, bigger than 81920 bytes', function(done){ it('CSV format, bigger than 81920 bytes', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql', url: '/api/v1/sql',
data: querystring.stringify({ data: querystring.stringify({
@ -59,7 +54,7 @@ test('CSV format, bigger than 81920 bytes', function(done){
}); });
test('CSV format from POST', function(done){ it('CSV format from POST', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql', url: '/api/v1/sql',
data: querystring.stringify({q: "SELECT * FROM untitle_table_4 LIMIT 1", format: 'csv'}), data: querystring.stringify({q: "SELECT * FROM untitle_table_4 LIMIT 1", format: 'csv'}),
@ -76,7 +71,7 @@ test('CSV format from POST', function(done){
}); });
}); });
test('CSV format, custom filename', function(done){ it('CSV format, custom filename', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=csv&filename=mycsv.csv', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=csv&filename=mycsv.csv',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -89,43 +84,45 @@ test('CSV format, custom filename', function(done){
var ct = res.header('Content-Type'); var ct = res.header('Content-Type');
assert.equal(true, /header=present/.test(ct), "CSV doesn't advertise header presence: " + ct); 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 row0 = res.body.substring(0, res.body.search(/[\n\r]/)).split(',');
var checkfields = {'name':1, 'cartodb_id':1, 'the_geom':1, 'the_geom_webmercator':1}; var checkFields = { name: true, cartodb_id: true, the_geom: true, the_geom_webmercator: true };
for ( var f in checkfields ) { Object.keys(checkFields).forEach(function(f) {
var idx = row0.indexOf(f); var idx = row0.indexOf(f);
if ( checkfields[f] ) { if ( checkFields[f] ) {
assert.ok(idx != -1, "result does not include '" + f + "'"); assert.ok(idx !== -1, "result does not include '" + f + "'");
} else { } else {
assert.ok(idx == -1, "result includes '" + f + "' ("+idx+")"); assert.ok(idx === -1, "result includes '" + f + "' ("+idx+")");
}
} }
});
done(); done();
}); });
}); });
test('skipfields controls fields included in CSV output', function(done){ it('skipfields controls fields included in CSV output', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=csv&skipfields=unexistant,cartodb_id', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=csv' +
'&skipfields=unexistant,cartodb_id',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
method: 'GET' method: 'GET'
},{ }, function(res){ },{ }, function(res){
assert.equal(res.statusCode, 200, res.body); assert.equal(res.statusCode, 200, res.body);
var row0 = res.body.substring(0, res.body.search(/[\n\r]/)).split(','); var row0 = res.body.substring(0, res.body.search(/[\n\r]/)).split(',');
var checkfields = {'name':1, 'cartodb_id':0, 'the_geom':1, 'the_geom_webmercator':1}; var checkFields = { name: true, cartodb_id: false, the_geom: true, the_geom_webmercator: true };
for ( var f in checkfields ) { Object.keys(checkFields).forEach(function(f) {
var idx = row0.indexOf(f); var idx = row0.indexOf(f);
if ( checkfields[f] ) { if ( checkFields[f] ) {
assert.ok(idx != -1, "result does not include '" + f + "'"); assert.ok(idx !== -1, "result does not include '" + f + "'");
} else { } else {
assert.ok(idx == -1, "result includes '" + f + "' ("+idx+")"); assert.ok(idx === -1, "result includes '" + f + "' ("+idx+")");
}
} }
});
done(); done();
}); });
}); });
test('GET /api/v1/sql as csv', function(done){ it('GET /api/v1/sql as csv', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20cartodb_id,ST_AsEWKT(the_geom)%20as%20geom%20FROM%20untitle_table_4%20LIMIT%201&format=csv', 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'}, headers: {host: 'vizzuality.cartodb.com'},
method: 'GET' method: 'GET'
},{ }, function(res){ },{ }, function(res){
@ -137,7 +134,7 @@ test('GET /api/v1/sql as csv', function(done){
}); });
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/60 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/60
test('GET /api/v1/sql as csv with no rows', function(done){ it('GET /api/v1/sql as csv with no rows', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20true%20WHERE%20false&format=csv', url: '/api/v1/sql?q=SELECT%20true%20WHERE%20false&format=csv',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -147,13 +144,12 @@ test('GET /api/v1/sql as csv with no rows', function(done){
var obtained_lines = res.body.split('\r\n'); var obtained_lines = res.body.split('\r\n');
assert.ok(obtained_lines.length <= 2, // may or may not have an header assert.ok(obtained_lines.length <= 2, // may or may not have an header
// See http://trac.osgeo.org/gdal/ticket/5234 // See http://trac.osgeo.org/gdal/ticket/5234
'Too many lines in output (' + obtained_lines.length + '): ' 'Too many lines in output (' + obtained_lines.length + '): ' + obtained_lines.join('\n'));
+ obtained_lines.join('\n'));
done(); done();
}); });
}); });
test('GET /api/v1/sql as csv, properly escaped', function(done){ it('GET /api/v1/sql as csv, properly escaped', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20cartodb_id,%20address%20FROM%20untitle_table_4%20LIMIT%201&format=csv', url: '/api/v1/sql?q=SELECT%20cartodb_id,%20address%20FROM%20untitle_table_4%20LIMIT%201&format=csv',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -166,23 +162,29 @@ test('GET /api/v1/sql as csv, properly escaped', function(done){
}); });
}); });
test('GET /api/v1/sql as csv, concurrently', function(done){ it('GET /api/v1/sql as csv, concurrently', function(done){
var concurrency = 4; var concurrency = 4;
var waiting = concurrency; var waiting = concurrency;
function validate(res){
var expected = 'cartodb_id,address\r\n1,"Calle de Pérez Galdós 9, Madrid, Spain"\r\n';
assert.equal(res.body, expected);
if ( ! --waiting ) {
done();
}
}
for (var i=0; i<concurrency; ++i) { for (var i=0; i<concurrency; ++i) {
assert.response(app,
assert.response(app, { {
url: '/api/v1/sql?q=SELECT%20cartodb_id,%20address%20FROM%20untitle_table_4%20LIMIT%201&format=csv', url: '/api/v1/sql?q=SELECT%20cartodb_id,%20address%20FROM%20untitle_table_4%20LIMIT%201&format=csv',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
method: 'GET' method: 'GET'
},{ }, function(res){ },
assert.equal(res.statusCode, 200, res.body); {
var expected = 'cartodb_id,address\r\n1,"Calle de Pérez Galdós 9, Madrid, Spain"\r\n'; status: 200
assert.equal(res.body, expected); },
if ( ! --waiting ) done(); validate
}); );
} }
}); });

View File

@ -1,15 +1,8 @@
require('../../helper'); require('../../helper');
require('../../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')() var app = require(global.settings.app_root + '/app/controllers/app')();
, assert = require('assert') var assert = require('../../support/assert');
, querystring = require('querystring') var querystring = require('querystring');
, _ = require('underscore')
, zipfile = require('zipfile')
, fs = require('fs')
, libxmljs = require('libxmljs')
;
// allow lots of emitters to be set to silence warning // allow lots of emitters to be set to silence warning
// TODO: check if still needed ... // TODO: check if still needed ...
@ -18,17 +11,18 @@ app.setMaxListeners(0);
// use dec_sep for internationalization // use dec_sep for internationalization
var checkDecimals = function(x, dec_sep){ var checkDecimals = function(x, dec_sep){
var tmp='' + x; var tmp='' + x;
if (tmp.indexOf(dec_sep)>-1) if (tmp.indexOf(dec_sep)>-1) {
return tmp.length-tmp.indexOf(dec_sep)-1; return tmp.length - tmp.indexOf(dec_sep) - 1;
else } else {
return 0; return 0;
} }
};
suite('export.geojson', function() { describe('export.geojson', function() {
// GEOJSON tests // GEOJSON tests
test('GET /api/v1/sql with SQL parameter and geojson format, ensuring content-disposition set to geojson', function(done){ it('GET /api/v1/sql with SQL parameter, ensuring content-disposition set to geojson', function(done) {
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -42,7 +36,7 @@ test('GET /api/v1/sql with SQL parameter and geojson format, ensuring content-di
}); });
}); });
test('POST /api/v1/sql with SQL parameter and geojson format, ensuring content-disposition set to geojson', function(done){ it('POST /api/v1/sql with SQL parameter, ensuring content-disposition set to geojson', function(done) {
assert.response(app, { assert.response(app, {
url: '/api/v1/sql', url: '/api/v1/sql',
data: querystring.stringify({q: "SELECT * FROM untitle_table_4", format: 'geojson' }), data: querystring.stringify({q: "SELECT * FROM untitle_table_4", format: 'geojson' }),
@ -57,7 +51,7 @@ test('POST /api/v1/sql with SQL parameter and geojson format, ensuring content-d
}); });
}); });
test('uses the last format parameter when multiple are used', function(done){ it('uses the last format parameter when multiple are used', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?format=csv&q=SELECT%20*%20FROM%20untitle_table_4&format=geojson', url: '/api/v1/sql?format=csv&q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -70,7 +64,7 @@ test('uses the last format parameter when multiple are used', function(done){
}); });
}); });
test('uses custom filename', function(done){ it('uses custom filename', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson&filename=x', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson&filename=x',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -83,7 +77,7 @@ test('uses custom filename', function(done){
}); });
}); });
test('does not include the_geom and the_geom_webmercator properties by default', function(done){ it('does not include the_geom and the_geom_webmercator properties by default', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -104,7 +98,7 @@ test('does not include the_geom and the_geom_webmercator properties by default',
}); });
}); });
test('skipfields controls fields included in GeoJSON output', function(done){ it('skipfields controls fields included in GeoJSON output', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson&skipfields=unexistant,cartodb_id', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson&skipfields=unexistant,cartodb_id',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -126,7 +120,7 @@ test('skipfields controls fields included in GeoJSON output', function(done){
}); });
test('GET /api/v1/sql as geojson limiting decimal places', function(done){ it('GET /api/v1/sql as geojson limiting decimal places', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: 'SELECT ST_MakePoint(0.123,2.3456) as the_geom', q: 'SELECT ST_MakePoint(0.123,2.3456) as the_geom',
@ -142,7 +136,7 @@ test('GET /api/v1/sql as geojson limiting decimal places', function(done){
}); });
}); });
test('GET /api/v1/sql as geojson with default dp as 6', function(done){ it('GET /api/v1/sql as geojson with default dp as 6', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: 'SELECT ST_MakePoint(0.12345678,2.3456787654) as the_geom', q: 'SELECT ST_MakePoint(0.12345678,2.3456787654) as the_geom',
@ -157,7 +151,7 @@ test('GET /api/v1/sql as geojson with default dp as 6', function(done){
}); });
}); });
test('null geometries in geojson output', function(done){ it('null geometries in geojson output', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: "SELECT 1 as gid, 'U' as name, null::geometry as the_geom ", q: "SELECT 1 as gid, 'U' as name, null::geometry as the_geom ",
@ -182,7 +176,7 @@ test('null geometries in geojson output', function(done){
}); });
}); });
test('stream response handle errors', function(done) { it('stream response handle errors', function(done) {
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: "SELECTT 1 as gid, null::geometry as the_geom ", q: "SELECTT 1 as gid, null::geometry as the_geom ",
@ -191,7 +185,6 @@ test('stream response handle errors', function(done) {
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
method: 'GET' method: 'GET'
},{ }, function(res){ },{ }, function(res){
console.log(res);
assert.equal(res.statusCode, 400, res.body); assert.equal(res.statusCode, 400, res.body);
var geoJson = JSON.parse(res.body); var geoJson = JSON.parse(res.body);
assert.ok(geoJson.error); assert.ok(geoJson.error);
@ -201,7 +194,7 @@ test('stream response handle errors', function(done) {
}); });
}); });
test('stream response with empty result set has valid output', function(done) { it('stream response with empty result set has valid output', function(done) {
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: "SELECT 1 as gid, null::geometry as the_geom limit 0", q: "SELECT 1 as gid, null::geometry as the_geom limit 0",

View File

@ -1,22 +1,16 @@
require('../../helper'); require('../../helper');
require('../../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')();
var app = require(global.settings.app_root + '/app/controllers/app')() var assert = require('../../support/assert');
, assert = require('assert') var querystring = require('querystring');
, querystring = require('querystring') var libxmljs = require('libxmljs');
, _ = require('underscore') var http = require('http');
, zipfile = require('zipfile') var server_utils = require('../../support/server_utils');
, fs = require('fs')
, libxmljs = require('libxmljs')
, http = require('http')
, server_utils = require('../../support/server_utils')
;
// allow lots of emitters to be set to silence warning // allow lots of emitters to be set to silence warning
app.setMaxListeners(0); app.setMaxListeners(0);
suite('export.kml', function() { describe('export.kml', function() {
// Check if an attribute is in the KML output // Check if an attribute is in the KML output
// //
@ -34,15 +28,21 @@ var hasAttribute = function(kml, att) {
var xpath; var xpath;
xpath = "//SimpleField[@name='" + att + "']"; xpath = "//SimpleField[@name='" + att + "']";
if ( doc.get(xpath) ) return true; if ( doc.get(xpath) ) {
return true;
}
xpath = "//Placemark/" + att; xpath = "//Placemark/" + att;
if ( doc.get(xpath) ) return true; if ( doc.get(xpath) ) {
return true;
}
var lcatt = att.toLowerCase(); var lcatt = att.toLowerCase();
if ( lcatt == 'name' || lcatt == 'description' ) { if ( lcatt === 'name' || lcatt === 'description' ) {
xpath = "//Placemark/" + lcatt; xpath = "//Placemark/" + lcatt;
if ( doc.get(xpath) ) return true; if ( doc.get(xpath) ) {
return true;
}
} }
//if ( lowerkml.indexOf('simplefield name="'+ loweratt + '"') != -1 ) return true; //if ( lowerkml.indexOf('simplefield name="'+ loweratt + '"') != -1 ) return true;
@ -59,13 +59,19 @@ var extractCoordinates = function(kml) {
var doc = libxmljs.parseXmlString(kml); var doc = libxmljs.parseXmlString(kml);
//console.log("doc: " + doc); //console.log("doc: " + doc);
if ( ! doc ) return; if ( ! doc ) {
return;
}
var coo = doc.get("//coordinates"); var coo = doc.get("//coordinates");
//console.log("coo: " + coo); //console.log("coo: " + coo);
if ( ! coo ) return; if ( ! coo ) {
return;
}
coo = coo.text(); coo = coo.text();
//console.log("coo: " + coo); //console.log("coo: " + coo);
if ( ! coo ) return; if ( ! coo ) {
return;
}
coo = coo.split(' '); coo = coo.split(' ');
//console.log("coo: " + coo); //console.log("coo: " + coo);
for (var i=0; i<coo.length; ++i) { for (var i=0; i<coo.length; ++i) {
@ -84,19 +90,25 @@ var extractFolderName = function(kml) {
var doc = libxmljs.parseXmlString(kml); var doc = libxmljs.parseXmlString(kml);
//console.log("doc: " + doc); //console.log("doc: " + doc);
if ( ! doc ) return; if ( ! doc ) {
return;
}
var coo = doc.get("//Document/Folder/name"); var coo = doc.get("//Document/Folder/name");
//console.log("coo: " + coo); //console.log("coo: " + coo);
if ( ! coo ) return; if ( ! coo ) {
return;
}
coo = coo.text(); coo = coo.text();
//console.log("coo: " + coo); //console.log("coo: " + coo);
if ( ! coo ) return; if ( ! coo ) {
return;
}
return coo; return coo;
}; };
// KML tests // KML tests
test('KML format, unauthenticated', function(done){ it('KML format, unauthenticated', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -119,7 +131,7 @@ test('KML format, unauthenticated', function(done){
}); });
}); });
test('KML format, unauthenticated, POST', function(done){ it('KML format, unauthenticated, POST', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql', url: '/api/v1/sql',
data: 'q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml', data: 'q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml',
@ -134,7 +146,7 @@ test('KML format, unauthenticated, POST', function(done){
}); });
}); });
test('KML format, bigger than 81920 bytes', function(done){ it('KML format, bigger than 81920 bytes', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql', url: '/api/v1/sql',
data: querystring.stringify({ data: querystring.stringify({
@ -153,7 +165,7 @@ test('KML format, bigger than 81920 bytes', function(done){
}); });
}); });
test('KML format, skipfields', function(done){ it('KML format, skipfields', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&skipfields=address,cartodb_id', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&skipfields=address,cartodb_id',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -176,7 +188,7 @@ test('KML format, skipfields', function(done){
}); });
}); });
test('KML format, unauthenticated, custom filename', function(done){ it('KML format, unauthenticated, custom filename', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&filename=kmltest', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&filename=kmltest',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -192,7 +204,7 @@ test('KML format, unauthenticated, custom filename', function(done){
}); });
}); });
test('KML format, authenticated', function(done){ it('KML format, authenticated', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&api_key=1234', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=kml&api_key=1234',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -205,27 +217,18 @@ test('KML format, authenticated', function(done){
}); });
}); });
test('KML format, unauthenticated, concurrent requests', function(done){ it('KML format, unauthenticated, concurrent requests', function(done){
var query = querystring.stringify({ 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", 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', format: 'kml',
filename: 'multi' filename: 'multi'
}); });
var concurrency = 4; var concurrency = 4;
var waiting = concurrency; var waiting = concurrency;
server_utils.startOnNextPort(app, function() {
var port = app.address().port; function onResponse(res) {
//console.log("Listening on port " + port);
for (var i=0; i<concurrency; ++i) {
//console.log("Sending request");
http.request({
host: 'localhost',
port: port,
path: '/api/v1/sql?' + query,
headers: {host: 'vizzuality.cartodb.com'},
agent: false // or should this be true ?
}).on('response', function(res) {
//console.log("Response started"); //console.log("Response started");
res.body = ''; res.body = '';
//res.setEncoding('binary'); //res.setEncoding('binary');
@ -244,15 +247,33 @@ test('KML format, unauthenticated, concurrent requests', function(done){
done(); done();
} }
}); });
}).on('error', function(err) { }
function onError(err) {
console.log("Response error" + err); console.log("Response error" + err);
}).end(); }
server_utils.startOnNextPort(app, function() {
var port = app.address().port;
//console.log("Listening on port " + port);
for (var i=0; i<concurrency; ++i) {
//console.log("Sending request");
http.request({
host: 'localhost',
port: port,
path: '/api/v1/sql?' + query,
headers: {host: 'vizzuality.cartodb.com'},
agent: false // or should this be true ?
})
.on('response', onResponse)
.on('error', onError)
.end();
} }
}); });
}); });
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/60 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/60
test('GET /api/v1/sql as kml with no rows', function(done){ it('GET /api/v1/sql as kml with no rows', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20true%20WHERE%20false&format=kml', url: '/api/v1/sql?q=SELECT%20true%20WHERE%20false&format=kml',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -260,7 +281,10 @@ test('GET /api/v1/sql as kml with no rows', function(done){
},{ }, function(res){ },{ }, function(res){
assert.equal(res.statusCode, 200, res.body); assert.equal(res.statusCode, 200, res.body);
// NOTE: GDAL-1.11+ added 'id="root_doc"' attribute to the output // 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 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,''); var body = res.body.replace(/\n/g,'');
assert.ok(body.match(pat), assert.ok(body.match(pat),
"Response:\n" + body + '\ndoes not match pattern:\n' + pat); "Response:\n" + body + '\ndoes not match pattern:\n' + pat);
@ -269,7 +293,7 @@ test('GET /api/v1/sql as kml with no rows', function(done){
}); });
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/90 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/90
test('GET /api/v1/sql as kml with ending semicolon', function(done){ it('GET /api/v1/sql as kml with ending semicolon', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: 'SELECT true WHERE false;', q: 'SELECT true WHERE false;',
@ -280,7 +304,10 @@ test('GET /api/v1/sql as kml with ending semicolon', function(done){
},{ }, function(res){ },{ }, function(res){
assert.equal(res.statusCode, 200, res.body); assert.equal(res.statusCode, 200, res.body);
// NOTE: GDAL-1.11+ added 'id="root_doc"' attribute to the output // 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 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,''); var body = res.body.replace(/\n/g,'');
assert.ok(body.match(pat), assert.ok(body.match(pat),
"Response:\n" + body + '\ndoes not match pattern:\n' + pat); "Response:\n" + body + '\ndoes not match pattern:\n' + pat);
@ -289,7 +316,7 @@ test('GET /api/v1/sql as kml with ending semicolon', function(done){
}); });
// See https://github.com/CartoDB/cartodb/issues/276 // See https://github.com/CartoDB/cartodb/issues/276
test('check point coordinates, unauthenticated', function(done){ it('check point coordinates, unauthenticated', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: 'SELECT * from untitle_table_4 WHERE cartodb_id = -1', q: 'SELECT * from untitle_table_4 WHERE cartodb_id = -1',
@ -307,7 +334,7 @@ test('check point coordinates, unauthenticated', function(done){
}); });
// See https://github.com/CartoDB/cartodb/issues/276 // See https://github.com/CartoDB/cartodb/issues/276
test('check point coordinates, authenticated', function(done){ it('check point coordinates, authenticated', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?' + querystring.stringify({ url: '/api/v1/sql?' + querystring.stringify({
q: 'SELECT * from untitle_table_4 WHERE cartodb_id = -1', q: 'SELECT * from untitle_table_4 WHERE cartodb_id = -1',
@ -325,7 +352,7 @@ test('check point coordinates, authenticated', function(done){
}); });
}); });
test('expects 1000 placemarks in public table', function(done){ it('expects 1000 placemarks in public table', function(done){
var numberOfRowsInPublicTable = 6, var numberOfRowsInPublicTable = 6,
seriesLimit = 200, seriesLimit = 200,
expectedRows = numberOfRowsInPublicTable * seriesLimit; expectedRows = numberOfRowsInPublicTable * seriesLimit;
@ -349,7 +376,7 @@ test('check point coordinates, authenticated', function(done){
); );
}); });
test('expects 1000 placemarks in private table using the API KEY', function(done){ it('expects 1000 placemarks in private table using the API KEY', function(done){
var numberOfRowsInPrivateTable = 5, var numberOfRowsInPrivateTable = 5,
seriesLimit = 200, seriesLimit = 200,
expectedRows = numberOfRowsInPrivateTable * seriesLimit; expectedRows = numberOfRowsInPrivateTable * seriesLimit;

View File

@ -1,24 +1,20 @@
require('../../helper'); require('../../helper');
require('../../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')();
var app = require(global.settings.app_root + '/app/controllers/app')() var assert = require('../../support/assert');
, assert = require('assert') var querystring = require('querystring');
, querystring = require('querystring') var _ = require('underscore');
, _ = require('underscore') var zipfile = require('zipfile');
, zipfile = require('zipfile') var fs = require('fs');
, fs = require('fs')
, libxmljs = require('libxmljs')
;
// allow lots of emitters to be set to silence warning // allow lots of emitters to be set to silence warning
app.setMaxListeners(0); app.setMaxListeners(0);
suite('export.shapefile', function() { describe('export.shapefile', function() {
// SHP tests // SHP tests
test('SHP format, unauthenticated', function(done){ it('SHP format, unauthenticated', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -31,7 +27,9 @@ test('SHP format, unauthenticated', function(done){
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd)); assert.equal(true, /filename=cartodb-query.zip/gi.test(cd));
var tmpfile = '/tmp/myshape.zip'; var tmpfile = '/tmp/myshape.zip';
var err = fs.writeFileSync(tmpfile, res.body, 'binary'); var err = fs.writeFileSync(tmpfile, res.body, 'binary');
if (err) { done(err); return } if (err) {
return done(err);
}
var zf = new zipfile.ZipFile(tmpfile); 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.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.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
@ -43,7 +41,7 @@ test('SHP format, unauthenticated', function(done){
}); });
}); });
test('SHP format, unauthenticated, POST', function(done){ it('SHP format, unauthenticated, POST', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql', url: '/api/v1/sql',
data: 'q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp', data: 'q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp',
@ -58,7 +56,7 @@ test('SHP format, unauthenticated, POST', function(done){
}); });
}); });
test('SHP format, big size, POST', function(done){ it('SHP format, big size, POST', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql', url: '/api/v1/sql',
data: querystring.stringify({ data: querystring.stringify({
@ -77,7 +75,7 @@ test('SHP format, big size, POST', function(done){
}); });
}); });
test('SHP format, unauthenticated, with custom filename', function(done){ it('SHP format, unauthenticated, with custom filename', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&filename=myshape', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&filename=myshape',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -90,7 +88,9 @@ test('SHP format, unauthenticated, with custom filename', function(done){
assert.equal(true, /filename=myshape.zip/gi.test(cd)); assert.equal(true, /filename=myshape.zip/gi.test(cd));
var tmpfile = '/tmp/myshape.zip'; var tmpfile = '/tmp/myshape.zip';
var err = fs.writeFileSync(tmpfile, res.body, 'binary'); var err = fs.writeFileSync(tmpfile, res.body, 'binary');
if (err) { done(err); return } if (err) {
return done(err);
}
var zf = new zipfile.ZipFile(tmpfile); 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.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.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
@ -101,7 +101,7 @@ test('SHP format, unauthenticated, with custom filename', function(done){
}); });
}); });
test('SHP format, unauthenticated, with custom, dangerous filename', function(done){ it('SHP format, unauthenticated, with custom, dangerous filename', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&filename=b;"%20()[]a', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&filename=b;"%20()[]a',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -115,7 +115,9 @@ test('SHP format, unauthenticated, with custom, dangerous filename', function(do
assert.equal(true, /filename=b_______a.zip/gi.test(cd), 'Unexpected SHP filename: ' + cd); assert.equal(true, /filename=b_______a.zip/gi.test(cd), 'Unexpected SHP filename: ' + cd);
var tmpfile = '/tmp/myshape.zip'; var tmpfile = '/tmp/myshape.zip';
var err = fs.writeFileSync(tmpfile, res.body, 'binary'); var err = fs.writeFileSync(tmpfile, res.body, 'binary');
if (err) { done(err); return } if (err) {
return done(err);
}
var zf = new zipfile.ZipFile(tmpfile); 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 + '.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 + '.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
@ -126,7 +128,7 @@ test('SHP format, unauthenticated, with custom, dangerous filename', function(do
}); });
}); });
test('SHP format, authenticated', function(done){ it('SHP format, authenticated', function(done){
assert.response(app, { assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&api_key=1234', url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp&api_key=1234',
headers: {host: 'vizzuality.cartodb.com'}, headers: {host: 'vizzuality.cartodb.com'},
@ -138,7 +140,9 @@ test('SHP format, authenticated', function(done){
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd)); assert.equal(true, /filename=cartodb-query.zip/gi.test(cd));
var tmpfile = '/tmp/myshape.zip'; var tmpfile = '/tmp/myshape.zip';
var err = fs.writeFileSync(tmpfile, res.body, 'binary'); var err = fs.writeFileSync(tmpfile, res.body, 'binary');
if (err) { done(err); return } if (err) {
return done(err);
}
var zf = new zipfile.ZipFile(tmpfile); 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.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.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
@ -152,7 +156,7 @@ test('SHP format, authenticated', function(done){
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/66 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/66
test('SHP format, unauthenticated, with utf8 data', function(done){ it('SHP format, unauthenticated, with utf8 data', function(done){
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT '♥♦♣♠' as f, st_makepoint(0,0,4326) as the_geom", q: "SELECT '♥♦♣♠' as f, st_makepoint(0,0,4326) as the_geom",
format: 'shp', format: 'shp',
@ -167,7 +171,9 @@ test('SHP format, unauthenticated, with utf8 data', function(done){
assert.equal(res.statusCode, 200, res.body); assert.equal(res.statusCode, 200, res.body);
var tmpfile = '/tmp/myshape.zip'; var tmpfile = '/tmp/myshape.zip';
var err = fs.writeFileSync(tmpfile, res.body, 'binary'); var err = fs.writeFileSync(tmpfile, res.body, 'binary');
if (err) { done(err); return } if (err) {
return done(err);
}
var zf = new zipfile.ZipFile(tmpfile); var zf = new zipfile.ZipFile(tmpfile);
var buffer = zf.readFileSync('myshape.dbf'); var buffer = zf.readFileSync('myshape.dbf');
fs.unlinkSync(tmpfile); fs.unlinkSync(tmpfile);
@ -178,10 +184,9 @@ test('SHP format, unauthenticated, with utf8 data', function(done){
}); });
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/66 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/66
test('mixed type geometry', function(done){ it('mixed type geometry', function(done){
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT 'POINT(0 0)'::geometry as g UNION ALL " q: "SELECT 'POINT(0 0)'::geometry as g UNION ALL SELECT 'LINESTRING(0 0, 1 0)'::geometry",
+ "SELECT 'LINESTRING(0 0, 1 0)'::geometry",
format: 'shp' format: 'shp'
}); });
assert.response(app, { assert.response(app, {
@ -194,19 +199,19 @@ test('mixed type geometry', function(done){
assert.deepEqual(res.headers['content-disposition'], 'inline'); assert.deepEqual(res.headers['content-disposition'], 'inline');
assert.equal(res.statusCode, 400, res.statusCode + ': ' +res.body); assert.equal(res.statusCode, 400, res.statusCode + ': ' +res.body);
var parsedBody = JSON.parse(res.body); var parsedBody = JSON.parse(res.body);
var expectedBody = {"error":["ERROR 1: Attempt to write non-point (LINESTRING) geometry to point shapefile."]} var expectedBody = {"error":["ERROR 1: Attempt to write non-point (LINESTRING) geometry to point shapefile."]};
assert.deepEqual(parsedBody, expectedBody); assert.deepEqual(parsedBody, expectedBody);
done(); done();
}); });
}); });
// See https://github.com/Vizzuality/CartoDB-SQL-API/issues/87 // See https://github.com/Vizzuality/CartoDB-SQL-API/issues/87
test('errors are not confused with warnings', function(done){ it('errors are not confused with warnings', function(done){
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT 'POINT(0 0)'::geometry as g" q: [
+ ", 1 as a_very_very_very_long_field_name" "SELECT 'POINT(0 0)'::geometry as g, 1 as a_very_very_very_long_field_name",
+ " UNION ALL " "SELECT 'LINESTRING(0 0, 1 0)'::geometry, 2"
+ "SELECT 'LINESTRING(0 0, 1 0)'::geometry, 2", ].join(" UNION ALL "),
format: 'shp' format: 'shp'
}); });
assert.response(app, { assert.response(app, {
@ -219,13 +224,13 @@ test('errors are not confused with warnings', function(done){
assert.deepEqual(res.headers['content-disposition'], 'inline'); assert.deepEqual(res.headers['content-disposition'], 'inline');
assert.equal(res.statusCode, 400, res.statusCode + ': ' +res.body); assert.equal(res.statusCode, 400, res.statusCode + ': ' +res.body);
var parsedBody = JSON.parse(res.body); var parsedBody = JSON.parse(res.body);
var expectedBody = {"error":["ERROR 1: Attempt to write non-point (LINESTRING) geometry to point shapefile."]} var expectedBody = {"error":["ERROR 1: Attempt to write non-point (LINESTRING) geometry to point shapefile."]};
assert.deepEqual(parsedBody, expectedBody); assert.deepEqual(parsedBody, expectedBody);
done(); done();
}); });
}); });
test('skipfields controls fields included in SHP output', function(done){ it('skipfields controls fields included in SHP output', function(done){
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT 111 as skipme, 222 as keepme, 'POINT(0 0)'::geometry as g", q: "SELECT 111 as skipme, 222 as keepme, 'POINT(0 0)'::geometry as g",
format: 'shp', format: 'shp',
@ -241,7 +246,9 @@ test('skipfields controls fields included in SHP output', function(done){
assert.equal(res.statusCode, 200, res.body); assert.equal(res.statusCode, 200, res.body);
var tmpfile = '/tmp/myshape.zip'; var tmpfile = '/tmp/myshape.zip';
var err = fs.writeFileSync(tmpfile, res.body, 'binary'); var err = fs.writeFileSync(tmpfile, res.body, 'binary');
if (err) { done(err); return } if (err) {
return done(err);
}
var zf = new zipfile.ZipFile(tmpfile); var zf = new zipfile.ZipFile(tmpfile);
var buffer = zf.readFileSync('myshape.dbf'); var buffer = zf.readFileSync('myshape.dbf');
fs.unlinkSync(tmpfile); fs.unlinkSync(tmpfile);
@ -251,23 +258,18 @@ test('skipfields controls fields included in SHP output', function(done){
}); });
}); });
test('SHP format, concurrently', function(done){ it('SHP format, concurrently', function(done){
var concurrency = 1; var concurrency = 1;
var waiting = concurrency; var waiting = concurrency;
for (var i=0; i<concurrency; ++i) { function validate(res){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp',
headers: {host: 'vizzuality.cartodb.com'},
encoding: 'binary',
method: 'GET'
},{ }, function(res){
assert.equal(res.statusCode, 200, res.body);
var cd = res.header('Content-Disposition'); var cd = res.header('Content-Disposition');
assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd); assert.equal(true, /^attachment/.test(cd), 'SHP is not disposed as attachment: ' + cd);
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd)); assert.equal(true, /filename=cartodb-query.zip/gi.test(cd));
var tmpfile = '/tmp/myshape.zip'; var tmpfile = '/tmp/myshape.zip';
var err = fs.writeFileSync(tmpfile, res.body, 'binary'); var err = fs.writeFileSync(tmpfile, res.body, 'binary');
if (err) { done(err); return } if (err) {
return done(err);
}
var zf = new zipfile.ZipFile(tmpfile); 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.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.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);
@ -275,13 +277,29 @@ test('SHP format, concurrently', function(done){
assert.ok(_.contains(zf.names, 'cartodb-query.prj'), 'SHP zipfile does not contain .prj: ' + zf.names); assert.ok(_.contains(zf.names, 'cartodb-query.prj'), 'SHP zipfile does not contain .prj: ' + zf.names);
// TODO: check DBF contents // TODO: check DBF contents
fs.unlinkSync(tmpfile); fs.unlinkSync(tmpfile);
if ( ! --waiting ) done(); if ( ! --waiting ) {
}); done();
}
}
for (var i=0; i<concurrency; ++i) {
assert.response(
app,
{
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4%20LIMIT%201&format=shp',
headers: {host: 'vizzuality.cartodb.com'},
encoding: 'binary',
method: 'GET'
},
{
status: 200
},
validate
);
} }
}); });
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/111 // See https://github.com/CartoDB/CartoDB-SQL-API/issues/111
test('point with null first', function(done){ it('point with null first', function(done){
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT null::geometry as g UNION ALL SELECT 'SRID=4326;POINT(0 0)'::geometry", q: "SELECT null::geometry as g UNION ALL SELECT 'SRID=4326;POINT(0 0)'::geometry",
format: 'shp' format: 'shp'
@ -297,7 +315,9 @@ test('point with null first', function(done){
assert.equal(true, /filename=cartodb-query.zip/gi.test(cd)); assert.equal(true, /filename=cartodb-query.zip/gi.test(cd));
var tmpfile = '/tmp/myshape.zip'; var tmpfile = '/tmp/myshape.zip';
var err = fs.writeFileSync(tmpfile, res.body, 'binary'); var err = fs.writeFileSync(tmpfile, res.body, 'binary');
if (err) { done(err); return } if (err) {
return done(err);
}
var zf = new zipfile.ZipFile(tmpfile); 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.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.shx'), 'SHP zipfile does not contain .shx: ' + zf.names);

View File

@ -1,22 +1,15 @@
require('../../helper'); require('../../helper');
require('../../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')();
var app = require(global.settings.app_root + '/app/controllers/app')() var assert = require('../../support/assert');
, assert = require('assert') var querystring = require('querystring');
, querystring = require('querystring')
, _ = require('underscore')
, zipfile = require('zipfile')
, fs = require('fs')
, libxmljs = require('libxmljs')
;
// allow lots of emitters to be set to silence warning // allow lots of emitters to be set to silence warning
app.setMaxListeners(0); app.setMaxListeners(0);
suite('export.svg', function() { describe('export.svg', function() {
test('GET /api/v1/sql with SVG format', function(done){ it('GET /api/v1/sql with SVG format', function(done){
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ", q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ",
format: "svg" format: "svg"
@ -36,7 +29,7 @@ test('GET /api/v1/sql with SVG format', function(done){
}); });
}); });
test('POST /api/v1/sql with SVG format', function(done){ it('POST /api/v1/sql with SVG format', function(done){
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ", q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ",
format: "svg" format: "svg"
@ -58,7 +51,7 @@ test('POST /api/v1/sql with SVG format', function(done){
}); });
}); });
test('GET /api/v1/sql with SVG format and custom filename', function(done){ it('GET /api/v1/sql with SVG format and custom filename', function(done){
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ", q: "SELECT 1 as cartodb_id, ST_MakeLine(ST_MakePoint(10, 10), ST_MakePoint(1034, 778)) AS the_geom ",
format: "svg", format: "svg",
@ -79,7 +72,7 @@ test('GET /api/v1/sql with SVG format and custom filename', function(done){
}); });
}); });
test('GET /api/v1/sql with SVG format and centered point', function(done){ it('GET /api/v1/sql with SVG format and centered point', function(done){
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT 1 as cartodb_id, ST_MakePoint(5000, -54) AS the_geom ", q: "SELECT 1 as cartodb_id, ST_MakePoint(5000, -54) AS the_geom ",
format: "svg" format: "svg"
@ -100,7 +93,7 @@ test('GET /api/v1/sql with SVG format and centered point', function(done){
}); });
}); });
test('GET /api/v1/sql with SVG format and trimmed decimals', function(done){ it('GET /api/v1/sql with SVG format and trimmed decimals', function(done){
var queryobj = { var queryobj = {
q: "SELECT 1 as cartodb_id, 'LINESTRING(0 0, 1024 768, 500.123456 600.98765432)'::geometry AS the_geom ", q: "SELECT 1 as cartodb_id, 'LINESTRING(0 0, 1024 768, 500.123456 600.98765432)'::geometry AS the_geom ",
format: "svg", format: "svg",
@ -138,7 +131,7 @@ test('GET /api/v1/sql with SVG format and trimmed decimals', function(done){
// Test adding "the_geom" to skipfields // Test adding "the_geom" to skipfields
// See http://github.com/Vizzuality/CartoDB-SQL-API/issues/73 // See http://github.com/Vizzuality/CartoDB-SQL-API/issues/73
test('SVG format with "the_geom" in skipfields', function(done){ it('SVG format with "the_geom" in skipfields', function(done){
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT 1 as cartodb_id, ST_MakePoint(5000, -54) AS the_geom ", q: "SELECT 1 as cartodb_id, ST_MakePoint(5000, -54) AS the_geom ",
format: "svg", format: "svg",
@ -159,7 +152,7 @@ test('SVG format with "the_geom" in skipfields', function(done){
}); });
}); });
test('SVG format with missing "the_geom" field', function(done){ it('SVG format with missing "the_geom" field', function(done){
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT 1 as cartodb_id, ST_MakePoint(5000, -54) AS something_else ", q: "SELECT 1 as cartodb_id, ST_MakePoint(5000, -54) AS something_else ",
format: "svg" format: "svg"

View File

@ -1,21 +1,15 @@
require('../../helper'); require('../../helper');
require('../../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')();
var app = require(global.settings.app_root + '/app/controllers/app')() var assert = require('../../support/assert');
, assert = require('assert') var querystring = require('querystring');
, querystring = require('querystring') var _ = require('underscore');
, _ = require('underscore')
, zipfile = require('zipfile')
, fs = require('fs')
, libxmljs = require('libxmljs')
;
// allow lots of emitters to be set to silence warning // allow lots of emitters to be set to silence warning
app.setMaxListeners(0); app.setMaxListeners(0);
suite('export.topojson', function() { describe('export.topojson', function() {
// TOPOJSON tests // TOPOJSON tests
@ -34,7 +28,7 @@ suite('export.topojson', function() {
}; };
} }
test('GET two polygons sharing an edge as topojson', function(done){ it('GET two polygons sharing an edge as topojson', function(done){
assert.response(app, assert.response(app,
getRequest( getRequest(
"SELECT 1 as gid, 'U' as name, 'POLYGON((-5 0,5 0,0 5,-5 0))'::geometry as the_geom " + "SELECT 1 as gid, 'U' as name, 'POLYGON((-5 0,5 0,0 5,-5 0))'::geometry as the_geom " +
@ -81,8 +75,8 @@ test('GET two polygons sharing an edge as topojson', function(done){
assert.equal(shell[1], 1); /* non-shared arc */ assert.equal(shell[1], 1); /* non-shared arc */
var props = obj.properties; var props = obj.properties;
assert.equal(_.keys(props).length, 2); // gid, name assert.equal(_.keys(props).length, 2); // gid, name
assert.equal(props['gid'], 1); assert.equal(props.gid, 1);
assert.equal(props['name'], 'U'); assert.equal(props.name, 'U');
obj = topojson.objects[1]; obj = topojson.objects[1];
//console.dir(obj); //console.dir(obj);
@ -99,8 +93,8 @@ test('GET two polygons sharing an edge as topojson', function(done){
assert.equal(shell[1], 2); /* non-shared arc */ assert.equal(shell[1], 2); /* non-shared arc */
props = obj.properties; props = obj.properties;
assert.equal(_.keys(props).length, 2); // gid, name assert.equal(_.keys(props).length, 2); // gid, name
assert.equal(props['gid'], 2); assert.equal(props.gid, 2);
assert.equal(props['name'], 'D'); assert.equal(props.name, 'D');
// Check arcs // Check arcs
assert.ok(topojson.hasOwnProperty('arcs')); assert.ok(topojson.hasOwnProperty('arcs'));
@ -140,7 +134,7 @@ test('GET two polygons sharing an edge as topojson', function(done){
}); });
}); });
test('null geometries', function(done){ it('null geometries', function(done){
assert.response(app, getRequest( assert.response(app, getRequest(
"SELECT 1 as gid, 'U' as name, 'POLYGON((-5 0,5 0,0 5,-5 0))'::geometry as the_geom " + "SELECT 1 as gid, 'U' as name, 'POLYGON((-5 0,5 0,0 5,-5 0))'::geometry as the_geom " +
" UNION ALL " + " UNION ALL " +
@ -185,8 +179,8 @@ test('null geometries', function(done){
assert.equal(shell[0], 0); /* non-shared arc */ assert.equal(shell[0], 0); /* non-shared arc */
var props = obj.properties; var props = obj.properties;
assert.equal(_.keys(props).length, 2); // gid, name assert.equal(_.keys(props).length, 2); // gid, name
assert.equal(props['gid'], 1); assert.equal(props.gid, 1);
assert.equal(props['name'], 'U'); assert.equal(props.name, 'U');
// Check arcs // Check arcs
assert.ok(topojson.hasOwnProperty('arcs')); assert.ok(topojson.hasOwnProperty('arcs'));
@ -198,7 +192,7 @@ test('null geometries', function(done){
}); });
}); });
test('skipped fields are not returned', function(done) { it('skipped fields are not returned', function(done) {
assert.response(app, assert.response(app,
getRequest( getRequest(
"SELECT 1 as gid, 'U' as name, 'POLYGON((-5 0,5 0,0 5,-5 0))'::geometry as the_geom", "SELECT 1 as gid, 'U' as name, 'POLYGON((-5 0,5 0,0 5,-5 0))'::geometry as the_geom",
@ -218,7 +212,7 @@ test('null geometries', function(done){
); );
}); });
test('jsonp callback is invoked', function(done){ it('jsonp callback is invoked', function(done){
assert.response( assert.response(
app, app,
getRequest( getRequest(
@ -233,10 +227,12 @@ test('null geometries', function(done){
function(res) { function(res) {
assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body);
var didRunJsonCallback = false; var didRunJsonCallback = false;
// jshint ignore:start
function foo_jsonp(body) { function foo_jsonp(body) {
didRunJsonCallback = true; didRunJsonCallback = true;
} }
eval(res.body); eval(res.body);
// jshint ignore:end
assert.ok(didRunJsonCallback); assert.ok(didRunJsonCallback);
done(); done();
} }

View File

@ -1,14 +1,8 @@
require('../helper'); require('../helper');
require('../support/assert');
var assert = require('assert') var assert = require('../support/assert');
, App = require(global.settings.app_root + '/app/controllers/app') var step = require('step');
, querystring = require('querystring') var net = require('net');
, _ = require('underscore')
, Step = require('step')
, net = require('net')
, http = require('http')
;
var sql_server_data_handler; var sql_server_data_handler;
var sql_server_port = 5556; var sql_server_port = 5556;
@ -23,22 +17,22 @@ var sql_server = net.createServer(function(c) {
}); });
}); });
suite('frontend abort', function() { describe('frontend abort', function() {
suiteSetup(function(done){ before(function(done){
sql_server.listen(sql_server_port, done); sql_server.listen(sql_server_port, done);
}); });
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/129 // See https://github.com/CartoDB/CartoDB-SQL-API/issues/129
test('aborts request', function(done){ it('aborts request', function(done){
//console.log("settings:"); console.dir(global.settings); //console.log("settings:"); console.dir(global.settings);
var db_host_backup = global.settings.db_host; var db_host_backup = global.settings.db_host;
var db_port_backup = global.settings.db_port; var db_port_backup = global.settings.db_port;
global.settings.db_host = 'localhost'; global.settings.db_host = 'localhost';
global.settings.db_port = sql_server_port; global.settings.db_port = sql_server_port;
var app = App(); var app = require(global.settings.app_root + '/app/controllers/app')();
var timeout; var timeout;
Step( step(
function sendQuery() { function sendQuery() {
var next = this; var next = this;
assert.response(app, { assert.response(app, {
@ -50,7 +44,7 @@ test('aborts request', function(done){
next(err, res); next(err, res);
}); });
}, },
function checkResponse(err, res) { function checkResponse(err/*, res*/) {
assert(err); // expect timeout assert(err); // expect timeout
assert.ok((''+err).match(/socket/), err); assert.ok((''+err).match(/socket/), err);
sql_server_data_handler = this; sql_server_data_handler = this;
@ -74,7 +68,7 @@ test('aborts request', function(done){
); );
}); });
suiteTeardown(function(done) { after(function(done) {
try { try {
sql_server.close(done); sql_server.close(done);
} catch (er) { } catch (er) {

View File

@ -1,12 +1,10 @@
require('../helper'); require('../helper');
require('../support/assert'); require('../support/assert');
var assert = require('assert'), var assert = require('assert');
App = require(global.settings.app_root + '/app/controllers/app'); var app = require(global.settings.app_root + '/app/controllers/app')();
var app = App(); describe('health checks', function() {
suite('health checks', function() {
beforeEach(function(done) { beforeEach(function(done) {
global.settings.health = { global.settings.health = {
@ -25,7 +23,7 @@ suite('health checks', function() {
} }
}; };
test('returns 200 and ok=true with disabled configuration', function(done) { it('returns 200 and ok=true with disabled configuration', function(done) {
global.settings.health.enabled = false; global.settings.health.enabled = false;
assert.response(app, assert.response(app,
@ -46,7 +44,7 @@ suite('health checks', function() {
); );
}); });
test('returns 200 and ok=true with enabled configuration', function(done) { it('returns 200 and ok=true with enabled configuration', function(done) {
assert.response(app, assert.response(app,
healthCheckRequest, healthCheckRequest,
{ {

View File

@ -13,25 +13,21 @@
* *
*/ */
require('../helper'); require('../helper');
require('../support/assert');
var assert = require('assert')
, App = require(global.settings.app_root + '/app/controllers/app')
, querystring = require('querystring')
, _ = require('underscore')
, Step = require('step')
;
suite('timeout', function() { var assert = require('../support/assert');
var step = require('step');
describe('timeout', function() {
// See https://github.com/CartoDB/CartoDB-SQL-API/issues/128 // See https://github.com/CartoDB/CartoDB-SQL-API/issues/128
test('after configured milliseconds', function(done){ it('after configured milliseconds', function(done){
var testTimeout = 10; var testTimeout = 10;
//console.log("settings:"); console.dir(global.settings); //console.log("settings:"); console.dir(global.settings);
var timeoutBackup = global.settings.node_socket_timeout; var timeoutBackup = global.settings.node_socket_timeout;
global.settings.node_socket_timeout = testTimeout; global.settings.node_socket_timeout = testTimeout;
var app = App(); var app = require(global.settings.app_root + '/app/controllers/app')();
Step( step(
function sendLongQuery() { function sendLongQuery() {
var next = this; var next = this;
assert.response(app, { assert.response(app, {
@ -42,7 +38,7 @@ test('after configured milliseconds', function(done){
next(err, res); next(err, res);
}); });
}, },
function checkResponse(err, res) { function checkResponse(err/*, res*/) {
assert.ok(err); assert.ok(err);
assert.ok(err.message.match(/hang up/), err); assert.ok(err.message.match(/hang up/), err);
return null; return null;

View File

@ -1,24 +1,23 @@
require('../helper'); require('../helper');
require('../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')() var app = require(global.settings.app_root + '/app/controllers/app')();
, assert = require('assert') var assert = require('../support/assert');
, querystring = require('querystring') var querystring = require('querystring');
, _ = require('underscore') var _ = require('underscore');
;
// allow lots of emitters to be set to silence warning // allow lots of emitters to be set to silence warning
app.setMaxListeners(0); app.setMaxListeners(0);
suite('x_cache_channel', function() { describe('x_cache_channel', function() {
assert.contains = function(ary, elem) { assert.contains = function(ary, elem) {
assert.ok(_.contains(ary,elem), 'missing "' + elem +'" from x-cache-channel: '+ ary); assert.ok(_.contains(ary,elem), 'missing "' + elem +'" from x-cache-channel: '+ ary);
}; };
test('supports joins', function(done) { it('supports joins', function(done) {
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT a.name as an, b.name as bn FROM untitle_table_4 a left join private_table b ON (a.cartodb_id = b.cartodb_id)", q: "SELECT a.name as an, b.name as bn FROM untitle_table_4 a " +
"left join private_table b ON (a.cartodb_id = b.cartodb_id)",
api_key: 1234 api_key: 1234
}); });
assert.response(app, { assert.response(app, {
@ -38,7 +37,7 @@ test('supports joins', function(done) {
}); });
}); });
test('supports multistatements', function(done) { it('supports multistatements', function(done) {
var query = querystring.stringify({ var query = querystring.stringify({
q: "SELECT * FROM untitle_table_4; SELECT * FROM private_table", q: "SELECT * FROM untitle_table_4; SELECT * FROM private_table",
api_key: 1234 api_key: 1234
@ -60,7 +59,7 @@ test('supports multistatements', function(done) {
}); });
}); });
test('supports explicit transactions', function(done) { it('supports explicit transactions', function(done) {
var query = querystring.stringify({ var query = querystring.stringify({
q: "BEGIN; SELECT * FROM untitle_table_4; COMMIT; BEGIN; SELECT * FROM private_table; COMMIT;", q: "BEGIN; SELECT * FROM untitle_table_4; COMMIT; BEGIN; SELECT * FROM private_table; COMMIT;",
api_key: 1234 api_key: 1234
@ -82,7 +81,7 @@ test('supports explicit transactions', function(done) {
}); });
}); });
test('survives partial transactions', function(done) { it('survives partial transactions', function(done) {
var query = querystring.stringify({ var query = querystring.stringify({
q: "BEGIN; SELECT * FROM untitle_table_4", q: "BEGIN; SELECT * FROM untitle_table_4",
api_key: 1234 api_key: 1234

View File

@ -1,11 +1,9 @@
require('../helper'); require('../helper');
var _ = require('underscore') var ApikeyAuth = require('../../app/auth/apikey');
, ApikeyAuth = require('../../app/auth/apikey') var assert = require('assert');
, assert = require('assert')
;
suite('has credentials', function() { describe('has credentials', function() {
var noCredentialsRequests = [ var noCredentialsRequests = [
{ {
@ -35,8 +33,8 @@ suite('has credentials', function() {
]; ];
noCredentialsRequests.forEach(function(request) { noCredentialsRequests.forEach(function(request) {
test('has no credentials if ' + request.des, function() { it('has no credentials if ' + request.des, function() {
testCredentials(request.req, false) testCredentials(request.req, false);
}); });
}); });
@ -60,8 +58,8 @@ suite('has credentials', function() {
]; ];
credentialsRequests.forEach(function(request) { credentialsRequests.forEach(function(request) {
test('has credentials if ' + request.des, function() { it('has credentials if ' + request.des, function() {
testCredentials(request.req, true) testCredentials(request.req, true);
}); });
}); });
@ -72,13 +70,13 @@ suite('has credentials', function() {
}); });
suite('verify credentials', function() { describe('verifyCredentials', function() {
test('verifyCredentials callbacks with true value when request api_key is the same', function(done) { it('callbacks with true value when request api_key is the same', function(done) {
testVerifyCredentials({query:{api_key: 'foo'}}, {apiKey: 'foo'}, true, done); testVerifyCredentials({query:{api_key: 'foo'}}, {apiKey: 'foo'}, true, done);
}); });
test('verifyCredentials callbacks with true value when request api_key is different', function(done) { it('callbacks with false value when request api_key is different', function(done) {
testVerifyCredentials({query:{api_key: 'foo'}}, {apiKey: 'bar'}, false, done); testVerifyCredentials({query:{api_key: 'foo'}}, {apiKey: 'bar'}, false, done);
}); });

View File

@ -1,8 +1,7 @@
require('../helper') require('../helper');
var assert = require('assert'), var assert = require('assert');
_ = require('underscore'), var HealthCheck = require('../../app/monitoring/health_check');
HealthCheck = require('../../app/monitoring/health_check');
var metadataBackend = {}; var metadataBackend = {};
@ -12,34 +11,34 @@ function PSQL(dbParams) {
var healthCheck = new HealthCheck(metadataBackend, PSQL); var healthCheck = new HealthCheck(metadataBackend, PSQL);
suite('health checks', function() { describe('health checks', function() {
test('error if disabled file exists', function(done) { it('errors if disabled file exists', function(done) {
var fs = require('fs'); var fs = require('fs');
var readFileFn = fs.readFile; var readFileFn = fs.readFile;
fs.readFile = function(filename, callback) { fs.readFile = function(filename, callback) {
callback(null, "Maintenance"); callback(null, "Maintenance");
} };
healthCheck.check('fake', 'select 1', function(err, result) { healthCheck.check('fake', 'select 1', function(err/*, result*/) {
assert.equal(err.message, "Maintenance"); assert.equal(err.message, "Maintenance");
assert.equal(err.http_status, 503); assert.equal(err.http_status, 503);
done();
fs.readFile = readFileFn; fs.readFile = readFileFn;
done();
}); });
}); });
test('not err if disabled file does not exists', function(done) { it('does not err if disabled file does not exists', function(done) {
var fs = require('fs'); var fs = require('fs');
var readFileFn = fs.readFile; var readFileFn = fs.readFile;
fs.readFile = function(filename, callback) { fs.readFile = function(filename, callback) {
callback(new Error("ENOENT"), null); callback(new Error("ENOENT"), null);
} };
healthCheck.check('fake', 'select 1', function(err, result) { healthCheck.check('fake', 'select 1', function(err/*, result*/) {
assert.equal(err, null); assert.equal(err, null);
done();
fs.readFile = readFileFn; fs.readFile = readFileFn;
done();
}); });
}); });

View File

@ -1,30 +1,30 @@
require('../../helper'); require('../../helper');
var assert = require('assert'); var assert = require('assert');
var ArrayBufferSer = require('../../../app/models/bin_encoder') var ArrayBufferSer = require('../../../app/models/bin_encoder');
suite('ArrayBufferSer', function() { describe('ArrayBufferSer', function() {
test('calculate size for basic types', function() { it('calculate size for basic types', function() {
var b = new ArrayBufferSer(ArrayBufferSer.INT16, [1,2,3,4]) var b = new ArrayBufferSer(ArrayBufferSer.INT16, [1,2,3,4]);
assert.equal(4*2, b.getDataSize()); assert.equal(4*2, b.getDataSize());
b = new ArrayBufferSer(ArrayBufferSer.INT8, [1,2,3,4]) b = new ArrayBufferSer(ArrayBufferSer.INT8, [1,2,3,4]);
assert.equal(4*1, b.getDataSize()); assert.equal(4, b.getDataSize());
b = new ArrayBufferSer(ArrayBufferSer.INT32, [1,2,3,4]) b = new ArrayBufferSer(ArrayBufferSer.INT32, [1,2,3,4]);
assert.equal(4*4, b.getDataSize()); assert.equal(4*4, b.getDataSize());
}); });
test('calculate size for arrays', function() { it('calculate size for arrays', function() {
var b = new ArrayBufferSer(ArrayBufferSer.STRING, ["test","kease"]) var b = new ArrayBufferSer(ArrayBufferSer.STRING, ["test","kease"]);
assert.equal((b.headerSize + 4 + 5)*2, b.getDataSize()); assert.equal((b.headerSize + 4 + 5)*2, b.getDataSize());
var ba = new ArrayBufferSer(ArrayBufferSer.INT16, [1,2,3,4]) var ba = new ArrayBufferSer(ArrayBufferSer.INT16, [1,2,3,4]);
var bc = new ArrayBufferSer(ArrayBufferSer.INT16, [1,4]) var bc = new ArrayBufferSer(ArrayBufferSer.INT16, [1,4]);
b = new ArrayBufferSer(ArrayBufferSer.BUFFER, [ba, bc]) b = new ArrayBufferSer(ArrayBufferSer.BUFFER, [ba, bc]);
assert.equal((b.headerSize + 4 + 2)*2, b.getDataSize()); assert.equal((b.headerSize + 4 + 2)*2, b.getDataSize());
assert.equal(b.type, ArrayBufferSer.BUFFER); assert.equal(b.type, ArrayBufferSer.BUFFER);
}); });
@ -36,28 +36,28 @@ suite('ArrayBufferSer', function() {
} }
} }
test('binary data is ok', function() { it('binary data is ok', function() {
var b = new ArrayBufferSer(ArrayBufferSer.INT16, [1,2,3,4]) var b = new ArrayBufferSer(ArrayBufferSer.INT16, [1,2,3,4]);
var bf = new Buffer([0, 0, 0, ArrayBufferSer.INT16, 0, 0, 0, 8, 1, 0, 2, 0, 3, 0, 4, 0]); var bf = new Buffer([0, 0, 0, ArrayBufferSer.INT16, 0, 0, 0, 8, 1, 0, 2, 0, 3, 0, 4, 0]);
assert_buffer_equals(bf, b.buffer); assert_buffer_equals(bf, b.buffer);
}); });
test('binary data is ok with arrays', function() { it('binary data is ok with arrays', function() {
var ba = new ArrayBufferSer(ArrayBufferSer.INT16, [1,2, 3, 4]) var ba = new ArrayBufferSer(ArrayBufferSer.INT16, [1,2, 3, 4]);
var bc = new ArrayBufferSer(ArrayBufferSer.INT16, [1,4]) var bc = new ArrayBufferSer(ArrayBufferSer.INT16, [1,4]);
var b = new ArrayBufferSer(ArrayBufferSer.BUFFER, [ba, bc]) var b = new ArrayBufferSer(ArrayBufferSer.BUFFER, [ba, bc]);
var bf = new Buffer([ var bf = new Buffer([
0, 0, 0, ArrayBufferSer.BUFFER, // type 0, 0, 0, ArrayBufferSer.BUFFER, // type
0, 0, 0, 28, 0, 0, 0, 28,
0, 0, 0, ArrayBufferSer.INT16, 0, 0, 0, 8, 1, 0, 2, 0, 3, 0, 4, 0, 0, 0, 0, ArrayBufferSer.INT16, 0, 0, 0, 8, 1, 0, 2, 0, 3, 0, 4, 0,
0, 0, 0, ArrayBufferSer.INT16, 0, 0, 0, 4, 1, 0, 4, 0]) 0, 0, 0, ArrayBufferSer.INT16, 0, 0, 0, 4, 1, 0, 4, 0]);
assert_buffer_equals(bf, b.buffer); assert_buffer_equals(bf, b.buffer);
}); });
test('binary data is ok with strings', function() { it('binary data is ok with strings', function() {
var s = 'test' var s = 'test';
var b = new ArrayBufferSer(ArrayBufferSer.STRING, [s]) var b = new ArrayBufferSer(ArrayBufferSer.STRING, [s]);
var bf = new Buffer([ var bf = new Buffer([
0, 0, 0, ArrayBufferSer.STRING, // type 0, 0, 0, ArrayBufferSer.STRING, // type
0, 0, 0, 16, 0, 0, 0, 16,

View File

@ -1,26 +1,39 @@
require('../helper'); require('../helper');
var _ = require('underscore') var _ = require('underscore');
, OAuthAuth = require('../../app/auth/oauth') var OAuthAuth = require('../../app/auth/oauth');
, MetadataDB = require('cartodb-redis') var MetadataDB = require('cartodb-redis');
, oAuth = require('../../app/auth/oauth').backend var oAuth = require('../../app/auth/oauth').backend;
, assert = require('assert') var assert = require('assert');
, tests = module.exports = {} var oauth_data_1 = {
, oauth_data_1 = {
oauth_consumer_key: "dpf43f3p2l4k3l03", oauth_consumer_key: "dpf43f3p2l4k3l03",
oauth_token: "nnch734d00sl2jdk", oauth_token: "nnch734d00sl2jdk",
oauth_signature_method: "HMAC-SHA1", oauth_signature_method: "HMAC-SHA1",
oauth_signature: "tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D", oauth_signature: "tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",
oauth_timestamp:"1191242096", oauth_timestamp:"1191242096",
oauth_nonce:"kllo9940pd9333jh" oauth_nonce:"kllo9940pd9333jh"
} };
, oauth_data_2 = { oauth_version:"1.0" } var oauth_data_2 = { oauth_version:"1.0" };
, oauth_data = _.extend(oauth_data_1, oauth_data_2) var oauth_data = _.extend(oauth_data_1, oauth_data_2);
, real_oauth_header = 'OAuth realm="http://vizzuality.testhost.lan/",oauth_consumer_key="fZeNGv5iYayvItgDYHUbot1Ukb5rVyX6QAg8GaY2",oauth_token="l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR",oauth_signature_method="HMAC-SHA1", oauth_signature="o4hx4hWP6KtLyFwggnYB4yPK8xI%3D",oauth_timestamp="1313581372",oauth_nonce="W0zUmvyC4eVL8cBd4YwlH1nnPTbxW0QBYcWkXTwe4",oauth_version="1.0"' var real_oauth_header = 'OAuth ' +
, oauth_header_tokens = 'oauth_consumer_key="dpf43f3p2l4k3l03",oauth_token="nnch734d00sl2jdk",oauth_signature_method="HMAC-SHA1", oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",oauth_timestamp="1191242096",oauth_nonce="kllo9940pd9333jh",oauth_version="1.0"' 'realm="http://vizzuality.testhost.lan/",' +
, full_oauth_header = 'OAuth realm="http://photos.example.net/"' + oauth_header_tokens; 'oauth_consumer_key="fZeNGv5iYayvItgDYHUbot1Ukb5rVyX6QAg8GaY2",' +
'oauth_token="l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR",' +
'oauth_signature_method="HMAC-SHA1", ' +
'oauth_signature="o4hx4hWP6KtLyFwggnYB4yPK8xI%3D",' +
'oauth_timestamp="1313581372",' +
'oauth_nonce="W0zUmvyC4eVL8cBd4YwlH1nnPTbxW0QBYcWkXTwe4",' +
'oauth_version="1.0"';
var oauth_header_tokens = 'oauth_consumer_key="dpf43f3p2l4k3l03",' +
'oauth_token="nnch734d00sl2jdk",' +
'oauth_signature_method="HMAC-SHA1", ' +
'oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D",' +
'oauth_timestamp="1191242096",' +
'oauth_nonce="kllo9940pd9333jh",' +
'oauth_version="1.0"';
var full_oauth_header = 'OAuth realm="http://photos.example.net/"' + oauth_header_tokens;
var metadataBackend = MetadataDB({ var metadataBackend = new MetadataDB({
host: global.settings.redis_host, host: global.settings.redis_host,
port: global.settings.redis_port, port: global.settings.redis_port,
max: global.settings.redisPool, max: global.settings.redisPool,
@ -28,44 +41,43 @@ var metadataBackend = MetadataDB({
reapIntervalMillis: global.settings.redisReapIntervalMillis reapIntervalMillis: global.settings.redisReapIntervalMillis
}); });
suite('oauth', function() { describe('oauth', function() {
test('test database number', function(){ it('test database number', function(){
assert.equal(oAuth.oauth_database, 3); assert.equal(oAuth.oauth_database, 3);
}); });
test('test oauth database key', function(){ it('test oauth database key', function(){
assert.equal(oAuth.oauth_user_key, "rails:oauth_access_tokens:<%= oauth_access_key %>"); assert.equal(oAuth.oauth_user_key, "rails:oauth_access_tokens:<%= oauth_access_key %>");
}); });
test('test parse tokens from full headers does not raise exception', function(){ it('test parse tokens from full headers does not raise exception', function(){
var req = {query:{}, headers:{authorization:full_oauth_header}}; var req = {query:{}, headers:{authorization:full_oauth_header}};
assert.doesNotThrow(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/); assert.doesNotThrow(function(){ oAuth.parseTokens(req); }, /incomplete oauth tokens in request/);
}); });
test('test parse all normal tokens raises no exception', function(){ it('test parse all normal tokens raises no exception', function(){
var req = {query:oauth_data, headers:{}}; var req = {query:oauth_data, headers:{}};
assert.doesNotThrow(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/); assert.doesNotThrow(function(){ oAuth.parseTokens(req); }, /incomplete oauth tokens in request/);
}); });
test('test headers take presedence over query parameters', function(){ it('test headers take presedence over query parameters', function(){
var req = {query:{oauth_signature_method: "MY_HASH"}, headers:{authorization:full_oauth_header}}; var req = {query:{oauth_signature_method: "MY_HASH"}, headers:{authorization:full_oauth_header}};
var tokens = oAuth.parseTokens(req); var tokens = oAuth.parseTokens(req);
assert.equal(tokens.oauth_signature_method, "HMAC-SHA1"); assert.equal(tokens.oauth_signature_method, "HMAC-SHA1");
}); });
test('test can access oauth hash for a user based on access token (oauth_token)', function(done){ it('test can access oauth hash for a user based on access token (oauth_token)', function(done){
var req = {query:{}, headers:{authorization:real_oauth_header}}; var req = {query:{}, headers:{authorization:real_oauth_header}};
var tokens = oAuth.parseTokens(req); var tokens = oAuth.parseTokens(req);
oAuth.getOAuthHash(metadataBackend, tokens.oauth_token, function(err, data){ oAuth.getOAuthHash(metadataBackend, tokens.oauth_token, function(err, data){
console.log(data);
assert.equal(tokens.oauth_consumer_key, data.consumer_key); assert.equal(tokens.oauth_consumer_key, data.consumer_key);
done(); done();
}); });
}); });
test('test non existant oauth hash for a user based on oauth_token returns empty hash', function(done){ it('test non existant oauth hash for a user based on oauth_token returns empty hash', function(done){
var req = {query:{}, headers:{authorization:full_oauth_header}}; var req = {query:{}, headers:{authorization:full_oauth_header}};
var tokens = oAuth.parseTokens(req); var tokens = oAuth.parseTokens(req);
@ -76,7 +88,7 @@ test('test non existant oauth hash for a user based on oauth_token returns empty
}); });
}); });
test('can return user for verified signature', function(done){ it('can return user for verified signature', function(done){
var req = {query:{}, var req = {query:{},
headers:{authorization:real_oauth_header, host: 'vizzuality.testhost.lan' }, headers:{authorization:real_oauth_header, host: 'vizzuality.testhost.lan' },
protocol: 'http', protocol: 'http',
@ -91,7 +103,7 @@ test('can return user for verified signature', function(done){
}); });
}); });
test('returns null user for unverified signatures', function(done){ it('returns null user for unverified signatures', function(done){
var req = {query:{}, var req = {query:{},
headers:{authorization:real_oauth_header, host: 'vizzuality.testyhost.lan' }, headers:{authorization:real_oauth_header, host: 'vizzuality.testyhost.lan' },
protocol: 'http', protocol: 'http',
@ -105,7 +117,7 @@ test('returns null user for unverified signatures', function(done){
}); });
}); });
test('returns null user for no oauth', function(done){ it('returns null user for no oauth', function(done){
var req = { var req = {
query:{}, query:{},
headers:{}, headers:{},
@ -120,14 +132,14 @@ test('returns null user for no oauth', function(done){
}); });
}); });
test('OAuthAuth reports it has credentials', function(done) { it('OAuthAuth reports it has credentials', function(done) {
var req = {query:{}, headers:{authorization:real_oauth_header}}; var req = {query:{}, headers:{authorization:real_oauth_header}};
var oAuthAuth = new OAuthAuth(req); var oAuthAuth = new OAuthAuth(req);
assert.ok(oAuthAuth.hasCredentials()); assert.ok(oAuthAuth.hasCredentials());
done(); done();
}); });
test('OAuthAuth reports it has no credentials', function(done) { it('OAuthAuth reports it has no credentials', function(done) {
var req = {query:{}, headers:{}}; var req = {query:{}, headers:{}};
var oAuthAuth = new OAuthAuth(req); var oAuthAuth = new OAuthAuth(req);
assert.equal(oAuthAuth.hasCredentials(), false); assert.equal(oAuthAuth.hasCredentials(), false);