var testHelper = require('../support/test_helper'); var assert = require('../support/assert'); var _ = require('underscore'); var redis = require('redis'); var CartodbWindshaft = require('../../lib/cartodb/server'); var serverOptions = require('../../lib/cartodb/server_options'); var LayergroupToken = require('../support/layergroup-token'); describe('render limits', function() { var layergroupUrl = '/api/v1/map'; var redisClient = redis.createClient(global.environment.redis.port); var server; var keysToDelete; beforeEach(function() { keysToDelete = {}; server = new CartodbWindshaft(serverOptions); server.setMaxListeners(0); }); afterEach(function(done) { testHelper.deleteRedisKeys(keysToDelete, done); }); var user = 'localhost'; var pointSleepSql = "SELECT pg_sleep(0.5)," + " 'SRID=3857;POINT(0 0)'::geometry the_geom_webmercator, 1 cartodb_id"; var pointCartoCss = '#layer { marker-fill:red; }'; var polygonSleepSql = "SELECT pg_sleep(0.5)," + " ST_Buffer('SRID=3857;POINT(0 0)'::geometry, 100000000) the_geom_webmercator, 1 cartodb_id"; var polygonCartoCss = '#layer { polygon-fill:red; }'; 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) { return { url: layergroupUrl, method: 'POST', headers: { host: userHost, 'Content-Type': 'application/json' }, data: JSON.stringify(layergroup) }; } function withRenderLimit(user, renderLimit, callback) { redisClient.SELECT(5, function(err) { if (err) { return callback(err); } var userLimitsKey = 'limits:tiler:' + user; redisClient.HSET(userLimitsKey, 'render', renderLimit, function(err) { if (err) { return callback(err); } keysToDelete[userLimitsKey] = 5; return callback(); }); }); } describe('with onTileErrorStrategy DISABLED', function() { var onTileErrorStrategyEnabled; before(function() { onTileErrorStrategyEnabled = global.environment.enabledFeatures.onTileErrorStrategy; global.environment.enabledFeatures.onTileErrorStrategy = false; }); after(function() { global.environment.enabledFeatures.onTileErrorStrategy = onTileErrorStrategyEnabled; }); it("layergroup creation fails if test tile is slow", function(done) { withRenderLimit(user, 50, function(err) { if (err) { return done(err); } var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss); assert.response(server, createRequest(layergroup, user), { status: 400 }, function(res) { var parsed = JSON.parse(res.body); assert.deepEqual(parsed, { errors: [ 'Render timed out' ] }); done(); } ); }); }); it("layergroup creation does not fail if user limit is high enough even if test tile is slow", function(done) { withRenderLimit(user, 5000, function(err) { if (err) { return done(err); } var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss); assert.response(server, createRequest(layergroup, user), { 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("layergroup creation works if test tile is fast but tile request fails if they are slow", function(done) { withRenderLimit(user, 50, function(err) { if (err) { return done(err); } var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss); assert.response(server, createRequest(layergroup, user), { status: 200 }, function(res) { keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0; keysToDelete['user:localhost:mapviews:global'] = 5; 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: 400 }, function(res) { var parsed = JSON.parse(res.body); assert.deepEqual(parsed, { errors: ['Render timed out'] }); done(); } ); } ); }); }); it("tile request does not fail if user limit is high enough", function(done) { withRenderLimit(user, 5000, function(err) { if (err) { return done(err); } var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss); assert.response(server, createRequest(layergroup, user), { status: 200 }, function(res) { keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0; keysToDelete['user:localhost:mapviews:global'] = 5; 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, headers: { 'Content-Type': 'image/png' } }, function(res, err) { done(err); } ); } ); }); }); }); describe('with onTileErrorStrategy', function() { it("layergroup creation works even if test tile is slow", function(done) { withRenderLimit(user, 50, function(err) { if (err) { return done(err); } var layergroup = singleLayergroupConfig(polygonSleepSql, polygonCartoCss); assert.response(server, createRequest(layergroup, user), { 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("layergroup creation and tile requests works even if they are slow but returns fallback", function(done) { withRenderLimit(user, 50, function(err) { if (err) { return done(err); } var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss); assert.response(server, createRequest(layergroup, user), { status: 200 }, function(res) { keysToDelete['map_cfg|' + LayergroupToken.parse(JSON.parse(res.body).layergroupid).token] = 0; keysToDelete['user:localhost:mapviews:global'] = 5; 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, headers: { 'Content-Type': 'image/png' } }, function(res, err) { if (err) { done(err); } var referenceImagePath = './test/fixtures/render-timeout-fallback.png'; assert.imageBufferIsSimilarToFile(res.body, referenceImagePath, 25, function(imgErr/*, similarity*/) { done(imgErr); } ); } ); } ); }); }); }); });