var testHelper = require('../support/test_helper'); var assert = require('../support/assert'); var _ = require('underscore'); var LayergroupToken = require('../support/layergroup-token'); var PgQueryRunner = require('../../lib/cartodb/backends/pg_query_runner'); var QueryTables = require('cartodb-query-tables'); var CartodbWindshaft = require('../../lib/cartodb/server'); var serverOptions = require('../../lib/cartodb/server_options'); var server = new CartodbWindshaft(serverOptions); server.setMaxListeners(0); describe('tests from old api translated to multilayer', function() { var layergroupUrl = '/api/v1/map'; var keysToDelete; beforeEach(function() { keysToDelete = {}; }); afterEach(function(done) { testHelper.deleteRedisKeys(keysToDelete, done); }); var wadusSql = 'select 1 as cartodb_id, null::geometry as the_geom_webmercator'; var pointSql = "SELECT 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator, 1::int as cartodb_id"; function singleLayergroupConfig(sql, cartocss) { return { version: '1.0.0', layers: [ { type: 'mapnik', options: { sql: sql, cartocss: cartocss, cartocss_version: '2.0.1' } } ] }; } function createRequest(layergroup, userHost, apiKey) { var url = layergroupUrl; if (apiKey) { url += '?api_key=' + apiKey; } return { url: url, method: 'POST', headers: { host: userHost || 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(layergroup) }; } it("layergroup creation fails if CartoCSS is bogus", function(done) { var layergroup = singleLayergroupConfig(wadusSql, '#my_table3{'); assert.response(server, createRequest(layergroup), { status: 400 }, function(res) { var parsed = JSON.parse(res.body); assert.ok(parsed.errors[0].match(/^style0/)); assert.ok(parsed.errors[0].match(/missing closing/)); done(); } ); }); it("multiple bad styles returns 400 with all errors", function(done) { var layergroup = singleLayergroupConfig(wadusSql, '#my_table4{backgxxxxxround-color:#fff;foo:bar}'); assert.response(server, createRequest(layergroup), { status: 400 }, function(res) { var parsed = JSON.parse(res.body); assert.equal(parsed.errors.length, 1); assert.ok(parsed.errors[0].match(/^style0/)); assert.ok(parsed.errors[0].match(/Unrecognized rule: backgxxxxxround-color/)); assert.ok(parsed.errors[0].match(/Unrecognized rule: foo/)); done(); } ); }); // Zoom is a special variable it("Specifying zoom level in CartoCSS does not need a 'zoom' variable in SQL output", function(done) { var layergroup = singleLayergroupConfig(pointSql, '#gadm4 [ zoom>=3] { marker-fill:red; }'); assert.response(server, createRequest(layergroup), { status: 200 }, function(res) { var parsed = JSON.parse(res.body); assert.ok(parsed.layergroupid); assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid); keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0; keysToDelete['user:localhost:mapviews:global'] = 5; done(); } ); }); // See https://github.com/CartoDB/Windshaft-cartodb/issues/88 it("getting a tile from a user-specific database should return an expected tile", function(done) { var layergroup = singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }'); var backupDBHost = global.environment.postgres.host; global.environment.postgres.host = '6.6.6.6'; assert.response(server, createRequest(layergroup, 'cartodb250user'), { status: 200 }, function(res) { var parsed = JSON.parse(res.body); assert.ok(parsed.layergroupid); assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid); keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0; keysToDelete['user:cartodb250user:mapviews:global'] = 5; global.environment.postgres.host = backupDBHost; done(); } ); }); // See https://github.com/CartoDB/Windshaft-cartodb/issues/89 it("getting a tile with a user-specific database password", function(done) { var layergroup = singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }'); var backupDBPass = global.environment.postgres_auth_pass; global.environment.postgres_auth_pass = '<%= user_password %>'; assert.response(server, createRequest(layergroup, 'cartodb250user', '4321'), { status: 200 }, function(res) { var parsed = JSON.parse(res.body); assert.ok(parsed.layergroupid); assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid); keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0; keysToDelete['user:cartodb250user:mapviews:global'] = 5; global.environment.postgres_auth_pass = backupDBPass; done(); } ); }); it("creating a layergroup from lzma param", function(done){ var params = { config: JSON.stringify(singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }')) }; testHelper.lzma_compress_to_base64(JSON.stringify(params), 1, function(err, lzma) { if (err) { return done(err); } assert.response(server, { url: layergroupUrl + '?lzma=' + encodeURIComponent(lzma), method: 'GET', headers: { host: 'localhost' }, encoding: 'binary' }, { status: 200 }, function(res) { var parsed = JSON.parse(res.body); assert.ok(parsed.layergroupid); keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0; keysToDelete['user:localhost:mapviews:global'] = 5; done(); } ); }); }); it("creating a layergroup from lzma param, invalid json input", function(done) { var params = { config: 'WADUS' }; testHelper.lzma_compress_to_base64(JSON.stringify(params), 1, function(err, lzma) { if (err) { return done(err); } assert.response(server, { url: layergroupUrl + '?lzma=' + encodeURIComponent(lzma), method: 'GET', headers: { host: 'localhost' }, encoding: 'binary' }, { status: 400 }, function(res) { var parsed = JSON.parse(res.body); assert.ok(parsed.errors); assert.equal(parsed.errors.length, 1); assert.ok(parsed.errors[0].match(/^Unexpected token W/)); done(); } ); }); }); it("uses queries postgresql to figure affected tables in query", function(done) { var tableName = 'gadm4'; var expectedCacheChannel = _.template('<%= databaseName %>:public.<%= tableName %>', { databaseName: _.template(global.environment.postgres_auth_user, {user_id:1}) + '_db', tableName: tableName }); var layergroup = singleLayergroupConfig('select * from ' + tableName, '#gadm4 { marker-fill: red; }'); assert.response(server, { url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)), method: 'GET', headers: { host: 'localhost' } }, { status: 200 }, function(res) { var parsed = JSON.parse(res.body); assert.ok(parsed.layergroupid); assert.ok(res.headers.hasOwnProperty('x-cache-channel')); assert.equal(res.headers['x-cache-channel'], expectedCacheChannel); assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid); keysToDelete['map_cfg|' + LayergroupToken.parse(parsed.layergroupid).token] = 0; keysToDelete['user:localhost:mapviews:global'] = 5; done(); } ); }); // https://github.com/CartoDB/cartodb-postgresql/issues/86 it.skip("should not fail with long table names because table name length limit", function(done) { var tableName = 'long_table_name_with_enough_chars_to_break_querytables_function'; var expectedCacheChannel = _.template('<%= databaseName %>:public.<%= tableName %>', { databaseName: _.template(global.environment.postgres_auth_user, {user_id:1}) + '_db', tableName: tableName }); var layergroup = singleLayergroupConfig('select * from ' + tableName, '#layer { marker-fill: red; }'); assert.response(server, { url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)), method: 'GET', headers: { host: 'localhost' } }, { status: 200 }, function(res) { var parsed = JSON.parse(res.body); assert.ok(parsed.layergroupid); assert.ok(res.headers.hasOwnProperty('x-cache-channel')); assert.equal(res.headers['x-cache-channel'], expectedCacheChannel); assert.equal(res.headers['x-layergroup-id'], parsed.layergroupid); done(); } ); }); it("creates layergroup fails when postgresql queries fail to figure affected tables in query", function(done) { var runQueryFn = PgQueryRunner.prototype.run; PgQueryRunner.prototype.run = function(username, query, callback) { return callback(new Error('fake error message'), []); }; var layergroup = singleLayergroupConfig('select * from gadm4', '#gadm4 { marker-fill: red; }'); assert.response(server, { url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)), method: 'GET', headers: { host: 'localhost' } }, { status: 400 }, function(res) { PgQueryRunner.prototype.run = runQueryFn; assert.ok(!res.headers.hasOwnProperty('x-cache-channel')); var parsed = JSON.parse(res.body); assert.deepEqual(parsed.errors, ["fake error message"]); done(); } ); }); it("tile requests works when postgresql queries fail to figure affected tables in query", function(done) { var layergroup = singleLayergroupConfig('select * from gadm4', '#gadm4 { marker-fill: red; }'); assert.response(server, { url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)), method: 'GET', headers: { host: 'localhost' } }, { status: 200 }, function(res) { keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0; keysToDelete['user:localhost:mapviews:global'] = 5; var affectedFn = QueryTables.getAffectedTablesFromQuery; QueryTables.getAffectedTablesFromQuery = function(sql, username, query, callback) { affectedFn({query: function(query, callback) { return callback(new Error('fake error message'), []); }}, username, query, callback); }; // reset internal cacheChannel cache server.layergroupAffectedTablesCache.cache.reset(); assert.response(server, { url: layergroupUrl + _.template('/<%= layergroupId %>/<%= z %>/<%= x %>/<%= y %>.png', { layergroupId: JSON.parse(res.body).layergroupid, z: 0, x: 0, y: 0 }), method: 'GET', headers: { host: 'localhost' }, encoding: 'binary' }, { status: 200 }, function(res) { assert.ok(!res.headers.hasOwnProperty('x-cache-channel')); QueryTables.getAffectedTablesFromQuery = affectedFn; done(); } ); } ); }); });