describe('SQL api client', function() { var USER = 'rambo'; var TEST_DATA = { test: 'good' }; var sql; var ajaxParams; var throwError = false; var jquery_ajax; var ajax; beforeEach(function() { ajaxParams = null; ajax = function(params) { ajaxParams = params; _.defer(function() { if(!throwError && params.success) params.success(TEST_DATA, 200); throwError && params.error && params.error({ responseText: JSON.stringify({ error: ['jaja'] }) }); }); } sql = new cartodb.SQL({ user: USER, protocol: 'https', ajax: ajax }) jquery_ajax = $.ajax; }); afterEach(function() { $.ajax = jquery_ajax; }); it("should compile the url if not completeDomain passed", function() { expect(sql._host()).toEqual('https://rambo.carto.com/api/v2/sql'); }); it("should compile the url if completeDomain passed", function() { var sqlBis = new cartodb.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 parse template", function() { sql.execute('select * from {{table}}', { table: 'rambo' }) expect(ajaxParams.url).toEqual( 'https://' + USER + '.carto.com/api/v2/sql?q=' + encodeURIComponent('select * from rambo') ) }); it("should execute a long query", function() { //Generating a giant query var long_sql = [] var i = 2000; while (--i) long_sql.push("10000"); var long_query = 'SELECT * ' + long_sql; sql.execute(long_query); expect(ajaxParams.url).toEqual( 'https://' + USER + '.carto.com/api/v2/sql' ) expect(ajaxParams.data.q).toEqual(long_query); expect(ajaxParams.type).toEqual('post'); expect(ajaxParams.dataType).toEqual('json'); expect(ajaxParams.crossDomain).toEqual(true); }); it("should execute a long query with params", function() { s = new cartodb.SQL({ user: 'rambo', format: 'geojson', protocol: 'http', host: 'charlies.com', api_key: 'testkey', rambo: 'test', ajax: ajax }) //Generating a giant query var long_sql = [] var i = 2000; while (--i) long_sql.push("10000"); var long_query = 'SELECT * ' + long_sql; s.execute(long_query, null, { dp: 2 }) expect(ajaxParams.url.indexOf('http://')).not.toEqual(-1); expect(ajaxParams.url.indexOf('rambo.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('&rambo')).toEqual(-1); //Check that we have the params in the body expect(ajaxParams.data.q).toEqual(long_query); expect(ajaxParams.data.format).toEqual('geojson'); expect(ajaxParams.data.api_key).toEqual('testkey'); expect(ajaxParams.data.dp).toEqual(2); expect(ajaxParams.rambo).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 earth_circumference = 40075017; var tile_size = 256; var srid = 3857; var full_resolution = earth_circumference/tile_size; var shift = earth_circumference / 2.0; var pw = full_resolution; 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(done) { var data; var data_callback; sql.execute('select * from bla', function(data) { data_callback = data }).done(function(d) { data = d; }); setTimeout(function() { expect(data).toEqual(TEST_DATA); expect(data_callback).toEqual(TEST_DATA); done() }, 500); //Fix cartodb.js issue #336 }); it("should call promise on error", function(done) { throwError = true; var err = false; sql.execute('select * from bla').error(function(d) { err = true; }); setTimeout(function() { expect(err).toEqual(true); done(); },10); }); it("should include url params", function() { s = new cartodb.SQL({ user: 'rambo', format: 'geojson', protocol: 'http', host: 'charlies.com', api_key: 'testkey', rambo: 'test', ajax: ajax }) s.execute('select * from rambo', null, { dp: 2 }) expect(ajaxParams.url.indexOf('http://')).not.toEqual(-1); expect(ajaxParams.url.indexOf('rambo.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('&rambo')).toEqual(-1); }); it("should include extra url params", function() { s = new cartodb.SQL({ user: 'rambo', format: 'geojson', protocol: 'http', host: 'charlies.com', api_key: 'testkey', rambo: 'test', ajax: ajax, extra_params: ['rambo'] }) s.execute('select * from rambo', null, { dp: 2 }) expect(ajaxParams.url.indexOf('http://')).not.toEqual(-1); expect(ajaxParams.url.indexOf('rambo.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('&rambo=test')).not.toEqual(-1); s.execute('select * from rambo', null, { dp: 2, rambo: 'test2' }) expect(ajaxParams.url.indexOf('&rambo=test2')).not.toEqual(-1); }); it("should use jsonp if browser does not support cors", function() { $.support.cors = false; s = new cartodb.SQL({ user: 'jaja', ajax: ajax }); expect(s.options.jsonp).toEqual(true); s.execute('select * from rambo', 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 = true; }); 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 rambo where id=2) as subq'; s = new cartodb.SQL({ user: 'jaja', ajax: ajax }); s.getBounds('select * from rambo where id={{id}}', {id: 2}); expect(ajaxParams.url.indexOf(encodeURIComponent(sql))).not.toEqual(-1); }); it("should get bounds for query with appostrophes", function() { s = new cartodb.SQL({ user: 'jaja', ajax: ajax }); s.getBounds("select * from country where name={{ name }}", { name: "'Spain'"}); expect(ajaxParams.url.indexOf("%26amp%3B%2339%3B")).toEqual(-1); }); }); describe('sql.table', function() { var USER = 'rambo'; var sql; beforeEach(function() { ajaxParams = null; sql = new cartodb.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("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 cartodb.SQL({ user: USER, protocol: 'https' }); sql.execute = function(sql, callback){ callback({}); } }); 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(data); } var callback = function(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":[{"bbox": '{"type":"Polygon","coordinates":[[[-179.9284,-65.2446],[-179.9284,81.8962],[179.9698,81.8962],[179.9698,-65.2446],[-179.9284,-65.2446]]]}',"geometry_type":"ST_Point","clusterrate":0.20359746623640493,"density":0.105333307745705}],"time":0.035,"fields":{"bbox":{"type":"string"},"geometry_type":{"type":"string"},"clusterrate":{"type":"number"},"density":{"type":"number"}},"total_rows":1}; callback(data); } var callback = function(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); expect(description.bbox.constructor).toEqual(Array); expect(typeof description.density).toEqual("number"); expect(typeof description.cluster_rate).toEqual("number"); }) }); 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(data); } var callback = function(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 numTypes = ["avg", "max", "min", "stddevmean", "weight", "stddev", "null_ratio", "count"]; for(var i = 0; i < numTypes.length; i++){ expect(typeof description[numTypes[i]]).toEqual("number"); } var arrayTypes = ["quantiles", "equalint", "jenks", "headtails", "cat_hist", "hist"]; for(var 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(data); } var callback = function(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"); }) }); });