Merge branch 'develop'

This commit is contained in:
Sandro Santilli 2012-07-16 19:14:37 +02:00
commit 639eea00cc
18 changed files with 661 additions and 196 deletions

8
Makefile Normal file
View File

@ -0,0 +1,8 @@
all:
npm install
clean:
rm -rf node_modules/*
check:
npm test

View File

@ -9,7 +9,8 @@ Provides a nodejs based API for running SQL queries against CartoDB.
core requirements
-------------
* postgres
* postgres 9.0+
* cartodb 0.9.5+ (for CDB_QueryTables)
* redis
* node > v0.4.8 && < v0.9.0
* npm

View File

@ -37,7 +37,7 @@ app.all('/api/v1/sql.:f', function(req, res) { handleQuery(req, res) } );
app.get('/api/v1/cachestatus', function(req, res) { handleCacheStatus(req, res) } );
// request handlers
function handleQuery(req, res){
function handleQuery(req, res) {
// extract input
var body = (req.body) ? req.body : {};
@ -50,8 +50,8 @@ function handleQuery(req, res){
var dp = req.query.dp;
// sanitize and apply defaults to input
dp = (dp === "" || _.isUndefined(dp)) ? '6' : dp;
format = (format === "" || _.isUndefined(format)) ? null : format;
dp = (dp === "" || _.isUndefined(dp)) ? '6' : dp;
format = (format === "" || _.isUndefined(format)) ? null : format.toLowerCase();
sql = (sql === "" || _.isUndefined(sql)) ? null : sql;
database = (database === "" || _.isUndefined(database)) ? null : database;
limit = (_.isNumber(limit)) ? limit : null;
@ -75,12 +75,18 @@ function handleQuery(req, res){
// 4. Run query with r/w or public user
// 5. package results and send back
Step(
function getDatabaseName(){
Meta.getDatabase(req, this);
function getDatabaseName() {
if (_.isNull(database)) {
Meta.getDatabase(req, this);
} else {
// database hardcoded in query string (deprecated??): don't use redis
return database;
}
},
function setDBGetUser(err, data) {
if (err) throw err;
database = (data == "" || _.isNull(data) || _.isUndefined(data)) ? database : data;
database = (data === "" || _.isNull(data) || _.isUndefined(data)) ? database : data;
// If the database could not be found, the user is non-existant
if (_.isNull(database)) {
@ -152,11 +158,17 @@ function handleQuery(req, res){
toCSV(result, res, this);
} else {
var end = new Date().getTime();
return {
'time' : ((end - start)/1000),
'total_rows': result.rows.length,
'rows' : result.rows
};
var json_result = {'time' : (end - start)/1000};
if (result.command === 'SELECT') {
json_result.total_rows = result.rows.length;
json_result.rows = result.rows;
} else {
json_result.total_rows = result.rowCount;
}
return json_result;
}
},
function sendResults(err, out){

View File

@ -9,7 +9,8 @@ var RedisPool = require("./redis_pool")
var oAuth = function(){
var me = {
oauth_database: 3,
oauth_user_key: "rails:oauth_access_tokens:<%= oauth_access_key %>"
oauth_user_key: "rails:oauth_access_tokens:<%= oauth_access_key %>",
is_oauth_request: true
};
// oauth token cases:
@ -76,11 +77,21 @@ var oAuth = function(){
},
function getOAuthHash(err, data){
if (err) throw err;
passed_tokens = data;
that.getOAuthHash(passed_tokens.oauth_token, this);
// this is oauth request only if oauth headers are present
this.is_oauth_request = !_.isEmpty(data);
if (this.is_oauth_request) {
passed_tokens = data;
that.getOAuthHash(passed_tokens.oauth_token, this);
} else {
return null;
}
},
function regenerateSignature(err, data){
if (err) throw err;
if (!this.is_oauth_request) return null;
ohash = data;
var consumer = OAuthUtil.createConsumer(ohash.consumer_key, ohash.consumer_secret);
var access_token = OAuthUtil.createToken(ohash.access_token_token, ohash.access_token_secret);
@ -110,6 +121,7 @@ var oAuth = function(){
},
function checkSignature(err, data){
if (err) throw err;
//console.log(data + " should equal the provided signature: " + signature);
callback(err, (signature === data && !_.isUndefined(data)) ? ohash.user_id : null);
}

View File

@ -47,10 +47,11 @@ var PSQL = function(user_id, db, limit, offset){
if (that.client) {
return callback(null, that.client);
} else {
pg.connect(conString, function(err, client){
that.client = client;
return callback(err, client);
});
var err = null;
var client = new pg.Client(conString);
client.connect();
that.client = client;
return callback(err, client);
}
};

View File

@ -6,7 +6,7 @@ module.exports.db_user = 'test_cartodb_user_<%= user_id %>';
module.exports.db_host = 'localhost';
module.exports.db_port = '5432';
module.exports.redis_host = '127.0.0.1';
module.exports.redis_port = 6379;
module.exports.redis_port = 6333;
module.exports.redisPool = 50;
module.exports.redisIdleTimeoutMillis = 1;
module.exports.redisReapIntervalMillis = 1;

106
npm-shrinkwrap.json generated Normal file
View File

@ -0,0 +1,106 @@
{
"name": "cartodb_api",
"version": "0.0.4",
"dependencies": {
"cluster": {
"version": "0.6.4",
"dependencies": {
"log": {
"version": "1.3.0"
}
}
},
"express": {
"version": "2.5.11",
"dependencies": {
"connect": {
"version": "1.9.2",
"dependencies": {
"formidable": {
"version": "1.0.11"
}
}
},
"mime": {
"version": "1.2.4"
},
"qs": {
"version": "0.4.2"
},
"mkdirp": {
"version": "0.3.0"
}
}
},
"underscore": {
"version": "1.1.7"
},
"underscore.string": {
"version": "1.1.5",
"dependencies": {
"underscore": {
"version": "1.1.6"
}
}
},
"pg": {
"version": "0.6.14",
"dependencies": {
"generic-pool": {
"version": "1.0.9"
}
}
},
"generic-pool": {
"version": "1.0.12"
},
"redis": {
"version": "0.7.1"
},
"hiredis": {
"version": "0.1.14"
},
"step": {
"version": "0.0.5"
},
"oauth-client": {
"version": "0.2.0",
"dependencies": {
"node-uuid": {
"version": "1.1.0"
}
}
},
"node-uuid": {
"version": "1.3.3"
},
"csv": {
"version": "0.0.13"
},
"mocha": {
"version": "1.2.1",
"dependencies": {
"commander": {
"version": "0.6.1"
},
"growl": {
"version": "1.5.1"
},
"jade": {
"version": "0.26.3",
"dependencies": {
"mkdirp": {
"version": "0.3.0"
}
}
},
"diff": {
"version": "1.0.2"
},
"debug": {
"version": "0.7.0"
}
}
}
}
}

View File

@ -10,7 +10,7 @@
},
"dependencies": {
"cluster": "0.6.4",
"express": "2.5.8",
"express": "~2.5.11",
"underscore" : "1.1.x",
"underscore.string": "1.1.5",
"pg": "0.6.14",
@ -20,14 +20,14 @@
"step": "0.0.x",
"oauth-client": "0.2.0",
"node-uuid":"1.3.3",
"zlib":"1.0.5",
"csv":"0.0.13"
},
"devDependencies": {
"expresso": "0.8.x"
"mocha": "1.2.1"
},
"scripts": {
"unit": "expresso ./test/unit/* | sh",
"acceptance": "expresso ./test/acceptance/* | sh"
"test": "test/run_tests.sh"
},
"engines": { "node": ">= 0.4.1 < 0.7.0" }
"engines": { "node": ">= 0.4.1 < 0.9" }
}

View File

@ -2,17 +2,20 @@ cartodb-sql-api tests
---------------------
Tests require you create a test database and set some redis keys before,
you can execute prepare_db.sh script, it will create database, users
and redis stuff for you. Be sure postgres and redis are running
and redis stuff for you. Be sure postgres and redis are running.
> cd test && ./prepare_db.sh
Note that "make check" from top-level dir will try to do everything
needed to prepare & run the tests.
Acceptance tests (need ctrl-C to exit)
--------------------------------------
> expresso test/acceptance/app.test.js
> expresso test/acceptance/app.auth.test.js
> mocha -u tdd test/acceptance/app.test.js
> mocha -u tdd test/acceptance/app.auth.test.js
Unit tests (need ctrl-C to exit)
Unit tests
--------------------------------
> expresso test/unit/*.js (or run the tests individually)
> mocha -u tdd test/unit/*.js (or run the tests individually)

View File

@ -1,32 +1,37 @@
require('../helper');
require('../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')
, assert = require('assert')
, tests = module.exports = {}
, querystring = require('querystring');
tests['valid api key should allow insert in protected tables'] = function(){
suite('app.auth', function() {
test('valid api key should allow insert in protected tables', function(done){
assert.response(app, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=1234&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('test')",
headers: {host: 'vizzuality.localhost.lan:8080' },
method: 'GET'
},{
status: 200
},{}, function(res) {
assert.equal(res.statusCode, 200, res.body);
done();
});
}
});
tests['invalid api key should NOT allow insert in protected tables'] = function(){
test('invalid api key should NOT allow insert in protected tables', function(done){
assert.response(app, {
// view prepare_db.sh to see where to set api_key
url: "/api/v1/sql?api_key=RAMBO&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('test')",
url: "/api/v1/sql?api_key=RAMBO&q=INSERT%20INTO%20private_table%20(name)%20VALUES%20('RAMBO')",
headers: {host: 'vizzuality.cartodb.com' },
method: 'GET'
},{
status: 400
});
}
}, function() { done(); });
});
});

View File

@ -12,6 +12,7 @@
*
*/
require('../helper');
require('../support/assert');
var app = require(global.settings.app_root + '/app/controllers/app')
, assert = require('assert')
@ -21,71 +22,86 @@ var app = require(global.settings.app_root + '/app/controllers/app')
// allow lots of emitters to be set to silence warning
app.setMaxListeners(0);
suite('app.test', function() {
var 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"';
// 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;
}
tests['GET /api/v1/sql'] = function(){
test('GET /api/v1/sql', function(done){
assert.response(app, {
url: '/api/v1/sql',
method: 'GET'
},{
body: '{"error":["You must indicate a sql query"]}',
status: 400
}, function(res) {
assert.deepEqual(JSON.parse(res.body), {"error":["You must indicate a sql query"]});
done();
});
};
});
tests['GET /api/v1/sql with SQL parameter on SELECT only. No oAuth included '] = function(){
test('GET /api/v1/sql with SQL parameter on SELECT only. No oAuth included ', function(done){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&database=cartodb_test_user_1_db',
method: 'GET'
},{
status: 200
},{ }, function(res) {
assert.equal(res.statusCode, 200, res.body);
done();
});
};
});
tests['GET /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers'] = function(){
test('GET /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers', function(done){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{
status: 200
},{ }, function(res) {
assert.equal(res.statusCode, 200, res.body);
done();
});
};
});
tests['POST /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers'] = function(){
test('POST /api/v1/sql with SQL parameter on SELECT only. no database param, just id using headers', function(done){
assert.response(app, {
url: '/api/v1/sql',
data: querystring.stringify({q: "SELECT * FROM untitle_table_4"}),
headers: {host: 'vizzuality.cartodb.com', 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'POST'
},{
status: 200
},{ }, function(res) {
assert.equal(res.statusCode, 200, res.body);
done();
});
};
});
tests['GET /api/v1/sql with SQL parameter on INSERT only. oAuth not used, so public user - should fail'] = function(){
test('GET /api/v1/sql with SQL parameter on INSERT only. oAuth not used, so public user - should fail', function(){
assert.response(app, {
url: "/api/v1/sql?q=INSERT%20INTO%20untitle_table_4%20(id)%20VALUES%20(1)&database=cartodb_dev_user_1_db",
method: 'GET'
},{
status: 400
});
};
});
tests['GET /api/v1/sql with SQL parameter on DROP DATABASE only. oAuth not used, so public user - should fail'] = function(){
test('GET /api/v1/sql with SQL parameter on DROP DATABASE only. oAuth not used, so public user - should fail', function(){
assert.response(app, {
url: "/api/v1/sql?q=DROP%20TABLE%20untitle_table_4&database=cartodb_dev_user_1_db",
method: 'GET'
},{
status: 400
});
};
});
tests['GET /api/v1/sql with SQL parameter on INSERT only. header based db - should fail'] = function(){
test('GET /api/v1/sql with SQL parameter on INSERT only. header based db - should fail', function(){
assert.response(app, {
url: "/api/v1/sql?q=INSERT%20INTO%20untitle_table_4%20(id)%20VALUES%20(1)",
headers: {host: 'vizzuality.cartodb.com'},
@ -93,9 +109,9 @@ tests['GET /api/v1/sql with SQL parameter on INSERT only. header based db - shou
},{
status: 400
});
};
});
tests['GET /api/v1/sql with SQL parameter on DROP DATABASE only.header based db - should fail'] = function(){
test('GET /api/v1/sql with SQL parameter on DROP DATABASE only.header based db - should fail', function(){
assert.response(app, {
url: "/api/v1/sql?q=DROP%20TABLE%20untitle_table_4",
headers: {host: 'vizzuality.cartodb.com'},
@ -103,35 +119,35 @@ tests['GET /api/v1/sql with SQL parameter on DROP DATABASE only.header based db
},{
status: 400
});
};
});
tests['GET /api/v1/sql with SQL parameter and geojson format, ensuring content-disposition set to geojson'] = function(){
test('GET /api/v1/sql with SQL parameter and geojson format, ensuring content-disposition set to geojson', function(done){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{
status: 200
}, function(res){
},{ }, function(res){
assert.equal(res.statusCode, 200, res.body);
var cd = res.header('Content-Disposition');
assert.equal(true, /filename=cartodb-query.geojson/gi.test(cd));
done();
});
};
});
tests['GET /api/v1/sql with SQL parameter and no format, ensuring content-disposition set to json'] = function(){
test('GET /api/v1/sql with SQL parameter and no format, ensuring content-disposition set to json', function(done){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{
status: 200
}, function(res){
},{ }, function(res){
assert.equal(res.statusCode, 200, res.body);
var cd = res.header('Content-Disposition');
assert.equal(true, /filename=cartodb-query.json/gi.test(cd));
done();
});
};
});
tests['GET /api/v1/sql ensure cross domain set on errors'] = function(){
test('GET /api/v1/sql ensure cross domain set on errors', function(done){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*gadfgadfg%20FROM%20untitle_table_4',
headers: {host: 'vizzuality.cartodb.com'},
@ -141,72 +157,73 @@ tests['GET /api/v1/sql ensure cross domain set on errors'] = function(){
}, function(res){
var cd = res.header('Access-Control-Allow-Origin');
assert.equal(cd, '*');
done();
});
};
});
tests['GET /api/v1/sql as geojson limiting decimal places'] = function(){
test('GET /api/v1/sql as geojson limiting decimal places', function(done){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson&dp=1',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{
status: 200
}, function(res){
},{ }, function(res){
assert.equal(res.statusCode, 200, res.body);
var result = JSON.parse(res.body);
assert.equal(1, checkDecimals(result.features[0].geometry.coordinates[0], '.'));
done();
});
};
});
tests['GET /api/v1/sql as geojson with default dp as 6'] = function(){
test('GET /api/v1/sql as geojson with default dp as 6', function(done){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{
status: 200
}, function(res){
},{ }, function(res){
assert.equal(res.statusCode, 200, res.body);
var result = JSON.parse(res.body);
assert.equal(6, checkDecimals(result.features[0].geometry.coordinates[0], '.'));
done();
});
};
});
tests['GET /api/v1/sql as csv'] = function(){
test('GET /api/v1/sql as csv', function(done){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20cartodb_id,ST_AsEWKT(the_geom)%20as%20geom%20FROM%20untitle_table_4%20LIMIT%201&format=csv',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{
status: 200
}, function(res){
},{ }, function(res){
assert.equal(res.statusCode, 200, res.body);
var body = "cartodb_id,geom\r\n1,SRID=4326;POINT(-3.699732 40.423012)";
assert.equal(body, res.body);
done();
});
};
});
tests['GET /api/v1/sql as csv, properly escaped'] = function(){
test('GET /api/v1/sql as csv, properly escaped', function(done){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20cartodb_id,%20address%20FROM%20untitle_table_4%20LIMIT%201&format=csv',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{
status: 200
}, function(res){
},{ }, function(res){
assert.equal(res.statusCode, 200, res.body);
var body = 'cartodb_id,address\r\n1,"Calle de Pérez Galdós 9, Madrid, Spain"';
assert.equal(body, res.body);
done();
});
};
});
tests['cannot GET system tables'] = function(){
test('cannot GET system tables', function(done){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20pg_attribute',
headers: {host: 'vizzuality.cartodb.com'},
method: 'GET'
},{
status: 403
});
};
}, function() { done(); });
});
tests['GET decent error if domain is incorrect'] = function(){
test('GET decent error if domain is incorrect', function(done){
assert.response(app, {
url: '/api/v1/sql?q=SELECT%20*%20FROM%20untitle_table_4&format=geojson',
headers: {host: 'vizzualinot.cartodb.com'},
@ -216,14 +233,8 @@ tests['GET decent error if domain is incorrect'] = function(){
}, function(res){
var result = JSON.parse(res.body);
assert.equal(result.error[0],"Sorry, we can't find this CartoDB. Please check that you have entered the correct domain.");
done();
});
};
});
// use dec_sep for internationalization
function checkDecimals(x, dec_sep){
tmp='' + x;
if (tmp.indexOf(dec_sep)>-1)
return tmp.length-tmp.indexOf(dec_sep)-1;
else
return 0;
}
});

View File

@ -1,22 +1,51 @@
#!/bin/sh
# this script prepare database and redis instance to run accpetance test
#!/bin/sh
echo "preparing redis..."
echo "HSET rails:users:vizzuality id 1" | redis-cli -n 5
echo "HSET rails:users:vizzuality database_name cartodb_test_user_1_db" | redis-cli -n 5
echo "SADD rails:users:vizzuality:map_key 1234" | redis-cli -n 5
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR consumer_key fZeNGv5iYayvItgDYHUbot1Ukb5rVyX6QAg8GaY2" | redis-cli -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR consumer_secret IBLCvPEefxbIiGZhGlakYV4eM8AbVSwsHxwEYpzx" | redis-cli -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR access_token_token l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR" | redis-cli -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR access_token_secret 22zBIek567fMDEebzfnSdGe8peMFVFqAreOENaDK" | redis-cli -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR user_id 1" | redis-cli -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR time sometime" | redis-cli -n 3
# this script prepare database and redis instance to run acceptance test
#
# NOTE: assumes existance of a "template_postgis" loaded with
# compatible version of postgis (legacy.sql included)
# This is where postgresql connection parameters are read from
TESTENV=../config/environments/test.js
# Extract postgres configuration
#PGUSER=`grep \.db_user ${TESTENV} | sed "s/.*= *'\([^']*\)'.*/\1/"`
#echo "PGUSER: [$PGUSER]"
PGHOST=`grep \.db_host ${TESTENV} | sed "s/.*= *'\([^']*\)'.*/\1/"`
echo "PGHOST: [$PGHOST]"
PGPORT=`grep \.db_port ${TESTENV} | sed "s/.*=[\t ]*'\([^']*\)'.*/\1/"`
echo "PGPORT: [$PGPORT]"
TEST_DB="cartodb_test_user_1_db"
REDIS_PORT=6333 # TODO: read from environment file
export PGHOST PGPORT
die() {
msg=$1
echo "${msg}" >&2
exit 1
}
echo "preparing postgres..."
dropdb -Upostgres -hlocalhost cartodb_test_user_1_db
createdb -Upostgres -hlocalhost -Ttemplate_postgis -Opostgres -EUTF8 cartodb_test_user_1_db
psql -Upostgres -hlocalhost cartodb_test_user_1_db < test.sql
dropdb ${TEST_DB} 2> /dev/null # error expected if doesn't exist
createdb -Ttemplate_postgis -EUTF8 ${TEST_DB} || die "Could not create test database"
psql -f test.sql ${TEST_DB}
echo "preparing redis..."
echo "HSET rails:users:vizzuality id 1" | redis-cli -p ${REDIS_PORT} -n 5
echo "HSET rails:users:vizzuality database_name ${TEST_DB}" | redis-cli -p ${REDIS_PORT} -n 5
echo "SADD rails:users:vizzuality:map_key 1234" | redis-cli -p ${REDIS_PORT} -n 5
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR consumer_key fZeNGv5iYayvItgDYHUbot1Ukb5rVyX6QAg8GaY2" | redis-cli -p ${REDIS_PORT} -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR consumer_secret IBLCvPEefxbIiGZhGlakYV4eM8AbVSwsHxwEYpzx" | redis-cli -p ${REDIS_PORT} -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR access_token_token l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR" | redis-cli -p ${REDIS_PORT} -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR access_token_secret 22zBIek567fMDEebzfnSdGe8peMFVFqAreOENaDK" | redis-cli -p ${REDIS_PORT} -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR user_id 1" | redis-cli -p ${REDIS_PORT} -n 3
echo "hset rails:oauth_access_tokens:l0lPbtP68ao8NfStCiA3V3neqfM03JKhToxhUQTR time sometime" | redis-cli -p ${REDIS_PORT} -n 3
echo "ok, you can run test now"

45
test/run_tests.sh Executable file
View File

@ -0,0 +1,45 @@
#!/bin/sh
# Must match redis_port in config/environments/test.js
# TODO: read from there
REDIS_PORT=6333
cleanup() {
echo "Cleaning up"
kill ${PID_REDIS}
}
cleanup_and_exit() {
cleanup
exit
}
die() {
msg=$1
echo "${msg}" >&2
cleanup
exit 1
}
trap 'cleanup_and_exit' 1 2 3 5 9 13
echo "Starting redis on port ${REDIS_PORT}"
echo "port ${REDIS_PORT}" | redis-server - > test/test.log &
PID_REDIS=$!
echo "Preparing the environment"
cd test; sh prepare_db.sh >> test.log || die "database preparation failure (see test.log)"; cd -;
PATH=node_modules/.bin/:$PATH
echo "Running tests"
mocha -u tdd \
test/unit/redis_pool.test.js \
test/unit/metadata.test.js \
test/unit/oauth.test.js \
test/unit/psql.test.js \
test/acceptance/app.test.js \
test/acceptance/app.auth.test.js
cleanup

201
test/support/assert.js Normal file
View File

@ -0,0 +1,201 @@
// Cribbed from the ever prolific Konstantin Kaefer
// https://github.com/mapbox/tilelive-mapnik/blob/master/test/support/assert.js
var fs = require('fs');
var http = require('http');
var path = require('path');
var exec = require('child_process').exec;
var assert = module.exports = exports = require('assert');
assert.imageEqualsFile = function(buffer, file_b, callback) {
if (!callback) callback = function(err) { if (err) throw err; };
file_b = path.resolve(file_b);
var file_a = '/tmp/' + (Math.random() * 1e16);
var err = fs.writeFileSync(file_a, buffer, 'binary');
if (err) throw err;
exec('compare -metric PSNR "' + file_a + '" "' +
file_b + '" /dev/null', function(err, stdout, stderr) {
if (err) {
fs.unlinkSync(file_a);
callback(err);
} else {
stderr = stderr.trim();
if (stderr === 'inf') {
fs.unlinkSync(file_a);
callback(null);
} else {
var similarity = parseFloat(stderr);
var err = new Error('Images not equal(' + similarity + '): ' +
file_a + ' ' + file_b);
err.similarity = similarity;
callback(err);
}
}
});
};
/**
* Assert response from `server` with
* the given `req` object and `res` assertions object.
*
* @param {Server} server
* @param {Object} req
* @param {Object|Function} res
* @param {String} msg
*/
assert.response = function(server, req, res, msg){
var port = 5555;
function check(){
try {
server.__port = server.address().port;
server.__listening = true;
} catch (err) {
process.nextTick(check);
return;
}
if (server.__deferred) {
server.__deferred.forEach(function(args){
assert.response.apply(assert, args);
});
server.__deferred = null;
}
}
// Check that the server is ready or defer
if (!server.fd) {
server.__deferred = server.__deferred || [];
server.listen(server.__port = port++, '127.0.0.1', check);
} else if (!server.__port) {
server.__deferred = server.__deferred || [];
process.nextTick(check);
}
// The socket was created but is not yet listening, so keep deferring
if (!server.__listening) {
server.__deferred.push(arguments);
return;
}
// Callback as third or fourth arg
var callback = typeof res === 'function'
? res
: typeof msg === 'function'
? msg
: function(){};
// Default messate to test title
if (typeof msg === 'function') msg = null;
msg = msg || assert.testTitle;
msg += '. ';
// Pending responses
server.__pending = server.__pending || 0;
server.__pending++;
// Create client
if (!server.fd) {
server.listen(server.__port = port++, '127.0.0.1', issue);
} else {
issue();
}
function issue(){
// Issue request
var timer,
method = req.method || 'GET',
status = res.status || res.statusCode,
data = req.data || req.body,
requestTimeout = req.timeout || 0,
encoding = req.encoding || 'utf8';
var request = http.request({
host: '127.0.0.1',
port: server.__port,
path: req.url,
method: method,
headers: req.headers,
agent: false
});
var check = function() {
if (--server.__pending === 0) {
server.close();
server.__listening = false;
}
};
// Timeout
if (requestTimeout) {
timer = setTimeout(function(){
check();
delete req.timeout;
assert.fail(msg + 'Request timed out after ' + requestTimeout + 'ms.');
}, requestTimeout);
}
if (data) request.write(data);
request.on('response', function(response){
response.body = '';
response.setEncoding(encoding);
response.on('data', function(chunk){ response.body += chunk; });
response.on('end', function(){
if (timer) clearTimeout(timer);
// Assert response body
if (res.body !== undefined) {
var eql = res.body instanceof RegExp
? res.body.test(response.body)
: res.body === response.body;
assert.ok(
eql,
msg + 'Invalid response body.\n'
+ ' Expected: ' + res.body + '\n'
+ ' Got: ' + response.body
);
}
// Assert response status
if (typeof status === 'number') {
assert.equal(
response.statusCode,
status,
msg + 'Invalid response status code.\n'
+ ' Expected: [green]{' + status + '}\n'
+ ' Got: [red]{' + response.statusCode + '}'
);
}
// Assert response headers
if (res.headers) {
var keys = Object.keys(res.headers);
for (var i = 0, len = keys.length; i < len; ++i) {
var name = keys[i],
actual = response.headers[name.toLowerCase()],
expected = res.headers[name],
eql = expected instanceof RegExp
? expected.test(actual)
: expected == actual;
assert.ok(
eql,
msg + 'Invalid response header [bold]{' + name + '}.\n'
+ ' Expected: [green]{' + expected + '}\n'
+ ' Got: [red]{' + actual + '}'
);
}
}
// Callback
callback(response);
check();
});
});
request.end();
}
};

View File

@ -6,18 +6,24 @@ var _ = require('underscore')
, assert = require('assert')
, tests = module.exports = {};
tests['test can retrieve database name from header and redis'] = function(){
suite('metadata', function() {
test('test can retrieve database name from header and redis', function(done){
var req = {headers: {host: 'vizzuality.cartodb.com'}};
MetaData.getDatabase(req, function(err, data){
assert.equal(data, 'cartodb_test_user_1_db');
done();
});
};
});
tests['test can retrieve id from header and redis'] = function(){
test('test can retrieve id from header and redis', function(done){
var req = {headers: {host: 'vizzuality.cartodb.com'}};
MetaData.getId(req, function(err, data){
assert.equal(data, '1');
done();
});
};
});
});

View File

@ -19,49 +19,53 @@ var _ = require('underscore')
, 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"'
, full_oauth_header = 'OAuth realm="http://photos.example.net/"' + oauth_header_tokens;
tests['test database number'] = function(){
suite('oauth', function() {
test('test database number', function(){
assert.equal(oAuth.oauth_database, 3);
};
});
tests['test oauth database key'] = function(){
test('test oauth database key', function(){
assert.equal(oAuth.oauth_user_key, "rails:oauth_access_tokens:<%= oauth_access_key %>");
};
});
tests['test parse tokens from full headers does not raise exception'] = function(){
test('test parse tokens from full headers does not raise exception', function(){
var req = {query:{}, headers:{authorization:full_oauth_header}};
assert.doesNotThrow(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
};
});
tests['test parse all normal tokens raises no exception'] = function(){
test('test parse all normal tokens raises no exception', function(){
var req = {query:oauth_data, headers:{}};
assert.doesNotThrow(function(){ oAuth.parseTokens(req) }, /incomplete oauth tokens in request/);
};
});
tests['test headers take presedence over query parameters'] = function(){
test('test headers take presedence over query parameters', function(){
var req = {query:{oauth_signature_method: "MY_HASH"}, headers:{authorization:full_oauth_header}};
var tokens = oAuth.parseTokens(req);
assert.equal(tokens.oauth_signature_method, "HMAC-SHA1");
};
});
tests['test can access oauth hash for a user based on access token (oauth_token)'] = function(){
test('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 tokens = oAuth.parseTokens(req);
oAuth.getOAuthHash(tokens.oauth_token, function(err, data){
assert.equal(tokens.oauth_consumer_key, data.consumer_key);
done();
});
};
});
tests['test non existant oauth hash for a user based on oauth_token returns empty hash'] = function(){
test('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 tokens = oAuth.parseTokens(req);
oAuth.getOAuthHash(tokens.oauth_token, function(err, data){
assert.eql(data, {});
assert.deepEqual(data, {});
done();
});
};
});
tests['can return user for verified signature'] = function(){
test('can return user for verified signature', function(done){
var req = {query:{},
headers:{authorization:real_oauth_header, host: 'vizzuality.testhost.lan' },
method: 'GET',
@ -69,11 +73,12 @@ tests['can return user for verified signature'] = function(){
};
oAuth.verifyRequest(req, function(err, data){
assert.eql(data, 1);
assert.equal(data, 1);
done();
}, true);
};
});
tests['returns null user for unverified signatures'] = function(){
test('returns null user for unverified signatures', function(done){
var req = {query:{},
headers:{authorization:real_oauth_header, host: 'vizzuality.testyhost.lan' },
method: 'GET',
@ -81,11 +86,12 @@ tests['returns null user for unverified signatures'] = function(){
};
oAuth.verifyRequest(req, function(err, data){
assert.eql(data, null);
assert.equal(data, null);
done();
}, true);
};
});
tests['returns null user for no oauth'] = function(){
test('returns null user for no oauth', function(done){
var req = {
query:{},
headers:{},
@ -94,6 +100,9 @@ tests['returns null user for no oauth'] = function(){
};
oAuth.verifyRequest(req,function(err,data){
assert.eql(data, null);
assert.equal(data, null);
done();
});
};
});
});

View File

@ -4,89 +4,96 @@ var _ = require('underscore')
, PSQL = require('../../app/models/psql')
, assert = require('assert');
exports['test throws error if no args passed to constructor'] = function(){
suite('psql', function() {
test('test throws error if no args passed to constructor', function(){
var msg;
try{
var pg = new PSQL();
} catch (err){
assert.equal(err.message, "Incorrect access parameters. If you are accessing via OAuth, please check your tokens are correct. For public users, please ensure your table is published.");
msg = err.message;
}
};
assert.equal(msg, "Incorrect access parameters. If you are accessing via OAuth, please check your tokens are correct. For public users, please ensure your table is published.");
});
exports['test instantiate with just user constructor'] = function(){
test('test instantiate with just user constructor', function(){
var pg = new PSQL("1", null);
assert.equal(pg.user_id, "1");
};
});
exports['test instantiate with just db constructor'] = function(){
test('test instantiate with just db constructor', function(){
var pg = new PSQL(null, 'my_database');
assert.equal(pg.db, "my_database");
};
});
exports['test username returns default user if not set'] = function(){
test('test username returns default user if not set', function(){
var pg = new PSQL(null, 'my_database');
assert.equal(pg.username(), "publicuser");
};
});
exports['test username returns interpolated user if set'] = function(){
test('test username returns interpolated user if set', function(){
var pg = new PSQL('simon', 'my_database');
assert.equal(pg.username(), "test_cartodb_user_simon");
};
});
exports['test username returns default db if user not set'] = function(){
test('test username returns default db if user not set', function(){
var pg = new PSQL(null, 'my_database');
assert.equal(pg.database(), "my_database");
};
});
exports['test username returns interpolated db if user set'] = function(){
test('test username returns interpolated db if user set', function(){
var pg = new PSQL('simon');
assert.equal(pg.database(), "cartodb_test_user_simon_db");
};
});
exports['test private user can execute SELECTS on db'] = function(){
test('test private user can execute SELECTS on db', function(done){
var pg = new PSQL('1');
var sql = "SELECT 1 as test_sum";
pg.query(sql, function(err, result){
assert.equal(result.rows[0].test_sum, 1);
pg.end();
done();
});
};
});
exports['test private user can execute CREATE on db'] = function(){
test('test private user can execute CREATE on db', function(done){
var pg = new PSQL('1');
var sql = "DROP TABLE IF EXISTS distributors; CREATE TABLE distributors (id integer, name varchar(40), UNIQUE(name))";
pg.query(sql, function(err, result){
assert.isNull(err);
assert.ok(_.isNull(err));
pg.end();
done();
});
};
});
exports['test private user can execute INSERT on db'] = function(){
test('test private user can execute INSERT on db', function(done){
var pg = new PSQL('1');
var sql = "DROP TABLE IF EXISTS distributors1; CREATE TABLE distributors1 (id integer, name varchar(40), UNIQUE(name))";
pg.query(sql, function(err, result){
sql = "INSERT INTO distributors1 (id, name) VALUES (1, 'fish')";
pg.query(sql,function(err, result){
assert.eql(result.rows, []);
assert.deepEqual(result.rows, []);
pg.end();
done();
});
});
};
});
exports['test publicuser can execute SELECT on enabled tables'] = function(){
test('test publicuser can execute SELECT on enabled tables', function(done){
var pg = new PSQL("1");
var sql = "DROP TABLE IF EXISTS distributors2; CREATE TABLE distributors2 (id integer, name varchar(40), UNIQUE(name)); GRANT SELECT ON distributors2 TO publicuser;";
pg.query(sql, function(err, result){
pg.end();
pg = new PSQL(null, 'cartodb_test_user_1_db');
pg.query("SELECT count(*) FROM distributors2", function(err, result){
assert.equal(result.rows[0].count, 0);
pg.end();
done();
});
});
};
});
exports['test publicuser cannot execute INSERT on db'] = function(){
test('test publicuser cannot execute INSERT on db', function(done){
var pg = new PSQL("1");
var sql = "DROP TABLE IF EXISTS distributors3; CREATE TABLE distributors3 (id integer, name varchar(40), UNIQUE(name)); GRANT SELECT ON distributors3 TO publicuser;";
pg.query(sql, function(err, result){
@ -94,8 +101,11 @@ exports['test publicuser cannot execute INSERT on db'] = function(){
pg = new PSQL(null, 'cartodb_test_user_1_db'); //anonymous user
pg.query("INSERT INTO distributors3 (id, name) VALUES (1, 'fishy')", function(err, result){
assert.eql(err.message, 'permission denied for relation distributors3');
assert.equal(err.message, 'permission denied for relation distributors3');
pg.end();
done();
});
});
};
});
});

View File

@ -3,41 +3,47 @@ var _ = require('underscore')
, redis_pool = require('../../app/models/redis_pool')
, assert = require('assert');
exports['test truth'] = function(){
suite('redis_pool', function() {
test('test truth', function(){
assert.ok(true, 'it is');
};
});
exports['test can instantiate a RedisPool object'] = function(){
test('test can instantiate a RedisPool object', function(){
assert.ok(redis_pool);
};
});
exports['test pool object has an aquire function'] = function(){
assert.includes(_.functions(redis_pool), 'acquire');
};
test('test pool object has an aquire function', function(){
assert.ok(_.includes(_.functions(redis_pool), 'acquire'));
});
exports['test calling aquire returns a redis client object that can get/set'] = function(beforeExit){
test('test calling aquire returns a redis client object that can get/set', function(done){
redis_pool.acquire(0, function(client){
client.set("key","value");
client.get("key", function(err,data){
assert.eql(data, "value");
assert.equal(data, "value");
redis_pool.release(0, client);
done();
});
});
};
});
exports['test calling aquire on another DB returns a redis client object that can get/set'] = function(beforeExit){
test('test calling aquire on another DB returns a redis client object that can get/set', function(done){
redis_pool.acquire("MYDATABASE", function(client){
client.set("key","value");
client.get("key", function(err,data){
assert.eql(data, "value");
assert.equal(data, "value");
redis_pool.release("MYDATABASE", client);
redis_pool.acquire("MYDATABASE", function(client){
client.get("key", function(err,data){
assert.equal(data, "value");
redis_pool.release("MYDATABASE", client);
done();
});
});
})
});
redis_pool.acquire("MYDATABASE", function(client){
client.get("key", function(err,data){
assert.eql(data, "value");
redis_pool.release("MYDATABASE", client);
});
});
};
});
});