first commit

This commit is contained in:
2023-05-19 00:42:48 +08:00
commit 53de9c6c51
243 changed files with 39485 additions and 0 deletions

1
test/support/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
CDB_*.sql

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

@@ -0,0 +1,127 @@
'use strict';
var assert = module.exports = exports = require('assert');
var request = require('request');
var debug = require('debug')('assert-response');
assert.response = function(server, req, res, callback) {
if (!callback) {
callback = res;
res = {};
}
var port = 5555,
host = '127.0.0.1';
var listeningAttempts = 0;
var listener;
function listen() {
if (listeningAttempts > 25) {
var message = 'Tried too many ports';
debug(message);
return callback(new Error(message));
}
listener = server.listen(port, host);
listener.on('error', function() {
port++;
listeningAttempts++;
listen();
});
listener.on('listening', onServerListening);
}
listen();
debug('Request definition', req);
// jshint maxcomplexity:10
function onServerListening() {
debug('Server listening on port = %d', port);
var status = res.status || res.statusCode;
var requestParams = {
url: 'http://' + host + ':' + port + req.url,
method: req.method || 'GET',
headers: req.headers || {},
timeout: req.timeout || 5000,
encoding: req.encoding || 'utf8'
};
if (req.body || req.data) {
requestParams.body = req.body || req.data;
}
if (req.formData) {
requestParams.formData = req.formData;
}
debug('Request params', requestParams);
request(requestParams, function assert$response$requestHandler(error, response, body) {
debug('Request response', error);
listener.close(function() {
debug('Server closed');
if (error) {
return callback(error);
}
response = response || {};
response.body = response.body || body;
debug('Response status', response.statusCode)
// Assert response body
if (res.body) {
var eql = res.body instanceof RegExp ? res.body.test(response.body) : res.body === response.body;
assert.ok(
eql,
colorize('[red]{Invalid response body.}\n' +
' Expected: [green]{' + res.body + '}\n' +
' Got: [red]{' + response.body + '}')
);
}
// Assert response status
if (typeof status === 'number') {
assert.equal(response.statusCode, status,
colorize('[red]{Invalid response status code.}\n' +
' Expected: [green]{' + status + '}\n' +
' Got: [red]{' + response.statusCode + '}\n' +
' Body: ' + response.body)
);
}
// 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],
headerEql = expected instanceof RegExp ? expected.test(actual) : expected === actual;
assert.ok(headerEql,
colorize('Invalid response header [bold]{' + name + '}.\n' +
' Expected: [green]{' + expected + '}\n' +
' Got: [red]{' + actual + '}')
);
}
}
// Callback
return callback(null, response);
});
});
}
};
/**
* Colorize the given string using ansi-escape sequences.
* Disabled when --boring is set.
*
* @param {String} str
* @return {String}
*/
function colorize(str) {
var colors = { bold: 1, red: 31, green: 32, yellow: 33 };
return str.replace(/\[(\w+)\]\{([^]*?)\}/g, function(_, color, str) {
return '\x1B[' + colors[color] + 'm' + str + '\x1B[0m';
});
}

View File

@@ -0,0 +1,300 @@
'use strict';
require('../helper');
var assert = require('assert');
var appServer = require('../../app/server');
var redisUtils = require('./redis_utils');
var debug = require('debug')('batch-test-client');
var JobStatus = require('../../batch/job_status');
var metadataBackend = require('cartodb-redis')({ pool: redisUtils.getPool() });
var batchFactory = require('../../batch/index');
function response(code) {
return {
status: code
};
}
var RESPONSE = {
OK: response(200),
CREATED: response(201),
BAD_REQUEST: response(400)
};
function BatchTestClient(config) {
this.config = config || {};
this.server = appServer();
this.batch = batchFactory(metadataBackend, redisUtils.getPool(), this.config.name);
this.batch.start();
this.pendingJobs = [];
this.ready = false;
this.batch.on('ready', function() {
this.ready = true;
this.pendingJobs.forEach(function(pendingJob) {
this.createJob(pendingJob.job, pendingJob.override, pendingJob.callback);
}.bind(this));
}.bind(this));
}
module.exports = BatchTestClient;
BatchTestClient.prototype.isReady = function() {
return this.ready;
};
BatchTestClient.prototype.getExpectedResponse = function (override) {
return override.response || this.config.response || RESPONSE.CREATED;
};
BatchTestClient.prototype.createJob = function(job, override, callback) {
if (!callback) {
callback = override;
override = {};
}
if (!this.isReady()) {
this.pendingJobs.push({
job: job,
override: override || {},
callback: callback
});
return debug('Waiting for Batch service to be ready');
}
assert.response(
this.server,
{
url: this.getUrl(override),
headers: {
host: this.getHost(override),
'Content-Type': 'application/json',
authorization: this.getAuthorization(override)
},
method: 'POST',
data: JSON.stringify(job)
},
this.getExpectedResponse(override),
function (err, res) {
if (err) {
return callback(err);
}
if (res.statusCode < 400) {
return callback(null, new JobResult(JSON.parse(res.body), this, override));
} else {
return callback(null, res);
}
}.bind(this)
);
};
BatchTestClient.prototype.getJobStatus = function(jobId, override, callback) {
assert.response(
this.server,
{
url: this.getUrl(override, jobId),
headers: {
host: this.getHost(override),
authorization: this.getAuthorization(override)
},
method: 'GET',
timeout: override.timeout
},
RESPONSE.OK,
function (err, res) {
if (err) {
return callback(err);
}
return callback(null, JSON.parse(res.body));
}
);
};
BatchTestClient.prototype.getWorkInProgressJobs = function(override, callback) {
if (!callback) {
callback = override;
override = {};
}
assert.response(
this.server,
{
url: '/api/v1/jobs-wip',
headers: {
host: this.getHost(override)
},
method: 'GET'
},
RESPONSE.OK,
function (err, res) {
if (err) {
return callback(err);
}
return callback(null, JSON.parse(res.body));
}
);
};
BatchTestClient.prototype.cancelJob = function(jobId, override, callback) {
assert.response(
this.server,
{
url: this.getUrl(override, jobId),
headers: {
host: this.getHost(override)
},
method: 'DELETE'
},
override.statusCode,
function (err, res) {
if (err) {
return callback(err);
}
return callback(null, JSON.parse(res.body));
}
);
};
BatchTestClient.prototype.drain = function(callback) {
this.batch.stop(function() {
return redisUtils.clean('batch:*', callback);
});
};
BatchTestClient.prototype.getHost = function(override) {
return override.host || this.config.host || 'vizzuality.cartodb.com';
};
BatchTestClient.prototype.getAuthorization = function (override) {
const auth = override.authorization || this.config.authorization;
if (auth) {
return `Basic ${new Buffer(auth).toString('base64')}`;
}
};
BatchTestClient.prototype.getUrl = function(override, jobId) {
var urlParts = ['/api/v2/sql/job'];
if (jobId) {
urlParts.push(jobId);
}
return `${urlParts.join('/')}${override.anonymous ? '' : '?api_key=' + this.getApiKey(override)}`;
};
BatchTestClient.prototype.getApiKey = function(override) {
return override.apiKey || this.config.apiKey || '1234';
};
/****************** JobResult ******************/
function JobResult(job, batchTestClient, override) {
this.job = job;
this.batchTestClient = batchTestClient;
this.override = override;
}
JobResult.prototype.getStatus = function(requiredStatus, callback) {
if (!callback) {
callback = requiredStatus;
requiredStatus = undefined;
}
var self = this;
var attempts = 1;
self.override.timeout = 1000;
var interval = setInterval(function () {
self.batchTestClient.getJobStatus(self.job.job_id, self.override, function (err, job) {
if (err) {
clearInterval(interval);
return callback(err);
}
attempts += 1;
if (attempts > 20) {
clearInterval(interval);
return callback(new Error('Reached maximum number of request (20) to check job status'));
}
if (hasRequiredStatus(job, requiredStatus)) {
clearInterval(interval);
self.job = job;
return callback(null, job);
} else {
debug('Job %s [status=%s] waiting to be done', self.job.job_id, job.status);
}
});
}, 100);
};
function hasRequiredStatus(job, requiredStatus) {
if (requiredStatus) {
return job.status === requiredStatus;
}
if (JobStatus.isFinal(job.status)) {
if (job.fallback_status !== undefined) {
if (JobStatus.isFinal(job.fallback_status) || job.fallback_status === JobStatus.SKIPPED) {
return true;
}
} else {
return true;
}
}
return false;
}
JobResult.prototype.cancel = function (callback) {
var self = this;
this.override.statusCode = response(RESPONSE.OK);
this.batchTestClient.cancelJob(this.job.job_id, this.override, function (err, job) {
if (err) {
return callback(err);
}
self.job = job;
callback(null, job);
});
};
JobResult.prototype.tryCancel = function (callback) {
var self = this;
this.override.statusCode = response();
this.batchTestClient.cancelJob(this.job.job_id, this.override, function (err, job) {
if (err) {
return callback(err);
}
self.job = job;
callback(null, job);
});
};
JobResult.prototype.validateExpectedResponse = function (expected) {
var actual = this.job.query;
actual.query.forEach(function(actualQuery, index) {
var expectedQuery = expected.query[index];
assert.ok(expectedQuery);
Object.keys(expectedQuery).forEach(function(expectedKey) {
assert.equal(
actualQuery[expectedKey],
expectedQuery[expectedKey],
'Expected value for key "' + expectedKey + '" does not match: ' + actualQuery[expectedKey] + ' ==' +
expectedQuery[expectedKey] + ' at query index=' + index + '. Full response: ' +
JSON.stringify(actual, null, 4)
);
});
var propsToCheckDate = ['started_at', 'ended_at'];
propsToCheckDate.forEach(function(propToCheckDate) {
if (actualQuery.hasOwnProperty(propToCheckDate)) {
assert.ok(new Date(actualQuery[propToCheckDate]));
}
});
});
assert.equal(actual.onsuccess, expected.onsuccess);
assert.equal(actual.onerror, expected.onerror);
};

View File

@@ -0,0 +1,7 @@
id,name
11,Paul
12,Peter
13,Matthew
14,
15,James
16,John
1 id name
2 11 Paul
3 12 Peter
4 13 Matthew
5 14
6 15 James
7 16 John

Binary file not shown.

36
test/support/db_utils.js Normal file
View File

@@ -0,0 +1,36 @@
'use strict';
const { Client } = require('pg');
const dbConfig = {
db_user: process.env.PGUSER || 'postgres',
db_host: global.settings.db_host,
db_port: global.settings.db_port,
db_batch_port: global.settings.db_batch_port
};
module.exports.resetPgBouncerConnections = function (callback) {
// We assume there's no pgbouncer if db_port === db_batch_port
if (dbConfig.db_port === dbConfig.db_batch_port) {
return callback();
}
const client = new Client({
database: 'pgbouncer',
user: dbConfig.db_user,
host: dbConfig.db_host,
port: dbConfig.db_port
});
// We just chain a PAUSE followed by a RESUME to reset internal pool connections of PgBouncer
client.connect();
client.query('PAUSE', err => {
if (err) {
return callback(err);
}
client.query('RESUME', err => {
client.end();
return callback(err);
});
});
};

BIN
test/support/libredis_cell.dylib Executable file

Binary file not shown.

BIN
test/support/libredis_cell.so Executable file

Binary file not shown.

View File

@@ -0,0 +1,39 @@
'use strict';
var RedisPool = require('redis-mpool');
var redisConfig = {
host: global.settings.redis_host,
port: global.settings.redis_port,
max: global.settings.redisPool,
idleTimeoutMillis: global.settings.redisIdleTimeoutMillis,
reapIntervalMillis: global.settings.redisReapIntervalMillis
};
var metadataBackend = require('cartodb-redis')(redisConfig);
module.exports.clean = function clean(pattern, callback) {
metadataBackend.redisCmd(5, 'KEYS', [ pattern ], function (err, keys) {
if (err) {
return callback(err);
}
if (!keys || !keys.length) {
return callback();
}
metadataBackend.redisCmd(5, 'DEL', keys, callback);
});
};
module.exports.getConfig = function getConfig() {
return redisConfig;
};
var pool = new RedisPool(redisConfig);
module.exports.getPool = function getPool() {
return pool;
};
module.exports.configureUserMetadata = function configureUserMetadata(action, params, callback) {
metadataBackend.redisCmd(5, action, params, callback);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
CREATE OR REPLACE FUNCTION py_sleep(t FLOAT8)
RETURNS void AS $$
import time
time.sleep(t)
$$ LANGUAGE plpythonu;

View File

@@ -0,0 +1,17 @@
-- See https://github.com/CartoDB/cartodb-postgresql/blob/master/scripts-available/CDB_Quota.sql
CREATE OR REPLACE FUNCTION _CDB_UserQuotaInBytes()
RETURNS int8 AS
$$
-- 250 MB
SELECT (250 * 1024 * 1024)::int8;
$$ LANGUAGE sql IMMUTABLE;
CREATE OR REPLACE FUNCTION CDB_UserDataSize(schema_name TEXT)
RETURNS bigint AS
$$
BEGIN
-- 100 MB
RETURN 100 * 1024 * 1024;
END;
$$ LANGUAGE 'plpgsql' VOLATILE;

228
test/support/sql/test.sql Normal file
View File

@@ -0,0 +1,228 @@
--
-- sql-api test database
--
-- To use:
--
-- > 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
--
-- NOTE: requires a postgis template called template_postgis with CDB functions included
--
SET statement_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = off;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET escape_string_warning = off;
SET search_path = public, pg_catalog;
SET default_tablespace = '';
SET default_with_oids = false;
-- first table
DROP TABLE IF EXISTS untitle_table_4;
CREATE TABLE untitle_table_4 (
updated_at timestamp without time zone DEFAULT now(),
created_at timestamp without time zone DEFAULT now(),
cartodb_id integer NOT NULL,
name character varying,
address character varying,
-- NOTE: the_geom_webmercator is intentionally listed _before_ the_geom
-- see https://github.com/CartoDB/CartoDB-SQL-API/issues/116
the_geom_webmercator geometry,
the_geom geometry,
CONSTRAINT enforce_dims_the_geom CHECK ((st_ndims(the_geom) = 2)),
CONSTRAINT enforce_dims_the_geom_webmercator CHECK ((st_ndims(the_geom_webmercator) = 2)),
CONSTRAINT enforce_geotype_the_geom CHECK (((geometrytype(the_geom) = 'POINT'::text) OR (the_geom IS NULL))),
CONSTRAINT enforce_geotype_the_geom_webmercator CHECK (((geometrytype(the_geom_webmercator) = 'POINT'::text) OR (the_geom_webmercator IS NULL))),
CONSTRAINT enforce_srid_the_geom CHECK ((st_srid(the_geom) = 4326)),
CONSTRAINT enforce_srid_the_geom_webmercator CHECK ((st_srid(the_geom_webmercator) = 3857))
);
CREATE SEQUENCE untitle_table_4_cartodb_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE untitle_table_4_cartodb_id_seq OWNED BY untitle_table_4.cartodb_id;
SELECT pg_catalog.setval('untitle_table_4_cartodb_id_seq', 60, true);
ALTER TABLE untitle_table_4 ALTER COLUMN cartodb_id SET DEFAULT nextval('untitle_table_4_cartodb_id_seq'::regclass);
INSERT INTO untitle_table_4
(updated_at, created_at, cartodb_id, name, address, the_geom, the_geom_webmercator)
VALUES
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.314252', 1, 'Hawai', 'Calle de Pérez Galdós 9, Madrid, Spain', '0101000020E6100000A6B73F170D990DC064E8D84125364440', '0101000020110F000076491621312319C122D4663F1DCC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.319101', 2, 'El Estocolmo', 'Calle de la Palma 72, Madrid, Spain', '0101000020E6100000C90567F0F7AB0DC0AB07CC43A6364440', '0101000020110F0000C4356B29423319C15DD1092DADCC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.324', 3, 'El Rey del Tallarín', 'Plaza Conde de Toreno 2, Madrid, Spain', '0101000020E610000021C8410933AD0DC0CB0EF10F5B364440', '0101000020110F000053E71AC64D3419C10F664E4659CC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.329509', 4, 'El Lacón', 'Manuel Fernández y González 8, Madrid, Spain', '0101000020E6100000BC5983F755990DC07D923B6C22354440', '0101000020110F00005DACDB056F2319C1EC41A980FCCA5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.334931', 5, 'El Pico', 'Calle Divino Pastor 12, Madrid, Spain', '0101000020E61000003B6D8D08C6A10DC0371B2B31CF364440', '0101000020110F00005F716E91992A19C17DAAA4D6DACC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21', -1, 'Test', 'Fake for testing', 'SRID=4326;POINT(33 16)', 'SRID=3857;POINT(3673543.19617803 1804722.76625729)');
ALTER TABLE ONLY untitle_table_4 ADD CONSTRAINT untitle_table_4_pkey PRIMARY KEY (cartodb_id);
CREATE INDEX untitle_table_4_the_geom_idx ON untitle_table_4 USING gist (the_geom);
CREATE INDEX untitle_table_4_the_geom_webmercator_idx ON untitle_table_4 USING gist (the_geom_webmercator);
-- second table
DROP TABLE IF EXISTS scoped_table_1;
CREATE TABLE scoped_table_1 (
updated_at timestamp without time zone DEFAULT now(),
created_at timestamp without time zone DEFAULT now(),
cartodb_id integer NOT NULL,
name character varying,
address character varying,
the_geom_webmercator geometry,
the_geom geometry,
CONSTRAINT enforce_dims_the_geom CHECK ((st_ndims(the_geom) = 2)),
CONSTRAINT enforce_dims_the_geom_webmercator CHECK ((st_ndims(the_geom_webmercator) = 2)),
CONSTRAINT enforce_geotype_the_geom CHECK (((geometrytype(the_geom) = 'POINT'::text) OR (the_geom IS NULL))),
CONSTRAINT enforce_geotype_the_geom_webmercator CHECK (((geometrytype(the_geom_webmercator) = 'POINT'::text) OR (the_geom_webmercator IS NULL))),
CONSTRAINT enforce_srid_the_geom CHECK ((st_srid(the_geom) = 4326)),
CONSTRAINT enforce_srid_the_geom_webmercator CHECK ((st_srid(the_geom_webmercator) = 3857))
);
CREATE SEQUENCE scoped_table_1_cartodb_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE scoped_table_1_cartodb_id_seq OWNED BY scoped_table_1.cartodb_id;
SELECT pg_catalog.setval('scoped_table_1_cartodb_id_seq', 60, true);
ALTER TABLE scoped_table_1 ALTER COLUMN cartodb_id SET DEFAULT nextval('scoped_table_1_cartodb_id_seq'::regclass);
INSERT INTO scoped_table_1
(updated_at, created_at, cartodb_id, name, address, the_geom, the_geom_webmercator)
VALUES
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.314252', 1, 'Hawai', 'Calle de Pérez Galdós 9, Madrid, Spain', '0101000020E6100000A6B73F170D990DC064E8D84125364440', '0101000020110F000076491621312319C122D4663F1DCC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.319101', 2, 'El Estocolmo', 'Calle de la Palma 72, Madrid, Spain', '0101000020E6100000C90567F0F7AB0DC0AB07CC43A6364440', '0101000020110F0000C4356B29423319C15DD1092DADCC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.324', 3, 'El Rey del Tallarín', 'Plaza Conde de Toreno 2, Madrid, Spain', '0101000020E610000021C8410933AD0DC0CB0EF10F5B364440', '0101000020110F000053E71AC64D3419C10F664E4659CC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.329509', 4, 'El Lacón', 'Manuel Fernández y González 8, Madrid, Spain', '0101000020E6100000BC5983F755990DC07D923B6C22354440', '0101000020110F00005DACDB056F2319C1EC41A980FCCA5241');
ALTER TABLE ONLY scoped_table_1 ADD CONSTRAINT scoped_table_1_pkey PRIMARY KEY (cartodb_id);
CREATE INDEX scoped_table_1_the_geom_idx ON scoped_table_1 USING gist (the_geom);
CREATE INDEX scoped_table_1_the_geom_webmercator_idx ON scoped_table_1 USING gist (the_geom_webmercator);
-- private table
DROP TABLE IF EXISTS private_table;
CREATE TABLE private_table (
updated_at timestamp without time zone DEFAULT now(),
created_at timestamp without time zone DEFAULT now(),
cartodb_id integer NOT NULL,
name character varying,
address character varying,
the_geom geometry,
the_geom_webmercator geometry,
CONSTRAINT enforce_dims_the_geom CHECK ((st_ndims(the_geom) = 2)),
CONSTRAINT enforce_dims_the_geom_webmercator CHECK ((st_ndims(the_geom_webmercator) = 2)),
CONSTRAINT enforce_geotype_the_geom CHECK (((geometrytype(the_geom) = 'POINT'::text) OR (the_geom IS NULL))),
CONSTRAINT enforce_geotype_the_geom_webmercator CHECK (((geometrytype(the_geom_webmercator) = 'POINT'::text) OR (the_geom_webmercator IS NULL))),
CONSTRAINT enforce_srid_the_geom CHECK ((st_srid(the_geom) = 4326)),
CONSTRAINT enforce_srid_the_geom_webmercator CHECK ((st_srid(the_geom_webmercator) = 3857))
);
CREATE SEQUENCE untitle_table_4_cartodb_id_seq_p
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE untitle_table_4_cartodb_id_seq_p OWNED BY private_table.cartodb_id;
SELECT pg_catalog.setval('untitle_table_4_cartodb_id_seq_p', 60, true);
ALTER TABLE private_table ALTER COLUMN cartodb_id SET DEFAULT nextval('untitle_table_4_cartodb_id_seq_p'::regclass);
INSERT INTO private_table VALUES
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.314252', 1, 'Hawai', 'Calle de Pérez Galdós 9, Madrid, Spain', '0101000020E6100000A6B73F170D990DC064E8D84125364440', '0101000020110F000076491621312319C122D4663F1DCC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.319101', 2, 'El Estocolmo', 'Calle de la Palma 72, Madrid, Spain', '0101000020E6100000C90567F0F7AB0DC0AB07CC43A6364440', '0101000020110F0000C4356B29423319C15DD1092DADCC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.324', 3, 'El Rey del Tallarín', 'Plaza Conde de Toreno 2, Madrid, Spain', '0101000020E610000021C8410933AD0DC0CB0EF10F5B364440', '0101000020110F000053E71AC64D3419C10F664E4659CC5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.329509', 4, 'El Lacón', 'Manuel Fernández y González 8, Madrid, Spain', '0101000020E6100000BC5983F755990DC07D923B6C22354440', '0101000020110F00005DACDB056F2319C1EC41A980FCCA5241'),
('2011-09-21 14:02:21.358706', '2011-09-21 14:02:21.334931', 5, 'El Pico', 'Calle Divino Pastor 12, Madrid, Spain', '0101000020E61000003B6D8D08C6A10DC0371B2B31CF364440', '0101000020110F00005F716E91992A19C17DAAA4D6DACC5241');
ALTER TABLE ONLY private_table ADD CONSTRAINT untitle_table_4_pkey_p PRIMARY KEY (cartodb_id);
CREATE INDEX untitle_table_4_the_geom_idx_p ON private_table USING gist (the_geom);
CREATE INDEX untitle_table_4_the_geom_webmercator_idx_p ON private_table USING gist (the_geom_webmercator);
-- public user role
DROP USER IF EXISTS :PUBLICUSER;
CREATE USER :PUBLICUSER WITH PASSWORD ':PUBLICPASS';
ALTER ROLE :PUBLICUSER SET statement_timeout = 2000;
GRANT SELECT ON TABLE scoped_table_1 TO :PUBLICUSER;
-- regular user role 1
DROP USER IF EXISTS regular_1;
CREATE USER regular_1 WITH PASSWORD 'regular1';
ALTER ROLE regular_1 SET statement_timeout = 2000;
GRANT ALL ON TABLE scoped_table_1 TO regular_1;
GRANT ALL ON SEQUENCE scoped_table_1_cartodb_id_seq TO regular_1;
-- regular user role 2
DROP USER IF EXISTS regular_2;
CREATE USER regular_2 WITH PASSWORD 'regular2';
ALTER ROLE regular_2 SET statement_timeout = 2000;
-- fallback user role
DROP USER IF EXISTS test_cartodb_user_2;
CREATE USER test_cartodb_user_2 WITH PASSWORD 'test_cartodb_user_2_pass';
GRANT ALL ON TABLE scoped_table_1 TO test_cartodb_user_2;
GRANT ALL ON SEQUENCE scoped_table_1_cartodb_id_seq TO test_cartodb_user_2;
-- db owner role
DROP USER IF EXISTS :TESTUSER;
CREATE USER :TESTUSER WITH PASSWORD ':TESTPASS';
GRANT ALL ON TABLE untitle_table_4 TO :TESTUSER;
GRANT SELECT ON TABLE untitle_table_4 TO :PUBLICUSER;
GRANT ALL ON TABLE private_table TO :TESTUSER;
GRANT ALL ON TABLE scoped_table_1 TO :TESTUSER;
GRANT ALL ON SEQUENCE untitle_table_4_cartodb_id_seq_p TO :TESTUSER;
GRANT ALL ON TABLE spatial_ref_sys TO :TESTUSER, :PUBLICUSER;
REVOKE ALL ON geometry_columns FROM public;
GRANT ALL ON geometry_columns TO :TESTUSER;
GRANT ALL ON geography_columns TO :TESTUSER;
GRANT SELECT ON geometry_columns TO :PUBLICUSER;
GRANT SELECT ON geography_columns TO :PUBLICUSER;
-- For https://github.com/CartoDB/CartoDB-SQL-API/issues/118
DROP TABLE IF EXISTS cpg_test;
CREATE TABLE cpg_test (a int);
GRANT ALL ON TABLE cpg_test TO :TESTUSER;
GRANT SELECT ON TABLE cpg_test TO :PUBLICUSER;
CREATE TABLE IF NOT EXISTS
CDB_TableMetadata (
tabname regclass not null primary key,
updated_at timestamp with time zone not null default now()
);
INSERT INTO CDB_TableMetadata (tabname, updated_at) VALUES ('untitle_table_4'::regclass, '2014-01-01T23:31:30.123Z');
INSERT INTO CDB_TableMetadata (tabname, updated_at) VALUES ('private_table'::regclass, '2015-01-01T23:31:30.123Z');
INSERT INTO CDB_TableMetadata (tabname, updated_at) VALUES ('scoped_table_1'::regclass, '2015-01-01T23:31:30.123Z');
GRANT SELECT ON CDB_TableMetadata TO :TESTUSER;
GRANT SELECT ON CDB_TableMetadata TO test_cartodb_user_2;
DROP TABLE IF EXISTS copy_endpoints_test;
CREATE TABLE copy_endpoints_test (
id integer,
name text,
age integer default 10
);
GRANT ALL ON TABLE copy_endpoints_test TO :TESTUSER;
GRANT ALL ON TABLE copy_endpoints_test TO :PUBLICUSER;

150
test/support/test-client.js Normal file
View File

@@ -0,0 +1,150 @@
'use strict';
require('../helper');
var assert = require('assert');
var appServer = require('../../app/server');
var redisUtils = require('./redis_utils');
const step = require('step');
const PSQL = require('cartodb-psql');
const _ = require('underscore');
function response(code) {
return {
status: code
};
}
var RESPONSE = {
OK: response(200),
CREATED: response(201)
};
function TestClient(config) {
this.config = config || {};
this.server = appServer();
}
module.exports = TestClient;
TestClient.prototype.getResult = function(query, override, callback) {
if (!callback) {
callback = override;
override = {};
}
assert.response(
this.server,
{
url: this.getUrl(override),
headers: {
host: this.getHost(override),
'Content-Type': this.getContentType(override),
authorization: this.getAuthorization(override)
},
method: 'POST',
data: this.getParser(override)({
q: query,
format: this.getFormat(override),
filename: this.getFilename(override)
})
},
this.getExpectedResponse(override),
function (err, res) {
if (err) {
return callback(err);
}
var result = JSON.parse(res.body);
if (res.statusCode > 299) {
return callback(null, result);
}
return callback(null, result.rows || [], result);
}
);
};
TestClient.prototype.getHost = function(override) {
return override.host || this.config.host || 'vizzuality.cartodb.com';
};
TestClient.prototype.getAuthorization = function (override) {
const auth = override.authorization || this.config.authorization;
if (auth) {
return `Basic ${new Buffer(auth).toString('base64')}`;
}
};
TestClient.prototype.getContentType = function(override) {
return override['Content-Type'] || this.config['Content-Type'] || 'application/json';
};
TestClient.prototype.getParser = function (override) {
return override.parser || this.config.parser || JSON.stringify
}
TestClient.prototype.getUrl = function(override) {
if (override.anonymous) {
return '/api/v1/sql?';
}
return '/api/v2/sql?api_key=' + (override.apiKey || this.config.apiKey || '1234');
};
TestClient.prototype.getExpectedResponse = function (override) {
return override.response || this.config.response || RESPONSE.OK;
};
TestClient.prototype.getFormat = function (override) {
return override.format || this.config.format || undefined;
};
TestClient.prototype.getFilename = function (override) {
return override.filename || this.config.filename || undefined;
};
TestClient.prototype.setUserRenderTimeoutLimit = function (user, userTimeoutLimit, callback) {
const userTimeoutLimitsKey = `limits:timeout:${user}`;
const params = [
userTimeoutLimitsKey,
'render', userTimeoutLimit,
'render_public', userTimeoutLimit
];
redisUtils.configureUserMetadata('hmset', params, callback);
};
TestClient.prototype.setUserDatabaseTimeoutLimit = function (user, timeoutLimit, callback) {
const dbname = _.template(global.settings.db_base_name, { user_id: 1 });
const dbuser = _.template(global.settings.db_user, { user_id: 1 })
const pass = _.template(global.settings.db_user_pass, { user_id: 1 })
const publicuser = global.settings.db_pubuser;
const psql = new PSQL({
user: 'postgres',
dbname: dbname,
host: global.settings.db_host,
port: global.settings.db_port
});
// we need to guarantee all new connections have the new settings
psql.end();
step(
function configureTimeouts () {
const timeoutSQLs = [
`ALTER ROLE "${publicuser}" SET STATEMENT_TIMEOUT TO ${timeoutLimit}`,
`ALTER ROLE "${dbuser}" SET STATEMENT_TIMEOUT TO ${timeoutLimit}`,
`ALTER DATABASE "${dbname}" SET STATEMENT_TIMEOUT TO ${timeoutLimit}`
];
const group = this.group();
timeoutSQLs.forEach(sql => psql.query(sql, group()));
},
callback
);
};