diff --git a/lib/cartodb/cache/named_map_provider_cache.js b/lib/cartodb/cache/named_map_provider_cache.js index a25850dc..9adb29b9 100644 --- a/lib/cartodb/cache/named_map_provider_cache.js +++ b/lib/cartodb/cache/named_map_provider_cache.js @@ -8,15 +8,15 @@ var queue = require('queue-async'); var LruCache = require("lru-cache"); -function NamedMapProviderCache(templateMaps, pgConnection, metadataBackend, userLimitsApi, overviewsAdapter, - turboCartoAdapter) { +function NamedMapProviderCache(templateMaps, pgConnection, metadataBackend, analysisBackend, userLimitsApi, + overviewsAdapter, turboCartoAdapter) { this.templateMaps = templateMaps; this.pgConnection = pgConnection; this.metadataBackend = metadataBackend; this.userLimitsApi = userLimitsApi; this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps); - this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter(); + this.analysisMapConfigAdapter = new AnalysisMapConfigAdapter(analysisBackend); this.overviewsAdapter = overviewsAdapter; this.turboCartoAdapter = turboCartoAdapter; diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index ed24f651..0f15e240 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -157,6 +157,7 @@ module.exports = function(serverOptions) { templateMaps, pgConnection, metadataBackend, + analysisBackend, userLimitsApi, overviewsAdapter, turboCartoAdapter diff --git a/test/acceptance/analysis/named-maps.js b/test/acceptance/analysis/named-maps.js index 13d84120..26341a0a 100644 --- a/test/acceptance/analysis/named-maps.js +++ b/test/acceptance/analysis/named-maps.js @@ -1,11 +1,11 @@ var assert = require('../../support/assert'); -var step = require('step'); var helper = require('../../support/test_helper'); var CartodbWindshaft = require('../../../lib/cartodb/server'); var serverOptions = require('../../../lib/cartodb/server_options'); var server = new CartodbWindshaft(serverOptions); +var TestClient = require('../../support/test-client'); var LayergroupToken = require('../../../lib/cartodb/models/layergroup_token'); @@ -16,230 +16,259 @@ describe('named-maps analysis', function() { var username = 'localhost'; var widgetsTemplateName = 'widgets-template'; - var layergroupid; - var layergroup; - var keysToDelete; - - beforeEach(function(done) { - keysToDelete = {}; - - var widgetsTemplate = { - version: '0.0.1', - name: widgetsTemplateName, - layergroup: { - version: '1.5.0', - layers: [ - { - "type": "cartodb", - "options": { - "source": { - "id": "HEAD" - }, - "cartocss": '#buffer { polygon-fill: red; }', - "cartocss_version": "2.3.0" - } - } - ], - dataviews: { - pop_max_histogram: { - source: { - id: 'HEAD' + var widgetsTemplate = { + version: '0.0.1', + name: widgetsTemplateName, + layergroup: { + version: '1.5.0', + layers: [ + { + "type": "cartodb", + "options": { + "source": { + "id": "HEAD" }, - type: 'histogram', - options: { - column: 'pop_max' - } + "cartocss": '#buffer { polygon-fill: red; }', + "cartocss_version": "2.3.0" } - }, - analyses: [ - { - "id": "HEAD", - "type": "buffer", - "params": { - "source": { - "id": "2570e105-7b37-40d2-bdf4-1af889598745", - "type": "source", - "params": { - "query": "select * from populated_places_simple_reduced" - } - }, - "radius": 50000 - } + } + ], + dataviews: { + pop_max_histogram: { + source: { + id: 'HEAD' + }, + type: 'histogram', + options: { + column: 'pop_max' } - ] - } - }; - - var template_params = {}; - - step( - function createTemplate() - { - var next = this; - assert.response( - server, - { - url: '/api/v1/map/named?api_key=1234', - method: 'POST', - headers: { - host: username, - 'Content-Type': 'application/json' + } + }, + analyses: [ + { + "id": "HEAD", + "type": "buffer", + "params": { + "source": { + "id": "2570e105-7b37-40d2-bdf4-1af889598745", + "type": "source", + "params": { + "query": "select * from populated_places_simple_reduced" + } }, - data: JSON.stringify(widgetsTemplate) - }, - { - status: 200 - }, - function(res, err) { - next(err, res); + "radius": 50000 } - ); - }, - function instantiateTemplate(err, res) { - assert.ifError(err); + } + ] + } + }; - assert.deepEqual(JSON.parse(res.body), { template_id: widgetsTemplateName }); - var next = this; - assert.response( - server, - { - url: '/api/v1/map/named/' + widgetsTemplateName, - method: 'POST', - headers: { - host: username, - 'Content-Type': 'application/json' - }, - data: JSON.stringify(template_params) - }, - { - status: 200 - }, - function(res) { - next(null, res); - } - ); - }, - function finish(err, res) { - assert.ifError(err); - - layergroup = JSON.parse(res.body); - assert.ok(layergroup.hasOwnProperty('layergroupid'), "Missing 'layergroupid' from: " + res.body); - layergroupid = layergroup.layergroupid; - - assert.ok( - Array.isArray(layergroup.metadata.analyses), - 'Missing "analyses" array metadata from: ' + res.body - ); - var analyses = layergroup.metadata.analyses; - assert.equal(analyses.length, 1, 'Invalid number of analyses in metadata'); - var nodes = analyses[0].nodes; - var nodesIds = Object.keys(nodes); - assert.deepEqual(nodesIds, ['2570e105-7b37-40d2-bdf4-1af889598745', 'HEAD']); - nodesIds.forEach(function(nodeId) { - var node = nodes[nodeId]; - assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node'); - assert.ok(node.hasOwnProperty('status'), 'Missing "status" attribute in node'); - assert.ok(!node.hasOwnProperty('query'), 'Unexpected "query" attribute in node'); - }); - - keysToDelete['map_cfg|' + LayergroupToken.parse(layergroup.layergroupid).token] = 0; - keysToDelete['user:localhost:mapviews:global'] = 5; - - return done(); - } - ); - - }); - - afterEach(function(done) { - step( - function deleteTemplate(err) { - assert.ifError(err); - var next = this; - assert.response( - server, - { - url: '/api/v1/map/named/' + widgetsTemplateName + '?api_key=1234', - method: 'DELETE', - headers: { - host: username - } - }, - { - status: 204 - }, - function(res, err) { - next(err, res); - } - ); - }, - function deleteRedisKeys(err) { - assert.ifError(err); - helper.deleteRedisKeys(keysToDelete, done); - } - ); - }); - - it('should be able to retrieve images from analysis', function(done) { + beforeEach(function createTemplate(done) { assert.response( server, { - url: '/api/v1/map/' + layergroupid + '/6/31/24.png', - method: 'GET', - encoding: 'binary', + url: '/api/v1/map/named?api_key=1234', + method: 'POST', + headers: { + host: username, + 'Content-Type': 'application/json' + }, + data: JSON.stringify(widgetsTemplate) + }, + { + status: 200 + }, + function(res, err) { + assert.deepEqual(JSON.parse(res.body), { template_id: widgetsTemplateName }); + return done(err); + } + ); + }); + + afterEach(function deleteTemplate(done) { + assert.response( + server, + { + url: '/api/v1/map/named/' + widgetsTemplateName + '?api_key=1234', + method: 'DELETE', headers: { host: username } }, { - status: 200, - headers: { - 'Content-Type': 'image/png' - } + status: 204 }, function(res, err) { - if (err) { - return done(err); - } + return done(err); + } + ); + }); - var fixturePath = './test/fixtures/analysis/named-map-buffer.png'; - assert.imageBufferIsSimilarToFile(res.body, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) { + describe('layergroup', function() { + var layergroupid; + var layergroup; + var keysToDelete; + + beforeEach(function(done) { + keysToDelete = {}; + + assert.response( + server, + { + url: '/api/v1/map/named/' + widgetsTemplateName, + method: 'POST', + headers: { + host: username, + 'Content-Type': 'application/json' + }, + data: JSON.stringify({}) + }, + { + status: 200 + }, + function(res, err) { + assert.ifError(err); + + layergroup = JSON.parse(res.body); + assert.ok(layergroup.hasOwnProperty('layergroupid'), "Missing 'layergroupid' from: " + res.body); + layergroupid = layergroup.layergroupid; + + assert.ok( + Array.isArray(layergroup.metadata.analyses), + 'Missing "analyses" array metadata from: ' + res.body + ); + var analyses = layergroup.metadata.analyses; + assert.equal(analyses.length, 1, 'Invalid number of analyses in metadata'); + var nodes = analyses[0].nodes; + var nodesIds = Object.keys(nodes); + assert.deepEqual(nodesIds, ['2570e105-7b37-40d2-bdf4-1af889598745', 'HEAD']); + nodesIds.forEach(function(nodeId) { + var node = nodes[nodeId]; + assert.ok(node.hasOwnProperty('url'), 'Missing "url" attribute in node'); + assert.ok(node.hasOwnProperty('status'), 'Missing "status" attribute in node'); + assert.ok(!node.hasOwnProperty('query'), 'Unexpected "query" attribute in node'); + }); + + keysToDelete['map_cfg|' + LayergroupToken.parse(layergroup.layergroupid).token] = 0; + keysToDelete['user:localhost:mapviews:global'] = 5; + + return done(); + } + ); + + }); + + afterEach(function(done) { + helper.deleteRedisKeys(keysToDelete, done); + }); + + it('should be able to retrieve images from analysis', function(done) { + assert.response( + server, + { + url: '/api/v1/map/' + layergroupid + '/6/31/24.png', + method: 'GET', + encoding: 'binary', + headers: { + host: username + } + }, + { + status: 200, + headers: { + 'Content-Type': 'image/png' + } + }, + function(res, err) { + if (err) { + return done(err); + } + + var fixturePath = './test/fixtures/analysis/named-map-buffer.png'; + assert.imageBufferIsSimilarToFile(res.body, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) { + assert.ok(!err, err); + done(); + }); + + } + ); + }); + + it('should be able to retrieve dataviews from analysis', function(done) { + assert.response( + server, + { + url: '/api/v1/map/' + layergroupid + '/dataview/pop_max_histogram', + method: 'GET', + headers: { + host: username + } + }, + { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }, + function(res, err) { + if (err) { + return done(err); + } + + var dataview = JSON.parse(res.body); + assert.equal(dataview.type, 'histogram'); + assert.equal(dataview.bins_start, 0); + + done(); + } + ); + }); + + it('should be able to retrieve static map preview via layergroup', function(done) { + assert.response( + server, + { + url: '/api/v1/map/static/center/' + layergroupid + '/4/42/-3/320/240.png', + method: 'GET', + encoding: 'binary', + headers: { + host: username + } + }, + { + status: 200, + headers: { + 'Content-Type': 'image/png' + } + }, + function(res, err) { + if (err) { + return done(err); + } + + var fixturePath = './test/fixtures/analysis/named-map-buffer-layergroup-static-preview.png'; + assert.imageBufferIsSimilarToFile(res.body, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) { + assert.ok(!err, err); + done(); + }); + + } + ); + }); + + }); + + describe('auto-instantiation', function() { + it('should be able to retrieve static map preview via fixed url', function(done) { + TestClient.getStaticMap(widgetsTemplateName, function(err, image) { + assert.ok(!err, err); + var fixturePath = './test/fixtures/analysis/named-map-buffer-static-preview.png'; + assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) { assert.ok(!err, err); done(); }); - - } - ); - }); - - it('should be able to retrieve dataviews from analysis', function(done) { - assert.response( - server, - { - url: '/api/v1/map/' + layergroupid + '/dataview/pop_max_histogram', - method: 'GET', - headers: { - host: username - } - }, - { - status: 200, - headers: { - 'Content-Type': 'application/json; charset=utf-8' - } - }, - function(res, err) { - if (err) { - return done(err); - } - - var dataview = JSON.parse(res.body); - assert.equal(dataview.type, 'histogram'); - assert.equal(dataview.bins_start, 0); - - done(); - } - ); + }); + }); }); }); diff --git a/test/fixtures/analysis/named-map-buffer-layergroup-static-preview.png b/test/fixtures/analysis/named-map-buffer-layergroup-static-preview.png new file mode 100644 index 00000000..deb0b60c Binary files /dev/null and b/test/fixtures/analysis/named-map-buffer-layergroup-static-preview.png differ diff --git a/test/fixtures/analysis/named-map-buffer-static-preview.png b/test/fixtures/analysis/named-map-buffer-static-preview.png new file mode 100644 index 00000000..d57b3a2a Binary files /dev/null and b/test/fixtures/analysis/named-map-buffer-static-preview.png differ diff --git a/test/support/test-client.js b/test/support/test-client.js index 5ced9f98..a9ec5bee 100644 --- a/test/support/test-client.js +++ b/test/support/test-client.js @@ -471,3 +471,41 @@ TestClient.prototype.getNodeStatus = function(nodeName, callback) { TestClient.prototype.drain = function(callback) { helper.deleteRedisKeys(this.keysToDelete, callback); }; + +module.exports.getStaticMap = function getStaticMap(templateName, params, callback) { + if (!callback) { + callback = params; + params = null; + } + + var url = '/api/v1/map/static/named/' + templateName + '/640/480.png'; + + if (params !== null) { + url += '?' + qs.stringify(params); + } + + var requestOptions = { + url: url, + method: 'GET', + headers: { + host: 'localhost' + }, + encoding: 'binary' + }; + + var expectedResponse = { + status: 200, + headers: { + 'Content-Type': 'image/png' + } + }; + + // this could be removed once named maps are invalidated, otherwise you hits the cache + var server = new CartodbWindshaft(serverOptions); + + assert.response(server, requestOptions, expectedResponse, function (res, err) { + helper.deleteRedisKeys({'user:localhost:mapviews:global': 5}, function() { + return callback(err, mapnik.Image.fromBytes(new Buffer(res.body, 'binary'))); + }); + }); +};