'use strict'; var testHelper = require('../support/test-helper'); var assert = require('../support/assert'); var _ = require('underscore'); var LayergroupToken = require('../../lib/models/layergroup-token'); var PgQueryRunner = require('../../lib/backends/pg-query-runner'); var QueryTables = require('cartodb-query-tables').queryTables; var CartodbWindshaft = require('../../lib/server'); var serverOptions = require('../../lib/server-options'); describe('tests from old api translated to multilayer', function () { var server; before(function () { server = new CartodbWindshaft(serverOptions); server.setMaxListeners(0); }); 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.strictEqual(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.strictEqual(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.strictEqual(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.strictEqual(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.strictEqual(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(Object.prototype.hasOwnProperty.call(res.headers, 'x-cache-channel')); assert.strictEqual(res.headers['x-cache-channel'], expectedCacheChannel); assert.strictEqual(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(Object.prototype.hasOwnProperty.call(res.headers, 'x-cache-channel')); assert.strictEqual(res.headers['x-cache-channel'], expectedCacheChannel); assert.strictEqual(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(!Object.prototype.hasOwnProperty.call(res.headers, 'x-cache-channel')); var parsed = JSON.parse(res.body); assert.deepStrictEqual(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.getQueryMetadataModel; QueryTables.getQueryMetadataModel = function () { return Promise.reject(new Error('fake error message')); }; // reset internal cacheChannel cache // FIXME: we need a better way to reset cache while running tests 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) { QueryTables.getQueryMetadataModel = affectedFn; assert.ok(!Object.prototype.hasOwnProperty.call(res.headers, 'x-cache-channel')); done(); } ); } ); }); });