cartodb/carto.js/test/spec/api/sql.spec.js
2020-06-15 10:58:47 +08:00

500 lines
18 KiB
JavaScript

var _ = require('underscore');
var $ = require('jquery');
var Backbone = require('backbone');
var SQL = require('../../../src/api/sql');
describe('api/sql', function () {
var USER = 'cartojs-test';
var sql;
var ajax;
var ajaxParams;
var TEST_DATA = { test: 'good' };
var NO_BOUNDS = { 'rows': [
{ 'maxx': null }
]};
var throwError;
var abort = jasmine.createSpy('abort');
beforeEach(function () {
jasmine.clock().install();
ajaxParams = null;
ajax = function (params) {
ajaxParams = params;
_.defer(function () {
params.complete && params.complete();
if (!throwError && params.success) params.success(TEST_DATA, 200);
throwError && params.error && params.error({
responseText: JSON.stringify({
error: ['jaja']
})
});
});
return {
abort: abort
};
};
spyOn($, 'ajax').and.callFake(ajax);
sql = new SQL({
user: USER,
protocol: 'https'
});
});
afterEach(function () {
jasmine.clock().uninstall();
});
it('should compile the url if not completeDomain passed', function () {
expect(sql._host()).toEqual('https://cartojs-test.carto.com/api/v2/sql');
});
it('should compile the url if completeDomain passed', function () {
var sqlBis = new SQL({
user: USER,
protocol: 'https',
completeDomain: 'http://troloroloro.com'
});
expect(sqlBis._host()).toEqual('http://troloroloro.com/api/v2/sql');
});
it('should execute a query', function () {
sql.execute('select * from table');
expect(ajaxParams.url).toEqual(
'https://' + USER + '.carto.com/api/v2/sql?q=' + encodeURIComponent('select * from table')
);
expect(ajaxParams.type).toEqual('get');
expect(ajaxParams.dataType).toEqual('json');
expect(ajaxParams.crossDomain).toEqual(true);
});
it('should be abortable', function () {
sql = new SQL({
user: USER,
protocol: 'https',
abortable: true
});
sql.execute('select * from table');
expect(abort).not.toHaveBeenCalled();
sql.execute('select * from table limit 10');
expect(abort).toHaveBeenCalled();
});
it('should parse template', function () {
sql.execute('select * from {{table}}', {
table: 'cartojs-test'
});
expect(ajaxParams.url).toEqual(
'https://' + USER + '.carto.com/api/v2/sql?q=' + encodeURIComponent('select * from cartojs-test')
);
});
it('should execute a long query', function () {
// Generating a giant query
var longSQL = [];
var i = 2000;
while (--i) longSQL.push('10000');
var longQuery = 'SELECT * ' + longSQL;
// required to have jquery as transport, is checked in the execute method
sql.execute(longQuery);
expect(ajaxParams.url).toEqual(
'https://' + USER + '.carto.com/api/v2/sql'
);
expect(ajaxParams.data.q).toEqual(longQuery);
expect(ajaxParams.type).toEqual('post');
expect(ajaxParams.dataType).toEqual('json');
expect(ajaxParams.crossDomain).toEqual(true);
});
it('should execute a long query with params', function () {
var s = new SQL({
user: 'cartojs-test',
format: 'geojson',
protocol: 'http',
host: 'charlies.com',
api_key: 'testkey',
'cartojs-test': 'test'
});
// Generating a giant query
var longSQL = [];
var i = 2000;
while (--i) longSQL.push('10000');
var longQuery = 'SELECT * ' + longSQL;
s.execute(longQuery, null, {
dp: 0
});
expect(ajaxParams.url.indexOf('http://')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('cartojs-test.charlies.com')).not.toEqual(-1);
// Check that we don't have params in the URI
expect(ajaxParams.url.indexOf('&format=geojson')).toEqual(-1);
expect(ajaxParams.url.indexOf('&api_key=testkey')).toEqual(-1);
expect(ajaxParams.url.indexOf('&dp=2')).toEqual(-1);
expect(ajaxParams.url.indexOf('&cartojs-test')).toEqual(-1);
// Check that we have the params in the body
expect(ajaxParams.data.q).toEqual(longQuery);
expect(ajaxParams.data.format).toEqual('geojson');
expect(ajaxParams.data.api_key).toEqual('testkey');
expect(ajaxParams.data.dp).toEqual(0);
expect(ajaxParams['cartojs-test']).toEqual('test');
});
it('should substitute mapnik tokens', function () {
sql.execute('select !pixel_width! as w, !pixel_height! as h, !bbox! as b from {{table}}', {
table: 't'
});
var earthCircumference = 40075017;
var tileSize = 256;
var srid = 3857;
var fullResolution = earthCircumference / tileSize;
var shift = earthCircumference / 2.0;
var pw = fullResolution;
var ph = pw;
var bbox = 'ST_MakeEnvelope(' + (-shift) + ',' + (-shift) + ',' +
shift + ',' + shift + ',' + srid + ')';
expect(ajaxParams.url).toEqual(
'https://' + USER + '.carto.com/api/v2/sql?q=' + encodeURIComponent(
'select ' + pw + ' as w, ' + ph + ' as h, ' +
bbox + ' as b from t')
);
});
it('should call promise', function () {
var data;
var dataCallback;
sql.execute('select * from bla', function (e, data) {
dataCallback = data;
}).done(function (d) {
data = d;
});
jasmine.clock().tick(100);
expect(data).toEqual(TEST_DATA);
expect(dataCallback).toEqual(TEST_DATA);
});
it('should call promise on error', function () {
throwError = true;
var err = false;
sql.execute('select * from bla').error(function () {
err = true;
});
jasmine.clock().tick(10);
expect(err).toEqual(true);
});
it('should include url params', function () {
var s = new SQL({
user: 'cartojs-test',
format: 'geojson',
protocol: 'http',
host: 'charlies.com',
api_key: 'testkey',
'cartojs-test': 'test'
});
s.execute('select * from cartojs-test', null, {
dp: 2
});
expect(ajaxParams.url.indexOf('http://')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('cartojs-test.charlies.com')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&format=geojson')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&api_key=testkey')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&dp=2')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&cartojs-test')).toEqual(-1);
});
it('should include extra url params', function () {
var s = new SQL({
user: 'cartojs-test',
format: 'geojson',
protocol: 'http',
host: 'charlies.com',
api_key: 'testkey',
'cartojs-test': 'test',
extra_params: ['cartojs-test']
});
s.execute('select * from cartojs-test', null, {
dp: 2
});
expect(ajaxParams.url.indexOf('http://')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('cartojs-test.charlies.com')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&format=geojson')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&api_key=testkey')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&dp=2')).not.toEqual(-1);
expect(ajaxParams.url.indexOf('&cartojs-test=test')).not.toEqual(-1);
s.execute('select * from cartojs-test', null, {
dp: 2,
'cartojs-test': 'test2'
});
expect(ajaxParams.url.indexOf('&cartojs-test=test2')).not.toEqual(-1);
});
it('should use jsonp if browser does not support cors', function () {
var corsPrev = $.support.cors;
$.support.cors = false;
var s = new SQL({ user: 'jaja' });
expect(s.options.jsonp).toEqual(true);
s.execute('select * from cartojs-test', null, {
dp: 2,
jsonpCallback: 'test_callback',
cache: false
});
expect(ajaxParams.dataType).toEqual('jsonp');
expect(ajaxParams.crossDomain).toEqual(undefined);
expect(ajaxParams.jsonp).toEqual(undefined);
expect(ajaxParams.jsonpCallback).toEqual('test_callback');
expect(ajaxParams.cache).toEqual(false);
$.support.cors = corsPrev;
});
describe('.getBounds', function () {
it('should get bounds for query', function () {
var sql = 'SELECT ST_XMin(ST_Extent(the_geom)) as minx,' +
' ST_YMin(ST_Extent(the_geom)) as miny,' +
' ST_XMax(ST_Extent(the_geom)) as maxx,' +
' ST_YMax(ST_Extent(the_geom)) as maxy' +
' from (select * from cartojs-test where id=2) as subq';
var s = new SQL({ user: 'jaja' });
s.getBounds('select * from cartojs-test where id={{id}}', {id: 2});
expect(ajaxParams.url.indexOf(encodeURIComponent(sql))).not.toEqual(-1);
});
it('should get bounds for query with appostrophes', function () {
var s = new SQL({ user: 'jaja' });
s.getBounds('select * from country where name={{ name }}', {name: "'Spain'"});
expect(ajaxParams.url.indexOf('%26amp%3B%2339%3B')).toEqual(-1);
});
it('should resolve promise as error in case there are no bounds', function () {
var prevTestData = TEST_DATA;
var actualErrors = null;
TEST_DATA = NO_BOUNDS;
throwError = false;
var s = new SQL({ user: 'jaja' });
s.getBounds('SELECT * FROM somewhere')
.error(function (err) {
actualErrors = err;
});
jasmine.clock().tick(10);
expect(actualErrors).not.toBeNull();
expect(actualErrors.length).toBe(1);
expect(actualErrors[0]).toEqual('No bounds');
// Cleaning
TEST_DATA = prevTestData;
});
it('should trigger the error callback in case there are no bounds', function () {
var prevTestData = TEST_DATA;
var actualErrors = null;
TEST_DATA = NO_BOUNDS;
throwError = false;
function cb (err) {
actualErrors = err;
}
var s = new SQL({ user: 'jaja' });
s.getBounds('SELECT * FROM somewhere', null, null, cb);
jasmine.clock().tick(10);
expect(actualErrors).not.toBeNull();
expect(actualErrors.length).toBe(1);
expect(actualErrors[0]).toEqual('No bounds');
// Cleaning
TEST_DATA = prevTestData;
});
});
});
describe('api/sql.table', function () {
var USER = 'cartojs-test';
var sql;
beforeEach(function () {
sql = new SQL({
user: USER,
protocol: 'https'
});
});
it('sql', function () {
var s = sql.table('test');
expect(s.sql()).toEqual('select * from test');
s.columns(['age', 'jeta']);
expect(s.sql()).toEqual('select age,jeta from test');
s.filter('age < 10');
expect(s.sql()).toEqual('select age,jeta from test where age < 10');
s.limit(15);
expect(s.sql()).toEqual('select age,jeta from test where age < 10 limit 15');
s.order_by('age');
expect(s.sql()).toEqual('select age,jeta from test where age < 10 limit 15 order by age');
});
});
describe('api/sql column descriptions', function () {
var USER = 'manolo';
var sql;
beforeAll(function () {
this.colDate = new Backbone.Model(JSON.parse('{"name":"object_postedtime","type":"date","geometry_type":"point","bbox":[[-28.92163128242129,-201.09375],[75.84516854027044,196.875]],"analyzed":true,"success":true,"stats":{"type":"date","start_time":"2015-02-19T15:13:16.000Z","end_time":"2015-02-22T04:34:05.000Z","range":220849000,"steps":1024,"null_ratio":0,"column":"object_postedtime"}}'));
this.colFloat = new Backbone.Model(JSON.parse('{"name":"asdfd","type":"number","geometry_type":"point"}'));
this.colString = new Backbone.Model(JSON.parse('{"name":"asdfd","type":"string","geometry_type":"point"}'));
this.colGeom = new Backbone.Model(JSON.parse('{"name":"asdfd","type":"geometry","geometry_type":"point"}'));
this.colBoolean = new Backbone.Model(JSON.parse('{"name":"asdfd","type":"boolean","geometry_type":"point"}'));
this.query = 'SELECT * FROM whatevs';
sql = new SQL({
user: USER,
protocol: 'https'
});
sql.execute = function (sql, callback) {
callback(null, {});
};
});
it('should deduct correct describe method', function () {
spyOn(sql, 'describeDate');
sql.describe(this.query, this.colDate, {type: this.colDate.get('type')}, function () {});
expect(sql.describeDate).toHaveBeenCalled();
spyOn(sql, 'describeFloat');
sql.describe(this.query, this.colFloat, {type: this.colFloat.get('type')}, function () {});
expect(sql.describeFloat).toHaveBeenCalled();
spyOn(sql, 'describeString');
sql.describe(this.query, this.colString, {type: this.colString.get('type')}, function () {});
expect(sql.describeString).toHaveBeenCalled();
spyOn(sql, 'describeGeom');
sql.describe(this.query, this.colGeom, {type: this.colGeom.get('type')}, function () {});
expect(sql.describeGeom).toHaveBeenCalled();
spyOn(sql, 'describeBoolean');
sql.describe(this.query, this.colBoolean, {type: this.colBoolean.get('type')}, function () {});
expect(sql.describeBoolean).toHaveBeenCalled();
});
describe('string describer', function () {
var description;
beforeAll(function (done) {
sql.execute = function (sql, callback) {
var data = JSON.parse('{"rows":[{"uniq":462,"cnt":487,"null_count":1,"null_ratio":0.002053388090349076,"skew":0.043121149897330596,"array_agg":""}],"time":0.01,"fields":{"uniq":{"type":"number"},"cnt":{"type":"number"},"null_count":{"type":"number"},"null_ratio":{"type":"number"},"skew":{"type":"number"},"array_agg":{"type":"unknown(2287)"}},"total_rows":1}');
callback(null, data);
};
var callback = function (e, stuff) {
description = stuff;
done();
};
sql.describeString(sql, this.colString, callback); // THE COLS DON'T MATCH!!!
});
it('should return correct properties', function () {
expect(description.hist.constructor).toEqual(Array); // Right now it's an empty array because JSON.parse doesn't like our way of notating histograms
expect(description.type).toEqual('string');
expect(typeof description.null_count).toEqual('number');
expect(typeof description.distinct).toEqual('number');
expect(typeof description.null_ratio).toEqual('number');
expect(typeof description.skew).toEqual('number');
expect(typeof description.weight).toEqual('number');
});
});
describe('geometry describer', function () {
var description;
beforeAll(function (done) {
sql.execute = function (sql, callback) {
var data = {'rows': [{'geometry_type': 'ST_Point'}], 'time': 0.035, 'fields': {'geometry_type': {'type': 'string'}}, 'total_rows': 1};
callback(null, data);
};
var callback = function (e, stuff) {
description = stuff;
done();
};
sql.describeGeom(sql, this.colGeom, callback);
});
it('should return correct properties', function () {
expect(description.type).toEqual('geom');
expect(['ST_Point', 'ST_Line', 'ST_Polygon'].indexOf(description.geometry_type) > -1).toBe(true);
});
});
describe('number describer', function () {
var description;
beforeAll(function (done) {
sql.execute = function (sql, callback) {
var data = JSON.parse('{"rows":[{"hist":"{\\"(1,empty,69368)\\",\\"(25,empty,11063)\\"}","min":0,"max":4,"avg":0.3745819397993311,"cnt":89401,"uniq":5,"null_ratio":0,"stddev":0.000009057366328792043,"stddevmean":2.1617223091836313,"dist_type":"U","quantiles":[0,1,2,2,3,4,4],"equalint":[0,0,0,0,0,0,0],"jenks":[0,1,2,3,4],"headtails":[0,1,2,3,4],"cat_hist":"{\\"(1,empty,69368)\\",\\"(25,empty,11063)\\"}"}],"time":1.442,"fields":{"hist":{"type":"unknown(2287)"},"min":{"type":"number"},"max":{"type":"number"},"avg":{"type":"number"},"cnt":{"type":"number"},"uniq":{"type":"number"},"null_ratio":{"type":"number"},"stddev":{"type":"number"},"stddevmean":{"type":"number"},"dist_type":{"type":"string"},"quantiles":{"type":"number[]"},"equalint":{"type":"number[]"},"jenks":{"type":"number[]"},"headtails":{"type":"number[]"},"cat_hist":{"type":"unknown(2287)"}},"total_rows":1}');
callback(null, data);
};
var callback = function (e, stuff) {
description = stuff;
done();
};
sql.describeFloat(sql, this.colGeom, callback);
});
it('should return correct properties', function () {
expect(description.type).toEqual('number');
expect(['A', 'U', 'F', 'J'].indexOf(description.dist_type) > -1).toBe(true);
var i;
var numTypes = ['avg', 'max', 'min', 'stddevmean', 'weight', 'stddev', 'null_ratio', 'count'];
for (i = 0; i < numTypes.length; i++) {
expect(typeof description[numTypes[i]]).toEqual('number');
}
var arrayTypes = ['quantiles', 'equalint', 'jenks', 'headtails', 'cat_hist', 'hist'];
for (i = 0; i < arrayTypes.length; i++) {
expect(description[arrayTypes[i]].constructor).toEqual(Array);
}
});
});
describe('boolean describer', function () {
var description;
beforeAll(function (done) {
sql.execute = function (sql, callback) {
var data = {'rows': [
{'true_ratio': 0.3377926421404682, 'null_ratio': 0, 'uniq': 2, 'cnt': 89401}
],
'time': 0.251,
'fields': {'true_ratio': {'type': 'number'}, 'null_ratio': {'type': 'number'}, 'uniq': {'type': 'number'}, 'cnt': {'type': 'number'}},
'total_rows': 1
};
callback(null, data);
};
var callback = function (e, stuff) {
description = stuff;
done();
};
sql.describeBoolean(sql, this.colGeom, callback);
});
it('should return correct properties', function () {
expect(description.type).toEqual('boolean');
expect(typeof description.true_ratio).toEqual('number');
expect(typeof description.distinct).toEqual('number');
expect(typeof description.count).toEqual('number');
expect(typeof description.null_ratio).toEqual('number');
});
});
});