require('../../support/test_helper'); var assert = require('../../support/assert'); var TestClient = require('../../support/test-client'); var dot = require('dot'); describe('analysis-layers', function() { var IMAGE_TOLERANCE_PER_MIL = 20; var multitypeStyleTemplate = dot.template([ "#points['mapnik::geometry_type'=1] {", " marker-fill-opacity: {{=it._opacity}};", " marker-line-color: #FFF;", " marker-line-width: 0.5;", " marker-line-opacity: {{=it._opacity}};", " marker-placement: point;", " marker-type: ellipse;", " marker-width: 8;", " marker-fill: {{=it._color}};", " marker-allow-overlap: true;", "}", "#lines['mapnik::geometry_type'=2] {", " line-color: {{=it._color}};", " line-width: 2;", " line-opacity: {{=it._opacity}};", "}", "#polygons['mapnik::geometry_type'=3] {", " polygon-fill: {{=it._color}};", " polygon-opacity: {{=it._opacity}};", " line-color: #FFF;", " line-width: 0.5;", " line-opacity: {{=it._opacity}};", "}" ].join('\n')); function cartocss(color, opacity) { return multitypeStyleTemplate({ _color: color || '#F11810', _opacity: Number.isFinite(opacity) ? opacity : 1 }); } function mapConfig(layers, dataviews, analysis) { return { version: '1.5.0', layers: layers, dataviews: dataviews || {}, analyses: analysis || [] }; } var DEFAULT_MULTITYPE_STYLE = cartocss(); var TILE_ANALYSIS_TABLES = { z: 0, x: 0, y: 0 }; var useCases = [ { desc: 'basic source-id mapnik layer', fixture: 'basic-source-id-mapnik-layer.png', mapConfig: mapConfig( [ { "type": "cartodb", "options": { "source": { "id": "2570e105-7b37-40d2-bdf4-1af889598745" }, "cartocss": DEFAULT_MULTITYPE_STYLE, "cartocss_version": "2.3.0" } } ], {}, [ { "id": "2570e105-7b37-40d2-bdf4-1af889598745", "type": "source", "params": { "query": "select * from populated_places_simple_reduced" } } ] ) }, { desc: 'buffer over source', fixture: 'buffer-over-source.png', tile: { z: 7, x: 61, y: 47 }, mapConfig: mapConfig( [ { "type": "cartodb", "options": { "source": { "id": "HEAD" }, "cartocss": DEFAULT_MULTITYPE_STYLE, "cartocss_version": "2.3.0" } } ], {}, [ { "id": "HEAD", "type": "buffer", "params": { "source": { "id": "2570e105-7b37-40d2-bdf4-1af889598745", "type": "source", "params": { "query": "select * from populated_places_simple_reduced" } }, "radius": 50000 } } ] ) } ]; useCases.forEach(function(useCase) { it('should implement use case: "' + useCase.desc + '"', function(done) { var testClient = new TestClient(useCase.mapConfig, 1234); var tile = useCase.tile || TILE_ANALYSIS_TABLES; testClient.getTile(tile.z, tile.x, tile.y, function(err, res, image) { assert.ok(!err, err); // To generate images use: // image.save('/tmp/' + useCase.desc.replace(/\s/g, '-') + '.png'); var fixturePath = './test/fixtures/analysis/' + useCase.fixture; assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) { assert.ok(!err, err); testClient.drain(done); }); }); }); }); it('should NOT fail for non-authenticated requests when it is just source', function(done) { var useCase = useCases[0]; // No API key here var testClient = new TestClient(useCase.mapConfig); testClient.getLayergroup(function(err, layergroupResult) { assert.ok(!err, err); assert.equal(layergroupResult.metadata.layers.length, 1); testClient.drain(done); }); }); it('should fail for non-authenticated requests that has a node other than "source"', function(done) { var useCase = useCases[1]; // No API key here var testClient = new TestClient(useCase.mapConfig); var PERMISSION_DENIED_RESPONSE = { status: 403, headers: { 'Content-Type': 'application/json; charset=utf-8' } }; testClient.getLayergroup({ response: PERMISSION_DENIED_RESPONSE }, function(err, layergroupResult) { assert.ok(!err, err); assert.deepEqual( layergroupResult.errors, ["Analysis requires authentication with API key: permission denied."] ); testClient.drain(done); }); }); it('should retrieve enough metadata about analyses', function(done) { var useCase = useCases[1]; // No API key here var testClient = new TestClient(useCase.mapConfig, 1234); testClient.getLayergroup(function(err, layergroupResult) { assert.ok(!err, err); assert.ok( Array.isArray(layergroupResult.metadata.analyses), 'Missing "analyses" array metadata from: ' + JSON.stringify(layergroupResult) ); var analyses = layergroupResult.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, ['HEAD', '2570e105-7b37-40d2-bdf4-1af889598745']); 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'), 'Missing "status" attribute in node'); }); testClient.drain(done); }); }); it('should have analysis metadata when dataviews point to source node', function(done) { var useCase = { desc: 'basic source-id mapnik layer', fixture: 'basic-source-id-mapnik-layer.png', mapConfig: mapConfig( [ { "type": "cartodb", "options": { "sql": "select * from populated_places_simple_reduced", "cartocss": DEFAULT_MULTITYPE_STYLE, "cartocss_version": "2.3.0" } }, { "type": "cartodb", "options": { "source": { "id": "2570e105-7b37-40d2-bdf4-1af889598745" }, "cartocss": DEFAULT_MULTITYPE_STYLE, "cartocss_version": "2.3.0" } }, { "type": "cartodb", "options": { "sql": "select * from populated_places_simple_reduced", "cartocss": DEFAULT_MULTITYPE_STYLE, "cartocss_version": "2.3.0" } } ], { pop_max_histogram: { source: { id: '2570e105-7b37-40d2-bdf4-1af889598745' }, type: 'histogram', options: { column: 'pop_max' } }, scalerank_histogram: { source: { id: '2570e105-7b37-40d2-bdf4-1af889598745' }, type: 'histogram', options: { column: 'scalerank' } } }, [ { "id": "2570e105-7b37-40d2-bdf4-1af889598745", "type": "source", "params": { "query": "select * from populated_places_simple_reduced" } } ] ) }; var testClient = new TestClient(useCase.mapConfig, 1234); testClient.getLayergroup(function(err, layergroupResult) { assert.ok(!err, err); assert.ok( Array.isArray(layergroupResult.metadata.analyses), 'Missing "analyses" array metadata from: ' + JSON.stringify(layergroupResult) ); var analyses = layergroupResult.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']); 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'), 'Missing "status" attribute in node'); }); testClient.drain(done); }); }); it('should response with custom cache headers for node status endpoints', function(done) { var useCase = useCases[1]; // No API key here var testClient = new TestClient(useCase.mapConfig, 1234); testClient.getNodeStatus('HEAD', function(err, response, nodeStatus) { assert.ok(!err, err); assert.equal(nodeStatus.status, 'ready'); var headers = response.headers; assert.equal(headers['cache-control'], 'public,max-age=5'); var lastModified = new Date(headers['last-modified']); var tenSecondsInMs = 1e5; assert.ok(Date.now() - lastModified.getTime() < tenSecondsInMs); testClient.drain(done); }); }); it('should wrap queries from analyses', function(done) { var testClient = new TestClient(mapConfig( [ { "type": "cartodb", "options": { "source": { "id": "2570e105-7b37-40d2-bdf4-1af889598745" }, "sql_wrap": "SELECT * FROM (<%= sql %>) __wrapped WHERE adm0cap = 1", "cartocss": DEFAULT_MULTITYPE_STYLE, "cartocss_version": "2.3.0" } } ], {}, [ { "id": "2570e105-7b37-40d2-bdf4-1af889598745", "type": "source", "params": { "query": "select * from populated_places_simple_reduced" } } ] ), 1234); var tile = TILE_ANALYSIS_TABLES; testClient.getTile(tile.z, tile.x, tile.y, function(err, res, image) { assert.ok(!err, err); var fixturePath = './test/fixtures/analysis/adm0cap-source-id-mapnik-layer.png'; assert.imageIsSimilarToFile(image, fixturePath, IMAGE_TOLERANCE_PER_MIL, function(err) { assert.ok(!err, err); testClient.drain(done); }); }); }); });