Windshaft-cartodb/test/acceptance/multilayer-server-test.js

403 lines
14 KiB
JavaScript

'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;
const createServer = require('../../lib/server');
var serverOptions = require('../../lib/server-options');
describe('tests from old api translated to multilayer', function () {
var server;
before(function () {
server = createServer(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();
}
);
}
);
});
});