From 21072645a4bbb95de03d6dcb58906abb74953c81 Mon Sep 17 00:00:00 2001 From: Raul Ochoa Date: Mon, 6 Apr 2015 18:44:57 +0200 Subject: [PATCH] Tests for both: onTileErrorStrategy enabled and disabled --- test/acceptance/limits.js | 318 ++++++++++++++-------- test/fixtures/render-timeout-fallback.png | Bin 0 -> 10177 bytes 2 files changed, 203 insertions(+), 115 deletions(-) create mode 100644 test/fixtures/render-timeout-fallback.png diff --git a/test/acceptance/limits.js b/test/acceptance/limits.js index 98273b47..fbe37186 100644 --- a/test/acceptance/limits.js +++ b/test/acceptance/limits.js @@ -88,132 +88,220 @@ describe('render limits', function() { } - 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(); - } - ); + describe('with onTileErrorStrategy DISABLED', function() { + var onTileErrorStrategyEnabled; + before(function() { + onTileErrorStrategyEnabled = global.environment.enabledFeatures.onTileErrorStrategy; + global.environment.enabledFeatures.onTileErrorStrategy = false; }); - }); - 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); - done(); - } - ); + 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); + 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); - } + 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) { - 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' + var layergroup = singleLayergroupConfig(pointSleepSql, pointCartoCss); + assert.response(server, + createRequest(layergroup, user), + { + status: 200 + }, + function(res) { + 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' }, - encoding: 'binary' - }, - { - status: 400 - }, - function(res) { - var parsed = JSON.parse(res.body); - assert.deepEqual(parsed, { error: '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) { - 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' + { + status: 400 }, - encoding: 'binary' - }, - { - status: 200, - headers: { - 'Content-Type': 'image/png' + function(res) { + var parsed = JSON.parse(res.body); + assert.deepEqual(parsed, { error: 'Render timed out' }); + done(); } - }, - function(res, err) { - done(err); - } - ); + ); - } - ); + } + ); + }); }); + + 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) { + 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); + 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) { + 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); + } + assert.imageEqualsFile(res.body, './test/fixtures/render-timeout-fallback.png', 25, + function(imgErr/*, similarity*/) { + done(imgErr); + } + ); + } + ); + + } + ); + }); + }); + }); }); diff --git a/test/fixtures/render-timeout-fallback.png b/test/fixtures/render-timeout-fallback.png new file mode 100644 index 0000000000000000000000000000000000000000..f80cdbf89aa661983aa198950fd2659e9440d793 GIT binary patch literal 10177 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 T(ii&~6;M-trd0gM%