diff --git a/.jshintrc b/.jshintrc index e8d605fc..4aedb3cf 100644 --- a/.jshintrc +++ b/.jshintrc @@ -84,6 +84,10 @@ // Custom Globals "globals" : { // additional predefined global variables + "describe": true, + "before": true, + "after": true, + "it": true, "suite": true, "suiteSetup": true, "test": true, diff --git a/Makefile b/Makefile index e8177443..11899d75 100644 --- a/Makefile +++ b/Makefile @@ -38,4 +38,4 @@ coverage: check: test -.PHONY: pre-install test +.PHONY: pre-install test jshint coverage diff --git a/NEWS.md b/NEWS.md index eb5dd295..cfc7d9d3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,58 +1,90 @@ -1.30.1 -- 2015-mm-dd --------------------- +# Changelog -1.30.0 -- 2015-03-11 --------------------- +## 2.0.0 + +Released 2015-mm-dd + +Announcements: + - Major release with **BREAKING CHANGES**: + * Removes `/:table/infowindow`, `/:table/map_metadata` and `/:table/flush_cache` endpoints + * Sample configuration removes `/tiles/template` and `/tiles/layergroup` + * URLs to use from now on are: `/api/v1/map/named` and `/api/v1/map` + * No more state changes for styles + * No more dump stats for renderers: SIGUSR1 and SIGUSR2 signals + * Removes query params: + - sql + - geom_type + - cache_buster + - cache_policy + - interactivity + - style + - style_version + - style_convert + - scale_factor + * Affected tables for x-cache-channel will use direct connection to postgresql + * Removes some metrics: authorized times ones + + +## 1.30.0 + +Released 2015-03-11 Announcements: - Upgrades windshaft to [0.40.0](https://github.com/CartoDB/Windshaft/releases/tag/0.40.0) -1.29.0 -- 2015-03-09 --------------------- +## 1.29.0 + +Released 2015-03-09 Announcements: - Upgrades windshaft to [0.39.0](https://github.com/CartoDB/Windshaft/releases/tag/0.39.0) -1.28.5 -- 2015-02-20 --------------------- +## 1.28.5 + +Released 2015-02-20 Announcements: - Upgrades windshaft to [0.37.5](https://github.com/CartoDB/Windshaft/releases/tag/0.37.5) -1.28.4 -- 2015-02-18 --------------------- +## 1.28.4 + +Released 2015-02-18 Announcements: - Upgrades windshaft to [0.37.4](https://github.com/CartoDB/Windshaft/releases/tag/0.37.4) -1.28.3 -- 2015-02-17 --------------------- +## 1.28.3 + +Released 2015-02-17 Announcements: - Upgrades windshaft to [0.37.3](https://github.com/CartoDB/Windshaft/releases/tag/0.37.3) -1.28.2 -- 2015-02-17 --------------------- +## 1.28.2 + +Released 2015-02-17 Announcements: - Upgrades windshaft to [0.37.2](https://github.com/CartoDB/Windshaft/releases/tag/0.37.2) -1.28.1 -- 2015-02-17 --------------------- +## 1.28.1 + +Released 2015-02-17 Announcements: - Upgrades windshaft to [0.37.1](https://github.com/CartoDB/Windshaft/releases/tag/0.37.1) -1.28.0 -- 2015-02-17 --------------------- +## 1.28.0 + +Released 2015-02-17 Announcements: - Upgrades windshaft to [0.37.0](https://github.com/CartoDB/Windshaft/releases/tag/0.37.0) @@ -62,8 +94,9 @@ New features: tables last update (#253) -1.27.0 -- 2015-02-16 --------------------- +## 1.27.0 + +Released 2015-02-16 Announcements: - Adds default image placeholder for http renderer to use as fallback @@ -76,29 +109,33 @@ Bugfixes: - Fixes tests with beforeEach and afterEach triggers -1.26.2 -- 2015-01-28 --------------------- +## 1.26.2 + +Released 2015-01-28 Bugfixes: - Accept 'open' string in templates' `auth` as authorized. -1.26.1 -- 2015-01-28 --------------------- +## 1.26.1 + +Released 2015-01-28 Announcements: - Upgrades windshaft to 0.35.1, see https://github.com/CartoDB/Windshaft/pull/254 -1.26.0 -- 2015-01-27 --------------------- +## 1.26.0 + +Released 2015-01-27 Announcements: - Upgrades windshaft to 0.35.0, supports mapconfig version `1.3.0` -1.25.0 -- 2015-01-26 --------------------- +## 1.25.0 + +Released 2015-01-26 Announcements: - No more signed maps (#227 and #238) @@ -134,22 +171,25 @@ New features: - SurrogateKeysCache is subscribed to TemplateMaps events to do the invalidations -1.24.0 -- 2015-01-15 --------------------- +## 1.24.0 + +Released 2015-01-15 Announcements: - Upgrades windshaft to 0.34.0 for retina support -1.23.1 -- 2015-01-14 --------------------- +## 1.23.1 + +Released 2015-01-14 Announcements: - Regenerate npm-shrinkwrap.json -1.23.0 -- 2015-01-14 --------------------- +## 1.23.0 + +Released 2015-01-14 Announcements: - Upgrades windshaft to 0.33.0 @@ -158,22 +198,25 @@ New features: - Sets HTTP renderer configuration in server_options -1.22.0 -- 2015-01-13 --------------------- +## 1.22.0 + +Released 2015-01-13 New features: - Health check endpoint -1.21.2 -- 2014-12-15 --------------------- +## 1.21.2 + +Released 2014-12-15 Announcements: - Upgrades windshaft to 0.32.4 -1.21.1 -- 2014-12-11 --------------------- +## 1.21.1 + +Released 2014-12-11 Announcements: - Upgrades windshaft to 0.32.2 @@ -183,29 +226,33 @@ Bugfixes: -1.21.0 -- 2014-10-24 --------------------- +## 1.21.0 + +Released 2014-10-24 New features: - Allow a different cache-control max-age for layergroup responses -1.20.2 -- 2014-10-20 --------------------- +## 1.20.2 + +Released 2014-10-20 Announcements: - Upgrades windshaft to 0.31.0 -1.20.1 -- 2014-10-17 --------------------- +## 1.20.1 + +Released 2014-10-17 Announcements: - Upgrades redis-mpool to 0.3.0 -1.20.0 -- 2014-10-15 --------------------- +## 1.20.0 + +Released 2014-10-15 New features: - Report to statsd the status of redis pools @@ -215,8 +262,9 @@ Enhancements: - Share one redis-mpool across the application -1.19.0 -- 2014-10-14 --------------------- +## 1.19.0 + +Released 2014-10-14 Announcements: - Dropping support for npm <1.2.1 @@ -225,8 +273,9 @@ Announcements: - Generates npm-shrinkwrap.json with npm >1.2.0 -1.18.2 -- 2014-10-13 --------------------- +## 1.18.2 + +Released 2014-10-13 Bug fixes: - Defaults resultSet to object if undefined in QueryTablesApi @@ -235,29 +284,33 @@ Announcements: - Upgrades windshaft to 0.28.1 -1.18.1 -- 2014-10-13 --------------------- +## 1.18.1 + +Released 2014-10-13 New features: - Allow to add more node.js' threadpool workers via process.env.UV_THREADPOOL_SIZE -1.18.0 -- 2014-10-03 --------------------- +## 1.18.0 + +Released 2014-10-03 Announcements: - Comes back to use mapnik 2.3.x based on cartodb/node-mapnik@1.4.15-cdb from windshaft@0.28.0 -1.17.2 -- 2014-10-01 --------------------- +## 1.17.2 + +Released 2014-10-01 Announcements: - Upgrades windshaft to 0.27.2 which downgrades node-mapnik to 0.7.26-cdb1 -1.17.1 -- 2014-09-30 --------------------- +## 1.17.1 + +Released 2014-09-30 Announcements: - Upgrades windshaft to 0.27.1 which downgrades node-mapnik to 1.4.10 @@ -267,8 +320,9 @@ Enhancements: - Upgrades mocha -1.17.0 -- 2014-09-25 --------------------- +## 1.17.0 + +Released 2014-09-25 New features: - Starts using mapnik 2.3.x @@ -279,14 +333,16 @@ Enhancements: - Metrics revamp: removes and adds some metrics - Adds poolSize configuration for mapnik -1.16.1 -- 2014-08-19 --------------------- +## 1.16.1 + +Released 2014-08-19 Enhancements: - Upgrades cartodb-redis -1.16.0 -- 2014-08-18 --------------------- +## 1.16.0 + +Released 2014-08-18 New features: - Configurable QueryTablesAPI to call directly postgresql using cartodb-psql @@ -302,8 +358,9 @@ Enhancements: - windshaft - request -1.15.0 -- 2014-08-13 --------------------- +## 1.15.0 + +Released 2014-08-13 Enhancements: - Upgrades dependencies: - redis-mpool @@ -313,8 +370,9 @@ Enhancements: - Slow pool configuration in example configurations -1.14.0 -- 2014-08-07 --------------------- +## 1.14.0 + +Released 2014-08-07 Enhancements: - SQL API requests moved to its own entity @@ -326,43 +384,49 @@ New features: dependencies for this matter -1.13.1 -- 2014-08-04 --------------------- +## 1.13.1 + +Released 2014-08-04 Enhancements: - Profiler header sent as JSON string -1.13.0 -- 2014-07-30 --------------------- +## 1.13.0 + +Released 2014-07-30 New features: - Support for postgresql schemas - Use public user from redis - Support for several auth tokens -1.12.1 -- 2014-06-24 --------------------- +## 1.12.1 + +Released 2014-06-24 Enhancements: - Caches layergroup and sets X-Cache-Channel in GET requests also in named maps -1.12.0 -- 2014-06-24 --------------------- +## 1.12.0 + +Released 2014-06-24 New features: - Caches layergroup and sets X-Cache-Channel in GET requests -1.11.1 -- 2014-05-07 --------------------- +## 1.11.1 + +Released 2014-05-07 Enhancements: - Upgrade Windshaft to 0.21.0, see http://github.com/CartoDB/Windshaft/blob/0.21.0/NEWS -1.11.0 -- 2014-04-28 --------------------- +## 1.11.0 + +Released 2014-04-28 New features: @@ -373,23 +437,26 @@ Enhancements: - Set default PostgreSQL application name to "cartodb_tiler" -1.10.2 -- 2014-04-08 --------------------- +## 1.10.2 + +Released 2014-04-08 Bug fixes: - Fix show_style tool broken since 1.8.1 - Fix X-Cache-Channel of tiles accessed via signed token (#188) -1.10.1 -- 2014-03-21 --------------------- +## 1.10.1 + +Released 2014-03-21 Bug fixes: - Do not cache non-success jsonp responses (#186) -1.10.0 -- 2014-03-20 --------------------- +## 1.10.0 + +Released 2014-03-20 New features: @@ -410,15 +477,17 @@ Other changes: - Switch to 3-clause BSD license (#184) -1.9.0 -- 2014-03-10 -------------------- +## 1.9.0 + +Released 2014-03-10 New features: - Allow to set server related configuration in serverMetadata (#182) -1.8.5 -- 2014-03-10 -------------------- +## 1.8.5 + +Released 2014-03-10 Enhancements: @@ -436,8 +505,9 @@ Bug fixes: - Do not cache map creation responses (#176) -1.8.4 -- 2014-03-03 -------------------- +## 1.8.4 + +Released 2014-03-03 Enhancements: @@ -454,8 +524,9 @@ Bug fixes: - Fix database connection settings on template instanciation (#174) -1.8.3 -- 2014-02-27 -------------------- +## 1.8.3 + +Released 2014-02-27 Enhancements: @@ -468,8 +539,9 @@ Enhancements: [ new sqlapi.timeout directive, defaults to 100 ms ] - Do not query CDB_TableMetadata for queries affected by no tables (#168) -1.8.2 -- 2014-02-25 -------------------- +## 1.8.2 + +Released 2014-02-25 Enhancements: @@ -483,8 +555,9 @@ Bug fixes: * Fix munin plugin after log format changes (#154) -1.8.1 -- 2014-02-19 -------------------- +## 1.8.1 + +Released 2014-02-19 Enhancements: @@ -494,8 +567,9 @@ Bug fixes: * Always generate X-Cache-Channel for token-based tile responses (#152) -1.8.0 -- 2014-02-18 -------------------- +## 1.8.0 + +Released 2014-02-18 Enhancements: @@ -510,16 +584,18 @@ Enhancements: * Allow limiting number of templates for each user (#136) * Allow configuring TTL of mapConfigs via "mapConfigTTL" -1.7.1 -- 2014-02-11 -------------------- +## 1.7.1 + +Released 2014-02-11 Enhancements: * Disable debug logging unless "debug" config param evaluates to true (#137) * Require windshaft 0.17.2 for further reducing log noise (#137) -1.7.0 -- 2014-02-11 -------------------- +## 1.7.0 + +Released 2014-02-11 New features: @@ -540,8 +616,9 @@ Bug fixes: * Allow passing numbers as values for numeric template variables (#130) -1.6.3 -- 2014-01-30 -------------------- +## 1.6.3 + +Released 2014-01-30 Bug fixes: @@ -556,8 +633,9 @@ Enhancements: * Stop processing XML on renderer creation, not needed anymore since 1.6.1 introduced on-demand XML generation. -1.6.2 -- 2014-01-23 -------------------- +## 1.6.2 + +Released 2014-01-23 Bug fixes: @@ -569,16 +647,18 @@ Enhancements: print XML style now it is not in redis anymore (#110) * Support CORS in template instanciation endpoint (#113) -1.6.1 -- 2014-01-15 -------------------- +## 1.6.1 + +Released 2014-01-15 Bug fixes: * Drop cache headers from error responses (#107) * Localize external CartoCSS resources at renderer creation time (#108) -1.6.0 -- 2014-01-10 -------------------- +## 1.6.0 + +Released 2014-01-10 New features: @@ -591,24 +671,27 @@ Other changes: * Update cartodb-redis dependency to "~0.3.0" * Update redis-server dependency to "2.4.0+" -1.5.2 -- 2013-12-05 -------------------- +## 1.5.2 + +Released 2013-12-05 Bug fixes: * Fix configuration-level compatibility with versions prior to 1.5 (#96) * Fix use of old layergroups on mapnik upgrade (#97) -1.5.1 -- 2013-11-28 -------------------- +## 1.5.1 + +Released 2013-11-28 Bug fixes: * Survive presence of malformed CartoCSS in redis (#94) * Accept useless point-transform:scale directives (#93) -1.5.0 -- 2013-11-19 -------------------- +## 1.5.0 + +Released 2013-11-19 NOTE: new configuration directives `postgres_auth_pass` and `postgres.password` added; see config/environments/*.example @@ -632,24 +715,28 @@ Other changes: * CartoDB redis interaction delegated to "cartodb-redis" module -1.4.1 -- 2013-11-08 -------------------- +## 1.4.1 + +Released 2013-11-08 * Fix support for exponential notation in CartoCSS filter values (#87) -1.4.0 -- 2013-10-31 -------------------- +## 1.4.0 + +Released 2013-10-31 * Add Support for Mapnik-2.2.0 (#78) -1.3.6 -- 2013-10-11 -------------------- +## 1.3.6 + +Released 2013-10-11 * Restore support for node-0.8.9 accidentally dropped by 1.3.5 NOTE: needs removing node_modules/windshaft and re-running npm install -1.3.5 -- 2013-10-03 -------------------- +## 1.3.5 + +Released 2013-10-03 * Fixing apostrophes in CartoCSS * Fix "sql/table must contain zoom variable" error when using @@ -658,8 +745,8 @@ Other changes: * Fix error for invalid text-name in CartoCSS (#81) * Do not let anonymous requests use authorized renderer caches -1.3.4 ------- +## 1.3.4 + NOTE: configuration sqlapi.host renamed to sqlapi.domain (support for "sqlapi.host" is retained for backward compatibility) @@ -668,38 +755,38 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain * Improve invalid mapnik-geometry-type CSS error message * Fix race condition in localization of network resources -1.3.3 ------- +## 1.3.3 + * Set Last-Modified header to allow for 304 responses * Add profiling support (needs useProfiler in env config file) * Fix double-checking for layergroups with no interactivity * Log full layergroup config at creation time (#76) -1.3.2 ------- +## 1.3.2 + * Set default layergroup TTL to 2 hours * Serve multilayer tiles and grid with persistent cache control -1.3.1 ------- +## 1.3.1 + * Fix deadlock on new style creation * Fix database authentication with multi-table layergroups * Add tile and grid fetching checks at layergroup creation time * Fix SQL error reporting to NOT split on newline * Fix support for CartoCSS attachments -1.3.0 ------- +## 1.3.0 + * Change stats format for multilayer map token request, see http://github.com/Vizzuality/Windshaft-cartodb/wiki/Redis-stats-format -1.2.1 ------- +## 1.2.1 + * Fix multilayer post from firefox * Fix multilayer cartocss layer name handling -1.2.0 ------- +## 1.2.0 + * Multilayer API changes * Layers passed by index in grid fetching url * Interactivity only specified in layergroup config @@ -707,14 +794,14 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain * Use ISO format for last_modified timestamp * Expected LZMA encoding changed to base64 -1.1.10 ------- +## 1.1.10 + * Fix regression with default interactivity parameter (#74) * More verbose logging for SQL api connection errors * Write stats for multilayer map token request -1.1.9 ------ +## 1.1.9 + * Handle SQL API errors by requesting no Varnish cache * Fix X-Cache-Channel for multilayer (by token) responses * Add last_modified field to layergroup creation response (#72) @@ -723,18 +810,22 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain * Add support for LZMA compressed GET parameters * Add support for creating layergroups via GET -1.1.8 ------ +## 1.1.8 + * Require Windshaft-0.9.1, to reduce harmfulness of cache_buster param -1.1.7 (DD//MM//YY) ------ +## 1.1.7 + +Released DD//MM//YY + * Do not let /etc/services confuse FD checker (munin plugin) * Multilayer support (#72) * Expose renderer settings in the environment config files -1.1.6 (19//02//13) ------ +## 1.1.6 + +Released 19//02//13 + * Require windshaft 0.8.5, fixing some stability issues and providing cache info on request * Require grainstore 0.10.9, fixing an issue with multi-geom markers @@ -743,20 +834,26 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain * Survive connection refusals from redis * Add maxConnection environment configuration, default to 128 -1.1.5 (DD//MM//YY) ------ +## 1.1.5 + +Released DD//MM//YY + * Fix bogus cached return of utf grid for fully contained tiles (#67) -1.1.4 (DD//MM//YY) ------ +## 1.1.4 + +Released DD//MM//YY + * Reduce default extent to allow for consistent proj4 round-tripping * Enhance reset_styles script to use full configuration (#62) * Have reset_styles script also drop extended keys (#58) * Fix example postgis parameter for simplifying input geoms (#63) * Add row_limit to example config (#64) -1.1.3 (30//11//12) ------ +## 1.1.3 + +Released 30//11//12 + * Fix reset_styles script to really skip extended keys * CartoCSS versioning * Mapnik-version dependent default styles @@ -764,16 +861,20 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain * styles with conditional markers * scale arrow markers by 50% -1.1.2 (DD//MM//YY) ------ +## 1.1.2 + +Released DD//MM//YY + * CartoCSS versioning * Fix use of "style_version" with GET (inline styles) * Enhance 2.0 -> 2.1 transforms: * styles with no semicolon * markers shift due to geometry clipping -1.1.1 (DD//MM//YY) ------ +## 1.1.1 + +Released DD//MM//YY + * Add support for persistent client cache headers * Fix crash on unknown user (#55) * Add /version entry point @@ -782,8 +883,10 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain * Support style_version and style_convert parameters in POST /style request * Support style_version in GET /:z/:x/:y request -1.1.0 (30/10/12) -======= +## 1.1.0 + +Released (30/10/12) + * Add /version entry point * CartoCSS versioning * Include version in GET /style response @@ -800,12 +903,16 @@ NOTE: configuration sqlapi.host renamed to sqlapi.domain * Replaced environment configs by .example ones * Fixed some issues with cluster2 -1.0.0 (03/10/12) ------ +## 1.0.0 + +Released 03/10/12 + * Migrated to node 0.8.x. -0.9.0 (25/09/12) ------ +## 0.9.0 + +Released 25/09/12 + * External resources in CartoCSS * Added X-Cache-Channel header in all the tiler GET requests * Small fixes diff --git a/app.js b/app.js index 61d3dfad..740d91c4 100755 --- a/app.js +++ b/app.js @@ -105,16 +105,6 @@ ws.on('listening', function() { + " (" + ENV + ")"); }); -// DEPRECATED, use SIGUSR2 -process.on('SIGUSR1', function() { - console.log('WARNING: handling of SIGUSR1 by Windshaft-CartoDB is deprecated, please send SIGUSR2 instead'); - ws.dumpCacheStats(); -}); - -process.on('SIGUSR2', function() { - ws.dumpCacheStats(); -}); - process.on('SIGHUP', function() { global.log4js.clearAndShutdownAppenders(function() { global.log4js.configure(log4js_config); diff --git a/config/environments/development.js.example b/config/environments/development.js.example index 4af948ae..d04ddf01 100644 --- a/config/environments/development.js.example +++ b/config/environments/development.js.example @@ -14,13 +14,11 @@ var config = { // Base url for the Templated Maps API // "/api/v1/map/named" is the new API, // "/tiles/template" is for compatibility with versions up to 1.6.x - ,base_url_templated: '(?:/api/v1/map/named|/tiles/template|/u/:user/api/v1/map/named|/u/:user/tiles/template)' + ,base_url_templated: '(?:/api/v1/map/named|/u/:user/api/v1/map/named)' // Base url for the Detached Maps API // "maps" is the the new API, // "tiles/layergroup" is for compatibility with versions up to 1.6.x - ,base_url_detached: '(?:/api/v1/map|/tiles/layergroup|/u/:user/api/v1/map|/u/:user/tiles/layergroup)' - // Base url for the Inline Maps and Table Maps API - ,base_url_legacy: '/tiles/:table' + ,base_url_detached: '(?:/api/v1/map|/u/:user/api/v1/map)' // Maximum number of connections for one process // 128 is a good value with a limit of 1024 open file descriptors @@ -133,26 +131,6 @@ var config = { statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd } } - ,sqlapi: { - protocol: 'http', - // If "host" is given, it will be used - // to connect to the SQL-API without a - // DNS lookup - host: '127.0.0.1', - port: 8080, - // The "domain" part will be appended to - // the cartodb username and passed to - // SQL-API requests in the Host HTTP header - domain: 'localhost.lan', - version: 'v1', - // Maximum lenght of SQL query for GET - // requests. Longer queries will be sent - // using POST. Defaults to 2048 - max_get_sql_length: 2048, - // Maximum time to wait for a response, - // in milliseconds. Defaults to 100. - timeout: 100 - } ,varnish: { host: 'localhost', port: 6082, // the por for the telnet interface where varnish is listening to diff --git a/config/environments/production.js.example b/config/environments/production.js.example index 505d454f..6b9cd40e 100644 --- a/config/environments/production.js.example +++ b/config/environments/production.js.example @@ -14,13 +14,11 @@ var config = { // Base url for the Templated Maps API // "/api/v1/map/named" is the new API, // "/tiles/template" is for compatibility with versions up to 1.6.x - ,base_url_templated: '(?:/api/v1/map/named|/tiles/template|/u/:user/api/v1/map/named|/u/:user/tiles/template)' + ,base_url_templated: '(?:/api/v1/map/named|/u/:user/api/v1/map/named)' // Base url for the Detached Maps API // "maps" is the the new API, // "tiles/layergroup" is for compatibility with versions up to 1.6.x - ,base_url_detached: '(?:/api/v1/map|/tiles/layergroup|/u/:user/api/v1/map|/u/:user/tiles/layergroup)' - // Base url for the Inline Maps and Table Maps API - ,base_url_legacy: '/tiles/:table' + ,base_url_detached: '(?:/api/v1/map|/u/:user/api/v1/map)' // Maximum number of connections for one process // 128 is a good value with a limit of 1024 open file descriptors @@ -127,26 +125,6 @@ var config = { statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd } } - ,sqlapi: { - protocol: 'https', - // If "host" is given, it will be used - // to connect to the SQL-API without a - // DNS lookup - //host: '127.0.0.1', - port: 8080, - // The "domain" part will be appended to - // the cartodb username and passed to - // SQL-API requests in the Host HTTP header - domain: 'cartodb.com', - version: 'v2', - // Maximum lenght of SQL query for GET - // requests. Longer queries will be sent - // using POST. Defaults to 2048 - max_get_sql_length: 2048, - // Maximum time to wait for a response, - // in milliseconds. Defaults to 100. - timeout: 100 - } ,varnish: { host: 'localhost', port: 6082, // the por for the telnet interface where varnish is listening to diff --git a/config/environments/staging.js.example b/config/environments/staging.js.example index 1f4a0409..aa3ccb4a 100644 --- a/config/environments/staging.js.example +++ b/config/environments/staging.js.example @@ -14,13 +14,11 @@ var config = { // Base url for the Templated Maps API // "/api/v1/maps/named" is the new API, // "/tiles/template" is for compatibility with versions up to 1.6.x - ,base_url_templated: '(?:/api/v1/map/named|/tiles/template|/u/:user/api/v1/map/named|/u/:user/tiles/template)' + ,base_url_templated: '(?:/api/v1/map/named|/u/:user/api/v1/map/named)' // Base url for the Detached Maps API // "/api/v1/maps" is the the new API, // "/tiles/layergroup" is for compatibility with versions up to 1.6.x - ,base_url_detached: '(?:/api/v1/map|/tiles/layergroup|/u/:user/api/v1/map|/u/:user/tiles/layergroup)' - // Base url for the Inline Maps and Table Maps API - ,base_url_legacy: '/tiles/:table' + ,base_url_detached: '(?:/api/v1/map|/u/:user/api/v1/map)' // Maximum number of connections for one process // 128 is a good value with a limit of 1024 open file descriptors @@ -127,26 +125,6 @@ var config = { statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd } } - ,sqlapi: { - protocol: 'https', - // If "host" is given, it will be used - // to connect to the SQL-API without a - // DNS lookup - //host: '127.0.0.1', - port: 8080, - // The "domain" part will be appended to - // the cartodb username and passed to - // SQL-API requests in the Host HTTP header - domain: 'cartodb.com', - version: 'v2', - // Maximum lenght of SQL query for GET - // requests. Longer queries will be sent - // using POST. Defaults to 2048 - max_get_sql_length: 2048, - // Maximum time to wait for a response, - // in milliseconds. Defaults to 100. - timeout: 100 - } ,varnish: { host: 'localhost', port: 6082, // the por for the telnet interface where varnish is listening to diff --git a/config/environments/test.js.example b/config/environments/test.js.example index f3af958b..9032752e 100644 --- a/config/environments/test.js.example +++ b/config/environments/test.js.example @@ -14,13 +14,11 @@ var config = { // Base url for the Templated Maps API // "/api/v1/map/named" is the new API, // "/tiles/template" is for compatibility with versions up to 1.6.x - ,base_url_templated: '(?:/api/v1/map/named|/tiles/template|/u/:user/api/v1/map/named|/u/:user/tiles/template)' + ,base_url_templated: '(?:/api/v1/map/named|/u/:user/api/v1/map/named)' // Base url for the Detached Maps API // "maps" is the the new API, // "tiles/layergroup" is for compatibility with versions up to 1.6.x - ,base_url_detached: '(?:/api/v1/map|/tiles/layergroup|/u/:user/api/v1/map|/u/:user/tiles/layergroup)' - // Base url for the Inline Maps and Table Maps API - ,base_url_legacy: '/tiles/:table' + ,base_url_detached: '(?:/api/v1/map|/u/:user/api/v1/map)' // Maximum number of connections for one process // 128 is a good value with a limit of 1024 open file descriptors @@ -129,28 +127,6 @@ var config = { statusInterval: 5000 // time, in ms, between each status report is emitted from the pool, status is sent to statsd } } - ,sqlapi: { - protocol: 'http', - // If "host" is given, it will be used - // to connect to the SQL-API without a - // DNS lookup - host: '127.0.0.1', - port: 1080, - // The "domain" part will be appended to - // the cartodb username and passed to - // SQL-API requests in the Host HTTP header - domain: 'donot_look_this_up', - // This port will be used by "make check" for testing purposes - // It must be available - version: 'v1', - // Maximum lenght of SQL query for GET - // requests. Longer queries will be sent - // using POST. Defaults to 2048 - max_get_sql_length: 2048, - // Maximum time to wait for a response, - // in milliseconds. Defaults to 100. - timeout: 100 - } ,varnish: { host: '', port: null, // the por for the telnet interface where varnish is listening to diff --git a/docs/metrics.md b/docs/metrics.md index ba356a48..0d641ac6 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -25,9 +25,7 @@ Again, each inner timer may have several inner timers. - **TemplateMaps_instance**: time to retrieve a map template instance, see *getTemplate* and *authorizedByCert* - **affectedTables**: time to check what are the affected tables for adding the cache channel, see *addCacheChannel* - **authorize**: time to authorize a request, see *authorizedByAPIKey*, *authorizedByCert*, *authorizedBySigner* -- **authorizedByAPIKey**: time to authorize using an API KEY - **authorizedByCert**: time to authorize a template instantiation -- **authorizedBySigner**: time to authorize a request with auth_token - **findLastUpdated**: time to retrieve the last update time for a list of tables, see *affectedTables* - **generateCacheChannel**: time to generate the headers for the cache channel based on the request, see *addCacheChannel* - **getSignerMapKey**: time to retrieve from redis the authorized user for a template map diff --git a/lib/cartodb/api/query_tables_api.js b/lib/cartodb/api/query_tables_api.js index 1db18489..63b87158 100644 --- a/lib/cartodb/api/query_tables_api.js +++ b/lib/cartodb/api/query_tables_api.js @@ -1,4 +1,3 @@ -var sqlApi = require('../sql/sql_api'); var PSQL = require('cartodb-psql'); var step = require('step'); @@ -74,56 +73,36 @@ function handleAffectedTablesAndLastUpdatedTimeRows(err, rows, callback) { QueryTablesApi.prototype.runQuery = function(username, query, queryHandler, callback) { var self = this; - if (shouldQueryPostgresDirectly()) { + var params = {}; - var params = {}; - - step( - function setAuth() { - self.pgConnection.setDBAuth(username, params, this); - }, - function setConn(err) { - if (err) { - throw err; - } - self.pgConnection.setDBConn(username, params, this); - }, - function executeQuery(err) { - if (err) { - throw err; - } - var psql = new PSQL({ - user: params.dbuser, - pass: params.dbpass, - host: params.dbhost, - port: params.dbport, - dbname: params.dbname - }); - psql.query(query, function(err, resultSet) { - resultSet = resultSet || {}; - var rows = resultSet.rows || []; - queryHandler(err, rows, callback); - }); + step( + function setAuth() { + self.pgConnection.setDBAuth(username, params, this); + }, + function setConn(err) { + if (err) { + throw err; } - ); - - } else { - - step( - function getApiKey() { - self.metadataBackend.getUserMapKey(username, this); - }, - function executeQuery(err, apiKey) { - if (err) { - throw err; - } - sqlApi.query(username, apiKey, query, function(err, rows) { - queryHandler(err, rows, callback); - }); + self.pgConnection.setDBConn(username, params, this); + }, + function executeQuery(err) { + if (err) { + throw err; } - ); - - } + var psql = new PSQL({ + user: params.dbuser, + pass: params.dbpass, + host: params.dbhost, + port: params.dbport, + dbname: params.dbname + }); + psql.query(query, function(err, resultSet) { + resultSet = resultSet || {}; + var rows = resultSet.rows || []; + queryHandler(err, rows, callback); + }); + } + ); }; @@ -135,10 +114,3 @@ function prepareSql(sql) { .replace(affectedTableRegexCache.pixel_height, '1') ; } - - -function shouldQueryPostgresDirectly() { - return global.environment && - global.environment.enabledFeatures && - global.environment.enabledFeatures.cdbQueryTablesFromPostgres; -} diff --git a/lib/cartodb/cache_validator.js b/lib/cartodb/cache_validator.js deleted file mode 100644 index 4b3f2181..00000000 --- a/lib/cartodb/cache_validator.js +++ /dev/null @@ -1,25 +0,0 @@ -var Varnish = require('node-varnish'); -var varnish_queue = null; - -function init(host, port, secret) { - varnish_queue = new Varnish.VarnishQueue(host, port, secret); - varnish_queue.on('error', function(e) { - console.log("[CACHE VALIDATOR ERROR] " + e); - }); -} - -function invalidate_db(dbname, table) { - var cmd = 'purge obj.http.X-Cache-Channel ~ "^' + dbname + - ':(.*'+ table +'.*)|(table)$"'; - try{ - varnish_queue.run_cmd(cmd, false); - } catch (e) { - console.log("[CACHE VALIDATOR ERROR] could not queue command " + - cmd + " -- " + e); - } -} - -module.exports = { - init: init, - invalidate_db: invalidate_db -}; diff --git a/lib/cartodb/cartodb_windshaft.js b/lib/cartodb/cartodb_windshaft.js index 28e5c40d..10830f50 100644 --- a/lib/cartodb/cartodb_windshaft.js +++ b/lib/cartodb/cartodb_windshaft.js @@ -1,7 +1,6 @@ var _ = require('underscore'); var step = require('step'); var Windshaft = require('windshaft'); -var Cache = require('./cache_validator'); var os = require('os'); var HealthCheck = require('./monitoring/health_check'); @@ -25,24 +24,6 @@ var CartodbWindshaft = function(serverOptions) { var templateMaps = serverOptions.templateMaps; - if(serverOptions.cache_enabled) { - console.log("cache invalidation enabled, varnish on ", serverOptions.varnish_host, ' ', - serverOptions.varnish_port); - Cache.init(serverOptions.varnish_host, serverOptions.varnish_port, serverOptions.varnish_secret); - serverOptions.afterStateChange = function(req, data, callback) { - Cache.invalidate_db(req.params.dbname, req.params.table); - callback(null, data); - }; - } - - serverOptions.beforeStateChange = function(req, callback) { - var err = null; - if ( ! req.params.hasOwnProperty('_authorizedByApiKey') ) { - err = new Error("map state cannot be changed by unauthenticated request!"); - } - callback(err, req); - }; - // This is for Templated maps // // "named" is the official, "template" is for backward compatibility up to 1.6.x @@ -172,56 +153,6 @@ var CartodbWindshaft = function(serverOptions) { * END Routing ******************************************************************************************************************/ - /** - * Helper to allow access to the layer to be used in the maps infowindow popup. - */ - ws.get(serverOptions.base_url + '/infowindow', function(req, res){ - ws.doCORS(res); - step( - function(){ - serverOptions.getInfowindow(req, this); - }, - function(err, data){ - if (err){ - ws.sendError(res, {error: err.message}, 500, 'GET INFOWINDOW', err); - //ws.sendResponse(res, [{error: err.message}, 500]); - } else { - ws.sendResponse(res, [{infowindow: data}, 200]); - } - } - ); - }); - - - /** - * Helper to allow access to metadata to be used in embedded maps. - */ - ws.get(serverOptions.base_url + '/map_metadata', function(req, res){ - ws.doCORS(res); - step( - function(){ - serverOptions.getMapMetadata(req, this); - }, - function(err, data){ - if (err){ - ws.sendError(res, {error: err.message}, 500, 'GET MAP_METADATA', err); - //ws.sendResponse(res, [err.message, 500]); - } else { - ws.sendResponse(res, [{map_metadata: data}, 200]); - } - } - ); - }); - - /** - * Helper API to allow per table tile cache (and sql cache) to be invalidated remotely. - * Keep endpoint for backwards compatibility - */ - ws.del(serverOptions.base_url + '/flush_cache', function(req, res){ - ws.doCORS(res); - ws.sendResponse(res, [{status: 'ok'}, 200]); - }); - var healthCheck = new HealthCheck(cartoData, Windshaft.tilelive); ws.get('/health', function(req, res) { var healthConfig = global.environment.health || {}; diff --git a/lib/cartodb/controllers/template_maps.js b/lib/cartodb/controllers/template_maps.js index 452f7225..e071d8a0 100644 --- a/lib/cartodb/controllers/template_maps.js +++ b/lib/cartodb/controllers/template_maps.js @@ -1,5 +1,6 @@ var step = require('step'); var _ = require('underscore'); +var CdbRequest = require('../models/cdb_request'); function TemplateMapsController(app, serverOptions, templateMaps, metadataBackend, templateBaseUrl, surrogateKeysCache, NamedMapsCacheEntry, pgConnection) { @@ -15,6 +16,7 @@ function TemplateMapsController(app, serverOptions, templateMaps, metadataBacken module.exports = TemplateMapsController; +var cdbRequest = new CdbRequest(); TemplateMapsController.prototype.register = function(app) { app.get(this.templateBaseUrl + '/:template_id/jsonp', this.jsonp.bind(this)); @@ -33,7 +35,7 @@ TemplateMapsController.prototype.create = function(req, res) { this.app.doCORS(res); - var cdbuser = self.serverOptions.userByReq(req); + var cdbuser = cdbRequest.userByReq(req); step( function checkPerms(){ @@ -80,7 +82,7 @@ TemplateMapsController.prototype.update = function(req, res) { this.app.doCORS(res); - var cdbuser = this.serverOptions.userByReq(req); + var cdbuser = cdbRequest.userByReq(req); var template; var tpl_id; step( @@ -140,7 +142,7 @@ TemplateMapsController.prototype.retrieve = function(req, res) { this.app.doCORS(res); - var cdbuser = this.serverOptions.userByReq(req); + var cdbuser = cdbRequest.userByReq(req); var tpl_id; step( function checkPerms(){ @@ -201,7 +203,7 @@ TemplateMapsController.prototype.destroy = function(req, res) { } this.app.doCORS(res); - var cdbuser = this.serverOptions.userByReq(req); + var cdbuser = cdbRequest.userByReq(req); var tpl_id; step( function checkPerms(){ @@ -254,7 +256,7 @@ TemplateMapsController.prototype.list = function(req, res) { } this.app.doCORS(res); - var cdbuser = this.serverOptions.userByReq(req); + var cdbuser = cdbRequest.userByReq(req); step( function checkPerms(){ @@ -353,7 +355,7 @@ TemplateMapsController.prototype.instantiateTemplate = function(req, res, templa var template; var layergroup; var fakereq; // used for call to createLayergroup - var cdbuser = self.serverOptions.userByReq(req); + var cdbuser = cdbRequest.userByReq(req); // Format of template_id: []@ var tpl_id = req.params.template_id.split('@'); if ( tpl_id.length > 1 ) { diff --git a/lib/cartodb/models/cdb_request.js b/lib/cartodb/models/cdb_request.js new file mode 100644 index 00000000..db5c93ea --- /dev/null +++ b/lib/cartodb/models/cdb_request.js @@ -0,0 +1,26 @@ +function CdbRequest() { + this.RE_USER_FROM_HOST = new RegExp(global.environment.user_from_host || + '^([^\\.]+)\\.' // would extract "strk" from "strk.cartodb.com" + ); +} + +module.exports = CdbRequest; + + +CdbRequest.prototype.userByReq = function(req) { + var host = req.headers.host; + if (req.params.user) { + return req.params.user; + } + var mat = host.match(this.RE_USER_FROM_HOST); + if ( ! mat ) { + console.error("Pattern '" + this.RE_USER_FROM_HOST + "' does not match hostname '" + host + "'"); + return; + } + // console.log("Matches: "); console.dir(mat); + if ( mat.length !== 2 ) { + console.error("Pattern '" + this.RE_USER_FROM_HOST + "' gave unexpected matches against '" + host + "': ", mat); + return; + } + return mat[1]; +}; diff --git a/lib/cartodb/server_options.js b/lib/cartodb/server_options.js index 5863f211..e7f8b855 100644 --- a/lib/cartodb/server_options.js +++ b/lib/cartodb/server_options.js @@ -2,37 +2,19 @@ var _ = require('underscore'); var step = require('step'); var QueryTablesApi = require('./api/query_tables_api'); var PgConnection = require('./backends/pg_connection'); -var crypto = require('crypto'); var LZMA = require('lzma').LZMA; var TemplateMaps = require('./template_maps.js'); var MapConfigNamedLayersAdapter = require('./models/mapconfig_named_layers_adapter'); - -// This is for backward compatibility with 1.3.3 -if ( _.isUndefined(global.environment.sqlapi.domain) ) { - // Only use "host" as "domain" if it contains alphanumeric characters - var host = global.environment.sqlapi.host; - if ( host && host.match(/[a-zA-Z]/) ) { - global.environment.sqlapi.domain = host; - } -} +var CdbRequest = require('./models/cdb_request'); +var assert = require('assert'); // Whitelist query parameters and attach format var REQUEST_QUERY_PARAMS_WHITELIST = [ - 'user', - 'sql', - 'geom_type', - 'cache_buster', - 'cache_policy', - 'callback', - 'interactivity', + 'config', 'map_key', 'api_key', 'auth_token', - 'style', - 'style_version', - 'style_convert', - 'config', - 'scale_factor' + 'callback' ]; module.exports = function(redisPool) { @@ -42,7 +24,8 @@ module.exports = function(redisPool) { var cartoData = require('cartodb-redis')({ pool: redisPool }), lzmaWorker = new LZMA(), pgConnection = new PgConnection(cartoData), - queryTablesApi = new QueryTablesApi(pgConnection, cartoData); + queryTablesApi = new QueryTablesApi(pgConnection, cartoData), + cdbRequest = new CdbRequest(); var rendererConfig = _.defaults(global.environment.renderer || {}, { cache_ttl: 60000, // milliseconds @@ -74,8 +57,7 @@ module.exports = function(redisPool) { cachedir: global.environment.millstone.cache_basedir, mapnik_version: global.environment.mapnik_version, mapnik_tile_format: global.environment.mapnik_tile_format || 'png', - default_layergroup_ttl: global.environment.mapConfigTTL || 7200, - gc_prob: 0.01 // @deprecated since Windshaft-1.8.0 + default_layergroup_ttl: global.environment.mapConfigTTL || 7200 }, mapnik: { poolSize: rendererConfig.poolSize, @@ -131,106 +113,81 @@ module.exports = function(redisPool) { return dbName + ':' + tableNames.join(','); }; - me.generateMD5 = function(data){ - var hash = crypto.createHash('md5'); - hash.update(data); - return hash.digest('hex'); - }; - me.generateCacheChannel = function(app, req, callback){ + // Build channelCache key + var dbName = req.params.dbname; + var cacheKey = [ dbName, req.params.token ].join(':'); - // Build channelCache key - var dbName = req.params.dbname; - var cacheKey = [ dbName ]; - if ( req.params.token ) cacheKey.push(req.params.token); - else if ( req.params.sql ) cacheKey.push( me.generateMD5(req.params.sql) ); - cacheKey = cacheKey.join(':'); - - var that = this; - - step ( - function checkCached() { - if ( me.channelCache.hasOwnProperty(cacheKey) ) { - callback(null, me.channelCache[cacheKey]); - return; - } - return null; - }, - function extractSQL(err) { - if ( err ) throw err; - - if ( req.params.token ) { - // TODO: cached cache channel for token-based access should - // be constructed at renderer cache creation time - // See http://github.com/CartoDB/Windshaft-cartodb/issues/152 - if ( ! app.mapStore ) { - throw new Error('missing channel cache for token ' + req.params.token); - } - var next = this; - var mapStore = app.mapStore; - step( - function loadFromStore() { - mapStore.load(req.params.token, this); - }, - function getSQL(err, mapConfig) { - if (req.profiler) req.profiler.done('mapStore_load'); - if ( err ) throw err; - var sql = []; - _.each(mapConfig.obj().layers, function(lyr) { - sql.push(lyr.options.sql); - }); - sql = sql.join(';'); - return sql; - }, - function finish(err, sql) { - next(err, sql); - } - ); - return; - } - - if ( ! req.params.sql ) { - return null; // no sql - } - - // We have sql, and no token... - - // strip out windshaft/mapnik inserted sql if present - var sql = req.params.sql.match(/^\((.*)\)\sas\scdbq$/); - sql = (sql !== null) ? sql[1] : req.params.sql; - - return sql; - }, - function findAffectedTables(err, sql) { - if ( err ) throw err; - if ( ! sql ) { - if ( ! req.params.table ) { - throw new Error("this request doesn't need an X-Cache-Channel generated"); - } - return [req.params.table]; - } - - queryTablesApi.getAffectedTablesInQuery(that.userByReq(req), sql, this); // in addCacheChannel - }, - function buildCacheChannel(err, tableNames) { - if ( err ) throw err; - if (req.profiler && ! req.params.table ) { - req.profiler.done('affectedTables'); - } - - var dbName = req.params.dbname; - var cacheChannel = me.buildCacheChannel(dbName,tableNames); - // store for caching from me.generateCacheChannel - // (not worth when table was specified in params) - if ( ! req.params.table ) { - me.channelCache[cacheKey] = cacheChannel; - } - return cacheChannel; - }, - function finish(err, cacheChannel) { - callback(err, cacheChannel); + // no token means no tables associated + if (!req.params.token) { + return callback(null, this.buildCacheChannel(dbName, [])); } - ); + + step( + function checkCached() { + if ( me.channelCache.hasOwnProperty(cacheKey) ) { + return callback(null, me.channelCache[cacheKey]); + } + return null; + }, + function extractSQL(err) { + assert.ifError(err); + + // TODO: cached cache channel for token-based access should + // be constructed at renderer cache creation time + // See http://github.com/CartoDB/Windshaft-cartodb/issues/152 + if ( ! app.mapStore ) { + throw new Error('missing channel cache for token ' + req.params.token); + } + var mapStore = app.mapStore; + step( + function loadFromStore() { + mapStore.load(req.params.token, this); + }, + function getSQL(err, mapConfig) { + if (req.profiler) { + req.profiler.done('mapStore_load'); + } + assert.ifError(err); + + var queries = mapConfig.getLayers() + .map(function(lyr) { + return lyr.options.sql; + }) + .filter(function(sql) { + return !!sql; + }); + + return queries.length ? queries.join(';') : null; + }, + this + ); + }, + function findAffectedTables(err, sql) { + assert.ifError(err); + + if ( ! sql ) { + throw new Error("this request doesn't need an X-Cache-Channel generated"); + } + + queryTablesApi.getAffectedTablesInQuery(cdbRequest.userByReq(req), sql, this); // in addCacheChannel + }, + function buildCacheChannel(err, tableNames) { + assert.ifError(err); + + if (req.profiler) { + req.profiler.done('affectedTables'); + } + + var cacheChannel = me.buildCacheChannel(dbName,tableNames); + me.channelCache[cacheKey] = cacheChannel; + + return cacheChannel; + }, + function finish(err, cacheChannel) { + callback(err, cacheChannel); + } + ); }; // Set the cache chanel info to invalidate the cache on the frontend server @@ -245,11 +202,11 @@ module.exports = function(redisPool) { me.addCacheChannel = function(app, req, cb) { // skip non-GET requests, or requests for which there's no response if ( req.method != 'GET' || ! req.res ) { cb(null, null); return; } - if (req.profiler) req.profiler.start('addCacheChannel'); + if (req.profiler) { + req.profiler.start('addCacheChannel'); + } var res = req.res; - var cache_policy = req.query.cache_policy; - if ( req.params.token ) cache_policy = 'persist'; - if ( cache_policy == 'persist' ) { + if ( req.params.token ) { res.header('Cache-Control', 'public,max-age=31536000'); // 1 year } else { var ttl = global.environment.varnish.ttl || 86400; @@ -268,8 +225,10 @@ module.exports = function(redisPool) { res.header('Last-Modified', lastUpdated.toUTCString()); me.generateCacheChannel(app, req, function(err, channel){ - if (req.profiler) req.profiler.done('generateCacheChannel'); - if (req.profiler) req.profiler.end(); + if (req.profiler) { + req.profiler.done('generateCacheChannel'); + req.profiler.end(); + } if ( ! err ) { res.header('X-Cache-Channel', channel); cb(null, channel); @@ -282,7 +241,7 @@ module.exports = function(redisPool) { }; me.beforeLayergroupCreate = function(req, requestMapConfig, callback) { - mapConfigNamedLayersAdapter.getLayers(this.userByReq(req), requestMapConfig.layers, pgConnection, + mapConfigNamedLayersAdapter.getLayers(cdbRequest.userByReq(req), requestMapConfig.layers, pgConnection, function(err, layers, datasource) { if (err) { return callback(err); @@ -297,7 +256,7 @@ module.exports = function(redisPool) { me.afterLayergroupCreate = function(req, mapconfig, response, callback) { var token = response.layergroupid; - var username = this.userByReq(req); + var username = cdbRequest.userByReq(req); var tasksleft = 2; // redis key and affectedTables var errors = []; @@ -324,8 +283,12 @@ module.exports = function(redisPool) { // take place before proceeding. Error will be logged // asyncronously cartoData.incMapviewCount(username, mapconfig.stat_tag, function(err) { - if (req.profiler) req.profiler.done('incMapviewCount'); - if ( err ) console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err); + if (req.profiler) { + req.profiler.done('incMapviewCount'); + } + if ( err ) { + console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err); + } done(); }); @@ -341,19 +304,17 @@ module.exports = function(redisPool) { queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this); }, function handleAffectedTablesAndLastUpdatedTime(err, result) { - if (req.profiler) req.profiler.done('queryTablesAndLastUpdated'); - if ( err ) throw err; + if (req.profiler) { + req.profiler.done('queryTablesAndLastUpdated'); + } + assert.ifError(err); var cacheChannel = me.buildCacheChannel(dbName, result.affectedTables); me.channelCache[cacheKey] = cacheChannel; if (req.res && req.method == 'GET') { var res = req.res; - if ( req.query && req.query.cache_policy == 'persist' ) { - res.header('Cache-Control', 'public,max-age=31536000'); // 1 year - } else { - var ttl = global.environment.varnish.layergroupTtl || 86400; - res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate'); - } + var ttl = global.environment.varnish.layergroupTtl || 86400; + res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate'); res.header('Last-Modified', (new Date()).toUTCString()); res.header('X-Cache-Channel', cacheChannel); } @@ -371,30 +332,6 @@ module.exports = function(redisPool) { /* X-Cache-Channel generation } */ - me.re_userFromHost = new RegExp( - global.environment.user_from_host || - '^([^\\.]+)\\.' // would extract "strk" from "strk.cartodb.com" - ); - - me.userByReq = function(req) { - var host = req.headers.host; - if (req.params.user) { - return req.params.user; - } - var mat = host.match(this.re_userFromHost); - if ( ! mat ) { - console.error("ERROR: user pattern '" + this.re_userFromHost + "' does not match hostname '" + host + "'"); - return; - } - // console.log("Matches: "); console.dir(mat); - if ( mat.length !== 2 ) { - console.error("ERROR: pattern '" + this.re_userFromHost + "' gave unexpected matches against '" + host + "': " + - mat); - return; - } - return mat[1]; - }; - // Check if a request is authorized by a signer // // @param req express request object @@ -402,34 +339,27 @@ module.exports = function(redisPool) { // null if the request is not signed by anyone // or will be a string cartodb username otherwise. // - me.authorizedBySigner = function(req, callback) - { - if ( ! req.params.token || ! req.params.signer ) { - //console.log("No signature provided"); // debugging - callback(null, null); // no signer requested - return; - } - - var signer = req.params.signer; - var layergroup_id = req.params.token; - var auth_token = req.params.auth_token; - - //console.log("Checking authorization from signer " + signer + " for resource " + layergroup_id + - // " with auth_token " + auth_token); - var mapStore = req.app.mapStore; - if (!mapStore) { - throw new Error('Unable to retrieve map configuration token'); - } - - mapStore.load(layergroup_id, function(err, mapConfig) { - if (err) { - throw err; + me.authorizedBySigner = function(req, callback) { + if ( ! req.params.token || ! req.params.signer ) { + return callback(null, null); // no signer requested } - var authorized = me.templateMaps.isAuthorized(mapConfig.obj().template, auth_token); - callback(null, authorized ? signer : null); - }); + var signer = req.params.signer; + var layergroup_id = req.params.token; + var auth_token = req.params.auth_token; + var mapStore = req.app.mapStore; + if (!mapStore) { + throw new Error('Unable to retrieve map configuration token'); + } + + mapStore.load(layergroup_id, function(err, mapConfig) { + assert.ifError(err); + + var authorized = me.templateMaps.isAuthorized(mapConfig.obj().template, auth_token); + + return callback(null, authorized ? signer : null); + }); }; // Check if a request is authorized by api_key @@ -450,13 +380,13 @@ module.exports = function(redisPool) { return; } //console.log("given ApiKey: " + givenKey); - var user = me.userByReq(req); + var user = cdbRequest.userByReq(req); step( function (){ cartoData.getUserMapKey(user, this); }, function checkApiKey(err, val){ - if (err) throw err; + assert.ifError(err); return ( val && givenKey == val ) ? 1 : 0; }, function finish(err, authorized) { @@ -473,15 +403,17 @@ module.exports = function(redisPool) { */ me.authorize = function(req, callback) { var that = this; - var user = me.userByReq(req); + var user = cdbRequest.userByReq(req); step( function (){ that.authorizedByAPIKey(req, this); }, function checkApiKey(err, authorized){ - if (req.profiler) req.profiler.done('authorizedByAPIKey'); - if (err) throw err; + if (req.profiler) { + req.profiler.done('authorizedByAPIKey'); + } + assert.ifError(err); // if not authorized by api_key, continue if (authorized !== 1) { @@ -491,28 +423,16 @@ module.exports = function(redisPool) { return; } - _.extend(req.params, { _authorizedByApiKey: true }); - // authorized by api key, login as the given username and stop pgConnection.setDBAuth(user, req.params, function(err) { callback(err, true); // authorized (or error) }); }, function checkSignAuthorized(err, signed_by){ - if (err) throw err; - if (req.profiler) { - if ( req.params._authorizedByApiKey ) { - req.profiler.done('setDBAuth'); - } else { - req.profiler.done('authorizedBySigner'); - } - } + assert.ifError(err); if ( ! signed_by ) { // request not authorized by signer. - - // if table was given, continue to check table privacy - if ( req.params.table ) return null; // if no signer name was given, let dbparams and // PostgreSQL do the rest. @@ -527,26 +447,12 @@ module.exports = function(redisPool) { return; } - // Authorized by "signed_by" ! - _.extend(req.params, { _authorizedBySigner: signed_by }); pgConnection.setDBAuth(signed_by, req.params, function(err) { - if (req.profiler) req.profiler.done('setDBAuth'); + if (req.profiler) { + req.profiler.done('setDBAuth'); + } callback(err, true); // authorized (or error) }); - }, - function getDatabase(err){ - if (err) throw err; - // NOTE: only used to get to table privacy - cartoData.getUserDBName(user, this); - }, - function getPrivacy(err, dbname){ - if (err) throw err; - if (req.profiler) req.profiler.done('tablePrivacy_getUserDBName'); - cartoData.getTablePrivacy(dbname, req.params.table, this); - }, - function(err, privacy){ - if (req.profiler) req.profiler.done('getTablePrivacy'); - callback(err, privacy !== "0"); } ); }; @@ -562,9 +468,6 @@ module.exports = function(redisPool) { if ( req.query.lzma ) { - // TODO: check ? - //console.log("type of req.query.lzma is " + typeof(req.query.lzma)); - // Decode (from base64) var lzma = new Buffer(req.query.lzma, 'base64') .toString('binary') @@ -577,7 +480,9 @@ module.exports = function(redisPool) { lzmaWorker.decompress( lzma, function(result) { - if (req.profiler) req.profiler.done('LZMA decompress'); + if (req.profiler) { + req.profiler.done('lzma'); + } try { delete req.query.lzma; _.extend(req.query, JSON.parse(result)); @@ -585,30 +490,29 @@ module.exports = function(redisPool) { } catch (err) { callback(new Error('Error parsing lzma as JSON: ' + err)); } - }, - function(/*percent*/) { // progress - //console.log("LZMA decompression " + percent + "%"); } ); return; } - var bad_query = _.difference(_.keys(req.query), REQUEST_QUERY_PARAMS_WHITELIST); + req.query = _.pick(req.query, REQUEST_QUERY_PARAMS_WHITELIST); + req.params = _.extend({}, req.params); // shuffle things as request is a strange array/object - _.each(bad_query, function(key){ delete req.query[key]; }); - req.params = _.extend({}, req.params); // shuffle things as request is a strange array/object - - var user = me.userByReq(req); + var user = cdbRequest.userByReq(req); if ( req.params.token ) { //console.log("Request parameters include token " + req.params.token); var tksplit = req.params.token.split(':'); req.params.token = tksplit[0]; - if ( tksplit.length > 1 ) req.params.cache_buster= tksplit[1]; + if ( tksplit.length > 1 ) { + req.params.cache_buster= tksplit[1]; + } tksplit = req.params.token.split('@'); if ( tksplit.length > 1 ) { req.params.signer = tksplit.shift(); - if ( ! req.params.signer ) req.params.signer = user; + if ( ! req.params.signer ) { + req.params.signer = user; + } else if ( req.params.signer !== user ) { var err = new Error('Cannot use map signature of user "' + req.params.signer + '" on database of user "' + user + '"'); @@ -627,18 +531,19 @@ module.exports = function(redisPool) { // bring all query values onto req.params object _.extend(req.params, req.query); - // for cartodb, ensure interactivity is cartodb_id or user specified - req.params.interactivity = req.params.interactivity || 'cartodb_id'; - - if (req.profiler) req.profiler.done('req2params.setup'); + if (req.profiler) { + req.profiler.done('req2params.setup'); + } step( function getPrivacy(){ me.authorize(req, this); }, function gatekeep(err, authorized){ - if (req.profiler) req.profiler.done('authorize'); - if(err) throw err; + if (req.profiler) { + req.profiler.done('authorize'); + } + assert.ifError(err); if(!authorized) { err = new Error("Sorry, you are unauthorized (permission denied)"); err.http_status = 403; @@ -647,21 +552,12 @@ module.exports = function(redisPool) { return null; }, function getDatabase(err){ - if(err) throw err; + assert.ifError(err); pgConnection.setDBConn(user, req.params, this); }, - function getGeometryType(err){ - if (req.profiler) req.profiler.done('setDBConn'); - if (err) throw err; - if ( ! req.params.table ) return null; - cartoData.getTableGeometryType(req.params.dbname, req.params.table, this); - }, - function finishSetup(err, data){ + function finishSetup(err) { if ( err ) { callback(err, req); return; } - if (!_.isNull(data)) - _.extend(req.params, {geom_type: data}); - // Add default database connection parameters // if none given _.defaults(req.params, { @@ -676,61 +572,5 @@ module.exports = function(redisPool) { ); }; - /** - * Little helper method to get the current list of infowindow variables and return to client - * @param req - * @param callback - */ - me.getInfowindow = function(req, callback){ - var that = this; - var user = me.userByReq(req); - - step( - function(){ - // TODO: if this step really needed ? - that.req2params(req, this); - }, - function getDatabase(err){ - if (err) throw err; - cartoData.getUserDBName(user, this); - }, - function getInfowindow(err, dbname){ - if (err) throw err; - cartoData.getTableInfowindow(dbname, req.params.table, this); - }, - function(err, data){ - callback(err, data); - } - ); - }; - - /** - * Little helper method to get map metadata and return to client - * @param req - * @param callback - */ - me.getMapMetadata = function(req, callback){ - var that = this; - var user = me.userByReq(req); - - step( - function(){ - // TODO: if this step really needed ? - that.req2params(req, this); - }, - function getDatabase(err){ - if (err) throw err; - cartoData.getUserDBName(user, this); - }, - function getMapMetadata(err, dbname){ - if (err) throw err; - cartoData.getTableMapMetadata(dbname, req.params.table, this); - }, - function(err, data){ - callback(err, data); - } - ); - }; - return me; }; diff --git a/lib/cartodb/sql/sql_api.js b/lib/cartodb/sql/sql_api.js deleted file mode 100644 index 32885e57..00000000 --- a/lib/cartodb/sql/sql_api.js +++ /dev/null @@ -1,66 +0,0 @@ -var _ = require('underscore'), - request = require('request'); - -module.exports.query = function (username, api_key, sql, callback) { - var api = global.environment.sqlapi; - - // build up api string - var sqlapihostname = username; - if ( api.domain ) sqlapihostname += '.' + api.domain; - - var sqlapi = api.protocol + '://'; - if ( api.host && api.host != api.domain ) sqlapi += api.host; - else sqlapi += sqlapihostname; - sqlapi += ':' + api.port + '/api/' + api.version + '/sql'; - - var qs = { q: sql }; - - // add api_key if given - if (_.isString(api_key) && api_key !== '') { qs.api_key = api_key; } - - // call sql api - // - // NOTE: using POST to avoid size limits: - // See http://github.com/CartoDB/Windshaft-cartodb/issues/111 - // - // NOTE: uses "host" header to allow IP based specification - // of sqlapi address (and avoid a DNS lookup) - // - // NOTE: allows for keeping up to "maxConnections" concurrent - // sockets opened per SQL-API host. - // See http://nodejs.org/api/http.html#http_agent_maxsockets - // - var maxSockets = global.environment.maxConnections || 128; - var maxGetLen = api.max_get_sql_length || 2048; - var maxSQLTime = api.timeout || 100; // 1/10 of a second by default - var reqSpec = { - url:sqlapi, - json:true, - headers:{host: sqlapihostname}, - // http://nodejs.org/api/http.html#http_agent_maxsockets - pool:{maxSockets:maxSockets}, - // timeout in milliseconds - timeout:maxSQLTime - }; - if ( sql.length > maxGetLen ) { - reqSpec.method = 'POST'; - reqSpec.body = qs; - } else { - reqSpec.method = 'GET'; - reqSpec.qs = qs; - } - request(reqSpec, function(err, res, body) { - if (err){ - console.log('ERROR connecting to SQL API on ' + sqlapi + ': ' + err); - callback(err); - return; - } - if (res.statusCode != 200) { - var msg = res.body.error ? res.body.error : res.body; - callback(new Error(msg)); - console.log('unexpected response status (' + res.statusCode + ') for sql query: ' + sql + ': ' + msg); - return; - } - callback(null, body.rows); - }); -}; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 4f437dd6..e9d23060 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "windshaft-cartodb", - "version": "1.30.1", + "version": "2.0.0", "dependencies": { "cartodb-psql": { "version": "0.4.0", @@ -14,11 +14,13 @@ "dependencies": { "generic-pool": { "version": "2.0.3", - "from": "generic-pool@2.0.3" + "from": "generic-pool@2.0.3", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.0.3.tgz" }, "buffer-writer": { "version": "1.0.0", - "from": "buffer-writer@1.0.0" + "from": "buffer-writer@1.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.0.tgz" } } } @@ -36,19 +38,23 @@ "dependencies": { "generic-pool": { "version": "2.1.1", - "from": "generic-pool@~2.1.1" + "from": "generic-pool@~2.1.1", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.1.1.tgz" }, "redis": { "version": "0.12.1", - "from": "redis@~0.12.1" + "from": "redis@~0.12.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz" }, "hiredis": { "version": "0.1.17", "from": "hiredis@~0.1.17", + "resolved": "https://registry.npmjs.org/hiredis/-/hiredis-0.1.17.tgz", "dependencies": { "bindings": { "version": "1.2.1", - "from": "bindings@*" + "from": "bindings@*", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz" }, "nan": { "version": "1.1.2", @@ -83,7 +89,8 @@ "dependencies": { "core-util-is": { "version": "1.0.1", - "from": "core-util-is@~1.0.0" + "from": "core-util-is@~1.0.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" }, "isarray": { "version": "0.0.1", @@ -92,11 +99,13 @@ }, "string_decoder": { "version": "0.10.31", - "from": "string_decoder@~0.10.x" + "from": "string_decoder@~0.10.x", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" }, "inherits": { "version": "2.0.1", - "from": "inherits@2" + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } } @@ -107,11 +116,6 @@ "from": "lzma@~1.3.7", "resolved": "https://registry.npmjs.org/lzma/-/lzma-1.3.7.tgz" }, - "node-varnish": { - "version": "0.3.0", - "from": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0", - "resolved": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0" - }, "queue-async": { "version": "1.0.7", "from": "queue-async@~1.0.7", @@ -124,19 +128,23 @@ "dependencies": { "generic-pool": { "version": "2.1.1", - "from": "generic-pool@~2.1.1" + "from": "generic-pool@~2.1.1", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.1.1.tgz" }, "redis": { "version": "0.12.1", - "from": "redis@~0.12.1" + "from": "redis@~0.12.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz" }, "hiredis": { "version": "0.1.17", "from": "hiredis@~0.1.17", + "resolved": "https://registry.npmjs.org/hiredis/-/hiredis-0.1.17.tgz", "dependencies": { "bindings": { "version": "1.2.1", - "from": "bindings@*" + "from": "bindings@*", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz" }, "nan": { "version": "1.1.2", @@ -164,17 +172,20 @@ }, "lru-cache": { "version": "2.2.4", - "from": "lru-cache@~2.2.1" + "from": "lru-cache@~2.2.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz" }, "json-stringify-safe": { "version": "5.0.0", - "from": "json-stringify-safe@~5.0.0" + "from": "json-stringify-safe@~5.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz" } } }, "step": { "version": "0.0.5", - "from": "step@~0.0.5" + "from": "step@~0.0.5", + "resolved": "https://registry.npmjs.org/step/-/step-0.0.5.tgz" }, "underscore": { "version": "1.6.0", @@ -182,8 +193,9 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz" }, "windshaft": { - "version": "0.40.0", - "from": "windshaft@~0.40.0", + "version": "0.41.0", + "from": "https://github.com/CartoDB/Windshaft/tarball/master", + "resolved": "https://github.com/CartoDB/Windshaft/tarball/master", "dependencies": { "chronograph": { "version": "0.1.0", @@ -193,6 +205,7 @@ "grainstore": { "version": "0.23.0", "from": "grainstore@~0.23.0", + "resolved": "https://registry.npmjs.org/grainstore/-/grainstore-0.23.0.tgz", "dependencies": { "redis-mpool": { "version": "0.1.0", @@ -201,19 +214,23 @@ "dependencies": { "generic-pool": { "version": "2.1.1", - "from": "generic-pool@~2.1.1" + "from": "generic-pool@~2.1.1", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.1.1.tgz" }, "redis": { "version": "0.12.1", - "from": "redis@~0.12.1" + "from": "redis@~0.12.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz" }, "hiredis": { "version": "0.1.17", "from": "hiredis@~0.1.17", + "resolved": "https://registry.npmjs.org/hiredis/-/hiredis-0.1.17.tgz", "dependencies": { "bindings": { "version": "1.2.1", - "from": "bindings@*" + "from": "bindings@*", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz" }, "nan": { "version": "1.1.2", @@ -246,7 +263,8 @@ "dependencies": { "sax": { "version": "0.5.8", - "from": "sax@0.5.x" + "from": "sax@0.5.x", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz" } } }, @@ -262,7 +280,8 @@ }, "minimist": { "version": "0.0.10", - "from": "minimist@~0.0.1" + "from": "minimist@~0.0.1", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" } } } @@ -275,7 +294,8 @@ "dependencies": { "generic-pool": { "version": "2.0.4", - "from": "generic-pool@~2.0.3" + "from": "generic-pool@~2.0.3", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.0.4.tgz" }, "request": { "version": "2.34.0", @@ -284,15 +304,18 @@ "dependencies": { "qs": { "version": "0.6.6", - "from": "qs@~0.6.0" + "from": "qs@~0.6.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz" }, "json-stringify-safe": { "version": "5.0.0", - "from": "json-stringify-safe@~5.0.0" + "from": "json-stringify-safe@~5.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz" }, "forever-agent": { "version": "0.5.2", - "from": "forever-agent@~0.5.0" + "from": "forever-agent@~0.5.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz" }, "node-uuid": { "version": "1.4.3", @@ -302,6 +325,7 @@ "tough-cookie": { "version": "0.12.1", "from": "tough-cookie@>=0.12.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-0.12.1.tgz", "dependencies": { "punycode": { "version": "1.3.2", @@ -313,6 +337,7 @@ "form-data": { "version": "0.1.4", "from": "form-data@~0.1.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", "dependencies": { "combined-stream": { "version": "0.0.7", @@ -335,7 +360,8 @@ }, "tunnel-agent": { "version": "0.3.0", - "from": "tunnel-agent@~0.3.0" + "from": "tunnel-agent@~0.3.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.3.0.tgz" }, "http-signature": { "version": "0.10.1", @@ -361,7 +387,8 @@ }, "oauth-sign": { "version": "0.3.0", - "from": "oauth-sign@~0.3.0" + "from": "oauth-sign@~0.3.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz" }, "hawk": { "version": "1.0.0", @@ -380,17 +407,20 @@ }, "cryptiles": { "version": "0.2.2", - "from": "cryptiles@0.2.x" + "from": "cryptiles@0.2.x", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz" }, "sntp": { "version": "0.2.4", - "from": "sntp@0.2.x" + "from": "sntp@0.2.x", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz" } } }, "aws-sign2": { "version": "0.5.0", - "from": "aws-sign2@~0.5.0" + "from": "aws-sign2@~0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz" } } }, @@ -1631,7 +1661,8 @@ }, "mkdirp": { "version": "0.3.5", - "from": "mkdirp@~0.3.3" + "from": "mkdirp@~0.3.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" }, "optimist": { "version": "0.6.1", @@ -1645,7 +1676,8 @@ }, "minimist": { "version": "0.0.10", - "from": "minimist@~0.0.1" + "from": "minimist@~0.0.1", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" } } } @@ -1704,7 +1736,8 @@ }, "minimist": { "version": "0.0.10", - "from": "minimist@~0.0.1" + "from": "minimist@~0.0.1", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" } } } @@ -1717,7 +1750,8 @@ "dependencies": { "generic-pool": { "version": "2.1.1", - "from": "generic-pool@~2.1.1" + "from": "generic-pool@~2.1.1", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.1.1.tgz" }, "mime": { "version": "1.2.11", @@ -1733,7 +1767,8 @@ "dependencies": { "nan": { "version": "1.2.0", - "from": "nan@~1.2.0" + "from": "nan@~1.2.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-1.2.0.tgz" }, "mapnik-vector-tile": { "version": "0.5.5", @@ -2144,7 +2179,8 @@ }, "minimist": { "version": "0.0.10", - "from": "minimist@~0.0.1" + "from": "minimist@~0.0.1", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" } } } @@ -2152,11 +2188,13 @@ }, "step-profiler": { "version": "0.2.1", - "from": "step-profiler@~0.2.1" + "from": "step-profiler@~0.2.1", + "resolved": "https://registry.npmjs.org/step-profiler/-/step-profiler-0.2.1.tgz" }, "torque.js": { "version": "2.11.0", - "from": "torque.js@~2.11.0" + "from": "torque.js@~2.11.0", + "resolved": "https://registry.npmjs.org/torque.js/-/torque.js-2.11.0.tgz" }, "request": { "version": "2.48.0", @@ -2175,7 +2213,8 @@ "dependencies": { "core-util-is": { "version": "1.0.1", - "from": "core-util-is@~1.0.0" + "from": "core-util-is@~1.0.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" }, "isarray": { "version": "0.0.1", @@ -2184,11 +2223,13 @@ }, "string_decoder": { "version": "0.10.31", - "from": "string_decoder@~0.10.x" + "from": "string_decoder@~0.10.x", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" }, "inherits": { "version": "2.0.1", - "from": "inherits@~2.0.1" + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } } @@ -2201,11 +2242,13 @@ }, "forever-agent": { "version": "0.5.2", - "from": "forever-agent@~0.5.0" + "from": "forever-agent@~0.5.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.5.2.tgz" }, "form-data": { "version": "0.1.4", "from": "form-data@~0.1.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", "dependencies": { "mime": { "version": "1.2.11", @@ -2221,7 +2264,8 @@ }, "json-stringify-safe": { "version": "5.0.0", - "from": "json-stringify-safe@~5.0.0" + "from": "json-stringify-safe@~5.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz" }, "mime-types": { "version": "1.0.2", @@ -2240,11 +2284,13 @@ }, "tunnel-agent": { "version": "0.4.0", - "from": "tunnel-agent@~0.4.0" + "from": "tunnel-agent@~0.4.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.0.tgz" }, "tough-cookie": { "version": "0.12.1", "from": "tough-cookie@>=0.12.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-0.12.1.tgz", "dependencies": { "punycode": { "version": "1.3.2", @@ -2297,21 +2343,25 @@ }, "cryptiles": { "version": "0.2.2", - "from": "cryptiles@0.2.x" + "from": "cryptiles@0.2.x", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz" }, "sntp": { "version": "0.2.4", - "from": "sntp@0.2.x" + "from": "sntp@0.2.x", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz" } } }, "aws-sign2": { "version": "0.5.0", - "from": "aws-sign2@~0.5.0" + "from": "aws-sign2@~0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz" }, "stringstream": { "version": "0.0.4", - "from": "stringstream@~0.0.4" + "from": "stringstream@~0.0.4", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.4.tgz" }, "combined-stream": { "version": "0.0.7", @@ -2329,8 +2379,8 @@ }, "abaculus": { "version": "1.1.0", - "from": "https://github.com/CartoDB/abaculus/tarball/cdb", - "resolved": "https://github.com/CartoDB/abaculus/tarball/cdb" + "from": "https://github.com/CartoDB/abaculus/tarball/cdb-stats", + "resolved": "https://github.com/CartoDB/abaculus/tarball/cdb-stats" }, "sphericalmercator": { "version": "1.0.2", diff --git a/package.json b/package.json index a4398db9..55414e65 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "windshaft-cartodb", - "version": "1.30.1", + "version": "2.0.0", "description": "A map tile server for CartoDB", "keywords": [ "cartodb" @@ -22,10 +22,9 @@ "Sandro Santilli " ], "dependencies": { - "node-varnish": "https://github.com/Vizzuality/node-varnish/tarball/0.3.0", "underscore" : "~1.6.0", "dot": "~1.0.2", - "windshaft": "~0.40.0", + "windshaft": "https://github.com/CartoDB/Windshaft/tarball/master", "step": "~0.0.5", "queue-async": "~1.0.7", "request": "~2.9.203", @@ -39,6 +38,7 @@ "devDependencies": { "istanbul": "~0.3.6", "mocha": "~1.21.4", + "nock": "~1.3.0", "jshint": "~2.6.0", "redis": "~0.8.6", "strftime": "~0.8.2", diff --git a/test/acceptance/cache/surrogate_keys_invalidation.js b/test/acceptance/cache/surrogate_keys_invalidation.js index 118c6a63..8b626701 100644 --- a/test/acceptance/cache/surrogate_keys_invalidation.js +++ b/test/acceptance/cache/surrogate_keys_invalidation.js @@ -1,27 +1,24 @@ +require(__dirname + '/../../support/test_helper'); + var assert = require('../../support/assert'); var redis = require('redis'); -var Step = require('step'); - -var helper = require(__dirname + '/../../support/test_helper'); - -var SqlApiEmulator = require(__dirname + '/../../support/SQLAPIEmu.js'); +var step = require('step'); var NamedMapsCacheEntry = require(__dirname + '/../../../lib/cartodb/cache/model/named_maps_entry'); -var SurrogateKeysCache = require(__dirname + '/../../../lib/cartodb/cache/surrogate_keys_cache'); - var CartodbWindshaft = require(__dirname + '/../../../lib/cartodb/cartodb_windshaft'); -var ServerOptions = require(__dirname + '/../../../lib/cartodb/server_options'); -var serverOptions = ServerOptions(); -suite('templates surrogate keys', function() { +describe('templates surrogate keys', function() { - var sqlApiServer; var redisClient = redis.createClient(global.environment.redis.port); // Enable Varnish purge for tests - serverOptions.varnish_purge_enabled = true; + var varnishHost = global.environment.varnish.host; + global.environment.varnish.host = '127.0.0.1'; + var varnishPurgeEnabled = global.environment.varnish.purge_enabled; + global.environment.varnish.purge_enabled = true; + var serverOptions = require('../../../lib/cartodb/server_options')(); var server = new CartodbWindshaft(serverOptions); var templateOwner = 'localhost', @@ -48,15 +45,28 @@ suite('templates surrogate keys', function() { }, expectedBody = { template_id: expectedTemplateId }; - suiteSetup(function(done) { - sqlApiServer = new SqlApiEmulator(global.environment.sqlapi.port, done); - }); + var varnishHttpUrl = [ + 'http://', global.environment.varnish.host, ':', global.environment.varnish.http_port + ].join(''); - var surrogateKeysCacheInvalidateFn = SurrogateKeysCache.prototype.invalidate; + var cacheEntryKey = new NamedMapsCacheEntry(templateOwner, templateName).key(); + var invalidationMatchHeader = '\\b' + cacheEntryKey + '\\b'; + + var nock = require('nock'); + nock.enableNetConnect(/(127.0.0.1:5555|cartocdn.com)/); + + after(function(done) { + serverOptions.varnish_purge_enabled = false; + global.environment.varnish.host = varnishHost; + global.environment.varnish.purge_enabled = varnishPurgeEnabled; + + nock.restore(); + done(); + }); function createTemplate(callback) { var postTemplateRequest = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: { host: templateOwner, @@ -65,7 +75,7 @@ suite('templates surrogate keys', function() { data: JSON.stringify(template) }; - Step( + step( function postTemplate() { var next = this; assert.response(server, @@ -92,15 +102,14 @@ suite('templates surrogate keys', function() { ); } - test("update template calls surrogate keys invalidation", function(done) { - var cacheEntryKey; - var surrogateKeysCacheInvalidateMethodInvoked = false; - SurrogateKeysCache.prototype.invalidate = function(cacheEntry) { - cacheEntryKey = cacheEntry.key(); - surrogateKeysCacheInvalidateMethodInvoked = true; - }; + it("invalidates surrogate keys on template update", function(done) { - Step( + var scope = nock(varnishHttpUrl) + .intercept('/key', 'PURGE') + .matchHeader('Invalidation-Match', invalidationMatchHeader) + .reply(204, ''); + + step( function createTemplateToUpdate() { createTemplate(this); }, @@ -109,7 +118,7 @@ suite('templates surrogate keys', function() { throw err; } var updateTemplateRequest = { - url: '/tiles/template/' + expectedTemplateId + '/?api_key=1234', + url: '/api/v1/map/named/' + expectedTemplateId + '/?api_key=1234', method: 'PUT', headers: { host: templateOwner, @@ -135,8 +144,7 @@ suite('templates surrogate keys', function() { var parsedBody = JSON.parse(res.body); assert.deepEqual(parsedBody, expectedBody); - assert.ok(surrogateKeysCacheInvalidateMethodInvoked); - assert.equal(cacheEntryKey, new NamedMapsCacheEntry(templateOwner, templateName).key()); + assert.equal(scope.pendingMocks().length, 0); return null; }, @@ -156,16 +164,14 @@ suite('templates surrogate keys', function() { ); }); - test("delete template calls surrogate keys invalidation", function(done) { + it("invalidates surrogate on template deletion", function(done) { - var cacheEntryKey; - var surrogateKeysCacheInvalidateMethodInvoked = false; - SurrogateKeysCache.prototype.invalidate = function(cacheEntry) { - cacheEntryKey = cacheEntry.key(); - surrogateKeysCacheInvalidateMethodInvoked = true; - }; + var scope = nock(varnishHttpUrl) + .intercept('/key', 'PURGE') + .matchHeader('Invalidation-Match', invalidationMatchHeader) + .reply(204, ''); - Step( + step( function createTemplateToDelete() { createTemplate(this); }, @@ -174,7 +180,7 @@ suite('templates surrogate keys', function() { throw err; } var deleteTemplateRequest = { - url: '/tiles/template/' + expectedTemplateId + '/?api_key=1234', + url: '/api/v1/map/named/' + expectedTemplateId + '/?api_key=1234', method: 'DELETE', headers: { host: templateOwner, @@ -197,8 +203,7 @@ suite('templates surrogate keys', function() { throw err; } - assert.ok(surrogateKeysCacheInvalidateMethodInvoked); - assert.equal(cacheEntryKey, new NamedMapsCacheEntry(templateOwner, templateName).key()); + assert.equal(scope.pendingMocks().length, 0); return null; }, @@ -208,11 +213,66 @@ suite('templates surrogate keys', function() { ); }); - suiteTeardown(function(done) { - SurrogateKeysCache.prototype.invalidate = surrogateKeysCacheInvalidateFn; - // Enable Varnish purge for tests - serverOptions.varnish_purge_enabled = false; - sqlApiServer.close(done); + it("should update template even if surrogate key invalidation fails", function(done) { + + var scope = nock(varnishHttpUrl) + .intercept('/key', 'PURGE') + .matchHeader('Invalidation-Match', invalidationMatchHeader) + .reply(503, ''); + + step( + function createTemplateToUpdate() { + createTemplate(this); + }, + function putValidTemplate(err) { + if (err) { + throw err; + } + var updateTemplateRequest = { + url: '/api/v1/map/named/' + expectedTemplateId + '/?api_key=1234', + method: 'PUT', + headers: { + host: templateOwner, + 'Content-Type': 'application/json' + }, + data: JSON.stringify(template) + }; + var next = this; + assert.response(server, + updateTemplateRequest, + { + status: 200 + }, + function(res) { + next(null, res); + } + ); + }, + function checkValidUpdate(err, res) { + if (err) { + throw err; + } + var parsedBody = JSON.parse(res.body); + assert.deepEqual(parsedBody, expectedBody); + + assert.equal(scope.pendingMocks().length, 0); + + return null; + }, + function finish(err) { + if ( err ) { + return done(err); + } + redisClient.keys("map_*|localhost", function(err, keys) { + if ( err ) { + return done(err); + } + redisClient.del(keys, function(err) { + return done(err); + }); + }); + } + ); }); }); diff --git a/test/acceptance/cache_validator.js b/test/acceptance/cache_validator.js deleted file mode 100644 index 79939740..00000000 --- a/test/acceptance/cache_validator.js +++ /dev/null @@ -1,21 +0,0 @@ -var assert = require('../support/assert'); -require(__dirname + '/../support/test_helper'); -var CacheValidator = require(__dirname + '/../../lib/cartodb/cache_validator'); - -var VarnishEmu = require('../support/VarnishEmu'); - -suite('cache_validator', function() { - - test('should call purge on varnish when invalidate database', function(done) { - var varnish = new VarnishEmu(function(cmds) { - assert.ok(cmds.length == 1); - assert.equal('purge obj.http.X-Cache-Channel ~ \"^test_db:(.*test_cache.*)|(table)$\"\n', cmds[0].toString('utf8')); - done(); - }, - function() { - CacheValidator.init('localhost', 1337); - CacheValidator.invalidate_db('test_db', 'test_cache'); - }); - }); - -}); diff --git a/test/acceptance/multilayer.js b/test/acceptance/multilayer.js index 3af4d95a..5ba35ca7 100644 --- a/test/acceptance/multilayer.js +++ b/test/acceptance/multilayer.js @@ -1,12 +1,8 @@ var assert = require('../support/assert'); -var tests = module.exports = {}; var _ = require('underscore'); var redis = require('redis'); -var querystring = require('querystring'); -var semver = require('semver'); -var Step = require('step'); +var step = require('step'); var strftime = require('strftime'); -var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js'); var redis_stats_db = 5; var helper = require(__dirname + '/../support/test_helper'); @@ -17,44 +13,39 @@ var IMAGE_EQUALS_TOLERANCE_PER_MIL = 20; var IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL = 25; var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft'); -var ServerOptions = require(__dirname + '/../../lib/cartodb/server_options'); -serverOptions = ServerOptions(); -var server = new CartodbWindshaft(serverOptions); +var serverOptions = require(__dirname + '/../../lib/cartodb/server_options'); +var server = new CartodbWindshaft(serverOptions()); server.setMaxListeners(0); -['/tiles/layergroup', '/u/localhost/tiles/layergroup'].forEach(function(layergroup_url) { -[true, false].forEach(function(cdbQueryTablesFromPostgresEnabledValue) { +['/api/v1/map', '/u/localhost/api/v1/map'].forEach(function(layergroup_url) { -var suiteName = 'multilayer:postgres=' + cdbQueryTablesFromPostgresEnabledValue + ";layergroup_url=" + layergroup_url; +var suiteName = 'multilayer:postgres=layergroup_url=' + layergroup_url; suite(suiteName, function() { + var cdbQueryTablesFromPostgresEnabledValue = true; + var redis_client = redis.createClient(global.environment.redis.port); - var sqlapi_server; var expected_last_updated_epoch = 1234567890123; // this is hard-coded into SQLAPIEmu var expected_last_updated = new Date(expected_last_updated_epoch).toISOString(); var test_user = _.template(global.environment.postgres_auth_user, {user_id:1}); - var test_pubuser = global.environment.postgres.user; var test_database = test_user + '_db'; - suiteSetup(function(done){ - global.environment.enabledFeatures = { cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue }; - sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done); - }); - test("layergroup with 2 layers, each with its style", function(done) { var layergroup = { version: '1.0.0', layers: [ { options: { - sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2', + sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' + + ' from test_table limit 2', cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }', cartocss_version: '2.0.1', interactivity: 'cartodb_id' } }, { options: { - sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, -5e6, 0) as the_geom_webmercator from test_table limit 2 offset 2', + sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, -5e6, 0) as the_geom_webmercator' + + ' from test_table limit 2 offset 2', cartocss: '#layer { marker-fill:blue; marker-allow-overlap:true; }', cartocss_version: '2.0.2', interactivity: 'cartodb_id' @@ -63,7 +54,7 @@ suite(suiteName, function() { }; var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7"; - Step( + step( function do_post() { var next = this; @@ -75,14 +66,6 @@ suite(suiteName, function() { }, {}, function(res) { assert.equal(res.statusCode, 200, res.body); var parsedBody = JSON.parse(res.body); - var expectedBody = { layergroupid: expected_token }; - // check last modified - var qTables = JSON.stringify({ - 'q': 'SELECT CDB_QueryTables($windshaft$' - + layergroup.layers[0].options.sql + ';' - + layergroup.layers[1].options.sql - + '$windshaft$)' - }); assert.equal(parsedBody.last_updated, expected_last_updated); if ( expected_token ) { assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch); @@ -116,19 +99,23 @@ suite(suiteName, function() { if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API var jsonquery = cc.substring(dbname.length + 1); var sentquery = JSON.parse(jsonquery); - var expectedQuery = [layergroup.layers[0].options.sql, ';', layergroup.layers[1].options.sql].join(''); - assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' - + expectedQuery - + '$windshaft$) as tablenames )' - + ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' - + ' FROM CDB_TableMetadata m' - + ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'); + var expectedQuery = [ + layergroup.layers[0].options.sql, ';', + layergroup.layers[1].options.sql + ].join(''); + assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' + + expectedQuery + + '$windshaft$) as tablenames )' + + ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' + + ' FROM CDB_TableMetadata m' + + ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'); } - assert.imageEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.png', IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL, - function(err, similarity) { - next(err); - }); + assert.imageEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.png', + IMAGE_EQUALS_HIGHER_TOLERANCE_PER_MIL, function(err/*, similarity*/) { + next(err); + } + ); }); }, // See https://github.com/CartoDB/Windshaft-cartodb/issues/170 @@ -154,15 +141,14 @@ suite(suiteName, function() { if ( err ) throw err; var next = this; assert.response(server, { - url: layergroup_url + "/" + expected_token - + '/0/0/0/0.grid.json', + url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json', headers: {host: 'localhost' }, method: 'GET' }, {}, function(res) { assert.equal(res.statusCode, 200, res.body); assert.equal(res.headers['content-type'], "application/json; charset=utf-8"); assert.utfgridEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.layer0.grid.json', 2, - function(err, similarity) { + function(err/*, similarity*/) { next(err); }); }); @@ -172,15 +158,14 @@ suite(suiteName, function() { if ( err ) throw err; var next = this; assert.response(server, { - url: layergroup_url + "/" + expected_token - + '/1/0/0/0.grid.json', + url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json', headers: {host: 'localhost' }, method: 'GET' }, {}, function(res) { assert.equal(res.statusCode, 200, res.body); assert.equal(res.headers['content-type'], "application/json; charset=utf-8"); assert.utfgridEqualsFile(res.body, 'test/fixtures/test_table_0_0_0_multilayer1.layer1.grid.json', 2, - function(err, similarity) { + function(err/*, similarity*/) { next(err); }); }); @@ -206,20 +191,20 @@ suite(suiteName, function() { test("should include serverMedata in the response", function(done) { - global.environment.serverMetadata = { cdn_url : { http:'test', https: 'tests' } } + global.environment.serverMetadata = { cdn_url : { http:'test', https: 'tests' } }; var layergroup = { version: '1.0.0', layers: [ { options: { - sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2', + sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' + + ' from test_table limit 2', cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }', cartocss_version: '2.0.1' } } ] }; - var expected_token; - Step( + step( function do_create_get() { var next = this; @@ -234,7 +219,7 @@ suite(suiteName, function() { assert.ok(_.isEqual(parsed.cdn_url, global.environment.serverMetadata.cdn_url)); done(); } - ) + ); }); @@ -244,7 +229,8 @@ suite(suiteName, function() { version: '1.0.0', layers: [ { options: { - sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2', + sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' + + ' from test_table limit 2', cartocss: '#layer { marker-fill:red; marker-width:32; marker-allow-overlap:true; }', cartocss_version: '2.0.1' } } @@ -252,7 +238,7 @@ suite(suiteName, function() { }; var expected_token; - Step( + step( function do_create_get() { var next = this; @@ -316,7 +302,8 @@ suite(suiteName, function() { version: '1.0.0', layers: [ { options: { - sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2', + sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' + + ' from test_table limit 2', cartocss: '#layer { invalid-rule:red; }', cartocss_version: '2.0.1' } } @@ -339,8 +326,8 @@ suite(suiteName, function() { version: '1.0.0', layers: [ { options: { - sql: 'select 1 as cartodb_id, ' - + 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator from test_table limit 1', + sql: 'select 1 as cartodb_id, ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!))' + + ' as the_geom_webmercator from test_table limit 1', cartocss: '#layer { polygon-fill:red; }', cartocss_version: '2.0.1', interactivity: 'cartodb_id' @@ -349,7 +336,7 @@ suite(suiteName, function() { }; var expected_token; // = "6d8e4ad5458e2d25cf0eef38e38717a6"; - Step( + step( function do_post() { var next = this; @@ -361,13 +348,6 @@ suite(suiteName, function() { }, {}, function(res) { assert.equal(res.statusCode, 200, res.body); var parsedBody = JSON.parse(res.body); - var expectedBody = { layergroupid: expected_token }; - // check last modified - var qTables = JSON.stringify({ - 'q': 'SELECT CDB_QueryTables($windshaft$' - + layergroup.layers[0].options.sql - + '$windshaft$)' - }); assert.equal(parsedBody.last_updated, expected_last_updated); if ( expected_token ) { assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch); @@ -401,16 +381,16 @@ suite(suiteName, function() { .replace(/!bbox!/g, 'ST_MakeEnvelope(0,0,0,0)') .replace(/!pixel_width!/g, '1') .replace(/!pixel_height!/g, '1'); - assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' - + expectedQuery - + '$windshaft$) as tablenames )' - + ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' - + ' FROM CDB_TableMetadata m' - + ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'); + assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' + + expectedQuery + + '$windshaft$) as tablenames )' + + ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' + + ' FROM CDB_TableMetadata m' + + ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'); } assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, - function(err, similarity) { + function(err/*, similarity*/) { next(err); }); }); @@ -440,16 +420,16 @@ suite(suiteName, function() { .replace('!bbox!', 'ST_MakeEnvelope(0,0,0,0)') .replace('!pixel_width!', '1') .replace('!pixel_height!', '1'); - assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' - + expectedQuery - + '$windshaft$) as tablenames )' - + ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' - + ' FROM CDB_TableMetadata m' - + ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'); + assert.equal(sentquery.q, 'WITH querytables AS ( SELECT * FROM CDB_QueryTables($windshaft$' + + expectedQuery + + '$windshaft$) as tablenames )' + + ' SELECT (SELECT tablenames FROM querytables), EXTRACT(EPOCH FROM max(updated_at)) as max' + + ' FROM CDB_TableMetadata m' + + ' WHERE m.tabname = any ((SELECT tablenames from querytables)::regclass[])'); } assert.imageEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, - function(err, similarity) { + function(err/*, similarity*/) { next(err); }); }); @@ -459,15 +439,14 @@ suite(suiteName, function() { if ( err ) throw err; var next = this; assert.response(server, { - url: layergroup_url + "/" + expected_token - + '/0/1/0/0.grid.json', + url: layergroup_url + "/" + expected_token + '/0/1/0/0.grid.json', headers: {host: 'localhost' }, method: 'GET' }, {}, function(res) { assert.equal(res.statusCode, 200, res.body); assert.equal(res.headers['content-type'], "application/json; charset=utf-8"); assert.utfgridEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.grid.json', 2, - function(err, similarity) { + function(err/*, similarity*/) { next(err); }); }); @@ -477,15 +456,14 @@ suite(suiteName, function() { if ( err ) throw err; var next = this; assert.response(server, { - url: layergroup_url + "/" + expected_token - + '/0/4/0/0.grid.json', + url: layergroup_url + "/" + expected_token + '/0/4/0/0.grid.json', headers: {host: 'localhost' }, method: 'GET' }, {}, function(res) { assert.equal(res.statusCode, 200, res.body); assert.equal(res.headers['content-type'], "application/json; charset=utf-8"); assert.utfgridEqualsFile(res.body, 'test/fixtures/test_multilayer_bbox.grid.json', 2, - function(err, similarity) { + function(err/*, similarity*/) { next(err); }); }); @@ -515,8 +493,8 @@ suite(suiteName, function() { version: '1.0.0', layers: [ { options: { - sql: 'select 1 as cartodb_id, !pixel_height! as h, ' - + 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator', + sql: 'select 1 as cartodb_id, !pixel_height! as h,' + + ' ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator', cartocss: '#layer { polygon-fill:red; }', cartocss_version: '2.0.1' } } @@ -527,7 +505,7 @@ suite(suiteName, function() { var expected_token; // will be set on first post and checked on second var now = strftime("%Y%m%d", new Date()); var errors = []; - Step( + step( function clean_stats() { var next = this; @@ -553,14 +531,13 @@ suite(suiteName, function() { }, function check_global_stats_1(err, val) { if ( err ) throw err; - assert.equal(val, 1, "Expected score of " + now + " in " - + statskey + ":global to be 1, got " + val); + assert.equal(val, 1, "Expected score of " + now + " in " + statskey + ":global to be 1, got " + val); redis_stats_client.zscore(statskey+':stat_tag:random_tag', now, this); }, function check_tag_stats_1_do_post_2(err, val) { if ( err ) throw err; - assert.equal(val, 1, "Expected score of " + now + " in " - + statskey + ":stat_tag:" + layergroup.stat_tag + " to be 1, got " + val); + assert.equal(val, 1, "Expected score of " + now + " in " + statskey + ":stat_tag:" + layergroup.stat_tag + + " to be 1, got " + val); var next = this; assert.response(server, { url: layergroup_url, @@ -576,15 +553,14 @@ suite(suiteName, function() { function check_global_stats_2(err, val) { if ( err ) throw err; - assert.equal(val, 2, "Expected score of " + now + " in " - + statskey + ":global to be 2, got " + val); + assert.equal(val, 2, "Expected score of " + now + " in " + statskey + ":global to be 2, got " + val); redis_stats_client.zscore(statskey+':stat_tag:' + layergroup.stat_tag, now, this); }, function check_tag_stats_2(err, val) { if ( err ) throw err; - assert.equal(val, 2, "Expected score of " + now + " in " - + statskey + ":stat_tag:" + layergroup.stat_tag + " to be 2, got " + val); + assert.equal(val, 2, "Expected score of " + now + " in " + statskey + ":stat_tag:" + layergroup.stat_tag + + " to be 2, got " + val); return 1; }, function cleanup_map_style(err) { @@ -614,8 +590,8 @@ suite(suiteName, function() { version: '1.0.0', layers: [ { options: { - sql: 'select 1 as cartodb_id, !pixel_height! as h' - + 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator', + sql: 'select 1 as cartodb_id, !pixel_height! as h' + + 'ST_Buffer(!bbox!, -32*greatest(!pixel_width!,!pixel_height!)) as the_geom_webmercator', cartocss: '#layer { polygon-fit:red; }', cartocss_version: '2.0.1' } } @@ -685,7 +661,7 @@ suite(suiteName, function() { }; var expected_token; // = "b4ed64d93a411a59f330ab3d798e4009"; - Step( + step( function do_post() { var next = this; @@ -697,14 +673,6 @@ suite(suiteName, function() { }, {}, function(res) { assert.equal(res.statusCode, 200, res.body); var parsedBody = JSON.parse(res.body); - var expectedBody = { layergroupid: expected_token }; - // check last modified - var qTables = JSON.stringify({ - 'q': 'SELECT CDB_QueryTables($windshaft$' - + layergroup.layers[0].options.sql + ';' - + layergroup.layers[1].options.sql - + '$windshaft$)' - }); assert.equal(parsedBody.last_updated, expected_last_updated); if ( expected_token ) { assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch); @@ -739,8 +707,7 @@ suite(suiteName, function() { if ( err ) throw err; var next = this; assert.response(server, { - url: layergroup_url + "/" + expected_token - + '/0/0/0/0.grid.json?map_key=1234', + url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json?map_key=1234', headers: {host: 'localhost' }, method: 'GET' }, {}, function(res) { @@ -753,8 +720,7 @@ suite(suiteName, function() { if ( err ) throw err; var next = this; assert.response(server, { - url: layergroup_url + "/" + expected_token - + '/1/0/0/0.grid.json?map_key=1234', + url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json?map_key=1234', headers: {host: 'localhost' }, method: 'GET' }, {}, function(res) { @@ -774,7 +740,7 @@ suite(suiteName, function() { encoding: 'binary' }, {}, function(res) { assert.equal(res.statusCode, 403); - var re = RegExp('permission denied'); + var re = new RegExp('permission denied'); assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body); next(err); }); @@ -784,13 +750,12 @@ suite(suiteName, function() { if ( err ) throw err; var next = this; assert.response(server, { - url: layergroup_url + "/" + expected_token - + '/0/0/0/0.grid.json', + url: layergroup_url + "/" + expected_token + '/0/0/0/0.grid.json', headers: {host: 'localhost' }, method: 'GET' }, {}, function(res) { assert.equal(res.statusCode, 403); - var re = RegExp('permission denied'); + var re = new RegExp('permission denied'); assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body); next(err); }); @@ -800,13 +765,12 @@ suite(suiteName, function() { if ( err ) throw err; var next = this; assert.response(server, { - url: layergroup_url + "/" + expected_token - + '/1/0/0/0.grid.json', + url: layergroup_url + "/" + expected_token + '/1/0/0/0.grid.json', headers: {host: 'localhost' }, method: 'GET' }, {}, function(res) { assert.equal(res.statusCode, 403); - var re = RegExp('permission denied'); + var re = new RegExp('permission denied'); assert.ok(res.body.match(re), 'No "permission denied" error: ' + res.body); next(err); }); @@ -846,7 +810,7 @@ suite(suiteName, function() { }; var expected_token; // = "b4ed64d93a411a59f330ab3d798e4009"; - Step( + step( function do_post() { var next = this; @@ -861,13 +825,6 @@ suite(suiteName, function() { if ( err ) throw err; assert.equal(res.statusCode, 200, res.body); var parsedBody = JSON.parse(res.body); - var expectedBody = { layergroupid: expected_token }; - // check last modified - var qTables = JSON.stringify({ - 'q': 'SELECT CDB_QueryTables($windshaft$' - + layergroup.layers[0].options.sql - + '$windshaft$)' - }); assert.equal(parsedBody.last_updated, expected_last_updated); if ( expected_token ) { assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch); @@ -898,11 +855,10 @@ suite(suiteName, function() { assert.equal(cc.substring(0, dbname.length), dbname); return null; }, - function do_restart_server(err, res) { + function do_restart_server(err/*, res*/) { if ( err ) throw err; // hack simulating restart... - serverOptions = ServerOptions(); - server = new CartodbWindshaft(serverOptions); + server = new CartodbWindshaft(serverOptions()); return null; }, function do_get1(err) @@ -956,13 +912,13 @@ suite(suiteName, function() { { options: { sql: "select 1 as cartodb_id, 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator", cartocss: '#sample { text-name: cartodb_id; text-face-name: "Dejagnu"; }', - cartocss_version: '2.1.0', + cartocss_version: '2.1.0' } } ] }; assert.response(server, { - url: '/tiles/layergroup?', + url: layergroup_url, method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(layergroup) @@ -984,18 +940,18 @@ suite(suiteName, function() { { options: { sql: "select 'single''quote' as n, 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator", cartocss: '#s [n="single\'quote" ] { marker-fill:red; }', - cartocss_version: '2.1.0', + cartocss_version: '2.1.0' } }, { options: { sql: "select 'double\"quote' as n, 'SRID=3857;POINT(2 0)'::geometry as the_geom_webmercator", cartocss: '#s [n="double\\"quote" ] { marker-fill:red; }', - cartocss_version: '2.1.0', + cartocss_version: '2.1.0' } } ] }; assert.response(server, { - url: '/tiles/layergroup?', + url: layergroup_url, method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(layergroup) @@ -1013,12 +969,12 @@ suite(suiteName, function() { { options: { sql: "select .4 as n, 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator", cartocss: '#s [n<=.2e-2] { marker-fill:red; }', - cartocss_version: '2.1.0', + cartocss_version: '2.1.0' } } ] }; assert.response(server, { - url: '/tiles/layergroup?', + url: layergroup_url, method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(layergroup) @@ -1041,7 +997,7 @@ suite(suiteName, function() { ] }; var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7"; - Step( + step( function do_post() { var next = this; @@ -1053,7 +1009,6 @@ suite(suiteName, function() { }, {}, function(res) { assert.equal(res.statusCode, 200, res.body); var parsedBody = JSON.parse(res.body); - var expectedBody = { layergroupid: expected_token }; if ( expected_token ) { assert.equal(parsedBody.layergroupid, expected_token + ':' + expected_last_updated_epoch); } @@ -1077,10 +1032,11 @@ suite(suiteName, function() { }, {}, function(res) { assert.equal(res.statusCode, 200, res.body); assert.equal(res.headers['content-type'], "image/png"); - assert.imageEqualsFile(res.body, windshaft_fixtures + '/test_default_mapnik_point.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, - function(err, similarity) { - next(err); - }); + assert.imageEqualsFile(res.body, windshaft_fixtures + '/test_default_mapnik_point.png', + IMAGE_EQUALS_TOLERANCE_PER_MIL, function(err/*, similarity*/) { + next(err); + } + ); }); }, function finish(err) { @@ -1117,7 +1073,7 @@ suite(suiteName, function() { ] }; var expected_token; // = "e34dd7e235138a062f8ba7ad051aa3a7"; - Step( + step( function do_post() { var next = this; @@ -1156,7 +1112,6 @@ suite(suiteName, function() { }, function check_get_tile(err, res) { if ( err ) throw err; - var next = this; assert.equal(res.statusCode, 200, res.body); return null; }, @@ -1188,10 +1143,10 @@ suite(suiteName, function() { // See https://github.com/CartoDB/Windshaft-cartodb/issues/111 test("sql string can be very long", function(done){ var long_val = 'pretty'; - for (var i=0; i<1024; ++i) long_val += ' long' + for (var i=0; i<1024; ++i) long_val += ' long'; long_val += ' string'; var sql = "SELECT "; - for (var i=0; i<16; ++i) + for (i=0; i<16; ++i) sql += "'" + long_val + "'::text as pretty_long_field_name_" + i + ", "; sql += "cartodb_id, the_geom_webmercator FROM gadm4 g"; var layergroup = { @@ -1206,7 +1161,7 @@ suite(suiteName, function() { }; var errors = []; var expected_token; - Step( + step( function do_post() { var data = JSON.stringify(layergroup); @@ -1225,10 +1180,6 @@ suite(suiteName, function() { var parsedBody = JSON.parse(res.body); var token_components = parsedBody.layergroupid.split(':'); expected_token = token_components[0]; - if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API - var last_request = sqlapi_server.getLastRequest(); - assert.equal(last_request.method, 'POST'); - } return null; }, function cleanup(err) { @@ -1259,13 +1210,14 @@ suite(suiteName, function() { version: '1.0.0', layers: [ { options: { - sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator from test_table limit 2', + sql: 'select cartodb_id, ST_Translate(the_geom_webmercator, 5e6, 0) as the_geom_webmercator' + + ' from test_table limit 2', interactivity: 'cartodb_id' } } ] }; - Step( + step( function do_post() { var next = this; @@ -1307,7 +1259,7 @@ suite(suiteName, function() { ] }; - Step( + step( function do_post() { var next = this; @@ -1415,7 +1367,7 @@ suite(suiteName, function() { { status: 403 }, - function(res, err) { + function(res) { assert.ok(res.body.match(/permission denied for relation test_table_private_1/)); done(); } @@ -1428,11 +1380,11 @@ suite(suiteName, function() { // This test will add map_style records, like // 'map_style|null|publicuser|my_table', redis_client.keys("map_style|*", function(err, matches) { - redis_client.del(matches, function(err) { - redis_client.select(5, function(err, matches) { + redis_client.del(matches, function() { + redis_client.select(5, function() { redis_client.keys("user:localhost:mapviews*", function(err, matches) { - redis_client.del(matches, function(err) { - sqlapi_server.close(done); + redis_client.del(matches, function() { + done(); }); }); }); @@ -1444,4 +1396,3 @@ suite(suiteName, function() { }); }); -}); diff --git a/test/acceptance/multilayer_server.js b/test/acceptance/multilayer_server.js new file mode 100644 index 00000000..79b59a3c --- /dev/null +++ b/test/acceptance/multilayer_server.js @@ -0,0 +1,338 @@ +var testHelper = require('../support/test_helper'); + +var assert = require('../support/assert'); + +var redis = require('redis'); +var _ = require('underscore'); + + +var QueryTablesApi = require('../../lib/cartodb/api/query_tables_api'); +var CartodbWindshaft = require('../../lib/cartodb/cartodb_windshaft'); +var serverOptions = require('../../lib/cartodb/server_options')(); +var server = new CartodbWindshaft(serverOptions); +server.setMaxListeners(0); + +describe('tests from old api translated to multilayer', function() { + + var layergroupUrl = '/api/v1/map'; + + var redisClient = redis.createClient(global.environment.redis.port); + after(function(done) { + // This test will add map_style records, like + // 'map_style|null|publicuser|my_table', + redisClient.keys("map_style|*", function(err, matches) { + redisClient.del(matches, function() { + done(); + }); + }); + }); + + var wadusSql = 'select 1 as cartodb_id, null::geometry as the_geom_webmercator'; + var pointSql = "SELECT 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator, 1::int as cartodb_id"; + + 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, apiKey) { + var url = layergroupUrl; + if (apiKey) { + url += '?api_key=' + apiKey; + } + return { + url: url, + method: 'POST', + headers: { + host: userHost || 'localhost', + 'Content-Type': 'application/json' + }, + data: JSON.stringify(layergroup) + }; + } + + it("layergroup creation fails if CartoCSS is bogus", function(done) { + var layergroup = singleLayergroupConfig(wadusSql, '#my_table3{'); + assert.response(server, + createRequest(layergroup), + { + status: 400 + }, + function(res) { + var parsed = JSON.parse(res.body); + assert.ok(parsed.errors[0].match(/^style0/)); + assert.ok(parsed.errors[0].match(/missing closing/)); + done(); + } + ); + }); + + it("multiple bad styles returns 400 with all errors", function(done) { + var layergroup = singleLayergroupConfig(wadusSql, '#my_table4{backgxxxxxround-color:#fff;foo:bar}'); + assert.response(server, + createRequest(layergroup), + { + status: 400 + }, + function(res) { + var parsed = JSON.parse(res.body); + assert.equal(parsed.errors.length, 1); + assert.ok(parsed.errors[0].match(/^style0/)); + assert.ok(parsed.errors[0].match(/Unrecognized rule: backgxxxxxround-color/)); + assert.ok(parsed.errors[0].match(/Unrecognized rule: foo/)); + done(); + } + ); + }); + + // Zoom is a special variable + it("Specifying zoom level in CartoCSS does not need a 'zoom' variable in SQL output", function(done) { + var layergroup = singleLayergroupConfig(pointSql, '#gadm4 [ zoom>=3] { marker-fill:red; }'); + + assert.response(server, + createRequest(layergroup), + { + status: 200 + }, + function(res) { + var parsed = JSON.parse(res.body); + assert.ok(parsed.layergroupid); + done(); + } + ); + }); + + // See https://github.com/CartoDB/Windshaft-cartodb/issues/88 + it("getting a tile from a user-specific database should return an expected tile", function(done) { + var layergroup = singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }'); + + var backupDBHost = global.environment.postgres.host; + global.environment.postgres.host = '6.6.6.6'; + + assert.response(server, + createRequest(layergroup, 'cartodb250user'), + { + status: 200 + }, + function(res) { + var parsed = JSON.parse(res.body); + assert.ok(parsed.layergroupid); + + global.environment.postgres.host = backupDBHost; + done(); + } + ); + }); + + // See https://github.com/CartoDB/Windshaft-cartodb/issues/89 + it("getting a tile with a user-specific database password", function(done) { + var layergroup = singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }'); + + var backupDBPass = global.environment.postgres_auth_pass; + global.environment.postgres_auth_pass = '<%= user_password %>'; + + assert.response(server, + createRequest(layergroup, 'cartodb250user', '4321'), + { + status: 200 + }, + function(res) { + var parsed = JSON.parse(res.body); + assert.ok(parsed.layergroupid); + + global.environment.postgres_auth_pass = backupDBPass; + done(); + } + ); + }); + + it("creating a layergroup from lzma param", function(done){ + var params = { + config: JSON.stringify(singleLayergroupConfig(pointSql, '#layer { marker-fill:red; }')) + }; + + testHelper.lzma_compress_to_base64(JSON.stringify(params), 1, function(err, lzma) { + if (err) { + return done(err); + } + assert.response(server, + { + url: layergroupUrl + '?lzma=' + encodeURIComponent(lzma), + method: 'GET', + headers: { + host: 'localhost' + }, + encoding: 'binary' + }, + { + status: 200 + }, + function(res) { + var parsed = JSON.parse(res.body); + assert.ok(parsed.layergroupid); + + done(); + } + ); + }); + }); + + it("creating a layergroup from lzma param, invalid json input", function(done) { + var params = { + config: 'WADUS' + }; + + testHelper.lzma_compress_to_base64(JSON.stringify(params), 1, function(err, lzma) { + if (err) { + return done(err); + } + assert.response(server, + { + url: layergroupUrl + '?lzma=' + encodeURIComponent(lzma), + method: 'GET', + headers: { + host: 'localhost' + }, + encoding: 'binary' + }, + { + status: 400 + }, + function(res) { + var parsed = JSON.parse(res.body); + assert.deepEqual(parsed, { errors: [ 'Unexpected token W' ] }); + + done(); + } + ); + }); + }); + + it("uses queries postgresql to figure affected tables in query", function(done) { + var tableName = 'gadm4'; + var expectedCacheChannel = _.template('<%= databaseName %>:public.<%= tableName %>', { + databaseName: _.template(global.environment.postgres_auth_user, {user_id:1}) + '_db', + tableName: tableName + }); + + var layergroup = singleLayergroupConfig('select * from ' + tableName, '#gadm4 { marker-fill: red; }'); + + assert.response(server, + { + url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)), + method: 'GET', + headers: { + host: 'localhost' + } + }, + { + status: 200 + }, + function(res) { + var parsed = JSON.parse(res.body); + assert.ok(parsed.layergroupid); + + assert.ok(res.headers.hasOwnProperty('x-cache-channel')); + assert.equal(res.headers['x-cache-channel'], expectedCacheChannel); + + done(); + } + ); + }); + + it("creates layergroup fails when postgresql queries fail to figure affected tables in query", function(done) { + + var runQueryFn = QueryTablesApi.prototype.runQuery; + QueryTablesApi.prototype.runQuery = function(username, query, queryHandler, callback) { + return queryHandler(new Error('fake error message'), [], callback); + }; + + var layergroup = singleLayergroupConfig('select * from gadm4', '#gadm4 { marker-fill: red; }'); + + assert.response(server, + { + url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)), + method: 'GET', + headers: { + host: 'localhost' + } + }, + { + status: 400 + }, + function(res) { + QueryTablesApi.prototype.runQuery = runQueryFn; + + assert.ok(!res.headers.hasOwnProperty('x-cache-channel')); + + var parsed = JSON.parse(res.body); + assert.deepEqual(parsed, { + errors: ["Error: could not fetch affected tables and last updated time: fake error message"] + }); + + done(); + } + ); + }); + + it("tile requests works when postgresql queries fail to figure affected tables in query", function(done) { + var layergroup = singleLayergroupConfig('select * from gadm4', '#gadm4 { marker-fill: red; }'); + assert.response(server, + { + url: layergroupUrl + '?config=' + encodeURIComponent(JSON.stringify(layergroup)), + method: 'GET', + headers: { + host: 'localhost' + } + }, + { + status: 200 + }, + function(res) { + var runQueryFn = QueryTablesApi.prototype.runQuery; + QueryTablesApi.prototype.runQuery = function(username, query, queryHandler, callback) { + return queryHandler(new Error('failed to query database for affected tables'), [], callback); + }; + + // reset internal cacheChannel cache + serverOptions.channelCache = {}; + + 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 + }, + function(res) { + assert.ok(!res.headers.hasOwnProperty('x-cache-channel')); + QueryTablesApi.prototype.runQuery = runQueryFn; + done(); + } + ); + } + ); + }); + +}); diff --git a/test/acceptance/named_layers.js b/test/acceptance/named_layers.js index 3b52f982..9e2be330 100644 --- a/test/acceptance/named_layers.js +++ b/test/acceptance/named_layers.js @@ -130,7 +130,7 @@ suite('named_layers', function() { var next = this; assert.response(server, { - url: '/tiles/layergroup', + url: '/api/v1/map', method: 'POST', headers: { host: 'localhost', @@ -183,7 +183,7 @@ suite('named_layers', function() { var next = this; assert.response(server, { - url: '/tiles/layergroup', + url: '/api/v1/map', method: 'POST', headers: { host: 'localhost', @@ -240,7 +240,7 @@ suite('named_layers', function() { var next = this; assert.response(server, { - url: '/tiles/layergroup', + url: '/api/v1/map', method: 'POST', headers: { host: 'localhost', @@ -293,7 +293,7 @@ suite('named_layers', function() { var next = this; assert.response(server, { - url: '/tiles/layergroup', + url: '/api/v1/map', method: 'POST', headers: { host: 'localhost', @@ -373,7 +373,7 @@ suite('named_layers', function() { var next = this; assert.response(server, { - url: '/tiles/layergroup', + url: '/api/v1/map', method: 'POST', headers: { host: 'localhost', @@ -408,7 +408,7 @@ suite('named_layers', function() { var next = this; assert.response(server, { - url: '/tiles/layergroup/' + layergroupId + '/0/0/0.png', + url: '/api/v1/map/' + layergroupId + '/0/0/0.png', method: 'GET', headers: { host: 'localhost' @@ -501,7 +501,7 @@ suite('named_layers', function() { var next = this; assert.response(server, { - url: '/tiles/layergroup', + url: '/api/v1/map', method: 'POST', headers: { host: 'localhost', @@ -536,7 +536,7 @@ suite('named_layers', function() { var next = this; assert.response(server, { - url: '/tiles/layergroup/' + layergroupId + '/0/0/0.png', + url: '/api/v1/map/' + layergroupId + '/0/0/0.png', method: 'GET', headers: { host: 'localhost' @@ -602,7 +602,7 @@ suite('named_layers', function() { var next = this; assert.response(server, { - url: '/tiles/layergroup', + url: '/api/v1/map', method: 'POST', headers: { host: 'localhost', diff --git a/test/acceptance/server.js b/test/acceptance/server.js index 9a8b2519..ffb32fe4 100644 --- a/test/acceptance/server.js +++ b/test/acceptance/server.js @@ -1,64 +1,20 @@ +require('../support/test_helper'); + var assert = require('../support/assert'); -var tests = module.exports = {}; -var _ = require('underscore'); -var redis = require('redis'); var querystring = require('querystring'); -var semver = require('semver'); -var Step = require('step'); -var http = require('http'); -var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js'); +var step = require('step'); -var helper = require(__dirname + '/../support/test_helper'); - -var IMAGE_EQUALS_TOLERANCE_PER_MIL = 20, - IMAGE_EQUALS_ZERO_TOLERANCE_PER_MIL = 0; - -var CartodbWindshaft = require(__dirname + '/../../lib/cartodb/cartodb_windshaft'); -var serverOptions = require(__dirname + '/../../lib/cartodb/server_options')(); +var CartodbWindshaft = require('../../lib/cartodb/cartodb_windshaft'); +var serverOptions = require('../../lib/cartodb/server_options')(); var server = new CartodbWindshaft(serverOptions); server.setMaxListeners(0); -[true, false].forEach(function(cdbQueryTablesFromPostgresEnabledValue) { -suite('server:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() { +suite('server', function() { - var redis_client = redis.createClient(global.environment.redis.port); - var sqlapi_server; - - var mapnik_version = global.environment.mapnik_version || server.getVersion().mapnik; - var test_database = _.template(global.environment.postgres_auth_user, {user_id:1}) + '_db'; - var default_style; - if ( semver.satisfies(mapnik_version, '<2.1.0') ) { - // 2.0.0 default - default_style = '#<%= table %>{marker-fill: #FF6600;marker-opacity: 1;marker-width: 8;marker-line-color: white;marker-line-width: 3;marker-line-opacity: 0.9;marker-placement: point;marker-type: ellipse;marker-allow-overlap: true;}'; - } - else if ( semver.satisfies(mapnik_version, '<2.2.0') ) { - // 2.1.0 default - default_style = '#<%= table %>[mapnik-geometry-type=1] {marker-fill: #FF6600;marker-opacity: 1;marker-width: 16;marker-line-color: white;marker-line-width: 3;marker-line-opacity: 0.9;marker-placement: point;marker-type: ellipse;marker-allow-overlap: true;}#<%= table %>[mapnik-geometry-type=2] {line-color:#FF6600; line-width:1; line-opacity: 0.7;}#<%= table %>[mapnik-geometry-type=3] {polygon-fill:#FF6600; polygon-opacity: 0.7; line-opacity:1; line-color: #FFFFFF;}'; - } - else { - // 2.2.0+ default - default_style = '#<%= table %>["mapnik::geometry_type"=1] {marker-fill: #FF6600;marker-opacity: 1;marker-width: 16;marker-line-color: white;marker-line-width: 3;marker-line-opacity: 0.9;marker-placement: point;marker-type: ellipse;marker-allow-overlap: true;}#<%= table %>["mapnik::geometry_type"=2] {line-color:#FF6600; line-width:1; line-opacity: 0.7;}#<%= table %>["mapnik::geometry_type"=3] {polygon-fill:#FF6600; polygon-opacity: 0.7; line-opacity:1; line-color: #FFFFFF;}'; - } - - // A couple of styles to use during testing - var test_style_black_200 = "#test_table{marker-fill:black;marker-line-color:red;marker-width:10}"; - var test_style_black_210 = "#test_table{marker-fill:black;marker-line-color:red;marker-width:20}"; - - suiteSetup(function(done){ - global.environment.enabledFeatures = { cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue }; - sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done); - }); - - ///////////////////////////////////////////////////////////////////////////////// - // - // GET UNSUPPORTED - // - ///////////////////////////////////////////////////////////////////////////////// - // TODO: I guess this should be a 404 instead... test("get call to server returns 200", function(done){ - Step( + step( function doGet() { var next = this; assert.response(server, { @@ -79,12 +35,6 @@ suite('server:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() { ); }); - ///////////////////////////////////////////////////////////////////////////////// - // - // GET VERSION - // - ///////////////////////////////////////////////////////////////////////////////// - test("get call to server returns 200", function(done){ assert.response(server, { url: '/version', @@ -107,1148 +57,9 @@ suite('server:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() { done(); }); }); +}); - ///////////////////////////////////////////////////////////////////////////////// - // - // GET STYLE - // - ///////////////////////////////////////////////////////////////////////////////// - - test("get'ing blank style returns default style", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_table/style', - method: 'GET' - },{ - status: 200, - headers: { 'X-Cache-Channel': test_database+':my_table' }, - }, function(res) { - var parsed = JSON.parse(res.body); - assert.equal(parsed.style, _.template(default_style, {table: 'my_table'})); - assert.equal(parsed.style_version, mapnik_version); - done(); - }); - }); - - // See https://github.com/Vizzuality/Windshaft-cartodb/issues/43 - test("get'ing style of private table should fail when unauthenticated", - function(done) { - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table_private_1/style', - method: 'GET' - },{ - }, function(res) { - assert.equal(res.statusCode, 403, res.statusCode + ':' + res.body); - assert.deepEqual(JSON.parse(res.body), - {error: 'Sorry, you are unauthorized (permission denied)'}); - assert.ok(!res.headers.hasOwnProperty('cache-control')); - done(); - }); - }); - - // See http://github.com/Vizzuality/Windshaft-cartodb/issues/55 - test("get'ing style of private table should fail on unknown username", - function(done) { - assert.response(server, { - headers: {host: 'unknown_user'}, - url: '/tiles/test_table_private_1/style', - method: 'GET' - },{ - }, function(res) { - // FIXME: should be 403 Forbidden or 404 User Not Found - assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body); - assert.deepEqual(JSON.parse(res.body), - {error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"}); - assert.ok(!res.headers.hasOwnProperty('cache-control')); - done(); - }); - }); - - test("get'ing style of private table should succeed when authenticated", - function(done) { - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table_private_1/style?map_key=1234', - method: 'GET' - },{ - }, function(res) { - assert.equal(res.statusCode, 200, res.body); - var parsed = JSON.parse(res.body); - var style = _.template(default_style, {table: 'test_table_private_1'}); - assert.equal(parsed.style, style); - assert.equal(parsed.style_version, mapnik_version); - done(); - }); - }); - - // See https://github.com/CartoDB/Windshaft-cartodb/issues/94 - test("get'ing unrenderable style", function(done) { - var base_key = 'map_style|'+test_database+'|issue94'; - var style = '#s{bogus}'; - Step( - function checkRedis() { - redis_client.keys(base_key+'*', this); - }, - function setupRedisBase(err, matches) { - if ( err ) throw err; - assert.equal(matches.length, 0, - 'Unexpected redis keys at test start: ' + matches.join("\n")); - redis_client.set(base_key, - JSON.stringify({ style: style }), - this); - }, - function getStyle(err) { - if ( err ) throw err; - var next = this; - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/issue94/style', - method: 'GET' - }, {}, function(res) { next(null, res); }); - }, - function checkStyle(err, res) { - if ( err ) throw err; - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var parsed = JSON.parse(res.body); - assert.equal(parsed.style, style); - return null - }, - function finish(err) { - done(err); - } - ); - }); - - ///////////////////////////////////////////////////////////////////////////////// - // - // POST STYLE - // - ///////////////////////////////////////////////////////////////////////////////// - - test("post'ing no style returns 400 with errors", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_table/style', - method: 'POST' - },{ - body: '{"error":"must send style information"}' - }, function(res) { - assert.equal(res.statusCode, 400); - assert.ok(!res.headers.hasOwnProperty('cache-control')); - done(); - }); - }); - - test("post'ing bad style returns 400 with error", function(done){ - assert.response(server, { - url: '/tiles/my_table3/style?map_key=1234', - method: 'POST', - headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' }, - data: querystring.stringify({style: '#my_table3{backgxxxxxround-color:#fff;}'}) - },{ - status: 400, - body: /Unrecognized rule: backgxxxxxround-color/ - }, function() { done(); }); - }); - - test("post'ing unparseable style returns 400 with error", function(done){ - assert.response(server, { - url: '/tiles/my_table3/style?map_key=1234', - method: 'POST', - headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' }, - data: querystring.stringify({style: '#my_table3{'}) - },{}, function(res) { - assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body); - assert.ok( RegExp(/missing closing/i).test(res.body) ); - done(); - }); - }); - - test("post'ing multiple bad styles returns 400 with error array", function(done){ - assert.response(server, { - url: '/tiles/my_table4/style?map_key=1234', - method: 'POST', - headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' }, - data: querystring.stringify({style: '#my_table4{backgxxxxxround-color:#fff;foo:bar}'}) - },{ - status: 400, - }, function(res) { - var parsed = JSON.parse(res.body); - assert.equal(parsed.length, 2); - assert.ok( RegExp(/Unrecognized rule: backgxxxxxround-color/).test(parsed[0]) ); - assert.ok( RegExp(/Unrecognized rule: foo/).test(parsed[1]) ); - done(); - }); - }); - - test("post'ing good style returns 200", function(done){ - assert.response(server, { - url: '/tiles/my_table5/style?map_key=1234', - method: 'POST', - headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' }, - data: querystring.stringify({style: 'Map { background-color:#fff; }'}) - },{ - }, function(res) { - assert.equal(res.statusCode, 200, res.body); - done(); - }); - }); - - // See https://github.com/Vizzuality/Windshaft-cartodb/issues/38 - test("post'ing good style with auth passed as api_key returns 200", function(done){ - assert.response(server, { - url: '/tiles/my_table5/style?api_key=1234', - method: 'POST', - headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' }, - data: querystring.stringify({style: 'Map { background-color:#fff; }'}) - },{}, function(res) { - assert.equal(res.statusCode, 200, res.body); - done(); - }); - }); - - // See https://github.com/Vizzuality/cartodb-management/issues/155 - test("post'ing good style with no authentication returns an error", function(done){ - assert.response(server, { - url: '/tiles/my_table5/style?map_key=1234', - method: 'POST', - headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' }, - data: querystring.stringify({style: 'Map { background-color:#fff; }'}) - },{ - }, function(res) { - assert.equal(res.statusCode, 200, res.body); - assert.response(server, { - url: '/tiles/my_table5/style', - method: 'POST', - headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' }, - data: querystring.stringify({style: 'Map { background-color:#aaa; }'}) - },{}, function(res) { - // FIXME: should be 403 Forbidden - assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body); - assert.ok(res.body.indexOf('map state cannot be changed by unauthenticated request') != -1, res.body); - - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_table5/style', - method: 'GET' - },{ - status: 200, - }, function(res) { - var parsed = JSON.parse(res.body); - assert.equal(parsed.style, 'Map { background-color:#fff; }'); - assert.equal(parsed.style_version, '2.0.0'); - done(); - }); - - }); - }); - }); - - test("post'ing good style returns 200 then getting returns original style", function(done){ - var style = 'Map { background-color:#fff; }'; - assert.response(server, { - url: '/tiles/my_table5/style?map_key=1234', - method: 'POST', - headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' }, - data: querystring.stringify({style: style, style_version: '2.0.2'}) - },{}, function(res) { - - assert.equal(res.statusCode, 200, res.body); - - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_table5/style', - method: 'GET' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.body); - var parsed = JSON.parse(res.body); - assert.equal(parsed.style, style); - assert.equal(parsed.style_version, '2.0.2'); - - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_table5/style?style_convert=true', - method: 'GET' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.body); - var parsed = JSON.parse(res.body); - assert.equal(parsed.style, style); - assert.equal(parsed.style_version, mapnik_version); - done(); - }); - }); - - }); - - }); - - test("post'ing good style with style_convert returns 200 then getting returns converted style", function(done){ - var style = 'Map { background-color:#fff; }'; - assert.response(server, { - url: '/tiles/my_table5/style?map_key=1234', - method: 'POST', - headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' }, - data: querystring.stringify({style: style, style_version: '2.0.2', style_convert: true}) - },{}, function(res) { - - assert.equal(res.statusCode, 200, res.body); - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_table5/style', - method: 'GET' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.body); - var parsed = JSON.parse(res.body); - // NOTE: no transform expected for the specific style - assert.equal(parsed.style, style); - assert.equal(parsed.style_version, mapnik_version); - done(); - }); - }); - }); - - ///////////////////////////////////////////////////////////////////////////////// - // - // DELETE STYLE - // - ///////////////////////////////////////////////////////////////////////////////// - - // Test that unauthenticated DELETE should fail - // See https://github.com/Vizzuality/cartodb-management/issues/155 - test("delete'ing style with no authentication returns an error", function(done){ - assert.response(server, { - url: '/tiles/my_table5/style', - method: 'DELETE', - headers: {host: 'localhost'}, - },{}, function(res) { - // FIXME: should be 403 Forbidden - assert.equal(res.statusCode, 400, res.body); - assert.ok(res.body.indexOf('map state cannot be changed by unauthenticated request') != -1, res.body); - // check that the style wasn't really deleted ! - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_table5/style?map_key=1234', - method: 'GET' - },{ - status: 200, - }, function(res) { - var parsed = JSON.parse(res.body); - assert.equal(parsed.style, 'Map { background-color:#fff; }'); - //assert.equal(parsed.version, '2.0.0'); - done(); - }); - }); - }); - - test("delete'ing style returns 200 then getting returns default style", function(done){ - // this is the default style - var style = _.template(default_style, {table: 'my_table5'}); - assert.response(server, { - url: '/tiles/my_table5/style?map_key=1234', - method: 'DELETE', - headers: {host: 'localhost'}, - },{}, function(res) { - assert.equal(res.statusCode, 200, res.body); - - // Retrive style with authenticated request - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_table5/style?map_key=1234', - method: 'GET' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.body); - assert.deepEqual(JSON.parse(res.body).style, style); - - // Now retrive style with unauthenticated request - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_table5/style', - method: 'GET' - }, {}, function(res) { - assert.equal(res.statusCode, 200, res.body); - assert.deepEqual(JSON.parse(res.body).style, style); - - done(); - }); - }); - - }); - }); - - // See https://github.com/Vizzuality/Windshaft-cartodb/issues/38 - test("delete'ing style with api_key is accepted", function(done){ - assert.response(server, { - url: '/tiles/my_table5/style?api_key=1234', - method: 'DELETE', - headers: {host: 'localhost'}, - },{}, function(res) { - assert.equal(res.statusCode, 200, res.body); - done(); - }); - }); - - ///////////////////////////////////////////////////////////////////////////////// - // - // GET INFOWINDOW - // - ///////////////////////////////////////////////////////////////////////////////// - - test("get'ing blank infowindow returns blank", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_tablez/infowindow', - method: 'GET' - },{ - status: 200, - headers: { 'X-Cache-Channel': test_database+':my_tablez' }, - body: '{"infowindow":null}' - }, function() { done(); }); - }); - - test("get'ing blank infowindow with callback returns blank with callback", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_tablez/infowindow?callback=simon', - method: 'GET' - },{ - status: 200, - body: 'simon({"infowindow":null});' - }, function() { done(); }); - }); - - - test("get'ing completed infowindow with callback returns information with callback", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/my_table/infowindow?callback=simon', - method: 'GET' - },{ - status: 200, - body: 'simon({"infowindow":"this, that, the other"});' - }, function() { done(); }); - }); - - test("get'ing infowindow of private table should fail when unauthenticated", - function(done) { - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table_private_1/infowindow', - method: 'GET' - },{}, function(res) { - // FIXME: should be 403 Forbidden - assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body); - done(); - }); - }); - - // See http://github.com/Vizzuality/Windshaft-cartodb/issues/55 - test("get'ing infowindow of private table should fail on unknown username", - function(done) { - assert.response(server, { - headers: {host: 'unknown_user'}, - url: '/tiles/test_table_private_1/infowindow', - method: 'GET' - },{ - }, function(res) { - // FIXME: should be 403 Forbidden - assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body); - assert.deepEqual(JSON.parse(res.body), - {error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"}); - done(); - }); - }); - - test("get'ing infowindow of private table should succeed when authenticated", - function(done) { - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table_private_1/infowindow?map_key=1234', - method: 'GET' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.body); - done(); - }); - }); - - ///////////////////////////////////////////////////////////////////////////////// - // - // GET GRID - // - ///////////////////////////////////////////////////////////////////////////////// - - test("get'ing a json with default style should return an grid", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.grid.json', - method: 'GET' - },{ - status: 200, - headers: { 'Content-Type': 'application/json; charset=utf-8', - 'X-Cache-Channel': test_database+':gadm4' } - }, function() { done(); }); - }); - - test("get'ing a json with default style should return an grid", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.grid.json', - method: 'GET' - },{ - status: 200, - headers: { 'Content-Type': 'application/json; charset=utf-8' } - }, function() { done(); }); - }); - - test("get'ing a json with default style and sql should return a constrained grid", function(done){ - var sql = querystring.stringify({sql: "SELECT * FROM gadm4 WHERE codineprov = '08'"}) - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.grid.json?' + sql, - method: 'GET' - },{ - status: 200, - headers: { 'Content-Type': 'application/json; charset=utf-8' } - }, function() { done(); }); - }); - - test("get'ing the grid of a private table should fail when unauthenticated", - function(done) { - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table_private_1/6/31/24.grid.json', - method: 'GET' - },{}, function(res) { - // 403 Forbidden - assert.equal(res.statusCode, 403, res.statusCode + ': ' + res.body); - done(); - }); - }); - - // See http://github.com/CartoDB/Windshaft-cartodb/issues/186 - test("get'ing the grid of a private table should fail when unauthenticated (jsonp)", - function(done) { - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table_private_1/6/31/24.grid.json?callback=x', - method: 'GET' - },{}, function(res) { - // It's forbidden, but jsonp calls for status = 200 - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - // Still, we do NOT want to add caching headers here - // See https://github.com/CartoDB/Windshaft-cartodb/issues/186 - assert.ok(!res.headers.hasOwnProperty('cache-control'), - "Unexpected Cache-Control: " + res.headers['cache-control']); - done(); - }); - }); - - // See http://github.com/Vizzuality/Windshaft-cartodb/issues/55 - test("get'ing grid of private table should fail on unknown username", - function(done) { - assert.response(server, { - headers: {host: 'unknown_user'}, - url: '/tiles/test_table_private_1/6/31/24.grid.json', - method: 'GET' - },{ - }, function(res) { - // FIXME: should be 403 Forbidden - assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body); - assert.deepEqual(JSON.parse(res.body), - {error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"}); - done(); - }); - }); - - test("get'ing the grid of a private table should succeed when authenticated", - function(done) { - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table_private_1/6/31/24.grid.json?map_key=1234', - method: 'GET' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.body); - done(); - }); - }); - - ///////////////////////////////////////////////////////////////////////////////// - // - // GET TILE - // - ///////////////////////////////////////////////////////////////////////////////// - - test("should send Cache-Control header with short expiration by default", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.png', - method: 'GET' - },{ - status: 200, - }, function(res) { - var cc = res.headers['cache-control']; - assert.ok(cc); - //assert.equal(cc, 'public,max-age=31536000'); // 1 year - assert.ok(cc.match('no-cache'), cc); - assert.ok(cc.match('must-revalidate'), cc); - assert.ok(cc.match('public'), cc); - done(); - }); - }); - - test("should send Cache-Control header with long expiration when requested", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.png?cache_policy=persist', - method: 'GET' - },{ - status: 200, - }, function(res) { - var cc = res.headers['cache-control']; - assert.ok(cc); - assert.equal(cc, 'public,max-age=31536000'); // 1 year - done(); - }); - }); - - test("get'ing a tile with default style should return an image", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.png?geom_type=polygon', - method: 'GET' - },{ - status: 200, - headers: { 'Content-Type': 'image/png', 'X-Cache-Channel': test_database+':gadm4' } - }, function() { done(); }); - }); - - test("get'ing a tile with default style and sql should return a constrained image", function(done){ - var sql = querystring.stringify({sql: "SELECT * FROM gadm4 WHERE codineprov = '08'"}); - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.png?' + sql, - method: 'GET' - },{ - status: 200, - headers: { 'Content-Type': 'image/png' } - }, function() { done(); }); - }); - - - test("get'ing a tile with default style and complex sql should return a constrained image", function(done){ - var sql = querystring.stringify({sql: "SELECT * FROM gadm4 WHERE codineprov = '08' AND codccaa > 60"}) - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.png?' + sql, - method: 'GET' - },{ - status: 200, - headers: { 'Content-Type': 'image/png' } - }, function() { done(); }); - }); - - test("get'ing a tile with data from private table should succeed when authenticated", function(done){ - // NOTE: may fail if grainstore < 0.3.0 is used by Windshaft - var sql = querystring.stringify({sql: "SELECT * FROM test_table_private_1", map_key: 1234}) - assert.response(server, { - headers: {host: 'localhost'}, - // NOTE: we encode a public table in the URL ! - url: '/tiles/gadm4/6/31/24.png?' + sql, - method: 'GET' - },{ - status: 200, - headers: { 'Content-Type': 'image/png' } - }, function() { done(); }); - }); - - // See https://github.com/Vizzuality/Windshaft-cartodb/issues/38 - test("get'ing a tile with data from private table should succeed when authenticated with api_key", function(done){ - // NOTE: may fail if grainstore < 0.3.0 is used by Windshaft - var sql = querystring.stringify({sql: "SELECT * FROM test_table_private_1", api_key: 1234}) - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.png?' + sql, - method: 'GET' - },{ - status: 200, - headers: { 'Content-Type': 'image/png' } - }, function() { done(); }); - }); - - test("get'ing a tile with data from private table should fail when unauthenticated", function(done){ - var sql = querystring.stringify({ - sql: "SELECT * FROM test_table_private_1", - cache_buster:2 // this is to avoid getting the cached response - }); - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.png?' + sql, - method: 'GET' - },{ - }, function(res) { - // 403 Forbidden - assert.equal(res.statusCode, 403, res.statusCode + ': ' + res.body); - done(); - }); - }); - - test("get'ing a tile with data from private table should fail on unknown username", function(done){ - var sql = querystring.stringify({ - sql: "SELECT * FROM test_table_private_1", - cache_buster:2 // this is to avoid getting the cached response - }); - assert.response(server, { - headers: {host: 'unknown_user'}, - url: '/tiles/gadm4/6/31/24.png?' + sql, - method: 'GET' - },{ - }, function(res) { - // FIXME: should be 403 Forbidden - assert.equal(res.statusCode, 400, res.statusCode + ': ' + res.body); - assert.deepEqual(JSON.parse(res.body), - {error:"missing unknown_user's database_name in redis (try CARTODB/script/restore_redis)"}); - assert.ok(!res.headers.hasOwnProperty('cache-control'), - "Unexpected Cache-Control: " + res.headers['cache-control']); - done(); - }); - }); - - test("get'ing a tile with data from private table should fail when unauthenticated (uses old redis key)", function(done){ - var sql = querystring.stringify({ - sql: "SELECT * FROM test_table_private_1", - cache_buster:3, - // 1235 is written in rails:users:localhost:map_key SET - // See https://github.com/Vizzuality/Windshaft-cartodb/issues/39 - map_key: 1235 - }); - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.png?' + sql, - method: 'GET' - },{ - }, function(res) { - // 403 Forbidden - assert.equal(res.statusCode, 403, res.statusCode + ': ' + res.body); - // Failed in 1.6.0 of https://github.com/CartoDB/Windshaft-cartodb/issues/107 - assert.ok(!res.headers.hasOwnProperty('cache-control'), - "Unexpected Cache-Control: " + res.headers['cache-control']); - done(); - }); - }); - - test("get'ing a tile with url specified 2.0.0 style should return an expected tile", function(done){ - var style = querystring.stringify({style: test_style_black_200, style_version: '2.0.0'}); - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table/15/16046/12354.png?cache_buster=4&' + style, // madrid - method: 'GET', - encoding: 'binary' - },{}, function(res){ - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var ct = res.headers['content-type']; - assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, - function(err, similarity) { - if (err) throw err; - done(); - }); - }); - }); - - // See https://github.com/CartoDB/Windshaft-cartodb/issues/88 - test("get'ing a tile from a user-specific database should return an expected tile", function(done){ - var style = querystring.stringify({style: test_style_black_200, style_version: '2.0.0'}); - var backupDBHost = global.environment.postgres.host; - global.environment.postgres.host = '6.6.6.6'; - Step ( - function() { - var next = this; - assert.response(server, { - headers: {host: 'cartodb250user'}, - url: '/tiles/test_table/15/16046/12354.png?cache_buster=4.10&' + style, - method: 'GET', - encoding: 'binary' - },{}, function(res){ - next(null, res); - }); - }, - function checkRes(err, res) { - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var ct = res.headers['content-type']; - assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, - './test/fixtures/test_table_15_16046_12354_styled_black.png', - IMAGE_EQUALS_TOLERANCE_PER_MIL, this); - }, - function checkImage(err, similarity) { - if (err) throw err; - return null - }, - function finish(err) { - global.environment.postgres.host = backupDBHost; - done(err); - } - ); - }); - - // See https://github.com/CartoDB/Windshaft-cartodb/issues/89 - test("get'ing a tile with a user-specific database password", function(done){ - var style = querystring.stringify({style: test_style_black_200, style_version: '2.0.0'}); - var backupDBPass = global.environment.postgres_auth_pass; - global.environment.postgres_auth_pass = '<%= user_password %>'; - Step ( - function() { - var next = this; - assert.response(server, { - headers: {host: 'cartodb250user'}, - url: '/tiles/test_table/15/16046/12354.png?' - + 'cache_buster=4.20&api_key=4321&' + style, - method: 'GET', - encoding: 'binary' - },{}, function(res){ - next(null, res); - }); - }, - function checkRes(err, res) { - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var ct = res.headers['content-type']; - assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, - './test/fixtures/test_table_15_16046_12354_styled_black.png', - IMAGE_EQUALS_TOLERANCE_PER_MIL, this); - }, - function checkImage(err, similarity) { - if (err) throw err; - return null - }, - function finish(err) { - global.environment.postgres_auth_pass = backupDBPass; - done(err); - } - ); - }); - - test("get'ing a tile with url specified 2.1.0 style should return an expected tile", function(done){ - var style = querystring.stringify({style: test_style_black_210, style_version: '2.1.0'}); - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table/15/16046/12354.png?cache_buster=4&' + style, // madrid - method: 'GET', - encoding: 'binary' - },{}, function(res){ - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var ct = res.headers['content-type']; - assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, - function(err, similarity) { - if (err) throw err; - done(); - }); - }); - }); - - test("get'ing a tile with url specified 2.1.0 style (lzma version)", function(done){ - var qo = { - style: test_style_black_210, - style_version: '2.1.0', - cache_buster: 5 - }; - Step ( - function compressQuery () { - //console.log("Compressing starts"); - var next = this; - helper.lzma_compress_to_base64(JSON.stringify(qo), 1, this); - }, - function sendRequest(err, lzma) { - if ( err ) throw err; - var next = this; - //console.log("Compressing ends: " + typeof(lzma) + " - " + lzma); - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table/15/16046/12354.png?lzma=' + encodeURIComponent(lzma), - method: 'GET', - encoding: 'binary' - },{}, function(res) { next(null, res); }); - }, - function checkResponse(err, res) { - if ( err ) throw err; - var next = this; - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var ct = res.headers['content-type']; - assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, - function(err, similarity) { - next(err); - }); - }, - function finish(err) { - done(err); - } - ); - }); - - // See http://github.com/Vizzuality/Windshaft-cartodb/issues/57 - test("GET'ing a tile as anonymous with style set by POST", function(done){ - var style = querystring.stringify({style: test_style_black_210, style_version: '2.1.0'}); - Step ( - function postStyle1() { - var next = this; - assert.response(server, { - method: 'POST', - url: '/tiles/test_table/style', - headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' }, - data: querystring.stringify({style: 'Map { background-color:#fff; }', map_key: 1234}) - },{}, function(res) { - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - next(); - }); - }, - // Load the new cache with results from Style1 above - function getTileAnon1(err) { - if ( err ) throw err; - var next = this; - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table/15/16046/12354.png', - method: 'GET', - encoding: 'binary' - },{}, function(res){ - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var ct = res.headers['content-type']; - assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/blank.png', IMAGE_EQUALS_ZERO_TOLERANCE_PER_MIL, - function(err, similarity) { - if (err) next(err); - else next(); - }); - }); - }, - // Get again with authentication - function getTileAuth1(err) { - if ( err ) throw err; - var next = this; - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table/15/16046/12354.png?map_key=1234', - method: 'GET', - encoding: 'binary' - },{}, function(res){ - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var ct = res.headers['content-type']; - assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/blank.png', IMAGE_EQUALS_ZERO_TOLERANCE_PER_MIL, - function(err, similarity) { - if (err) next(err); - else next(); - }); - }); - }, - // Change the style - function postStyle2(err) { - if ( err ) throw err; - var next = this; - assert.response(server, { - method: 'POST', - url: '/tiles/test_table/style', - headers: {host: 'localhost', 'Content-Type': 'application/x-www-form-urlencoded' }, - data: querystring.stringify({style: test_style_black_200, map_key: 1234}) - },{}, function(res) { - try { - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - next(); - } - catch (err) { next(err); } - }); - }, - // Verify the Style2 is applied. NOTE: pass the SAME cache_buster as before! - function getTileAnon2(err) { - if ( err ) throw err; - var next = this; - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table/15/16046/12354.png', - method: 'GET', - encoding: 'binary' - },{}, function(res){ - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var ct = res.headers['content-type']; - assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, - function(err, similarity) { - // NOTE: we expect them to be EQUAL here - if (err) { next(err); return; } - next(); - }); - }); - }, - // Delete the style - function delStyle(err) { - if ( err ) throw err; - var next = this; - assert.response(server, { - method: 'DELETE', - url: '/tiles/test_table/style?map_key=1234', - headers: {host: 'localhost'} - },{}, function(res) { - try { - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - next(); - } - catch (err) { next(err); } - }); - }, - // Verify the default style is applied. - function getTileAnon3(err) { - if ( err ) throw err; - var next = this; - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table/15/16046/12354.png?cache_buster=2314', - method: 'GET', - encoding: 'binary' - },{}, function(res){ - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var ct = res.headers['content-type']; - assert.equal(ct, 'image/png'); - assert.imageEqualsFile(res.body, './test/fixtures/test_table_15_16046_12354_styled_black.png', IMAGE_EQUALS_TOLERANCE_PER_MIL, - function(err, similarity) { - // NOTE: we expect them to be different here - if (err) next(); - else next(new Error('Last posted style still in effect after delete')); - }); - }); - }, - function finish(err) { - done(err); - } - ); - }); - - test("uses sqlapi to figure source data of query", function(done){ - var qo = { - sql: "SELECT g.cartodb_id, g.codineprov, t.the_geom_webmercator " - + "FROM gadm4 g, test_table t " - + "WHERE g.cartodb_id = t.cartodb_id", - map_key: 1234 - }; - var sqlapi; - Step( - function sendRequest(err) { - var next = this; - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.png?' + querystring.stringify(qo), - method: 'GET' - },{}, function(res) { next(null, res); }); - }, - function checkResponse(err, res) { - if ( err ) throw err; - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var ct = res.headers['content-type']; - assert.equal(ct, 'image/png'); - var cc = res.headers['x-cache-channel']; - assert(cc, 'Missing X-Cache-Channel'); - var dbname = test_database; - assert.equal(cc.substring(0, dbname.length), dbname); - if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API - var jsonquery = cc.substring(dbname.length + 1); - var sentquery = JSON.parse(jsonquery); - assert.equal(sentquery.api_key, qo.map_key); - assert.equal(sentquery.q, 'SELECT CDB_QueryTables($windshaft$' + qo.sql + '$windshaft$)'); - } - return null; - }, - function finish(err) { - done(err); - } - ); - }); - - if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was a post if it's using the SQL API - test("passes hostname header to sqlapi", function(done){ - var qo = { - sql: "SELECT * from gadm4", - map_key: 1234 - }; - var sqlapi; - Step( - function sendRequest(err) { - var next = this; - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.png?' + querystring.stringify(qo), - method: 'GET' - },{}, function(res) { next(null, res); }); - }, - function checkResponse(err, res) { - if ( err ) throw err; - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var last_request = sqlapi_server.getLastRequest(); - assert.ok(last_request); - var host = last_request.headers['host']; - assert.ok(host); - assert.equal(last_request.method, 'GET'); - assert.equal(host, 'localhost.donot_look_this_up'); - return null; - }, - function finish(err) { - done(err); - } - ); - }); - } - - if (!cdbQueryTablesFromPostgresEnabledValue) { // only test if it was using the SQL API - test("requests to skip cache on sqlapi error", function(done){ - var qo = { - sql: "SELECT g.cartodb_id, g.codineprov, t.the_geom_webmercator " - + ", 'SQLAPIERROR' is not null " - + "FROM gadm4 g, test_table t " - + "WHERE g.cartodb_id = t.cartodb_id", - map_key: 1234 - }; - var sqlapi; - Step( - function sendRequest(err) { - var next = this; - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/6/31/24.png?' + querystring.stringify(qo), - method: 'GET' - },{}, function(res) { next(null, res); }); - }, - function checkResponse(err, res) { - if ( err ) throw err; - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - var ct = res.headers['content-type']; - assert.equal(ct, 'image/png'); - // does NOT send an x-cache-channel - assert.ok(!res.headers.hasOwnProperty('x-cache-channel')); - return null; - }, - function finish(err) { - done(err); - } - ); - }); - } - - // Zoom is a special variable - test("Specifying zoom level in CartoCSS does not need a 'zoom' variable in SQL output", function(done){ - // NOTE: may fail if grainstore < 0.3.0 is used by Windshaft - var query = querystring.stringify({ - sql: "SELECT 'SRID=3857;POINT(0 0)'::geometry as the_geom_webmercator, 1::int as cartodb_id", - style: '#gadm4 [ zoom>=3] { marker-fill:red; }' - }); - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/0/0/0.png?' + query, - method: 'GET' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - done(); - }); - }); +suite('server old_api', function() { // See https://github.com/CartoDB/Windshaft-cartodb/issues/115 test.skip("get'ing tile with not-strictly-valid style", function(done) { @@ -1264,137 +75,4 @@ suite('server:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() { }); }); - ///////////////////////////////////////////////////////////////////////////////// - // - // DELETE CACHE - // - ///////////////////////////////////////////////////////////////////////////////// - - test("forbids flushing cache without specifying table name", function(done) { - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/flush_cache', - method: 'DELETE' - },{}, function(res) { - assert.equal(res.statusCode, 404, res.statusCode + ': ' + res.body); - assert.ok(!res.headers.hasOwnProperty('cache-control')); - done(); - }); - }); - - test("allows flushing table cache by unauthenticated user", function(done) { - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/flush_cache', - method: 'DELETE' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - // TODO: also check that varnish is signalled (using VarnishEmu) - // NOTE: requires enable_cache=1 in test.js - done(); - }); - }); - - ///////////////////////////////////////////////////////////////////////////////// - // - // GET METADATA - // - ///////////////////////////////////////////////////////////////////////////////// - - test("does not provide metadata of private table to unauthenticated requests", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table_private_1/map_metadata', - method: 'GET' - },{}, function(res) { - // FIXME: should be 403 instead - assert.equal(res.statusCode, 500, res.statusCode + ': ' + res.body); - assert.ok(!res.headers.hasOwnProperty('cache-control')); - done(); - }); - }); - - test("does provide metadata of private table to authenticated requests", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/test_table_private_1/map_metadata?map_key=1234', - method: 'GET' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - done(); - }); - }); - - test("does provide metadata of public table to unauthenticated requests", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/map_metadata', - method: 'GET' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - // TODO: show metadata ? - done(); - }); - }); - - test("does provide metadata of public table to authenticated requests", function(done){ - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/map_metadata?map_key=1234', - method: 'GET' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - // TODO: show metadata ? - done(); - }); - }); - - ///////////////////////////////////////////////////////////////////////////////// - // - // DELETE CACHE - // - ///////////////////////////////////////////////////////////////////////////////// - - test("forbids flushing cache without specifying table name", function(done) { - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/flush_cache', - method: 'DELETE' - },{}, function(res) { - assert.equal(res.statusCode, 404, res.statusCode + ': ' + res.body); - assert.ok(!res.headers.hasOwnProperty('cache-control')); - done(); - }); - }); - - test("allows flushing table cache by unauthenticated user", function(done) { - assert.response(server, { - headers: {host: 'localhost'}, - url: '/tiles/gadm4/flush_cache', - method: 'DELETE' - },{}, function(res) { - assert.equal(res.statusCode, 200, res.statusCode + ': ' + res.body); - // TODO: also check that varnish is signalled (using VarnishEmu) - // NOTE: requires enable_cache=1 in test.js - done(); - }); - }); - - ///////////////////////////////////////////////////////////////////////////////// - // - // Tear down - // - ///////////////////////////////////////////////////////////////////////////////// - - suiteTeardown(function(done) { - // This test will add map_style records, like - // 'map_style|null|publicuser|my_table', - redis_client.keys("map_style|*", function(err, matches) { - _.each(matches, function(k) { redis_client.del(k); }); - sqlapi_server.close(done); - }); - }); - -}); - }); diff --git a/test/acceptance/templates.js b/test/acceptance/templates.js index f0df7414..70339e6e 100644 --- a/test/acceptance/templates.js +++ b/test/acceptance/templates.js @@ -6,7 +6,6 @@ var semver = require('semver'); var Step = require('step'); var strftime = require('strftime'); var NamedMapsCacheEntry = require(__dirname + '/../../lib/cartodb/cache/model/named_maps_entry'); -var SQLAPIEmu = require(__dirname + '/../support/SQLAPIEmu.js'); var redis_stats_db = 5; // Pollute the PG environment to make sure @@ -25,22 +24,13 @@ var serverOptions = ServerOptions(); var server = new CartodbWindshaft(serverOptions); server.setMaxListeners(0); -[true, false].forEach(function(cdbQueryTablesFromPostgresEnabledValue) { - -suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, function() { +suite('template_api', function() { serverOptions.channelCache = {}; var redis_client = redis.createClient(global.environment.redis.port); - var sqlapi_server; var expected_last_updated_epoch = 1234567890123; // this is hard-coded into SQLAPIEmu var expected_last_updated = new Date(expected_last_updated_epoch).toISOString(); - suiteSetup(function(done){ - global.environment.enabledFeatures = { cdbQueryTablesFromPostgres: cdbQueryTablesFromPostgresEnabledValue }; - sqlapi_server = new SQLAPIEmu(global.environment.sqlapi.port, done); - // TODO: check redis is clean ? - }); - var template_acceptance1 = { version: '0.0.1', name: 'acceptance1', @@ -87,7 +77,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio var expected_failure = false; var expected_tpl_id = "localhost@acceptance1"; var post_request_1 = { - url: '/tiles/template', + url: '/api/v1/map/named', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_acceptance1) @@ -174,7 +164,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio broken_template.auth.method = 'token'; delete broken_template.auth.tokens; var post_request_1 = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(broken_template) @@ -204,7 +194,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio broken_template.auth.method = 'token'; broken_template.auth.tokens = []; var post_request_1 = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(broken_template) @@ -231,7 +221,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio var broken_template = JSON.parse(JSON.stringify(template_acceptance1)); broken_template.name = 'broken1'; var post_request_1 = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(broken_template) @@ -255,7 +245,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio broken_template.auth.method = 'token'; broken_template.auth.tokens = []; var put_request_1 = { - url: '/tiles/template/' + tpl_id + '/?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '/?api_key=1234', method: 'PUT', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(broken_template) @@ -276,7 +266,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio 'Error for invalid authentication on PUT does not match ' + re + ': ' + parsed.error); var del_request = { - url: '/tiles/template/' + tpl_id + '?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '?api_key=1234', method: 'DELETE', headers: {host: 'localhost'} } @@ -312,7 +302,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio Step(function postTemplate1(err, res) { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost.localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_acceptance1) @@ -321,7 +311,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio }, function testCORS() { assert.response(server, { - url: '/tiles/template/acceptance1', + url: '/api/v1/map/named/acceptance1', method: 'OPTIONS' },{ status: 200, @@ -341,7 +331,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio Step(function postTemplate1(err, res) { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(tmpl) @@ -353,7 +343,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio function testCORS() { var next = this; assert.response(server, { - url: '/tiles/template/' + tmpl.name, + url: '/api/v1/map/named/' + tmpl.name, method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, },{ @@ -367,7 +357,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio function deleteTemplate(err) { if ( err ) throw err; var del_request = { - url: '/tiles/template/' + tmpl.name + '?api_key=1234', + url: '/api/v1/map/named/' + tmpl.name + '?api_key=1234', method: 'DELETE', headers: {host: 'localhost', 'Content-Type': 'application/json' } } @@ -390,7 +380,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_acceptance1) @@ -411,7 +401,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio var backup_name = template_acceptance1.name; template_acceptance1.name += '_new'; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_acceptance1) @@ -430,7 +420,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio tplid2 = parsed.template_id; var next = this; var get_request = { - url: '/tiles/template', + url: '/api/v1/map/named', method: 'GET', headers: {host: 'localhost'} } @@ -448,7 +438,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.ok(err.match(/authenticated user/), err); var next = this; var get_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'GET', headers: {host: 'localhost'} } @@ -507,7 +497,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(makeTemplate()) @@ -526,7 +516,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio var backup_name = template_acceptance1.name; template_acceptance1.name = 'changed_name'; var put_request = { - url: '/tiles/template/' + tpl_id + '/?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '/?api_key=1234', method: 'PUT', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_acceptance1) @@ -545,7 +535,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.ok(parsedBody.error.match(/cannot update name/i), 'Unexpected error for invalid update: ' + parsedBody.error); var put_request = { - url: '/tiles/template/unexistent/?api_key=1234', + url: '/api/v1/map/named/unexistent/?api_key=1234', method: 'PUT', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(makeTemplate()) @@ -563,7 +553,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.ok(parsedBody.error.match(/cannot update name/i), 'Unexpected error for invalid update: ' + parsedBody.error); var put_request = { - url: '/tiles/template/' + tpl_id + '/?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '/?api_key=1234', method: 'PUT', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(makeTemplate()) @@ -619,7 +609,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(makeTemplate()) @@ -636,7 +626,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'template_id' from response body: " + res.body); tpl_id = parsed.template_id; var get_request = { - url: '/tiles/template/' + tpl_id, + url: '/api/v1/map/named/' + tpl_id, method: 'GET', headers: {host: 'localhost'} } @@ -653,7 +643,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.ok(parsedBody.error.match(/only.*authenticated.*user/i), 'Unexpected error for unauthenticated template get: ' + parsedBody.error); var get_request = { - url: '/tiles/template/' + tpl_id + '?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '?api_key=1234', method: 'GET', headers: {host: 'localhost'} } @@ -708,7 +698,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(makeTemplate()) @@ -725,7 +715,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'template_id' from response body: " + res.body); tpl_id = parsed.template_id; var get_request = { - url: '/tiles/template/' + tpl_id + '?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '?api_key=1234', method: 'GET', headers: {host: 'localhost'} } @@ -742,7 +732,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'template' from response body: " + res.body); assert.deepEqual(extendDefaultsTemplate(makeTemplate()), parsed.template); var del_request = { - url: '/tiles/template/' + tpl_id, + url: '/api/v1/map/named/' + tpl_id, method: 'DELETE', headers: {host: 'localhost'} } @@ -760,7 +750,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.ok(parsed.error.match(/only.*authenticated.*user/i), 'Unexpected error for unauthenticated template get: ' + parsed.error); var del_request = { - url: '/tiles/template/' + tpl_id + '?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '?api_key=1234', method: 'DELETE', headers: {host: 'localhost'} } @@ -774,7 +764,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.equal(res.statusCode, 204, res.statusCode + ': ' + res.body); assert.ok(!res.body, 'Unexpected body in DELETE /template response'); var get_request = { - url: '/tiles/template/' + tpl_id + '?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '?api_key=1234', method: 'GET', headers: {host: 'localhost'} } @@ -852,7 +842,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_acceptance2) @@ -869,7 +859,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'template_id' from response body: " + res.body); tpl_id = parsed.template_id; var post_request = { - url: '/tiles/template/' + tpl_id, + url: '/api/v1/map/named/' + tpl_id, method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_params) @@ -890,7 +880,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.ok(parsed.error.match(/unauthorized/i), 'Unexpected error for unauthorized instance : ' + parsed.error); var post_request = { - url: '/tiles/template/' + tpl_id + '?auth_token=valid2', + url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2', method: 'POST', headers: {host: 'foreign', 'Content-Type': 'application/json' }, data: JSON.stringify(template_params) @@ -909,7 +899,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.ok(parsed.error.match(/cannot instanciate/i), 'Unexpected error for forbidden instance : ' + parsed.error); var post_request = { - url: '/tiles/template/' + tpl_id + '?auth_token=valid2', + url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_params) @@ -933,7 +923,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'last_updated' from response body: " + res.body); // TODO: check value of last_updated ? var get_request = { - url: '/tiles/layergroup/' + layergroupid + ':cb0/0/0/0.png', + url: '/api/v1/map/' + layergroupid + ':cb0/0/0/0.png', method: 'GET', headers: {host: 'localhost' }, encoding: 'binary' @@ -953,7 +943,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio 'Unexpected error for unauthorized instance ' + '(expected /permission denied/): ' + parsed.error); var get_request = { - url: '/tiles/layergroup/' + layergroupid + '/0/0/0.png?auth_token=valid1', + url: '/api/v1/map/' + layergroupid + '/0/0/0.png?auth_token=valid1', method: 'GET', headers: {host: 'localhost' }, encoding: 'binary' @@ -975,7 +965,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio if ( err ) throw err; var foreignsigned = layergroupid.replace(/[^@]*@/, 'foreign@'); var get_request = { - url: '/tiles/layergroup/' + foreignsigned + '/0/0/0.png?auth_token=valid1', + url: '/api/v1/map/' + foreignsigned + '/0/0/0.png?auth_token=valid1', method: 'GET', headers: {host: 'localhost' }, encoding: 'binary' @@ -1001,7 +991,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { if ( err ) throw err; var del_request = { - url: '/tiles/template/' + tpl_id + '?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '?api_key=1234', method: 'DELETE', headers: {host: 'localhost'} } @@ -1014,7 +1004,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.equal(res.statusCode, 204, 'Deleting template: ' + res.statusCode + ':' + res.body); var get_request = { - url: '/tiles/layergroup/' + layergroupid + '/0/0/0.png?auth_token=valid1', + url: '/api/v1/map/' + layergroupid + '/0/0/0.png?auth_token=valid1', method: 'GET', headers: {host: 'localhost' }, encoding: 'binary' @@ -1086,7 +1076,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template) @@ -1103,7 +1093,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'template_id' from response body: " + res.body); tpl_id = parsed.template_id; var post_request = { - url: '/tiles/template/' + tpl_id, + url: '/api/v1/map/named/' + tpl_id, method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_params) @@ -1124,7 +1114,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.ok(parsed.error.match(/unauthorized/i), 'Unexpected error for unauthorized instance : ' + parsed.error); var post_request = { - url: '/tiles/template/' + tpl_id + '?auth_token=valid2', + url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_params) @@ -1148,7 +1138,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'last_updated' from response body: " + res.body); // TODO: check value of last_updated ? var get_request = { - url: '/tiles/layergroup/' + layergroupid + ':cb0/0/0/0/0.json.torque', + url: '/api/v1/map/' + layergroupid + ':cb0/0/0/0/0.json.torque', method: 'GET', headers: {host: 'localhost' }, encoding: 'binary' @@ -1168,7 +1158,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio 'Unexpected error for unauthorized instance ' + '(expected /permission denied): ' + parsed.error); var get_request = { - url: '/tiles/layergroup/' + layergroupid + ':cb1/0/0/0/0.json.torque?auth_token=valid1', + url: '/api/v1/map/' + layergroupid + ':cb1/0/0/0/0.json.torque?auth_token=valid1', method: 'GET', headers: {host: 'localhost' }, encoding: 'binary' @@ -1190,7 +1180,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio serverOptions = ServerOptions(); // need to clean channel cache server = new CartodbWindshaft(serverOptions); var get_request = { - url: '/tiles/layergroup/' + layergroupid + ':cb1/0/0/0/1.json.torque?auth_token=valid1', + url: '/api/v1/map/' + layergroupid + ':cb1/0/0/0/1.json.torque?auth_token=valid1', method: 'GET', headers: {host: 'localhost' }, encoding: 'binary' @@ -1214,7 +1204,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { if ( err ) throw err; var del_request = { - url: '/tiles/template/' + tpl_id + '?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '?api_key=1234', method: 'DELETE', headers: {host: 'localhost'} } @@ -1227,7 +1217,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.equal(res.statusCode, 204, 'Deleting template: ' + res.statusCode + ':' + res.body); var get_request = { - url: '/tiles/layergroup/' + layergroupid + ':cb2/0/0/0/0.json.torque?auth_token=valid1', + url: '/api/v1/map/' + layergroupid + ':cb2/0/0/0/0.json.torque?auth_token=valid1', method: 'GET', headers: {host: 'localhost' }, encoding: 'binary' @@ -1301,7 +1291,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template) @@ -1318,7 +1308,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'template_id' from response body: " + res.body); tpl_id = parsed.template_id; var post_request = { - url: '/tiles/template/' + tpl_id, + url: '/api/v1/map/named/' + tpl_id, method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_params) @@ -1339,7 +1329,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.ok(parsed.error.match(/unauthorized/i), 'Unexpected error for unauthorized instance : ' + parsed.error); var post_request = { - url: '/tiles/template/' + tpl_id + '?auth_token=valid2', + url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_params) @@ -1363,7 +1353,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'last_updated' from response body: " + res.body); // TODO: check value of last_updated ? var get_request = { - url: '/tiles/layergroup/' + layergroupid + ':cb0/0/attributes/5', + url: '/api/v1/map/' + layergroupid + ':cb0/0/attributes/5', method: 'GET', headers: {host: 'localhost' }, encoding: 'binary' @@ -1383,7 +1373,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio 'Unexpected error for unauthorized getAttributes ' + '(expected /permission denied/): ' + parsed.error); var get_request = { - url: '/tiles/layergroup/' + layergroupid + ':cb1/0/attributes/5?auth_token=valid2', + url: '/api/v1/map/' + layergroupid + ':cb1/0/attributes/5?auth_token=valid2', method: 'GET', headers: {host: 'localhost' }, encoding: 'binary' @@ -1404,7 +1394,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { if ( err ) throw err; var del_request = { - url: '/tiles/template/' + tpl_id + '?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '?api_key=1234', method: 'DELETE', headers: {host: 'localhost'} } @@ -1417,7 +1407,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.equal(res.statusCode, 204, 'Deleting template: ' + res.statusCode + ':' + res.body); var get_request = { - url: '/tiles/layergroup/' + layergroupid + ':cb2/0/attributes/5?auth_token=valid2', + url: '/api/v1/map/' + layergroupid + ':cb2/0/attributes/5?auth_token=valid2', method: 'GET', headers: {host: 'localhost' }, encoding: 'binary' @@ -1491,7 +1481,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_acceptance_open) @@ -1508,7 +1498,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'template_id' from response body: " + res.body); tpl_id = parsed.template_id; var post_request = { - url: '/tiles/template/' + tpl_id, + url: '/api/v1/map/named/' + tpl_id, method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_params) @@ -1560,7 +1550,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_acceptance_open) @@ -1577,7 +1567,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'template_id' from response body: " + res.body); tpl_id = parsed.template_id; var post_request = { - url: '/tiles/template/' + tpl_id + "/jsonp?callback=test", + url: '/api/v1/map/named/' + tpl_id + "/jsonp?callback=test", method: 'GET', headers: {host: 'localhost' } } @@ -1634,7 +1624,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_acceptance_open) @@ -1651,7 +1641,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'template_id' from response body: " + res.body); tpl_id = parsed.template_id; var post_request = { - url: '/tiles/template/' + tpl_id + "/jsonp?callback=test%config=" + JSON.stringify('{color:blue}'), + url: '/api/v1/map/named/' + tpl_id + "/jsonp?callback=test%config=" + JSON.stringify('{color:blue}'), method: 'GET', headers: {host: 'localhost' } } @@ -1712,7 +1702,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { if ( err ) throw err; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template) @@ -1726,7 +1716,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.equal(res.statusCode, 200, res.body); template_id = JSON.parse(res.body).template_id; var post_request = { - url: '/tiles/template/' + template_id, + url: '/api/v1/map/named/' + template_id, method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify({}) @@ -1761,7 +1751,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { if ( err ) throw err; var del_request = { - url: '/tiles/template/' + template_id + '?api_key=1234', + url: '/api/v1/map/named/' + template_id + '?api_key=1234', method: 'DELETE', headers: {host: 'localhost'} } @@ -1814,7 +1804,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio { var next = this; var post_request = { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_acceptance2) @@ -1831,7 +1821,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'template_id' from response body: " + res.body); tpl_id = parsed.template_id; var post_request = { - url: '/tiles/template/' + tpl_id + '?auth_token=valid2', + url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_params) @@ -1858,7 +1848,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio var changedTemplate = JSON.parse(JSON.stringify(template_acceptance2)); changedTemplate.auth.method = 'open'; var post_request = { - url: '/tiles/template/' + tpl_id + '/?api_key=1234', + url: '/api/v1/map/named/' + tpl_id + '/?api_key=1234', method: 'PUT', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(changedTemplate) @@ -1876,7 +1866,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio "Missing 'template_id' from response body: " + res.body); assert.equal(tpl_id, parsed.template_id); var post_request = { - url: '/tiles/template/' + tpl_id + '?auth_token=valid2', + url: '/api/v1/map/named/' + tpl_id + '?auth_token=valid2', method: 'POST', headers: {host: 'localhost', 'Content-Type': 'application/json' }, data: JSON.stringify(template_params) @@ -1971,7 +1961,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.response( server, { - url: '/tiles/template?api_key=1234', + url: '/api/v1/map/named?api_key=1234', method: 'POST', headers: { host: username, @@ -1996,7 +1986,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.response( server, { - url: '/tiles/template/' + expectedTemplateId, + url: '/api/v1/map/named/' + expectedTemplateId, method: 'POST', headers: { host: username, @@ -2025,7 +2015,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.response( server, { - url: '/tiles/layergroup/' + layergroupid + '/all/0/0/0.png', + url: '/api/v1/map/' + layergroupid + '/all/0/0/0.png', method: 'GET', headers: { host: username @@ -2055,7 +2045,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio assert.response( server, { - url: '/tiles/template/' + expectedTemplateId + '?api_key=1234', + url: '/api/v1/map/named/' + expectedTemplateId + '?api_key=1234', method: 'DELETE', headers: { host: username @@ -2111,7 +2101,7 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio redis_client.select(5, function(err) { redis_client.keys("user:localhost:mapviews*", function(err, keys) { redis_client.del(keys, function(err) { - sqlapi_server.close(done); + done(); }); }); }); @@ -2121,5 +2111,3 @@ suite('template_api:postgres=' + cdbQueryTablesFromPostgresEnabledValue, functio }); }); - -}); diff --git a/test/support/SQLAPIEmu.js b/test/support/SQLAPIEmu.js deleted file mode 100644 index 074e7ada..00000000 --- a/test/support/SQLAPIEmu.js +++ /dev/null @@ -1,81 +0,0 @@ -var http = require('http'); -var url = require('url'); -var _ = require('underscore'); - -var SQLAPIEmulator = function(port, cb) { - - this.queries = []; - var that = this; - this.requests = []; - - this.sqlapi_server = http.createServer(function(req,res) { - //console.log("server got request with method " + req.method); - var query; - - that.requests.push(req); - - if ( req.method == 'GET' ) { - query = url.parse(req.url, true).query; - that.handleQuery(query, res); - } - else if ( req.method == 'POST') { - var data = ''; - req.on('data', function(chunk) { - //console.log("GOT Chunk " + chunk); - data += chunk; - }); - req.on('end', function() { - //console.log("Data is: "); console.dir(data); - query = JSON.parse(data); - //console.log("handleQuery is " + that.handleQuery); - that.handleQuery(query, res); - }); - } - else { - that.handleQuery('SQLAPIEmu does not support method' + req.method, res); - } - }).listen(port, cb); -}; - -SQLAPIEmulator.prototype.handleQuery = function(query, res) { - this.queries.push(query); - if ( query.q.match('SQLAPIERROR') ) { - res.statusCode = 400; - res.write(JSON.stringify({'error':'Some error occurred'})); - } else if ( query.q.match('SQLAPINOANSWER') ) { - console.log("SQLAPIEmulator will never respond, on request"); - return; - } else if (query.q.match('tablenames')) { - var tableNames = JSON.stringify(query); - res.write(queryResult({tablenames: '{' + tableNames + '}', max: 1234567890.123})); - } else if ( query.q.match('EPOCH.* as max') ) { - // This is the structure of the known query sent by tiler - res.write(queryResult({max: 1234567890.123})); - } else { - if ( query.q.match('_private_') && query.api_key === undefined) { - res.statusCode = 403; - res.write(JSON.stringify({'error':'forbidden: ' + JSON.stringify(query)})); - } else { - var qs = JSON.stringify(query); - res.write(queryResult({cdb_querytables: '{' + qs + '}', max: 1234567890.123})); - } - } - res.end(); - }; - -SQLAPIEmulator.prototype.close = function(cb) { - this.sqlapi_server.close(cb); -}; - -SQLAPIEmulator.prototype.getLastRequest = function() { - return this.requests.pop(); -}; - -function queryResult(row) { - return JSON.stringify({ - rows: [row] - }); -} - -module.exports = SQLAPIEmulator; - diff --git a/test/support/VarnishEmu.js b/test/support/VarnishEmu.js deleted file mode 100644 index 6cd636d0..00000000 --- a/test/support/VarnishEmu.js +++ /dev/null @@ -1,25 +0,0 @@ -var net = require('net'); - -module.exports = function(on_cmd_recieved, test_callback) { - var self = this; - var welcome_msg = 'hi, im a varnish emu, right?'; - - self.commands_recieved = []; - - var server = net.createServer(function (socket) { - var command = ''; - socket.write("200 " + welcome_msg.length + "\n"); - socket.write(welcome_msg); - socket.on('data', function(data) { - self.commands_recieved.push(data); - on_cmd_recieved && on_cmd_recieved(self.commands_recieved); - socket.write('200 0\n'); - }); - }); - server.listen(1337, "127.0.0.1"); - - server.on('listening', function(){ - test_callback(); - }); -}; - diff --git a/test/unit/cartodb/cdb_request.test.js b/test/unit/cartodb/cdb_request.test.js new file mode 100644 index 00000000..04b2c1b9 --- /dev/null +++ b/test/unit/cartodb/cdb_request.test.js @@ -0,0 +1,60 @@ +require('../../support/test_helper'); +var assert = require('assert'); + +var CdbRequest = require('../../../lib/cartodb/models/cdb_request'); + +describe('req2params', function() { + + function createRequest(host, userParam) { + var req = { + params: {}, + headers: { + host: host + } + }; + + if (userParam) { + req.params.user = userParam; + } + + return req; + } + + it('extracts name from host header', function() { + var cdbRequest = new CdbRequest(); + var user = cdbRequest.userByReq(createRequest('localhost')); + + assert.equal(user, 'localhost'); + }); + + it('extracts name from subdomain host header in case of no config', function() { + var userFromHostConfig = global.environment.user_from_host; + global.environment.user_from_host = null; + + var cdbRequest = new CdbRequest(); + var user = cdbRequest.userByReq(createRequest('development.localhost.lan')); + + global.environment.user_from_host = userFromHostConfig; + + assert.equal(user, 'development'); + }); + + it('considers user param before headers', function() { + var cdbRequest = new CdbRequest(); + var user = cdbRequest.userByReq(createRequest('localhost', 'development')); + + assert.equal(user, 'development'); + }); + + it('returns undefined when it cannot extract username', function() { + var userFromHostConfig = global.environment.user_from_host; + global.environment.user_from_host = null; + + var cdbRequest = new CdbRequest(); + var user = cdbRequest.userByReq(createRequest('localhost')); + + global.environment.user_from_host = userFromHostConfig; + + assert.equal(user, undefined); + }); +}); diff --git a/test/unit/cartodb/req2params.test.js b/test/unit/cartodb/req2params.test.js index 90b96169..932bec05 100644 --- a/test/unit/cartodb/req2params.test.js +++ b/test/unit/cartodb/req2params.test.js @@ -1,8 +1,6 @@ -var assert = require('assert') - , _ = require('underscore') - , redis = require('redis') - , test_helper = require('../../support/test_helper') - , tests = module.exports = {}; +var assert = require('assert'); +var _ = require('underscore'); +var test_helper = require('../../support/test_helper'); suite('req2params', function() { @@ -24,7 +22,7 @@ suite('req2params', function() { assert.ok(_.isObject(req.query), 'request has query'); assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query'); assert.ok(req.hasOwnProperty('params'), 'request has params'); - assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity'); + assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity'); assert.equal(req.params.dbname, test_database, 'could forge dbname: '+ req.params.dbname); assert.ok(req.params.dbuser === test_pubuser, 'could inject dbuser ('+req.params.dbuser+')'); done(); @@ -38,7 +36,7 @@ suite('req2params', function() { assert.ok(_.isObject(req.query), 'request has query'); assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query'); assert.ok(req.hasOwnProperty('params'), 'request has params'); - assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity'); + assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity'); assert.equal(req.params.dbname, test_database); assert.ok(req.params.dbuser === test_pubuser, 'could inject dbuser ('+req.params.dbuser+')'); done(); @@ -52,7 +50,7 @@ suite('req2params', function() { assert.ok(_.isObject(req.query), 'request has query'); assert.ok(!req.query.hasOwnProperty('dbuser'), 'dbuser was removed from query'); assert.ok(req.hasOwnProperty('params'), 'request has params'); - assert.ok(req.params.hasOwnProperty('interactivity'), 'request params have interactivity'); + assert.ok(!req.params.hasOwnProperty('interactivity'), 'request params do not have interactivity'); assert.equal(req.params.dbname, test_database); assert.equal(req.params.dbuser, test_user); @@ -65,23 +63,34 @@ suite('req2params', function() { }); test('it should extend params with decoded lzma', function(done) { - var qo = { - style: 'test', - style_version: '2.1.0', - cache_buster: 5 - }; - test_helper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) { - opts.req2params({ headers: { host:'localhost' }, query: { non_included: 'toberemoved', api_key: 'test', style: 'override', lzma: data }}, function(err, req) { - if ( err ) { done(err); return; } - var query = req.params - assert.equal(qo.style, query.style) - assert.equal(qo.style_version, query.style_version) - assert.equal(qo.cache_buster, query.cache_buster) - assert.equal('test', query.api_key) - assert.equal(undefined, query.non_included) - done(); + var qo = { + config: { + version: '1.3.0' + } + }; + test_helper.lzma_compress_to_base64(JSON.stringify(qo), 1, function(err, data) { + var req = { + headers: { + host:'localhost' + }, + query: { + non_included: 'toberemoved', + api_key: 'test', + style: 'override', + lzma: data + } + }; + opts.req2params(req, function(err, req) { + if ( err ) { + return done(err); + } + var query = req.params; + assert.deepEqual(qo.config, query.config); + assert.equal('test', query.api_key); + assert.equal(undefined, query.non_included); + done(); + }); }); - }); }); - + });