diff --git a/lib/cartodb/server.js b/lib/cartodb/server.js index 13d64804..deb88e16 100644 --- a/lib/cartodb/server.js +++ b/lib/cartodb/server.js @@ -310,6 +310,25 @@ function bootstrap(opts) { app.enable('jsonp callback'); app.disable('x-powered-by'); app.disable('etag'); + + // Fix: https://github.com/CartoDB/Windshaft-cartodb/issues/705 + // See: http://expressjs.com/en/4x/api.html#app.set + app.set('json replacer', function (key, value) { + if (value !== value) { + return 'NaN'; + } + + if (value === Infinity) { + return 'Infinity'; + } + + if (value === -Infinity) { + return '-Infinity'; + } + + return value; + }); + app.use(bodyParser.json()); app.use(function bootstrap$prepareRequestResponse(req, res, next) { diff --git a/test/acceptance/special-numeric-values.js b/test/acceptance/special-numeric-values.js new file mode 100644 index 00000000..2b596b8e --- /dev/null +++ b/test/acceptance/special-numeric-values.js @@ -0,0 +1,71 @@ +require('../support/test_helper'); + +var assert = require('../support/assert'); +var TestClient = require('../support/test-client'); + +describe('special numeric values', function() { + + afterEach(function(done) { + if (this.testClient) { + this.testClient.drain(done); + } else { + done(); + } + }); + + var ATTRIBUTES_LAYER = 1; + + function createMapConfig(sql, id, columns) { + return { + version: '1.6.0', + layers: [ + { + type: 'mapnik', + options: { + sql: "select 1 as id, 'SRID=4326;POINT(0 0)'::geometry as the_geom", + cartocss: '#style { }', + cartocss_version: '2.0.1' + } + }, + { + type: 'mapnik', + options: { + sql: sql || "select 1 as i, 6 as n, 'SRID=4326;POINT(0 0)'::geometry as the_geom", + attributes: { + id: id || 'i', + columns: columns || ['n'] + }, + cartocss: '#style { }', + cartocss_version: '2.0.1' + } + } + ] + }; + } + + it('should retrieve special numeric values', function (done) { + var featureId = 1; + var sql = [ + 'SELECT', + ' 1 as cartodb_id,', + ' null::geometry the_geom_webmercator,', + ' \'infinity\'::float as infinity,', + ' \'-infinity\'::float as _infinity,', + ' \'NaN\'::float as nan' + ].join('\n'); + var id = 'cartodb_id'; + var columns = ['infinity', '_infinity', 'nan']; + + var mapConfig = createMapConfig(sql, id, columns); + + this.testClient = new TestClient(mapConfig, 1234); + this.testClient.getFeatureAttributes(featureId, ATTRIBUTES_LAYER, {}, function (err, attributes) { + assert.ifError(err); + assert.equal(attributes.infinity, 'Infinity'); + assert.equal(attributes._infinity, '-Infinity'); + assert.equal(attributes.nan, 'NaN'); + done(); + }); + }); +}); + diff --git a/test/support/test-client.js b/test/support/test-client.js index 3b9a003d..bc00bc15 100644 --- a/test/support/test-client.js +++ b/test/support/test-client.js @@ -414,6 +414,105 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) { ); }; +TestClient.prototype.getFeatureAttributes = function(featureId, layerId, params, callback) { + var self = this; + + if (!callback) { + callback = params; + params = {}; + } + + var extraParams = {}; + if (this.apiKey) { + extraParams.api_key = this.apiKey; + } + if (params && params.filters) { + extraParams.filters = JSON.stringify(params.filters); + } + + var url = '/api/v1/map'; + if (Object.keys(extraParams).length > 0) { + url += '?' + qs.stringify(extraParams); + } + + var expectedResponse = params.response || { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + var layergroupId; + step( + function createLayergroup() { + var next = this; + assert.response(server, + { + url: url, + method: 'POST', + headers: { + host: 'localhost', + 'Content-Type': 'application/json' + }, + data: JSON.stringify(self.mapConfig) + }, + { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }, + function(res, err) { + if (err) { + return next(err); + } + + var parsedBody = JSON.parse(res.body); + + if (parsedBody.layergroupid) { + self.keysToDelete['map_cfg|' + LayergroupToken.parse(parsedBody.layergroupid).token] = 0; + self.keysToDelete['user:localhost:mapviews:global'] = 5; + } + + return next(null, parsedBody.layergroupid); + } + ); + }, + function getFeatureAttributes(err, layergroupId) { + assert.ifError(err); + + var next = this; + + url = '/api/v1/map/' + layergroupId + '/' + layerId + '/attributes/' + featureId; + + assert.response(server, + { + url: url, + method: 'GET', + headers: { + host: 'localhost' + } + }, + expectedResponse, + function(res, err) { + if (err) { + return next(err); + } + + next(null, JSON.parse(res.body)); + } + ); + }, + function finish(err, attributes) { + if (err) { + return callback(err); + } + + return callback(null, attributes); + } + ); +}; + TestClient.prototype.getTile = function(z, x, y, params, callback) { var self = this;