From 63d303eebc597e09788ed80ff343fdf0e68b0fb9 Mon Sep 17 00:00:00 2001 From: Javier Arce Date: Mon, 22 Jun 2015 17:28:52 +0200 Subject: [PATCH 01/17] Adds search --- .../wms/select_layer.jst.ejs | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/select_layer.jst.ejs b/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/select_layer.jst.ejs index 3ac0ea051f3c..1d2f78b5625b 100644 --- a/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/select_layer.jst.ejs +++ b/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/select_layer.jst.ejs @@ -1,14 +1,31 @@
- +
+ + + + + - <% if (q && layersAvailableCount == 0) { %> + <% if (searchQuery && layersFound.length == 0) { %>
diff --git a/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/select_layer_view.js b/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/select_layer_view.js index 496f94ea711d..b5410fdf50b4 100644 --- a/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/select_layer_view.js +++ b/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/select_layer_view.js @@ -13,10 +13,13 @@ module.exports = cdb.core.View.extend({ }, render: function() { + + this.clearSubViews(); + var $el = $( cdb.templates.getTemplate('common/dialogs/add_custom_basemap/wms/select_layer')({ - q: this.options.q, - layersFound: this.model.get('layers'), + searchQuery: this.model.get('searchQuery'), + layersFound: this.model.getLayers(), layersAvailableCount: this.model.layersAvailableCount(), pluralizeStr: pluralizeStr }) @@ -28,7 +31,7 @@ module.exports = cdb.core.View.extend({ }, _renderedLayers: function() { - return this.model.get('layers').map(function(layer) { + return this.model.getLayers().map(function(layer) { var view = new LayerView({ model: layer, baseLayers: this.model.get('baseLayers') diff --git a/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/wms_model.js b/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/wms_model.js index cded7834ac2e..0f1d3e0a318a 100644 --- a/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/wms_model.js +++ b/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/wms_model.js @@ -63,6 +63,17 @@ module.exports = cdb.core.Model.extend({ } }, + getLayers: function() { + if (this.get("searchQuery")) { + var regExp = new RegExp(this.get("searchQuery"), 'i'); + return this.get("layers").filter(function(layer) { + return layer.get("name").match(regExp); + }, this); + } else { + return this.get("layers"); + } + }, + hasAlreadyAddedLayer: function() { // Already added layers are disabled to be saved for each layer return false; diff --git a/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/wms_view.js b/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/wms_view.js index 2a2af92c98cf..315a6cc686bc 100644 --- a/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/wms_view.js +++ b/lib/assets/javascripts/cartodb/common/dialogs/add_custom_basemap/wms/wms_view.js @@ -38,7 +38,6 @@ module.exports = cdb.core.View.extend({ break; case 'selectLayer': view = new SelectLayerView({ - q: this.q, model: this.model }); break; @@ -64,10 +63,6 @@ module.exports = cdb.core.View.extend({ this.addView(view); this.$el.append(view.render().el); - if (!this.q) { - this._hideCleanSearchButton(); - } - this.$(".js-search-input").focus(); return this; @@ -83,6 +78,7 @@ module.exports = cdb.core.View.extend({ _initBinds: function() { this.model.bind('change', this.render, this); + this.model.bind('change', this._onChangeSearchQuery, this); this.model.get("layers").bind('reset', this.render, this); }, @@ -111,40 +107,22 @@ module.exports = cdb.core.View.extend({ _submitSearch: function(ev) { this.killEvent(ev); - this.q = this.$(".js-search-input").val(); - - if (!this.q) { - this._cleanSearch(); - } else { + this.model.set("searchQuery", this.$(".js-search-input").val()); + }, - if (!this.fetchedLayers) { // make a copy of the fetched layers - var layers = this.model.get("layers"); - this.fetchedLayers = _.clone(layers); - } + _onChangeSearchQuery: function() { - var results = this._searchFetchedLayers(this.q); - this.model.get("layers").reset(results); + var searchQuery = this.model.get("searchQuery"); + if (!searchQuery) { + this._hideCleanSearchButton(); } - }, - - _searchFetchedLayers: function(search_term) { - var regExp = new RegExp(search_term, 'i'); - return this.fetchedLayers.filter(function(layer) { - return layer.get("name").match(regExp); - }, this); + }, _onCleanSearchClick: function(ev) { this.killEvent(ev); - this.q = null; - this._cleanSearch(); - }, - - _cleanSearch: function(ev) { - if (this.fetchedLayers) { - this.model.get("layers").reset(this.fetchedLayers.models); - } + this.model.set("searchQuery", ""); }, _onClickFetchLayers: function(ev) { From 2c498ae57eafcb017851c5f2964efa0679cab361 Mon Sep 17 00:00:00 2001 From: Kartones Date: Tue, 23 Jun 2015 16:16:02 +0200 Subject: [PATCH 13/17] #4166 removed old controllers code --- .../api/json/overlays_controller.rb | 28 +-- .../api/json/visualizations_controller.rb | 193 +----------------- 2 files changed, 10 insertions(+), 211 deletions(-) diff --git a/app/controllers/api/json/overlays_controller.rb b/app/controllers/api/json/overlays_controller.rb index 5dbe62b4f237..dade9a5047e1 100644 --- a/app/controllers/api/json/overlays_controller.rb +++ b/app/controllers/api/json/overlays_controller.rb @@ -8,24 +8,9 @@ class Api::Json::OverlaysController < Api::ApplicationController include CartoDB - ssl_required :index, :show, :create, :update, :destroy - before_filter :check_owner_by_vis, only: [ :index, :create ] - before_filter :check_owner_by_id, only: [ :show, :update, :destroy ] - - def index - # TODO: PATCH - vis_id = params.fetch('visualization_id') - vis_id, schema = table_and_schema_from(vis_id) - - vis, = locator.get(vis_id, CartoDB.extract_subdomain(request)) - visualization_id = vis.id - collection = Overlay::Collection.new( - visualization_id: visualization_id, - ).fetch - render_jsonp(collection) - rescue KeyError - head :not_found - end + ssl_required :create, :update, :destroy + before_filter :check_owner_by_vis, only: [ :create ] + before_filter :check_owner_by_id, only: [ :update, :destroy ] def create member_attributes = payload.merge( @@ -39,13 +24,6 @@ def create render_jsonp(member.attributes) end - def show - member = Overlay::Member.new(id: params.fetch('id')).fetch - render_jsonp(member.attributes) - rescue KeyError - head :not_found - end - def update member = Overlay::Member.new(id: params.fetch('id')).fetch member.attributes = payload diff --git a/app/controllers/api/json/visualizations_controller.rb b/app/controllers/api/json/visualizations_controller.rb index 6287abcdff95..f32720bbefd7 100644 --- a/app/controllers/api/json/visualizations_controller.rb +++ b/app/controllers/api/json/visualizations_controller.rb @@ -14,20 +14,13 @@ class Api::Json::VisualizationsController < Api::ApplicationController include CartoDB - ssl_allowed :vizjson2, :notify_watching, :list_watching, :likes_count, :likes_list, :add_like, :is_liked, - :remove_like - ssl_required :index, :show, :create, :update, :destroy, :set_next_id unless Rails.env.development? || Rails.env.test? - skip_before_filter :api_authorization_required, only: [:vizjson2, :likes_count, :likes_list, :add_like, - :is_liked, :remove_like, :index] - before_filter :optional_api_authorization, only: [:likes_count, :likes_list, :add_like, :is_liked, :remove_like, - :index, :vizjson2] - before_filter :table_and_schema_from_params, only: [:show, :update, :destroy, :stats, :vizjson2, - :notify_watching, :list_watching, :likes_count, :likes_list, - :add_like, :is_liked, :remove_like, :set_next_id] - - def index - current_user ? index_logged_in : index_not_logged_in - end + ssl_allowed :notify_watching, :list_watching, :add_like, :remove_like + ssl_required :create, :update, :destroy, :set_next_id unless Rails.env.development? || Rails.env.test? + skip_before_filter :api_authorization_required, only: [:add_like, :remove_like] + before_filter :optional_api_authorization, only: [:add_like, :remove_like] + before_filter :table_and_schema_from_params, only: [:update, :destroy, :stats, + :notify_watching, :list_watching, + :add_like, :remove_like, :set_next_id] def create vis_data = payload @@ -125,15 +118,6 @@ def create render_jsonp({ errors: { named_maps: exception } }, 400) end - def show - vis, = locator.get(@table_id, CartoDB.extract_subdomain(request)) - return(head 404) unless vis - return(head 403) unless vis.has_permission?(current_user, Visualization::Member::PERMISSION_READONLY) - render_jsonp(vis) - rescue KeyError - head(404) - end - def update vis, = locator.get(@table_id, CartoDB.extract_subdomain(request)) return(head 404) unless vis @@ -200,28 +184,6 @@ def destroy render_jsonp({ errors: { named_maps: exception } }, 400) end - def vizjson2 - visualization, = locator.get(@table_id, CartoDB.extract_subdomain(request)) - return(head 404) unless visualization - return(head 403) unless allow_vizjson_v2_for?(visualization) - set_vizjson_response_headers_for(visualization) - render_jsonp(visualization.to_vizjson({https_request: request.protocol == 'https://'})) - rescue KeyError => exception - render(text: exception.message, status: 403) - rescue CartoDB::NamedMapsWrapper::HTTPResponseError => exception - CartoDB.notify_exception(exception, { user: current_user, template_data: exception.template_data }) - render_jsonp({ errors: { named_maps_api: "Communication error with tiler API. HTTP Code: #{exception.message}" } }, 400) - rescue CartoDB::NamedMapsWrapper::NamedMapDataError => exception - CartoDB.notify_exception(exception) - render_jsonp({ errors: { named_map: exception.message } }, 400) - rescue CartoDB::NamedMapsWrapper::NamedMapsDataError => exception - CartoDB.notify_exception(exception) - render_jsonp({ errors: { named_maps: exception.message } }, 400) - rescue => exception - CartoDB.notify_exception(exception) - raise exception - end - def notify_watching vis = Visualization::Member.new(id: @table_id).fetch return(head 403) unless vis.has_permission?(current_user, Visualization::Member::PERMISSION_READONLY) @@ -270,36 +232,6 @@ def set_next_id rescue render_jsonp({ errors: ['Unknown error'] }, 400) end - - # Does not mandate a current_viewer except if vis is not public - def likes_count - vis = Visualization::Member.new(id: @table_id).fetch - if vis.privacy != Visualization::Member::PRIVACY_PUBLIC && vis.privacy != Visualization::Member::PRIVACY_LINK - raise KeyError if current_viewer.nil? || !vis.has_permission?(current_viewer, Visualization::Member::PERMISSION_READONLY) - end - - render_jsonp({ - id: vis.id, - likes: vis.likes.count - }) - rescue KeyError => exception - render(text: exception.message, status: 403) - end - - # Does not mandate a current_viewer except if vis is not public - def likes_list - vis = Visualization::Member.new(id: @table_id).fetch - if vis.privacy != Visualization::Member::PRIVACY_PUBLIC && vis.privacy != Visualization::Member::PRIVACY_LINK - raise KeyError if current_viewer.nil? || !vis.has_permission?(current_viewer, Visualization::Member::PERMISSION_READONLY) - end - - render_jsonp({ - id: vis.id, - likes: vis.likes.map { |like| {actor_id: like.actor } } - }) - rescue KeyError => exception - render(text: exception.message, status: 403) - end def add_like return(head 403) unless current_viewer @@ -322,31 +254,6 @@ def add_like render(text: "You've already liked this visualization", status: 400) end - def is_liked - if current_viewer - vis = Visualization::Member.new(id: @table_id).fetch - raise KeyError if vis.privacy != Visualization::Member::PRIVACY_PUBLIC && - vis.privacy != Visualization::Member::PRIVACY_LINK && - !vis.has_permission?(current_viewer, Visualization::Member::PERMISSION_READONLY) - render_jsonp({ - id: vis.id, - likes: vis.likes.count, - liked: vis.liked_by?(current_viewer.id) - }) - else - vis = Visualization::Member.new(id: @table_id).fetch - raise KeyError if vis.privacy != Visualization::Member::PRIVACY_PUBLIC && - vis.privacy != Visualization::Member::PRIVACY_LINK - render_jsonp({ - id: vis.id, - likes: vis.likes.count, - liked: false - }) - end - rescue KeyError => exception - render(text: exception.message, status: 403) - end - def remove_like return(head 403) unless current_viewer @@ -452,92 +359,6 @@ def prepare_params_for_total_count(params) end end - def index_not_logged_in - public_visualizations = [] - total_liked_entries = 0 - total_shared_entries = 0 - total_user_entries = 0 - user = User.where(username: CartoDB.extract_subdomain(request)).first - - unless user.nil? - filtered_params = params.dup.merge(scope_for(user)) - filtered_params[:unauthenticated] = true - - params_for_total_count = prepare_params_for_total_count(filtered_params) - total_user_entries = Visualization::Collection.new.count_total(params_for_total_count) - - collection = Visualization::Collection.new.fetch(filtered_params) - public_visualizations = collection.map { |vis| - begin - vis.to_hash( - public_fields_only: true, - related: false, - table: vis.table - ) - rescue => exception - puts exception.to_s + exception.backtrace.join("\n") - end - }.compact - - total_liked_entries = collection.total_liked_entries(params_for_total_count[:type]) - total_shared_entries = collection.total_shared_entries(params_for_total_count[:type]) - end - - response = { - visualizations: public_visualizations, - total_entries: public_visualizations.length, - total_user_entries: total_user_entries, - total_likes: total_liked_entries, - total_shared: total_shared_entries - } - render_jsonp(response) - end - - def index_logged_in - users_cache = {} - filters = params.dup.merge(scope_for(current_user)) - - collection = Visualization::Collection.new.fetch(filters) - - params_for_total_count = prepare_params_for_total_count(filters) - total_user_entries = Visualization::Collection.new.count_total(params_for_total_count) - - table_data = collection.map { |vis| - if vis.table.nil? - nil - else - users_cache[vis.user_id] ||= vis.user - { - name: vis.table.name, - schema: users_cache[vis.user_id].database_schema - } - end - }.compact - synchronizations = synchronizations_by_table_name(table_data) - representation = collection.map { |vis| - begin - vis.to_hash( - related: false, - table_data: !(params[:table_data] =~ /false/), - user: current_user, - table: vis.table, - synchronization: synchronizations[vis.name] - ) - rescue => exception - puts exception.to_s + exception.backtrace.join("\n") - end - }.compact - - response = { - visualizations: representation, - total_entries: collection.total_entries, - total_user_entries: total_user_entries, - total_likes: collection.total_liked_entries(params_for_total_count[:type]), - total_shared: collection.total_shared_entries(params_for_total_count[:type]) - } - render_jsonp(response) - end - # Need to always send request object to visualizations upon rendering their json def render_jsonp(obj, status = 200, options = {}) super(obj, status, options.merge({request: request})) From a6cd4408e5e0087d07bd3869e8bf43a0d0950305 Mon Sep 17 00:00:00 2001 From: Kartones Date: Tue, 23 Jun 2015 17:04:24 +0200 Subject: [PATCH 14/17] #4166 specs updated accordingly --- .../overlays_controller_shared_examples.rb | 77 - ...sualizations_controller_shared_examples.rb | 1302 ---------------- .../carto/api/overlays_controller_spec.rb | 80 +- .../api/visualizations_controller_spec.rb | 1330 ++++++++++++++++- 4 files changed, 1366 insertions(+), 1423 deletions(-) diff --git a/spec/requests/api/json/overlays_controller_shared_examples.rb b/spec/requests/api/json/overlays_controller_shared_examples.rb index 6f186d48d748..877bf438aeae 100644 --- a/spec/requests/api/json/overlays_controller_shared_examples.rb +++ b/spec/requests/api/json/overlays_controller_shared_examples.rb @@ -2,81 +2,4 @@ shared_examples_for "overlays controllers" do - describe '#both endpoints tests' do - - before(:all) do - @user = create_user( - username: 'test', - email: 'client@example.com', - password: 'clientex' - ) - @api_key = @user.api_key - - host! 'test.localhost.lan' - end - - before(:each) do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true) - delete_user_data @user - @table = create_table :user_id => @user.id - end - - after(:all) do - @user.destroy - end - - - let(:params) { { :api_key => @user.api_key, visualization_id: @table.table_visualization.id } } - - - it "Lists all overlays" do - - existing_overlay_ids = [] - get_json api_v1_visualizations_overlays_index_url(params) do |response| - response.status.should be_success - response.body.count.should == 5 # Newly created overlays have this amount of layers - existing_overlay_ids = response.body.map { |overlay| overlay['id'] } - end - - header_overlay = Overlay::Member.new({ - type: 'header', - visualization_id: params[:visualization_id], - order: 1 - }) - header_overlay.store - - text_overlay = Overlay::Member.new({ - type: 'text', - visualization_id: params[:visualization_id], - order: 2 - }) - text_overlay.store - - new_overlay_ids = [header_overlay.id, text_overlay.id] - - get_json api_v1_visualizations_overlays_index_url(params) do |response| - response.status.should be_success - current_overlay_ids = response.body.map { |overlay| overlay['id'] } - response.body.count.should == new_overlay_ids.count + existing_overlay_ids.count - # == checks order, while intersection doesn't - (current_overlay_ids & (existing_overlay_ids + new_overlay_ids) == current_overlay_ids).should eq true - end - - end - - it "Gets the details of an overlay" do - header_overlay = Overlay::Member.new({ - type: 'header', - visualization_id: params[:visualization_id] - }) - header_overlay.store - - get_json api_v1_visualizations_overlays_show_url(params.merge(id: header_overlay.id)) do |response| - response.status.should be_success - response.body[:id].should == header_overlay.id - response.body[:visualization_id].should == params[:visualization_id] - end - end - - end end diff --git a/spec/requests/api/json/visualizations_controller_shared_examples.rb b/spec/requests/api/json/visualizations_controller_shared_examples.rb index 60916c80b333..e169cbe5c79a 100644 --- a/spec/requests/api/json/visualizations_controller_shared_examples.rb +++ b/spec/requests/api/json/visualizations_controller_shared_examples.rb @@ -1,1310 +1,8 @@ # encoding: utf-8 -require_relative '../../../../app/models/visualization/member' - shared_examples_for "visualization controllers" do include CacheHelper - TEST_UUID = '00000000-0000-0000-0000-000000000000' - - DATE_ATTRIBUTES = %w{ created_at updated_at } - NORMALIZED_ASSOCIATION_ATTRIBUTES = { - attributes: DATE_ATTRIBUTES, - associations: { - 'permission' => { - attributes: DATE_ATTRIBUTES, - associations: {} - }, - 'table' => { - attributes: DATE_ATTRIBUTES, - associations: {} - } - } - } - - # Custom hash comparation, since in the ActiveModel-based controllers - # we allow some differences: - # - x to many associations can return [] instead of nil - def normalize_hash(h, normalized_attributes = NORMALIZED_ASSOCIATION_ATTRIBUTES) - h.each { |k, v| - h[k] = nil if v == [] - h[k] = '' if normalized_attributes[:attributes].include?(k) - if normalized_attributes[:associations].keys.include?(k) - normalize_hash(v, normalized_attributes[:associations][k]) - end - } - end - - NEW_ATTRIBUTES = { - attributes: [], - associations: { - 'table' => { - attributes: [], - associations: { - 'permission' => { - attributes: [], - associations: { - 'owner' => { - attributes: [ 'email', 'quota_in_bytes', 'db_size_in_bytes' ], - associations: {} - } - } - } - } - }, - 'permission' => { - attributes: [], - associations: { - 'owner' => { - attributes: [ 'email', 'quota_in_bytes', 'db_size_in_bytes' ], - associations: {} - } - } - } - } - } - - # INFO: this test uses comparison against old data structures to check validity. - # You can use this method to remove that new data so next comparisons will work. - def remove_data_only_in_new_controllers(visualization_hash, new_attributes = NEW_ATTRIBUTES) - visualization_hash.each { |k, v| - if new_attributes[:attributes].include?(k) - removed = visualization_hash.delete(k) - elsif new_attributes[:associations].include?(k) - remove_data_only_in_new_controllers(v, new_attributes[:associations][k]) - end - } - end - - def login(user) - login_as(user, scope: user.subdomain) - host! "#{user.subdomain}.localhost.lan" - end - - def base_url - '/api/v1/viz' - end - - def response_body(params=nil) - get base_url, params, @headers - last_response.status.should == 200 - body = JSON.parse(last_response.body) - body['visualizations'] = body['visualizations'].map { |v| normalize_hash(v) }.map { |v| remove_data_only_in_new_controllers(v) } - body - end - - def factory(user, attributes={}) - visualization_template(user, attributes) - end - - def table_factory(options={}) - privacy = options.fetch(:privacy, 1) - - seed = rand(9999) - payload = { - name: "table #{seed}", - description: "table #{seed} description" - } - post api_v1_tables_create_url(api_key: @api_key), - payload.to_json, @headers - - table_attributes = JSON.parse(last_response.body) - table_id = table_attributes.fetch('id') - - put api_v1_tables_update_url(id: table_id, api_key: @api_key), - { privacy: privacy }.to_json, @headers - - table_attributes - end - - def api_visualization_creation(user, headers, additional_fields = {}) - post api_v1_visualizations_create_url(user_domain: user.username, api_key: user.api_key), factory(user).merge(additional_fields).to_json, headers - id = JSON.parse(last_response.body).fetch('id') - CartoDB::Visualization::Member.new(id: id).fetch - end - - def test_organization - organization = Organization.new - organization.name = org_name = "org#{rand(9999)}" - organization.quota_in_bytes = 1234567890 - organization.seats = 5 - organization - end - - describe 'index' do - include_context 'visualization creation helpers' - include_context 'users helper' - - before(:each) do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true) - - login(@user1) - @headers = {'CONTENT_TYPE' => 'application/json'} - host! "#{@user1.subdomain}.localhost.lan" - end - - it 'returns success, empty response for empty user' do - response_body.should == { 'visualizations' => [], 'total_entries' => 0, 'total_user_entries' => 0, 'total_likes' => 0, 'total_shared' => 0} - end - - it 'returns valid information for a user with one table' do - table1 = create_random_table(@user1) - expected_visualization = JSON.parse(table1.table_visualization.to_hash( - related: false, - table_data: true, - user: @user1, - table: table1, - synchronization: nil - ).to_json) - expected_visualization = normalize_hash(expected_visualization) - - response_body(type: CartoDB::Visualization::Member::TYPE_CANONICAL).should == { 'visualizations' => [expected_visualization], 'total_entries' => 1, 'total_user_entries' => 1, 'total_likes' => 0, 'total_shared' => 0} - end - - it 'returns liked count' do - table1 = create_random_table(@user1) - table1b = create_random_table(@user1) - table2 = create_random_table(@user2) - table2b = create_random_table(@user2) - visualization2 = table2.table_visualization - visualization2.privacy = Visualization::Member::PRIVACY_PUBLIC - visualization2.store - visualization2.add_like_from(@user1.id) - - response_body(type: CartoDB::Visualization::Member::TYPE_CANONICAL)['total_likes'].should == 1 - end - - it 'does a partial match search' do - create_random_table(@user1, "foo") - create_random_table(@user1, "bar") - create_random_table(@user1, "foo_patata_bar") - create_random_table(@user1, "foo_patata_baz") - - #body = response_body("#{BASE_URL}/?q=patata")['total_entries'].should == 2 - body = response_body(q: 'patata', type: CartoDB::Visualization::Member::TYPE_CANONICAL) - body['total_entries'].should == 2 - body['total_user_entries'].should == 4 - end - - end - - describe 'main behaviour' do - # INFO: this tests come from spec/requests/api/visualizations_spec.rb - - before(:all) do - CartoDB::Varnish.any_instance.stubs(:send_command).returns(true) - @user = create_user( - username: 'test', - email: 'client@example.com', - password: 'clientex', - private_tables_enabled: true - ) - @api_key = @user.api_key - end - - before(:each) do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true) - - CartoDB::Varnish.any_instance.stubs(:send_command).returns(true) - @db = Rails::Sequel.connection - Sequel.extension(:pagination) - - CartoDB::Visualization.repository = DataRepository::Backend::Sequel.new(@db, :visualizations) - CartoDB::Overlay.repository = DataRepository::Backend::Sequel.new(@db, :overlays) - - begin - delete_user_data @user - rescue => exception - # Silence named maps problems only here upon data cleaning, not in specs - raise unless exception.class.to_s == 'CartoDB::NamedMapsWrapper::HTTPResponseError' - end - - @headers = { - 'CONTENT_TYPE' => 'application/json', - } - host! 'test.localhost.lan' - end - - after(:all) do - @user.destroy if @user - end - - it 'tests exclude_shared and only_shared filters' do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - - user_1 = create_user( - username: "test#{rand(9999)}-1", - email: "client#{rand(9999)}@cartodb.com", - password: 'clientex', - private_tables_enabled: false - ) - - user_2 = create_user( - username: "test#{rand(9999)}-2", - email: "client#{rand(9999)}@cartodb.com", - password: 'clientex', - private_tables_enabled: false - ) - - organization = test_organization.save - - user_org = CartoDB::UserOrganization.new(organization.id, user_1.id) - user_org.promote_user_to_admin - organization.reload - user_1.reload - - user_2.organization_id = organization.id - user_2.save.reload - organization.reload - - table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table#{rand(9999)}_1", user_id: user_1.id) - u1_t_1_id = table.table_visualization.id - u1_t_1_perm_id = table.table_visualization.permission.id - - table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table#{rand(9999)}_2", user_id: user_2.id) - u2_t_1_id = table.table_visualization.id - - post api_v1_visualizations_create_url(user_domain: user_1.username, api_key: user_1.api_key), - factory(user_1).to_json, @headers - last_response.status.should == 200 - u1_vis_1_id = JSON.parse(last_response.body).fetch('id') - u1_vis_1_perm_id = JSON.parse(last_response.body).fetch('permission').fetch('id') - - post api_v1_visualizations_create_url(user_domain: user_2.username, api_key: user_2.api_key), - factory(user_2).to_json, @headers - last_response.status.should == 200 - u2_vis_1_id = JSON.parse(last_response.body).fetch('id') - - get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - vis = body['visualizations'].first - vis['id'].should eq u1_t_1_id - - get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - vis = body['visualizations'].first - vis['id'].should eq u1_vis_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - vis = body['visualizations'].first - vis['id'].should eq u2_t_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - vis = body['visualizations'].first - vis['id'].should eq u2_vis_1_id - - # Share u1 vis with u2 - put api_v1_permissions_update_url(user_domain:user_1.username, api_key: user_1.api_key, id: u1_vis_1_perm_id), - {acl: [{ - type: CartoDB::Permission::TYPE_USER, - entity: { - id: user_2.id, - }, - access: CartoDB::Permission::ACCESS_READONLY - }]}.to_json, @headers - last_response.status.should == 200 - - # Vis listing checks - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 2 - # Permissions don't change updated_at - body['visualizations'][0]['id'].should eq u2_vis_1_id - body['visualizations'][1]['id'].should eq u1_vis_1_id - - get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - body['visualizations'][0]['id'].should eq u1_vis_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', - exclude_shared: false), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 2 - body['visualizations'][0]['id'].should eq u2_vis_1_id - body['visualizations'][1]['id'].should eq u1_vis_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', - only_shared: false), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 2 - body['visualizations'][0]['id'].should eq u2_vis_1_id - body['visualizations'][1]['id'].should eq u1_vis_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', - exclude_shared: true), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - body['visualizations'][0]['id'].should eq u2_vis_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', - only_shared: true), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - body['visualizations'][0]['id'].should eq u1_vis_1_id - - # Same with 'shared' filter (convenience alias for not handling both exclude_shared and only_shared) - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', - shared: CartoDB::Visualization::Collection::FILTER_SHARED_YES), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 2 - body['visualizations'][0]['id'].should eq u2_vis_1_id - body['visualizations'][1]['id'].should eq u1_vis_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', - shared: CartoDB::Visualization::Collection::FILTER_SHARED_NO), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - body['visualizations'][0]['id'].should eq u2_vis_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', - shared: CartoDB::Visualization::Collection::FILTER_SHARED_ONLY), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - body['visualizations'][0]['id'].should eq u1_vis_1_id - - # Share u1 table with u2 - put api_v1_permissions_update_url(user_domain:user_1.username, api_key: user_1.api_key, id: u1_t_1_perm_id), - {acl: [{ - type: CartoDB::Permission::TYPE_USER, - entity: { - id: user_2.id, - }, - access: CartoDB::Permission::ACCESS_READONLY - }]}.to_json, @headers - last_response.status.should == 200 - - # Dunno why (rack test error?) but this call seems to cache previous params, so just call it to "flush" them - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at', - shared: 'wadus', - exclude_shared: false, - only_shared: false), - @headers - # ------------- - - # Table listing checks - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 2 - body['visualizations'][0]['id'].should eq u2_t_1_id - body['visualizations'][1]['id'].should eq u1_t_1_id - - get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - body['visualizations'][0]['id'].should eq u1_t_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at', - exclude_shared: false), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 2 - body['visualizations'][0]['id'].should eq u2_t_1_id - body['visualizations'][1]['id'].should eq u1_t_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at', - only_shared: false), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 2 - body['visualizations'][0]['id'].should eq u2_t_1_id - body['visualizations'][1]['id'].should eq u1_t_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at', - exclude_shared: true), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - body['visualizations'][0]['id'].should eq u2_t_1_id - - get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at', - only_shared: true), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - body['visualizations'][0]['id'].should eq u1_t_1_id - end - - describe 'tests visualization likes endpoints' do - include_context 'users helper' - # TODO: currently new endpoint doesn't match this endpoint - - it 'tests like endpoints' do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - - vis_1_id = create_visualization(@user1).id - - get api_v1_visualizations_likes_count_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - JSON.parse(last_response.body).fetch('likes').to_i.should eq 0 - - get api_v1_visualizations_likes_list_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - JSON.parse(last_response.body).fetch('likes').should eq [] - - get api_v1_visualizations_is_liked_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - - post api_v1_visualizations_add_like_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - last_response.status.should == 200 - JSON.parse(last_response.body).fetch('likes').to_i.should eq 1 - - get api_v1_visualizations_is_liked_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - JSON.parse(last_response.body).fetch('liked').should eq true - - get api_v1_visualizations_likes_count_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - JSON.parse(last_response.body).fetch('likes').to_i.should eq 1 - - get api_v1_visualizations_likes_list_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - JSON.parse(last_response.body).fetch('likes').should eq [{'actor_id' => @user1.id}] - - post api_v1_visualizations_add_like_url(user_domain: @user2.username, id: vis_1_id, api_key: @user2.api_key) - last_response.status.should == 200 - JSON.parse(last_response.body).fetch('likes').to_i.should eq 2 - - get api_v1_visualizations_likes_list_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - # Careful with order of array items - (JSON.parse(last_response.body).fetch('likes') - [ - {'actor_id' => @user1.id}, - {'actor_id' => @user2.id} - ]).should eq [] - - delete api_v1_visualizations_remove_like_url(user_domain: @user2.username, id: vis_1_id, api_key: @user2.api_key) - last_response.status.should == 200 - JSON.parse(last_response.body).fetch('likes').to_i.should eq 1 - - # No effect expected - delete api_v1_visualizations_remove_like_url(user_domain: @user2.username, id: vis_1_id, api_key: @user2.api_key) - last_response.status.should == 200 - JSON.parse(last_response.body).fetch('likes').to_i.should eq 1 - - post api_v1_visualizations_add_like_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - last_response.status.should == 400 - last_response.body.should eq "You've already liked this visualization" - - delete api_v1_visualizations_remove_like_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - last_response.status.should == 200 - JSON.parse(last_response.body).fetch('likes').to_i.should eq 0 - - post api_v1_visualizations_add_like_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - last_response.status.should == 200 - JSON.parse(last_response.body).fetch('likes').to_i.should eq 1 - - get api_v1_visualizations_likes_list_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) - JSON.parse(last_response.body).fetch('likes').should eq [{'actor_id' => @user1.id}] - end - - end - - describe 'tests visualization likes endpoints in organizations' do - include_context 'organization with users helper' - - it 'tests totals calculations' do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - - # user 1 will have 1 table and 1 vis - # user 2 will have 2 of each - # user 2 will share 1 table and 1 vis with the org - # user 2 will share the other table and other vis with user 1 - - table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table_#{rand(9999)}_1_1", user_id: @org_user_1.id) - u1_t_1_id = table.table_visualization.id - - table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table_#{rand(9999)}_2_2", user_id: @org_user_2.id) - u2_t_1_id = table.table_visualization.id - u2_t_1_perm_id = table.table_visualization.permission.id - - table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table_#{rand(9999)}_2_2", user_id: @org_user_2.id) - u2_t_2 = table - u2_t_2_id = table.table_visualization.id - u2_t_2_perm_id = table.table_visualization.permission.id - - post api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key), - factory(@org_user_1).to_json, @headers - last_response.status.should == 200 - u1_vis_1_id = JSON.parse(last_response.body).fetch('id') - - post api_v1_visualizations_create_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key), - factory(@org_user_2).to_json, @headers - last_response.status.should == 200 - u2_vis_1_id = JSON.parse(last_response.body).fetch('id') - u2_vis_1_perm_id = JSON.parse(last_response.body).fetch('permission').fetch('id') - - post api_v1_visualizations_create_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key), - factory(@org_user_2).to_json, @headers - last_response.status.should == 200 - u2_vis_2_id = JSON.parse(last_response.body).fetch('id') - u2_vis_2_perm_id = JSON.parse(last_response.body).fetch('permission').fetch('id') - - get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - body['total_likes'].should eq 0 - body['total_shared'].should eq 0 - vis = body['visualizations'].first - vis['id'].should eq u1_t_1_id - - get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 1 - body['total_likes'].should eq 0 - body['total_shared'].should eq 0 - vis = body['visualizations'].first - vis['id'].should eq u1_vis_1_id - - # Share u2 vis1 with organization - put api_v1_permissions_update_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key, id: u2_vis_1_perm_id), - {acl: [{ - type: CartoDB::Permission::TYPE_ORGANIZATION, - entity: { - id: @organization.id, - }, - access: CartoDB::Permission::ACCESS_READONLY - }]}.to_json, @headers - last_response.status.should == 200 - - get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 2 - body['total_likes'].should eq 0 - body['total_shared'].should eq 1 - - # Share u2 vis2 with u1 - put api_v1_permissions_update_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key, id: u2_vis_2_perm_id), - {acl: [{ - type: CartoDB::Permission::TYPE_USER, - entity: { - id: @org_user_1.id, - }, - access: CartoDB::Permission::ACCESS_READONLY - }]}.to_json, @headers - last_response.status.should == 200 - - get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 3 - body['total_likes'].should eq 0 - body['total_shared'].should eq 2 - - post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_vis_1_id, api_key: @org_user_1.api_key) - - get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 3 - body['total_likes'].should eq 1 - body['total_shared'].should eq 2 - - # Multiple likes to same vis shouldn't increment total as is per vis - post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_vis_1_id, api_key: @org_user_1.api_key) - - get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 3 - body['total_likes'].should eq 1 - body['total_shared'].should eq 2 - - - # Share u2 table1 with org - put api_v1_permissions_update_url(user_domain:@org_user_2.username, api_key: @org_user_2.api_key, id: u2_t_1_perm_id), - {acl: [{ - type: CartoDB::Permission::TYPE_ORGANIZATION, - entity: { - id: @organization.id, - }, - access: CartoDB::Permission::ACCESS_READONLY - }]}.to_json, @headers - - get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 2 - body['total_likes'].should eq 0 - body['total_shared'].should eq 1 - - # Share u2 table2 with org - put api_v1_permissions_update_url(user_domain:@org_user_2.username, api_key: @org_user_2.api_key, id: u2_t_2_perm_id), - {acl: [{ - type: CartoDB::Permission::TYPE_USER, - entity: { - id: @org_user_1.id, - }, - access: CartoDB::Permission::ACCESS_READONLY - }]}.to_json, @headers - - get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 3 - body['total_likes'].should eq 0 - body['total_shared'].should eq 2 - body['visualizations'][0]['table']['name'].should == "public.#{u2_t_2.name}" - - post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_t_1_id, api_key: @org_user_1.api_key) - - get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 3 - body['total_likes'].should eq 1 - body['total_shared'].should eq 2 - - # Multiple likes to same table shouldn't increment total as is per vis - post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_t_1_id, api_key: @org_user_2.api_key) - - get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, - type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers - body = JSON.parse(last_response.body) - body['total_entries'].should eq 3 - body['total_likes'].should eq 1 - body['total_shared'].should eq 2 - end - - end - - describe 'index endpoint' do - - it 'tests normal users authenticated and unauthenticated calls' do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - - user_2 = create_user( - username: 'testindexauth', - email: 'test_auth@example.com', - password: 'testauth', - private_maps_enabled: true - ) - - collection = CartoDB::Visualization::Collection.new.fetch(user_id: user_2.id) - collection.map(&:delete) - - post api_v1_visualizations_create_url(user_domain: user_2.username, api_key: user_2.api_key), - factory(user_2).to_json, @headers - last_response.status.should == 200 - pub_vis_id = JSON.parse(last_response.body).fetch('id') - - put api_v1_visualizations_update_url(user_domain: user_2.username, api_key: user_2.api_key, id: pub_vis_id), - { - privacy: CartoDB::Visualization::Member::PRIVACY_PUBLIC - }.to_json, @headers - last_response.status.should == 200 - - post api_v1_visualizations_create_url(user_domain: user_2.username, api_key: user_2.api_key), - factory(user_2).to_json, @headers - last_response.status.should == 200 - priv_vis_id = JSON.parse(last_response.body).fetch('id') - - put api_v1_visualizations_update_url(user_domain: user_2.username, api_key: user_2.api_key, id: priv_vis_id), - { - privacy: CartoDB::Visualization::Member::PRIVACY_PRIVATE - }.to_json, @headers - last_response.status.should == 200 - - get api_v1_visualizations_index_url(user_domain: user_2.username, type: 'derived'), @headers - body = JSON.parse(last_response.body) - - body['total_entries'].should eq 1 - vis = body['visualizations'].first - vis['id'].should eq pub_vis_id - vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase - - get api_v1_visualizations_index_url(user_domain: user_2.username, type: 'derived', api_key: user_2.api_key, - order: 'updated_at'), - {}, @headers - body = JSON.parse(last_response.body) - - body['total_entries'].should eq 2 - vis = body['visualizations'][0] - vis['id'].should eq priv_vis_id - vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PRIVATE.upcase - vis = body['visualizations'][1] - vis['id'].should eq pub_vis_id - vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase - end - - it 'tests organization users authenticated and unauthenticated calls' do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - - organization = test_organization.save - - user_2 = create_user( - username: "test#{rand(9999)}", - email: "client#{rand(9999)}@cartodb.com", - password: 'clientex' - ) - - user_org = CartoDB::UserOrganization.new(organization.id, user_2.id) - user_org.promote_user_to_admin - organization.reload - user_2.reload - - post "http://#{organization.name}.cartodb.test#{api_v1_visualizations_create_path(user_domain: user_2.username, - api_key: user_2.api_key)}", - factory(user_2).to_json, @headers - last_response.status.should == 200 - pub_vis_id = JSON.parse(last_response.body).fetch('id') - - put "http://#{organization.name}.cartodb.test#{api_v1_visualizations_update_path(user_domain: user_2.username, - api_key: user_2.api_key, - id: pub_vis_id)}", - { - privacy: CartoDB::Visualization::Member::PRIVACY_PUBLIC - }.to_json, @headers - last_response.status.should == 200 - - post "http://#{organization.name}.cartodb.test#{api_v1_visualizations_create_path(user_domain: user_2.username, - api_key: user_2.api_key)}", - factory(user_2).to_json, @headers - last_response.status.should == 200 - priv_vis_id = JSON.parse(last_response.body).fetch('id') - - put "http://#{organization.name}.cartodb.test#{api_v1_visualizations_update_path(user_domain: user_2.username, - api_key: user_2.api_key, - id: priv_vis_id)}", - { - privacy: CartoDB::Visualization::Member::PRIVACY_PRIVATE - }.to_json, @headers - last_response.status.should == 200 - - get "http://#{organization.name}.cartodb.test#{api_v1_visualizations_index_path(user_domain: user_2.username, - type: 'derived')}", @headers - body = JSON.parse(last_response.body) - - body['total_entries'].should eq 1 - vis = body['visualizations'].first - vis['id'].should eq pub_vis_id - vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase - - - get "http://#{organization.name}.cartodb.test#{api_v1_visualizations_index_path(user_domain: user_2.username, - api_key: user_2.api_key, - type: 'derived', - order: 'updated_at')}", {}, @headers - body = JSON.parse(last_response.body) - - body['total_entries'].should eq 2 - vis = body['visualizations'][0] - vis['id'].should eq priv_vis_id - vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PRIVATE.upcase - vis = body['visualizations'][1] - vis['id'].should eq pub_vis_id - vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase - - user_2.destroy - end - - it 'tests privacy of vizjsons' do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - - user_1 = create_user( - username: "test#{rand(9999)}-1", - email: "client#{rand(9999)}@cartodb.com", - password: 'clientex', - private_tables_enabled: true - ) - - user_2 = create_user( - username: "test#{rand(9999)}-2", - email: "client#{rand(9999)}@cartodb.com", - password: 'clientex', - private_tables_enabled: true - ) - - organization = test_organization.save - - user_org = CartoDB::UserOrganization.new(organization.id, user_1.id) - user_org.promote_user_to_admin - organization.reload - user_1.reload - - user_2.organization_id = organization.id - user_2.save.reload - organization.reload - - post api_v1_visualizations_create_url(user_domain: user_1.username, api_key: user_1.api_key), - factory(user_1).to_json, @headers - last_response.status.should == 200 - body = JSON.parse(last_response.body) - u1_vis_1_id = body.fetch('id') - u1_vis_1_perm_id = body.fetch('permission').fetch('id') - # By default derived vis from private tables are WITH_LINK, so setprivate - put api_v1_visualizations_update_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key), - { privacy: CartoDB::Visualization::Member::PRIVACY_PRIVATE }.to_json, @headers - last_response.status.should == 200 - - # Share vis with user_2 in readonly (vis can never be shared in readwrite) - put api_v1_permissions_update_url(user_domain: user_1.username, api_key: user_1.api_key, id: u1_vis_1_perm_id), - {acl: [{ - type: CartoDB::Permission::TYPE_USER, - entity: { - id: user_2.id, - }, - access: CartoDB::Permission::ACCESS_READONLY - }]}.to_json, @headers - last_response.status.should == 200 - - # privacy private checks - # ---------------------- - - # Owner, authenticated - get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key) - last_response.status.should == 200 - body = JSON.parse(last_response.body) - body['id'].should eq u1_vis_1_id - - # Other user, has it shared in readonly mode - get api_v2_visualizations_vizjson_url(user_domain: user_2.username, id: u1_vis_1_id, api_key: user_2.api_key) - last_response.status.should == 200 - body = JSON.parse(last_response.body) - body['id'].should eq u1_vis_1_id - - # Unauthenticated user - get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user.api_key) - last_response.status.should == 403 - - # Unauthenticated user - get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user.api_key) - last_response.status.should == 403 - - # Now with link - # ------------- - put api_v1_visualizations_update_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key), - { privacy: CartoDB::Visualization::Member::PRIVACY_LINK }.to_json, @headers - last_response.status.should == 200 - - # Owner authenticated - get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key) - last_response.status.should == 200 - body = JSON.parse(last_response.body) - body['id'].should eq u1_vis_1_id - - # Other user has it shared in readonly mode - get api_v2_visualizations_vizjson_url(user_domain: user_2.username, id: u1_vis_1_id, api_key: user_2.api_key) - last_response.status.should == 200 - body = JSON.parse(last_response.body) - body['id'].should eq u1_vis_1_id - - # Unauthenticated user - get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user.api_key) - last_response.status.should == 200 - body = JSON.parse(last_response.body) - body['id'].should eq u1_vis_1_id - - # Now public - # ---------- - put api_v1_visualizations_update_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key), - { privacy: CartoDB::Visualization::Member::PRIVACY_LINK }.to_json, @headers - last_response.status.should == 200 - - get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key) - last_response.status.should == 200 - body = JSON.parse(last_response.body) - body['id'].should eq u1_vis_1_id - - # Other user has it shared in readonly mode - get api_v2_visualizations_vizjson_url(user_domain: user_2.username, id: u1_vis_1_id, api_key: user_2.api_key) - last_response.status.should == 200 - body = JSON.parse(last_response.body) - body['id'].should eq u1_vis_1_id - - # Unauthenticated user - get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user.api_key) - last_response.status.should == 200 - body = JSON.parse(last_response.body) - body['id'].should eq u1_vis_1_id - - end - - it 'Sanitizes vizjson callback' do - valid_callback = 'my_function' - valid_callback2 = 'a' - invalid_callback1 = 'alert(1);' - invalid_callback2 = '%3B' - invalid_callback3 = '123func' # JS names cannot start by number - - table_attributes = table_factory - table_id = table_attributes.fetch('id') - get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: valid_callback), {}, @headers - last_response.status.should == 200 - (last_response.body =~ /^#{valid_callback}\(\{/i).should eq 0 - - get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: invalid_callback1), {}, @headers - last_response.status.should == 400 - - get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: invalid_callback2), {}, @headers - last_response.status.should == 400 - - get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: invalid_callback3), {}, @headers - last_response.status.should == 400 - - # if param specified, must not be empty - get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: ''), {}, @headers - last_response.status.should == 400 - - get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: valid_callback2), {}, @headers - last_response.status.should == 200 - (last_response.body =~ /^#{valid_callback2}\(\{/i).should eq 0 - - get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key), {}, @headers - last_response.status.should == 200 - (last_response.body =~ /^\{/i).should eq 0 - end - - end - - describe 'GET /api/v1/viz' do - before(:each) do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - delete_user_data(@user) - end - - it 'retrieves a collection of visualizations' do - payload = factory(@user) - post api_v1_visualizations_create_url(api_key: @api_key), payload.to_json, @headers - id = JSON.parse(last_response.body).fetch('id') - - get api_v1_visualizations_index_url(api_key: @api_key), - {}, @headers - - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.first.fetch('id').should == id - end - - it 'is updated after creating a visualization' do - payload = factory(@user) - post api_v1_visualizations_create_url(api_key: @api_key), - payload.to_json, @headers - - get api_v1_visualizations_index_url(api_key: @api_key), - {}, @headers - - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.size.should == 1 - - payload = factory(@user).merge('name' => 'another one') - post api_v1_visualizations_create_url(api_key: @api_key), - payload.to_json, @headers - - get api_v1_visualizations_index_url(api_key: @api_key), - {}, @headers - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.size.should == 2 - end - - it 'is updated after deleting a visualization' do - payload = factory(@user) - post api_v1_visualizations_create_url(api_key: @api_key), - payload.to_json, @headers - id = JSON.parse(last_response.body).fetch('id') - - get api_v1_visualizations_index_url(api_key: @api_key), - {}, @headers - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.should_not be_empty - - delete api_v1_visualizations_destroy_url(id: id, api_key: @api_key), - {}, @headers - get api_v1_visualizations_index_url(api_key: @api_key), - {}, @headers - - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.should be_empty - end - - it 'paginates results' do - per_page = 10 - total_entries = 20 - - total_entries.times do - post api_v1_visualizations_index_url(api_key: @api_key), - factory(@user).to_json, @headers - end - - get api_v1_visualizations_index_url(api_key: @api_key, page: 1, per_page: per_page), {}, @headers - - last_response.status.should == 200 - - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.length.should == per_page - response.fetch('total_entries').should == total_entries - end - - it 'returns filtered results' do - post api_v1_visualizations_create_url(api_key: @api_key), - factory(@user).to_json, @headers - - get api_v1_visualizations_index_url(api_key: @api_key, type: 'table'), - {}, @headers - last_response.status.should == 200 - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.should be_empty - - post api_v1_visualizations_create_url(api_key: @api_key), - factory(@user).to_json, @headers - post api_v1_visualizations_create_url(api_key: @api_key), - factory(@user).merge(type: 'table').to_json, @headers - get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived'), - {}, @headers - - last_response.status.should == 200 - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.size.should == 2 - end - - it 'creates a visualization from a list of tables' do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - table1 = table_factory - table2 = table_factory - table3 = table_factory - - payload = { - name: 'new visualization', - tables: [ - table1.fetch('name'), - table2.fetch('name'), - table3.fetch('name') - ], - privacy: 'public' - } - - post api_v1_visualizations_create_url(api_key: @api_key), - payload.to_json, @headers - last_response.status.should == 200 - - visualization = JSON.parse(last_response.body) - - # TODO: this endpoint doesn't exist now. Current replacement? - #get "/api/v1/viz/#{visualization.fetch('id')}/viz?api_key=#{@api_key}", - # {}, @headers - #last_response.status.should == 403 - - get api_v2_visualizations_vizjson_url(id: visualization.fetch('id'), api_key: @api_key), - {}, @headers - last_response.status.should == 200 - - # include overlays - - get api_v1_visualizations_overlays_index_url(visualization_id: visualization.fetch('id'), api_key: @api_key), - {}, @headers - last_response.status.should == 200 - overlays = JSON.parse(last_response.body) - overlays.length.should == 5 - end - - end - - describe 'GET /api/v1/viz/:id' do - - before(:each) do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - delete_user_data(@user) - end - - it 'returns a visualization' do - payload = factory(@user) - post api_v1_visualizations_create_url(api_key: @api_key), - payload.to_json, @headers - id = JSON.parse(last_response.body).fetch('id') - - get api_v1_visualizations_show_url(id: id, api_key: @api_key), {}, @headers - - last_response.status.should == 200 - response = JSON.parse(last_response.body) - - response.fetch('id') .should_not be_nil - response.fetch('map_id') .should_not be_nil - response.fetch('tags') .should_not be_empty - response.fetch('description') .should_not be_nil - response.fetch('related_tables') .should_not be_nil - end - - end - - describe 'GET /api/v2/viz/:id/viz' do - before(:each) do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - delete_user_data(@user) - end - - it 'renders vizjson v2' do - table_attributes = table_factory - table_id = table_attributes.fetch('id') - get "/api/v2/viz/#{table_id}/viz?api_key=#{@api_key}", - {}, @headers - last_response.status.should == 200 - ::JSON.parse(last_response.body).keys.length.should > 1 - end - - it 'returns children (slides) vizjson' do - parent = api_visualization_creation(@user, @headers, { privacy: Visualization::Member::PRIVACY_PUBLIC, type: Visualization::Member::TYPE_DERIVED }) - child = api_visualization_creation(@user, @headers, { privacy: Visualization::Member::PRIVACY_PUBLIC, type: Visualization::Member::TYPE_SLIDE, parent_id: parent.id }) - - get "/api/v2/viz/#{parent.id}/viz?api_key=#{@api_key}", {}, @headers - - last_response.status.should == 200 - response = JSON.parse(last_response.body) - slides = response.fetch('slides') - slides.count.should == 1 - end - - it "comes with proper surrogate-key" do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true) - table = table_factory(privacy: 1) - source_visualization = table.fetch('table_visualization') - - - payload = { source_visualization_id: source_visualization.fetch('id'), privacy: 'PUBLIC' } - - post api_v1_visualizations_create_url(user_domain: @user.username, api_key: @api_key), - payload.to_json, @headers - - viz_id = JSON.parse(last_response.body).fetch('id') - - put api_v1_visualizations_show_url(user_domain: @user.username, id: viz_id, api_key: @api_key), - { privacy: 'PUBLIC' }.to_json, @headers - - get api_v2_visualizations_vizjson_url(user_domain: @user.username, id: viz_id, api_key: @api_key), - {}, @headers - - last_response.status.should == 200 - last_response.headers.should have_key('Surrogate-Key') - last_response['Surrogate-Key'].should include(CartoDB::SURROGATE_NAMESPACE_VIZJSON) - last_response['Surrogate-Key'].should include(get_surrogate_key(CartoDB::SURROGATE_NAMESPACE_VISUALIZATION, viz_id)) - - delete api_v1_visualizations_show_url(user_domain: @user.username, id: viz_id, api_key: @api_key), - { }, @headers - end - end - - describe 'tests visualization listing filters' do - before(:each) do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - delete_user_data(@user) - end - - it 'uses locked filter' do - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) - - post api_v1_visualizations_create_url(api_key: @api_key), factory(@user, locked: true).to_json, @headers - vis_1_id = JSON.parse(last_response.body).fetch('id') - post api_v1_visualizations_create_url(api_key: @api_key), factory(@user, locked: false).to_json, @headers - vis_2_id = JSON.parse(last_response.body).fetch('id') - - get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived'), {}, @headers - last_response.status.should == 200 - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.length.should eq 2 - - get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived', locked: true), {}, @headers - last_response.status.should == 200 - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.length.should eq 1 - collection.first.fetch('id').should eq vis_1_id - - get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived', locked: false), {}, @headers - last_response.status.should == 200 - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.length.should eq 1 - collection.first.fetch('id').should eq vis_2_id - end - - it 'searches by tag' do - post api_v1_visualizations_create_url(api_key: @api_key), factory(@user, locked: true, tags: ['test1']).to_json, @headers - vis_1_id = JSON.parse(last_response.body).fetch('id') - post api_v1_visualizations_create_url(api_key: @api_key), factory(@user, locked: false, tags: ['test2']).to_json, @headers - - get api_v1_visualizations_index_url(api_key: @api_key, tags: 'test1'), {}, @headers - last_response.status.should == 200 - response = JSON.parse(last_response.body) - collection = response.fetch('visualizations') - collection.length.should eq 1 - collection.first['id'].should == vis_1_id - end - - end - - describe 'non existent visualization' do - it 'returns 404' do - get api_v1_visualizations_show_url(id: TEST_UUID, api_key: @api_key), {}, @headers - last_response.status.should == 404 - - put api_v1_visualizations_update_url(id: TEST_UUID, api_key: @api_key), {}, @headers - last_response.status.should == 404 - - delete api_v1_visualizations_destroy_url(id: TEST_UUID, api_key: @api_key), {}, @headers - last_response.status.should == 404 - - get "/api/v2/viz/#{TEST_UUID}/viz?api_key=#{@api_key}", {}, @headers - last_response.status.should == 404 - end - end - - describe '/api/v1/viz/:id/watching' do - - before(:all) do - @user_1 = create_test_user - @user_2 = create_test_user - - organization = test_organization.save - - user_org = CartoDB::UserOrganization.new(organization.id, @user_1.id) - user_org.promote_user_to_admin - @user_1.reload - - @user_2.organization_id = organization.id - @user_2.save.reload - end - - it 'returns an empty array if no other user is watching' do - CartoDB::Visualization::Watcher.any_instance.stubs(:list).returns([]) - - CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true) - - login(@user_1) - post api_v1_visualizations_create_url(api_key: @user_1.api_key), factory(@user_1, locked: true).to_json, @headers - id = JSON.parse(last_response.body).fetch('id') - - login(@user_1) - get api_v1_visualizations_notify_watching_url(id: id, api_key: @user_1.api_key) - body = JSON.parse(last_response.body) - body.should == [] - end - end - end end diff --git a/spec/requests/carto/api/overlays_controller_spec.rb b/spec/requests/carto/api/overlays_controller_spec.rb index e0f93e2dfe4d..1041041d2641 100644 --- a/spec/requests/carto/api/overlays_controller_spec.rb +++ b/spec/requests/carto/api/overlays_controller_spec.rb @@ -10,27 +10,83 @@ it_behaves_like 'overlays controllers' do end - before(:all) do + describe '#both endpoints tests' do - # Spec the routes so that it uses the new controller. Needed for alternative routes testing - Rails.application.routes.draw do + before(:all) do + @user = create_user( + username: 'test', + email: 'client@example.com', + password: 'clientex' + ) + @api_key = @user.api_key - # new controller - scope :module => 'carto/api', :format => :json do - get '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:visualization_id/overlays' => 'overlays#index', as: :api_v1_visualizations_overlays_index, constraints: { visualization_id: /[^\/]+/ } - get '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:visualization_id/overlays/:id' => 'overlays#show', as: :api_v1_visualizations_overlays_show, constraints: { visualization_id: /[^\/]+/ } + host! 'test.localhost.lan' + end + + before(:each) do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true) + delete_user_data @user + @table = create_table :user_id => @user.id + end + + after(:all) do + @user.destroy + end + + + let(:params) { { :api_key => @user.api_key, visualization_id: @table.table_visualization.id } } + + + it "Lists all overlays" do + + existing_overlay_ids = [] + get_json api_v1_visualizations_overlays_index_url(params) do |response| + response.status.should be_success + response.body.count.should == 5 # Newly created overlays have this amount of layers + existing_overlay_ids = response.body.map { |overlay| overlay['id'] } end - # old controller - scope :module => 'api/json', :format => :json do + header_overlay = Overlay::Member.new({ + type: 'header', + visualization_id: params[:visualization_id], + order: 1 + }) + header_overlay.store + + text_overlay = Overlay::Member.new({ + type: 'text', + visualization_id: params[:visualization_id], + order: 2 + }) + text_overlay.store + + new_overlay_ids = [header_overlay.id, text_overlay.id] + + get_json api_v1_visualizations_overlays_index_url(params) do |response| + response.status.should be_success + current_overlay_ids = response.body.map { |overlay| overlay['id'] } + response.body.count.should == new_overlay_ids.count + existing_overlay_ids.count + # == checks order, while intersection doesn't + (current_overlay_ids & (existing_overlay_ids + new_overlay_ids) == current_overlay_ids).should eq true end end - end + it "Gets the details of an overlay" do + header_overlay = Overlay::Member.new({ + type: 'header', + visualization_id: params[:visualization_id] + }) + header_overlay.store + + get_json api_v1_visualizations_overlays_show_url(params.merge(id: header_overlay.id)) do |response| + response.status.should be_success + response.body[:id].should == header_overlay.id + response.body[:visualization_id].should == params[:visualization_id] + end + end - after(:all) do - Rails.application.reload_routes! end + end diff --git a/spec/requests/carto/api/visualizations_controller_spec.rb b/spec/requests/carto/api/visualizations_controller_spec.rb index 2238d58113e1..ca4786381cad 100644 --- a/spec/requests/carto/api/visualizations_controller_spec.rb +++ b/spec/requests/carto/api/visualizations_controller_spec.rb @@ -4,46 +4,1231 @@ require_relative '../../api/json/visualizations_controller_shared_examples' require_relative '../../../../app/controllers/carto/api/visualizations_controller' -def factory(user, attributes={}) - visualization_template(user, attributes) -end +# TODO: Remove once Carto::Visualization is complete enough +require_relative '../../../../app/models/visualization/member' describe Carto::Api::VisualizationsController do it_behaves_like 'visualization controllers' do end - before(:all) do - # Spec the routes so that it uses the new controller. Needed for alternative routes testing - Rails.application.routes.draw do + TEST_UUID = '00000000-0000-0000-0000-000000000000' + + DATE_ATTRIBUTES = %w{ created_at updated_at } + NORMALIZED_ASSOCIATION_ATTRIBUTES = { + attributes: DATE_ATTRIBUTES, + associations: { + 'permission' => { + attributes: DATE_ATTRIBUTES, + associations: {} + }, + 'table' => { + attributes: DATE_ATTRIBUTES, + associations: {} + } + } + } + + NEW_ATTRIBUTES = { + attributes: [], + associations: { + 'table' => { + attributes: [], + associations: { + 'permission' => { + attributes: [], + associations: { + 'owner' => { + attributes: [ 'email', 'quota_in_bytes', 'db_size_in_bytes' ], + associations: {} + } + } + } + } + }, + 'permission' => { + attributes: [], + associations: { + 'owner' => { + attributes: [ 'email', 'quota_in_bytes', 'db_size_in_bytes' ], + associations: {} + } + } + } + } + } + + + describe 'index' do + include_context 'visualization creation helpers' + include_context 'users helper' + + before(:each) do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true) + + login(@user1) + @headers = {'CONTENT_TYPE' => 'application/json'} + host! "#{@user1.subdomain}.localhost.lan" + end + + it 'returns success, empty response for empty user' do + response_body.should == { 'visualizations' => [], 'total_entries' => 0, 'total_user_entries' => 0, 'total_likes' => 0, 'total_shared' => 0} + end + + it 'returns valid information for a user with one table' do + table1 = create_random_table(@user1) + expected_visualization = JSON.parse(table1.table_visualization.to_hash( + related: false, + table_data: true, + user: @user1, + table: table1, + synchronization: nil + ).to_json) + expected_visualization = normalize_hash(expected_visualization) + + response_body(type: CartoDB::Visualization::Member::TYPE_CANONICAL).should == { 'visualizations' => [expected_visualization], 'total_entries' => 1, 'total_user_entries' => 1, 'total_likes' => 0, 'total_shared' => 0} + end + + it 'returns liked count' do + table1 = create_random_table(@user1) + table1b = create_random_table(@user1) + table2 = create_random_table(@user2) + table2b = create_random_table(@user2) + visualization2 = table2.table_visualization + visualization2.privacy = Visualization::Member::PRIVACY_PUBLIC + visualization2.store + visualization2.add_like_from(@user1.id) + + response_body(type: CartoDB::Visualization::Member::TYPE_CANONICAL)['total_likes'].should == 1 + end + + it 'does a partial match search' do + create_random_table(@user1, "foo") + create_random_table(@user1, "bar") + create_random_table(@user1, "foo_patata_bar") + create_random_table(@user1, "foo_patata_baz") + + #body = response_body("#{BASE_URL}/?q=patata")['total_entries'].should == 2 + body = response_body(q: 'patata', type: CartoDB::Visualization::Member::TYPE_CANONICAL) + body['total_entries'].should == 2 + body['total_user_entries'].should == 4 + end + + end + + describe 'main behaviour' do + # INFO: this tests come from spec/requests/api/visualizations_spec.rb + + before(:all) do + CartoDB::Varnish.any_instance.stubs(:send_command).returns(true) + @user = create_user( + username: 'test', + email: 'client@example.com', + password: 'clientex', + private_tables_enabled: true + ) + @api_key = @user.api_key + end + + before(:each) do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true) + + CartoDB::Varnish.any_instance.stubs(:send_command).returns(true) + @db = Rails::Sequel.connection + Sequel.extension(:pagination) + + CartoDB::Visualization.repository = DataRepository::Backend::Sequel.new(@db, :visualizations) + CartoDB::Overlay.repository = DataRepository::Backend::Sequel.new(@db, :overlays) + + begin + delete_user_data @user + rescue => exception + # Silence named maps problems only here upon data cleaning, not in specs + raise unless exception.class.to_s == 'CartoDB::NamedMapsWrapper::HTTPResponseError' + end + + @headers = { + 'CONTENT_TYPE' => 'application/json', + } + host! 'test.localhost.lan' + end + + after(:all) do + @user.destroy if @user + end + + it 'tests exclude_shared and only_shared filters' do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + + user_1 = create_user( + username: "test#{rand(9999)}-1", + email: "client#{rand(9999)}@cartodb.com", + password: 'clientex', + private_tables_enabled: false + ) + + user_2 = create_user( + username: "test#{rand(9999)}-2", + email: "client#{rand(9999)}@cartodb.com", + password: 'clientex', + private_tables_enabled: false + ) + + organization = test_organization.save + + user_org = CartoDB::UserOrganization.new(organization.id, user_1.id) + user_org.promote_user_to_admin + organization.reload + user_1.reload + + user_2.organization_id = organization.id + user_2.save.reload + organization.reload + + table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table#{rand(9999)}_1", user_id: user_1.id) + u1_t_1_id = table.table_visualization.id + u1_t_1_perm_id = table.table_visualization.permission.id + + table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table#{rand(9999)}_2", user_id: user_2.id) + u2_t_1_id = table.table_visualization.id + + post api_v1_visualizations_create_url(user_domain: user_1.username, api_key: user_1.api_key), + factory(user_1).to_json, @headers + last_response.status.should == 200 + u1_vis_1_id = JSON.parse(last_response.body).fetch('id') + u1_vis_1_perm_id = JSON.parse(last_response.body).fetch('permission').fetch('id') + + post api_v1_visualizations_create_url(user_domain: user_2.username, api_key: user_2.api_key), + factory(user_2).to_json, @headers + last_response.status.should == 200 + u2_vis_1_id = JSON.parse(last_response.body).fetch('id') + + get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + vis = body['visualizations'].first + vis['id'].should eq u1_t_1_id + + get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + vis = body['visualizations'].first + vis['id'].should eq u1_vis_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + vis = body['visualizations'].first + vis['id'].should eq u2_t_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + vis = body['visualizations'].first + vis['id'].should eq u2_vis_1_id + + # Share u1 vis with u2 + put api_v1_permissions_update_url(user_domain:user_1.username, api_key: user_1.api_key, id: u1_vis_1_perm_id), + {acl: [{ + type: CartoDB::Permission::TYPE_USER, + entity: { + id: user_2.id, + }, + access: CartoDB::Permission::ACCESS_READONLY + }]}.to_json, @headers + last_response.status.should == 200 + + # Vis listing checks + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 2 + # Permissions don't change updated_at + body['visualizations'][0]['id'].should eq u2_vis_1_id + body['visualizations'][1]['id'].should eq u1_vis_1_id + + get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + body['visualizations'][0]['id'].should eq u1_vis_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', + exclude_shared: false), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 2 + body['visualizations'][0]['id'].should eq u2_vis_1_id + body['visualizations'][1]['id'].should eq u1_vis_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', + only_shared: false), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 2 + body['visualizations'][0]['id'].should eq u2_vis_1_id + body['visualizations'][1]['id'].should eq u1_vis_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', + exclude_shared: true), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + body['visualizations'][0]['id'].should eq u2_vis_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', + only_shared: true), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + body['visualizations'][0]['id'].should eq u1_vis_1_id + + # Same with 'shared' filter (convenience alias for not handling both exclude_shared and only_shared) + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', + shared: CartoDB::Visualization::Collection::FILTER_SHARED_YES), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 2 + body['visualizations'][0]['id'].should eq u2_vis_1_id + body['visualizations'][1]['id'].should eq u1_vis_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', + shared: CartoDB::Visualization::Collection::FILTER_SHARED_NO), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + body['visualizations'][0]['id'].should eq u2_vis_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at', + shared: CartoDB::Visualization::Collection::FILTER_SHARED_ONLY), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + body['visualizations'][0]['id'].should eq u1_vis_1_id + + # Share u1 table with u2 + put api_v1_permissions_update_url(user_domain:user_1.username, api_key: user_1.api_key, id: u1_t_1_perm_id), + {acl: [{ + type: CartoDB::Permission::TYPE_USER, + entity: { + id: user_2.id, + }, + access: CartoDB::Permission::ACCESS_READONLY + }]}.to_json, @headers + last_response.status.should == 200 + + # Dunno why (rack test error?) but this call seems to cache previous params, so just call it to "flush" them + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at', + shared: 'wadus', + exclude_shared: false, + only_shared: false), + @headers + # ------------- + + # Table listing checks + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 2 + body['visualizations'][0]['id'].should eq u2_t_1_id + body['visualizations'][1]['id'].should eq u1_t_1_id + + get api_v1_visualizations_index_url(user_domain: user_1.username, api_key: user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + body['visualizations'][0]['id'].should eq u1_t_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at', + exclude_shared: false), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 2 + body['visualizations'][0]['id'].should eq u2_t_1_id + body['visualizations'][1]['id'].should eq u1_t_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at', + only_shared: false), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 2 + body['visualizations'][0]['id'].should eq u2_t_1_id + body['visualizations'][1]['id'].should eq u1_t_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at', + exclude_shared: true), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + body['visualizations'][0]['id'].should eq u2_t_1_id + + get api_v1_visualizations_index_url(user_domain: user_2.username, api_key: user_2.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at', + only_shared: true), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + body['visualizations'][0]['id'].should eq u1_t_1_id + end + + describe 'tests visualization likes endpoints' do + include_context 'users helper' + # TODO: currently new endpoint doesn't match this endpoint + + it 'tests like endpoints' do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + + vis_1_id = create_visualization(@user1).id + + get api_v1_visualizations_likes_count_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + JSON.parse(last_response.body).fetch('likes').to_i.should eq 0 + + get api_v1_visualizations_likes_list_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + JSON.parse(last_response.body).fetch('likes').should eq [] + + get api_v1_visualizations_is_liked_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + + post api_v1_visualizations_add_like_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + last_response.status.should == 200 + JSON.parse(last_response.body).fetch('likes').to_i.should eq 1 + + get api_v1_visualizations_is_liked_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + JSON.parse(last_response.body).fetch('liked').should eq true + + get api_v1_visualizations_likes_count_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + JSON.parse(last_response.body).fetch('likes').to_i.should eq 1 + + get api_v1_visualizations_likes_list_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + JSON.parse(last_response.body).fetch('likes').should eq [{'actor_id' => @user1.id}] + + post api_v1_visualizations_add_like_url(user_domain: @user2.username, id: vis_1_id, api_key: @user2.api_key) + last_response.status.should == 200 + JSON.parse(last_response.body).fetch('likes').to_i.should eq 2 + + get api_v1_visualizations_likes_list_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + # Careful with order of array items + (JSON.parse(last_response.body).fetch('likes') - [ + {'actor_id' => @user1.id}, + {'actor_id' => @user2.id} + ]).should eq [] + + delete api_v1_visualizations_remove_like_url(user_domain: @user2.username, id: vis_1_id, api_key: @user2.api_key) + last_response.status.should == 200 + JSON.parse(last_response.body).fetch('likes').to_i.should eq 1 + + # No effect expected + delete api_v1_visualizations_remove_like_url(user_domain: @user2.username, id: vis_1_id, api_key: @user2.api_key) + last_response.status.should == 200 + JSON.parse(last_response.body).fetch('likes').to_i.should eq 1 + + post api_v1_visualizations_add_like_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + last_response.status.should == 400 + last_response.body.should eq "You've already liked this visualization" + + delete api_v1_visualizations_remove_like_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + last_response.status.should == 200 + JSON.parse(last_response.body).fetch('likes').to_i.should eq 0 + + post api_v1_visualizations_add_like_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + last_response.status.should == 200 + JSON.parse(last_response.body).fetch('likes').to_i.should eq 1 + + get api_v1_visualizations_likes_list_url(user_domain: @user1.username, id: vis_1_id, api_key: @user1.api_key) + JSON.parse(last_response.body).fetch('likes').should eq [{'actor_id' => @user1.id}] + end + + end + + describe 'tests visualization likes endpoints in organizations' do + include_context 'organization with users helper' + + it 'tests totals calculations' do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + + # user 1 will have 1 table and 1 vis + # user 2 will have 2 of each + # user 2 will share 1 table and 1 vis with the org + # user 2 will share the other table and other vis with user 1 + + table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table_#{rand(9999)}_1_1", user_id: @org_user_1.id) + u1_t_1_id = table.table_visualization.id + + table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table_#{rand(9999)}_2_2", user_id: @org_user_2.id) + u2_t_1_id = table.table_visualization.id + u2_t_1_perm_id = table.table_visualization.permission.id + + table = create_table(privacy: UserTable::PRIVACY_PUBLIC, name: "table_#{rand(9999)}_2_2", user_id: @org_user_2.id) + u2_t_2 = table + u2_t_2_id = table.table_visualization.id + u2_t_2_perm_id = table.table_visualization.permission.id + + post api_v1_visualizations_create_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key), + factory(@org_user_1).to_json, @headers + last_response.status.should == 200 + u1_vis_1_id = JSON.parse(last_response.body).fetch('id') + + post api_v1_visualizations_create_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key), + factory(@org_user_2).to_json, @headers + last_response.status.should == 200 + u2_vis_1_id = JSON.parse(last_response.body).fetch('id') + u2_vis_1_perm_id = JSON.parse(last_response.body).fetch('permission').fetch('id') + + post api_v1_visualizations_create_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key), + factory(@org_user_2).to_json, @headers + last_response.status.should == 200 + u2_vis_2_id = JSON.parse(last_response.body).fetch('id') + u2_vis_2_perm_id = JSON.parse(last_response.body).fetch('permission').fetch('id') + + get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + body['total_likes'].should eq 0 + body['total_shared'].should eq 0 + vis = body['visualizations'].first + vis['id'].should eq u1_t_1_id + + get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 1 + body['total_likes'].should eq 0 + body['total_shared'].should eq 0 + vis = body['visualizations'].first + vis['id'].should eq u1_vis_1_id + + # Share u2 vis1 with organization + put api_v1_permissions_update_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key, id: u2_vis_1_perm_id), + {acl: [{ + type: CartoDB::Permission::TYPE_ORGANIZATION, + entity: { + id: @organization.id, + }, + access: CartoDB::Permission::ACCESS_READONLY + }]}.to_json, @headers + last_response.status.should == 200 + + get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 2 + body['total_likes'].should eq 0 + body['total_shared'].should eq 1 + + # Share u2 vis2 with u1 + put api_v1_permissions_update_url(user_domain: @org_user_2.username, api_key: @org_user_2.api_key, id: u2_vis_2_perm_id), + {acl: [{ + type: CartoDB::Permission::TYPE_USER, + entity: { + id: @org_user_1.id, + }, + access: CartoDB::Permission::ACCESS_READONLY + }]}.to_json, @headers + last_response.status.should == 200 + + get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 3 + body['total_likes'].should eq 0 + body['total_shared'].should eq 2 + + post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_vis_1_id, api_key: @org_user_1.api_key) + + get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 3 + body['total_likes'].should eq 1 + body['total_shared'].should eq 2 + + # Multiple likes to same vis shouldn't increment total as is per vis + post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_vis_1_id, api_key: @org_user_1.api_key) + + get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_DERIVED, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 3 + body['total_likes'].should eq 1 + body['total_shared'].should eq 2 + + + # Share u2 table1 with org + put api_v1_permissions_update_url(user_domain:@org_user_2.username, api_key: @org_user_2.api_key, id: u2_t_1_perm_id), + {acl: [{ + type: CartoDB::Permission::TYPE_ORGANIZATION, + entity: { + id: @organization.id, + }, + access: CartoDB::Permission::ACCESS_READONLY + }]}.to_json, @headers + + get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 2 + body['total_likes'].should eq 0 + body['total_shared'].should eq 1 + + # Share u2 table2 with org + put api_v1_permissions_update_url(user_domain:@org_user_2.username, api_key: @org_user_2.api_key, id: u2_t_2_perm_id), + {acl: [{ + type: CartoDB::Permission::TYPE_USER, + entity: { + id: @org_user_1.id, + }, + access: CartoDB::Permission::ACCESS_READONLY + }]}.to_json, @headers + + get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 3 + body['total_likes'].should eq 0 + body['total_shared'].should eq 2 + body['visualizations'][0]['table']['name'].should == "public.#{u2_t_2.name}" + + post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_t_1_id, api_key: @org_user_1.api_key) + + get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 3 + body['total_likes'].should eq 1 + body['total_shared'].should eq 2 + + # Multiple likes to same table shouldn't increment total as is per vis + post api_v1_visualizations_add_like_url(user_domain: @org_user_1.username, id: u1_t_1_id, api_key: @org_user_2.api_key) + + get api_v1_visualizations_index_url(user_domain: @org_user_1.username, api_key: @org_user_1.api_key, + type: CartoDB::Visualization::Member::TYPE_CANONICAL, order: 'updated_at'), @headers + body = JSON.parse(last_response.body) + body['total_entries'].should eq 3 + body['total_likes'].should eq 1 + body['total_shared'].should eq 2 + end + + end + + describe 'index endpoint' do + + it 'tests normal users authenticated and unauthenticated calls' do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + + user_2 = create_user( + username: 'testindexauth', + email: 'test_auth@example.com', + password: 'testauth', + private_maps_enabled: true + ) + + collection = CartoDB::Visualization::Collection.new.fetch(user_id: user_2.id) + collection.map(&:delete) + + post api_v1_visualizations_create_url(user_domain: user_2.username, api_key: user_2.api_key), + factory(user_2).to_json, @headers + last_response.status.should == 200 + pub_vis_id = JSON.parse(last_response.body).fetch('id') + + put api_v1_visualizations_update_url(user_domain: user_2.username, api_key: user_2.api_key, id: pub_vis_id), + { + privacy: CartoDB::Visualization::Member::PRIVACY_PUBLIC + }.to_json, @headers + last_response.status.should == 200 + + post api_v1_visualizations_create_url(user_domain: user_2.username, api_key: user_2.api_key), + factory(user_2).to_json, @headers + last_response.status.should == 200 + priv_vis_id = JSON.parse(last_response.body).fetch('id') + + put api_v1_visualizations_update_url(user_domain: user_2.username, api_key: user_2.api_key, id: priv_vis_id), + { + privacy: CartoDB::Visualization::Member::PRIVACY_PRIVATE + }.to_json, @headers + last_response.status.should == 200 + + get api_v1_visualizations_index_url(user_domain: user_2.username, type: 'derived'), @headers + body = JSON.parse(last_response.body) + + body['total_entries'].should eq 1 + vis = body['visualizations'].first + vis['id'].should eq pub_vis_id + vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase + + get api_v1_visualizations_index_url(user_domain: user_2.username, type: 'derived', api_key: user_2.api_key, + order: 'updated_at'), + {}, @headers + body = JSON.parse(last_response.body) + + body['total_entries'].should eq 2 + vis = body['visualizations'][0] + vis['id'].should eq priv_vis_id + vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PRIVATE.upcase + vis = body['visualizations'][1] + vis['id'].should eq pub_vis_id + vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase + end + + it 'tests organization users authenticated and unauthenticated calls' do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + + organization = test_organization.save + + user_2 = create_user( + username: "test#{rand(9999)}", + email: "client#{rand(9999)}@cartodb.com", + password: 'clientex' + ) + + user_org = CartoDB::UserOrganization.new(organization.id, user_2.id) + user_org.promote_user_to_admin + organization.reload + user_2.reload + + post "http://#{organization.name}.cartodb.test#{api_v1_visualizations_create_path(user_domain: user_2.username, + api_key: user_2.api_key)}", + factory(user_2).to_json, @headers + last_response.status.should == 200 + pub_vis_id = JSON.parse(last_response.body).fetch('id') + + put "http://#{organization.name}.cartodb.test#{api_v1_visualizations_update_path(user_domain: user_2.username, + api_key: user_2.api_key, + id: pub_vis_id)}", + { + privacy: CartoDB::Visualization::Member::PRIVACY_PUBLIC + }.to_json, @headers + last_response.status.should == 200 + + post "http://#{organization.name}.cartodb.test#{api_v1_visualizations_create_path(user_domain: user_2.username, + api_key: user_2.api_key)}", + factory(user_2).to_json, @headers + last_response.status.should == 200 + priv_vis_id = JSON.parse(last_response.body).fetch('id') + + put "http://#{organization.name}.cartodb.test#{api_v1_visualizations_update_path(user_domain: user_2.username, + api_key: user_2.api_key, + id: priv_vis_id)}", + { + privacy: CartoDB::Visualization::Member::PRIVACY_PRIVATE + }.to_json, @headers + last_response.status.should == 200 + + get "http://#{organization.name}.cartodb.test#{api_v1_visualizations_index_path(user_domain: user_2.username, + type: 'derived')}", @headers + body = JSON.parse(last_response.body) + + body['total_entries'].should eq 1 + vis = body['visualizations'].first + vis['id'].should eq pub_vis_id + vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase + + + get "http://#{organization.name}.cartodb.test#{api_v1_visualizations_index_path(user_domain: user_2.username, + api_key: user_2.api_key, + type: 'derived', + order: 'updated_at')}", {}, @headers + body = JSON.parse(last_response.body) + + body['total_entries'].should eq 2 + vis = body['visualizations'][0] + vis['id'].should eq priv_vis_id + vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PRIVATE.upcase + vis = body['visualizations'][1] + vis['id'].should eq pub_vis_id + vis['privacy'].should eq CartoDB::Visualization::Member::PRIVACY_PUBLIC.upcase + + user_2.destroy + end + + it 'tests privacy of vizjsons' do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + + user_1 = create_user( + username: "test#{rand(9999)}-1", + email: "client#{rand(9999)}@cartodb.com", + password: 'clientex', + private_tables_enabled: true + ) + + user_2 = create_user( + username: "test#{rand(9999)}-2", + email: "client#{rand(9999)}@cartodb.com", + password: 'clientex', + private_tables_enabled: true + ) + + organization = test_organization.save + + user_org = CartoDB::UserOrganization.new(organization.id, user_1.id) + user_org.promote_user_to_admin + organization.reload + user_1.reload + + user_2.organization_id = organization.id + user_2.save.reload + organization.reload + + post api_v1_visualizations_create_url(user_domain: user_1.username, api_key: user_1.api_key), + factory(user_1).to_json, @headers + last_response.status.should == 200 + body = JSON.parse(last_response.body) + u1_vis_1_id = body.fetch('id') + u1_vis_1_perm_id = body.fetch('permission').fetch('id') + # By default derived vis from private tables are WITH_LINK, so setprivate + put api_v1_visualizations_update_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key), + { privacy: CartoDB::Visualization::Member::PRIVACY_PRIVATE }.to_json, @headers + last_response.status.should == 200 + + # Share vis with user_2 in readonly (vis can never be shared in readwrite) + put api_v1_permissions_update_url(user_domain: user_1.username, api_key: user_1.api_key, id: u1_vis_1_perm_id), + {acl: [{ + type: CartoDB::Permission::TYPE_USER, + entity: { + id: user_2.id, + }, + access: CartoDB::Permission::ACCESS_READONLY + }]}.to_json, @headers + last_response.status.should == 200 + + # privacy private checks + # ---------------------- + + # Owner, authenticated + get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key) + last_response.status.should == 200 + body = JSON.parse(last_response.body) + body['id'].should eq u1_vis_1_id + + # Other user, has it shared in readonly mode + get api_v2_visualizations_vizjson_url(user_domain: user_2.username, id: u1_vis_1_id, api_key: user_2.api_key) + last_response.status.should == 200 + body = JSON.parse(last_response.body) + body['id'].should eq u1_vis_1_id + + # Unauthenticated user + get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user.api_key) + last_response.status.should == 403 + + # Unauthenticated user + get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user.api_key) + last_response.status.should == 403 + + # Now with link + # ------------- + put api_v1_visualizations_update_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key), + { privacy: CartoDB::Visualization::Member::PRIVACY_LINK }.to_json, @headers + last_response.status.should == 200 + + # Owner authenticated + get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key) + last_response.status.should == 200 + body = JSON.parse(last_response.body) + body['id'].should eq u1_vis_1_id + + # Other user has it shared in readonly mode + get api_v2_visualizations_vizjson_url(user_domain: user_2.username, id: u1_vis_1_id, api_key: user_2.api_key) + last_response.status.should == 200 + body = JSON.parse(last_response.body) + body['id'].should eq u1_vis_1_id + + # Unauthenticated user + get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user.api_key) + last_response.status.should == 200 + body = JSON.parse(last_response.body) + body['id'].should eq u1_vis_1_id + + # Now public + # ---------- + put api_v1_visualizations_update_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key), + { privacy: CartoDB::Visualization::Member::PRIVACY_LINK }.to_json, @headers + last_response.status.should == 200 + + get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: user_1.api_key) + last_response.status.should == 200 + body = JSON.parse(last_response.body) + body['id'].should eq u1_vis_1_id + + # Other user has it shared in readonly mode + get api_v2_visualizations_vizjson_url(user_domain: user_2.username, id: u1_vis_1_id, api_key: user_2.api_key) + last_response.status.should == 200 + body = JSON.parse(last_response.body) + body['id'].should eq u1_vis_1_id + + # Unauthenticated user + get api_v2_visualizations_vizjson_url(user_domain: user_1.username, id: u1_vis_1_id, api_key: @user.api_key) + last_response.status.should == 200 + body = JSON.parse(last_response.body) + body['id'].should eq u1_vis_1_id + + end + + it 'Sanitizes vizjson callback' do + valid_callback = 'my_function' + valid_callback2 = 'a' + invalid_callback1 = 'alert(1);' + invalid_callback2 = '%3B' + invalid_callback3 = '123func' # JS names cannot start by number + + table_attributes = table_factory + table_id = table_attributes.fetch('id') + get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: valid_callback), {}, @headers + last_response.status.should == 200 + (last_response.body =~ /^#{valid_callback}\(\{/i).should eq 0 + + get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: invalid_callback1), {}, @headers + last_response.status.should == 400 + + get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: invalid_callback2), {}, @headers + last_response.status.should == 400 + + get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: invalid_callback3), {}, @headers + last_response.status.should == 400 + + # if param specified, must not be empty + get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: ''), {}, @headers + last_response.status.should == 400 + + get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key, callback: valid_callback2), {}, @headers + last_response.status.should == 200 + (last_response.body =~ /^#{valid_callback2}\(\{/i).should eq 0 + + get api_v2_visualizations_vizjson_url(id: table_id, api_key: @api_key), {}, @headers + last_response.status.should == 200 + (last_response.body =~ /^\{/i).should eq 0 + end + + end + + describe 'GET /api/v1/viz' do + before(:each) do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + delete_user_data(@user) + end + + it 'retrieves a collection of visualizations' do + payload = factory(@user) + post api_v1_visualizations_create_url(api_key: @api_key), payload.to_json, @headers + id = JSON.parse(last_response.body).fetch('id') + + get api_v1_visualizations_index_url(api_key: @api_key), + {}, @headers + + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.first.fetch('id').should == id + end + + it 'is updated after creating a visualization' do + payload = factory(@user) + post api_v1_visualizations_create_url(api_key: @api_key), + payload.to_json, @headers + + get api_v1_visualizations_index_url(api_key: @api_key), + {}, @headers + + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.size.should == 1 + + payload = factory(@user).merge('name' => 'another one') + post api_v1_visualizations_create_url(api_key: @api_key), + payload.to_json, @headers + + get api_v1_visualizations_index_url(api_key: @api_key), + {}, @headers + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.size.should == 2 + end + + it 'is updated after deleting a visualization' do + payload = factory(@user) + post api_v1_visualizations_create_url(api_key: @api_key), + payload.to_json, @headers + id = JSON.parse(last_response.body).fetch('id') + + get api_v1_visualizations_index_url(api_key: @api_key), + {}, @headers + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.should_not be_empty + + delete api_v1_visualizations_destroy_url(id: id, api_key: @api_key), + {}, @headers + get api_v1_visualizations_index_url(api_key: @api_key), + {}, @headers + + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.should be_empty + end + + it 'paginates results' do + per_page = 10 + total_entries = 20 + + total_entries.times do + post api_v1_visualizations_index_url(api_key: @api_key), + factory(@user).to_json, @headers + end + + get api_v1_visualizations_index_url(api_key: @api_key, page: 1, per_page: per_page), {}, @headers + + last_response.status.should == 200 + + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.length.should == per_page + response.fetch('total_entries').should == total_entries + end + + it 'returns filtered results' do + post api_v1_visualizations_create_url(api_key: @api_key), + factory(@user).to_json, @headers + + get api_v1_visualizations_index_url(api_key: @api_key, type: 'table'), + {}, @headers + last_response.status.should == 200 + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.should be_empty + + post api_v1_visualizations_create_url(api_key: @api_key), + factory(@user).to_json, @headers + post api_v1_visualizations_create_url(api_key: @api_key), + factory(@user).merge(type: 'table').to_json, @headers + get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived'), + {}, @headers + + last_response.status.should == 200 + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.size.should == 2 + end + + it 'creates a visualization from a list of tables' do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + table1 = table_factory + table2 = table_factory + table3 = table_factory + + payload = { + name: 'new visualization', + tables: [ + table1.fetch('name'), + table2.fetch('name'), + table3.fetch('name') + ], + privacy: 'public' + } + + post api_v1_visualizations_create_url(api_key: @api_key), + payload.to_json, @headers + last_response.status.should == 200 + + visualization = JSON.parse(last_response.body) + + # TODO: this endpoint doesn't exist now. Current replacement? + #get "/api/v1/viz/#{visualization.fetch('id')}/viz?api_key=#{@api_key}", + # {}, @headers + #last_response.status.should == 403 + + get api_v2_visualizations_vizjson_url(id: visualization.fetch('id'), api_key: @api_key), + {}, @headers + last_response.status.should == 200 + + # include overlays - # new controller - scope :module => 'carto/api', :format => :json do - get '(/user/:user_domain)(/u/:user_domain)/api/v1/viz' => 'visualizations#index', as: :api_v1_visualizations_index - get '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:id' => 'visualizations#show', as: :api_v1_visualizations_show, constraints: { id: /[^\/]+/ } - get '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:id/likes' => 'visualizations#likes_count', as: :api_v1_visualizations_likes_count, constraints: { id: /[^\/]+/ } - get '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:id/likes/detailed' => 'visualizations#likes_list', as: :api_v1_visualizations_likes_list, constraints: { id: /[^\/]+/ } - get '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:id/like' => 'visualizations#is_liked', as: :api_v1_visualizations_is_liked, constraints: { id: /[^\/]+/ } + get api_v1_visualizations_overlays_index_url(visualization_id: visualization.fetch('id'), api_key: @api_key), + {}, @headers + last_response.status.should == 200 + overlays = JSON.parse(last_response.body) + overlays.length.should == 5 + end + + end + + describe 'GET /api/v1/viz/:id' do + + before(:each) do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + delete_user_data(@user) + end + + it 'returns a visualization' do + payload = factory(@user) + post api_v1_visualizations_create_url(api_key: @api_key), + payload.to_json, @headers + id = JSON.parse(last_response.body).fetch('id') + + get api_v1_visualizations_show_url(id: id, api_key: @api_key), {}, @headers + + last_response.status.should == 200 + response = JSON.parse(last_response.body) + + response.fetch('id') .should_not be_nil + response.fetch('map_id') .should_not be_nil + response.fetch('tags') .should_not be_empty + response.fetch('description') .should_not be_nil + response.fetch('related_tables') .should_not be_nil + end + + end + + describe 'GET /api/v2/viz/:id/viz' do + before(:each) do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + delete_user_data(@user) + end + + it 'renders vizjson v2' do + table_attributes = table_factory + table_id = table_attributes.fetch('id') + get "/api/v2/viz/#{table_id}/viz?api_key=#{@api_key}", + {}, @headers + last_response.status.should == 200 + ::JSON.parse(last_response.body).keys.length.should > 1 + end + + it 'returns children (slides) vizjson' do + parent = api_visualization_creation(@user, @headers, { privacy: Visualization::Member::PRIVACY_PUBLIC, type: Visualization::Member::TYPE_DERIVED }) + child = api_visualization_creation(@user, @headers, { privacy: Visualization::Member::PRIVACY_PUBLIC, type: Visualization::Member::TYPE_SLIDE, parent_id: parent.id }) + + get "/api/v2/viz/#{parent.id}/viz?api_key=#{@api_key}", {}, @headers + + last_response.status.should == 200 + response = JSON.parse(last_response.body) + slides = response.fetch('slides') + slides.count.should == 1 + end + + it "comes with proper surrogate-key" do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true) + table = table_factory(privacy: 1) + source_visualization = table.fetch('table_visualization') + + + payload = { source_visualization_id: source_visualization.fetch('id'), privacy: 'PUBLIC' } + + post api_v1_visualizations_create_url(user_domain: @user.username, api_key: @api_key), + payload.to_json, @headers + + viz_id = JSON.parse(last_response.body).fetch('id') + + put api_v1_visualizations_show_url(user_domain: @user.username, id: viz_id, api_key: @api_key), + { privacy: 'PUBLIC' }.to_json, @headers + + get api_v2_visualizations_vizjson_url(user_domain: @user.username, id: viz_id, api_key: @api_key), + {}, @headers + + last_response.status.should == 200 + last_response.headers.should have_key('Surrogate-Key') + last_response['Surrogate-Key'].should include(CartoDB::SURROGATE_NAMESPACE_VIZJSON) + last_response['Surrogate-Key'].should include(get_surrogate_key(CartoDB::SURROGATE_NAMESPACE_VISUALIZATION, viz_id)) + + delete api_v1_visualizations_show_url(user_domain: @user.username, id: viz_id, api_key: @api_key), + { }, @headers + end + end + + describe 'tests visualization listing filters' do + before(:each) do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + delete_user_data(@user) + end + + it 'uses locked filter' do + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true, :delete => true) + + post api_v1_visualizations_create_url(api_key: @api_key), factory(@user, locked: true).to_json, @headers + vis_1_id = JSON.parse(last_response.body).fetch('id') + post api_v1_visualizations_create_url(api_key: @api_key), factory(@user, locked: false).to_json, @headers + vis_2_id = JSON.parse(last_response.body).fetch('id') + + get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived'), {}, @headers + last_response.status.should == 200 + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.length.should eq 2 + + get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived', locked: true), {}, @headers + last_response.status.should == 200 + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.length.should eq 1 + collection.first.fetch('id').should eq vis_1_id + + get api_v1_visualizations_index_url(api_key: @api_key, type: 'derived', locked: false), {}, @headers + last_response.status.should == 200 + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.length.should eq 1 + collection.first.fetch('id').should eq vis_2_id + end + + it 'searches by tag' do + post api_v1_visualizations_create_url(api_key: @api_key), factory(@user, locked: true, tags: ['test1']).to_json, @headers + vis_1_id = JSON.parse(last_response.body).fetch('id') + post api_v1_visualizations_create_url(api_key: @api_key), factory(@user, locked: false, tags: ['test2']).to_json, @headers + + get api_v1_visualizations_index_url(api_key: @api_key, tags: 'test1'), {}, @headers + last_response.status.should == 200 + response = JSON.parse(last_response.body) + collection = response.fetch('visualizations') + collection.length.should eq 1 + collection.first['id'].should == vis_1_id + end + + end - get '(/user/:user_domain)(/u/:user_domain)/api/v2/viz/:id/viz' => 'visualizations#vizjson2', as: :api_v2_visualizations_vizjson, constraints: { id: /[^\/]+/ } + describe 'non existent visualization' do + it 'returns 404' do + get api_v1_visualizations_show_url(id: TEST_UUID, api_key: @api_key), {}, @headers + last_response.status.should == 404 - # overlays (also needed in some tests) - get '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:visualization_id/overlays' => 'overlays#index', as: :api_v1_visualizations_overlays_index, constraints: { visualization_id: /[^\/]+/ } + put api_v1_visualizations_update_url(id: TEST_UUID, api_key: @api_key), {}, @headers + last_response.status.should == 404 - # watching - get '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:id/watching' => 'visualizations#list_watching', as: :api_v1_visualizations_notify_watching, constraints: { id: /[^\/]+/ } + delete api_v1_visualizations_destroy_url(id: TEST_UUID, api_key: @api_key), {}, @headers + last_response.status.should == 404 + + get "/api/v2/viz/#{TEST_UUID}/viz?api_key=#{@api_key}", {}, @headers + last_response.status.should == 404 end + end + + describe '/api/v1/viz/:id/watching' do - # old controller - scope :module => 'api/json', :format => :json do - post '(/user/:user_domain)(/u/:user_domain)/api/v1/viz' => 'visualizations#create', as: :api_v1_visualizations_create - put '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:id' => 'visualizations#update', as: :api_v1_visualizations_update, constraints: { id: /[^\/]+/ } - put '(/user/:user_domain)(/u/:user_domain)/api/v1/perm/:id' => 'permissions#update', as: :api_v1_permissions_update - post '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:id/like' => 'visualizations#add_like', as: :api_v1_visualizations_add_like, constraints: { id: /[^\/]+/ } - delete '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:id/like' => 'visualizations#remove_like', as: :api_v1_visualizations_remove_like, constraints: { id: /[^\/]+/ } - delete '(/user/:user_domain)(/u/:user_domain)/api/v1/viz/:id' => 'visualizations#destroy', as: :api_v1_visualizations_destroy, constraints: { id: /[^\/]+/ } + before(:all) do + @user_1 = create_test_user + @user_2 = create_test_user - post '(/user/:user_domain)(/u/:user_domain)/api/v1/tables' => 'tables#create', as: :api_v1_tables_create - put '(/user/:user_domain)(/u/:user_domain)/api/v1/tables/:id' => 'tables#update', as: :api_v1_tables_update, constraints: { id: /[^\/]+/ } + organization = test_organization.save + + user_org = CartoDB::UserOrganization.new(organization.id, @user_1.id) + user_org.promote_user_to_admin + @user_1.reload + + @user_2.organization_id = organization.id + @user_2.save.reload + end + + it 'returns an empty array if no other user is watching' do + CartoDB::Visualization::Watcher.any_instance.stubs(:list).returns([]) + + CartoDB::NamedMapsWrapper::NamedMaps.any_instance.stubs(:get => nil, :create => true, :update => true) + + login(@user_1) + post api_v1_visualizations_create_url(api_key: @user_1.api_key), factory(@user_1, locked: true).to_json, @headers + id = JSON.parse(last_response.body).fetch('id') + + login(@user_1) + get api_v1_visualizations_notify_watching_url(id: id, api_key: @user_1.api_key) + body = JSON.parse(last_response.body) + body.should == [] end end @@ -104,10 +1289,91 @@ def factory(user, attributes={}) end - after(:all) do - Rails.application.reload_routes! - end - include Rack::Test::Methods include Warden::Test::Helpers + include CacheHelper + + private + + # Custom hash comparation, since in the ActiveModel-based controllers + # we allow some differences: + # - x to many associations can return [] instead of nil + def normalize_hash(h, normalized_attributes = NORMALIZED_ASSOCIATION_ATTRIBUTES) + h.each { |k, v| + h[k] = nil if v == [] + h[k] = '' if normalized_attributes[:attributes].include?(k) + if normalized_attributes[:associations].keys.include?(k) + normalize_hash(v, normalized_attributes[:associations][k]) + end + } + end + + # INFO: this test uses comparison against old data structures to check validity. + # You can use this method to remove that new data so next comparisons will work. + def remove_data_only_in_new_controllers(visualization_hash, new_attributes = NEW_ATTRIBUTES) + visualization_hash.each { |k, v| + if new_attributes[:attributes].include?(k) + removed = visualization_hash.delete(k) + elsif new_attributes[:associations].include?(k) + remove_data_only_in_new_controllers(v, new_attributes[:associations][k]) + end + } + end + + def login(user) + login_as(user, scope: user.subdomain) + host! "#{user.subdomain}.localhost.lan" + end + + def base_url + '/api/v1/viz' + end + + def response_body(params=nil) + get base_url, params, @headers + last_response.status.should == 200 + body = JSON.parse(last_response.body) + body['visualizations'] = body['visualizations'].map { |v| normalize_hash(v) }.map { |v| remove_data_only_in_new_controllers(v) } + body + end + + def factory(user, attributes={}) + visualization_template(user, attributes) + end + + def table_factory(options={}) + privacy = options.fetch(:privacy, 1) + + seed = rand(9999) + payload = { + name: "table #{seed}", + description: "table #{seed} description" + } + post api_v1_tables_create_url(api_key: @api_key), + payload.to_json, @headers + + table_attributes = JSON.parse(last_response.body) + table_id = table_attributes.fetch('id') + + put api_v1_tables_update_url(id: table_id, api_key: @api_key), + { privacy: privacy }.to_json, @headers + + table_attributes + end + + def api_visualization_creation(user, headers, additional_fields = {}) + post api_v1_visualizations_create_url(user_domain: user.username, api_key: user.api_key), factory(user).merge(additional_fields).to_json, headers + id = JSON.parse(last_response.body).fetch('id') + CartoDB::Visualization::Member.new(id: id).fetch + end + + def test_organization + organization = Organization.new + organization.name = org_name = "org#{rand(9999)}" + organization.quota_in_bytes = 1234567890 + organization.seats = 5 + organization + end + + end From 1bdcfdb9c63f19ea1ccc312867f1fd836ba067e9 Mon Sep 17 00:00:00 2001 From: Kartones Date: Tue, 23 Jun 2015 17:57:02 +0200 Subject: [PATCH 15/17] #4166 trying to fix weird issue with rspec --- spec/models/user_presenter_spec.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/models/user_presenter_spec.rb b/spec/models/user_presenter_spec.rb index e7e85d009c0b..7a8992089f0d 100644 --- a/spec/models/user_presenter_spec.rb +++ b/spec/models/user_presenter_spec.rb @@ -146,7 +146,11 @@ def compare_data(old_data, new_data, org_user = false) new_data[:organization].keys.should == old_data[:organization].keys # This is an implicit test of OrganizationPresenter... - new_data[:organization][:created_at].should == old_data[:organization][:created_at] + # INFO: we have a weird error sometimes running builds that fails comparing dates despite having equal value... + # > Diff:2015-06-23 17:27:02 +0200.==(2015-06-23 17:27:02 +0200) returned false even though the diff between + # 2015-06-23 17:27:02 +0200 and 2015-06-23 17:27:02 +0200 is empty. Check the implementation of + # 2015-06-23 17:27:02 +0200.==. + new_data[:organization][:created_at].to_s.should == old_data[:organization][:created_at].to_s new_data[:organization][:description].should == old_data[:organization][:description] new_data[:organization][:discus_shortname].should == old_data[:organization][:discus_shortname] new_data[:organization][:display_name].should == old_data[:organization][:display_name] @@ -164,7 +168,8 @@ def compare_data(old_data, new_data, org_user = false) new_data[:organization][:geocoding_block_price].should == old_data[:organization][:geocoding_block_price] new_data[:organization][:seats].should == old_data[:organization][:seats] new_data[:organization][:twitter_username].should == old_data[:organization][:twitter_username] - new_data[:organization][:updated_at].should == old_data[:organization][:updated_at] + # Same as [:organization][:created_at] issue above + new_data[:organization][:updated_at].to_s.should == old_data[:organization][:updated_at].to_s #owner is excluded from the users list new_data[:organization][:website].should == old_data[:organization][:website] new_data[:organization][:avatar_url].should == old_data[:organization][:avatar_url] From 5e4407219a8efbff75caf3919731e715d06eb563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Ignacio=20S=C3=A1nchez=20Lara?= Date: Wed, 24 Jun 2015 11:06:53 +0200 Subject: [PATCH 16/17] current_viewer nil check fixes #4181 --- app/controllers/carto/api/visualizations_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/carto/api/visualizations_controller.rb b/app/controllers/carto/api/visualizations_controller.rb index 1e88802b1618..3dad8cc7469f 100644 --- a/app/controllers/carto/api/visualizations_controller.rb +++ b/app/controllers/carto/api/visualizations_controller.rb @@ -65,7 +65,7 @@ def is_liked render_jsonp({ id: @visualization.id, likes: @visualization.likes.count, - liked: @visualization.is_liked_by_user_id?(current_viewer.id) + liked: current_viewer ? @visualization.is_liked_by_user_id?(current_viewer.id) : false }) end From 6eca9997996ac8502801939966ba4ccd8f2b7f73 Mon Sep 17 00:00:00 2001 From: Kartones Date: Wed, 24 Jun 2015 12:00:05 +0200 Subject: [PATCH 17/17] #4183 ported legacy code to read by id or name in the case of vizjsons --- .../carto/api/visualizations_controller.rb | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/app/controllers/carto/api/visualizations_controller.rb b/app/controllers/carto/api/visualizations_controller.rb index 3dad8cc7469f..e16732933a6e 100644 --- a/app/controllers/carto/api/visualizations_controller.rb +++ b/app/controllers/carto/api/visualizations_controller.rb @@ -2,12 +2,14 @@ require_relative 'vizjson_presenter' require_relative '../../../models/visualization/stats' require_relative 'paged_searcher' +require_dependency 'carto/uuidhelper' module Carto module Api class VisualizationsController < ::Api::ApplicationController include VisualizationSearcher include PagedSearcher + include Carto::UUIDHelper ssl_required :index, :show ssl_allowed :vizjson2, :likes_count, :likes_list, :is_liked, :list_watching @@ -17,7 +19,7 @@ class VisualizationsController < ::Api::ApplicationController before_filter :optional_api_authorization, only: [:index, :vizjson2, :is_liked] before_filter :id_and_schema_from_params - before_filter :load_table, only: [:vizjson2] + before_filter :load_by_name_or_id, only: [:vizjson2] before_filter :load_visualization, only: [:likes_count, :likes_list, :is_liked, :show, :stats, :list_watching] def show @@ -96,20 +98,15 @@ def list_watching private - def load_table - # TODO: refactor this for vizjson, that uses to look for a visualization, so it should come first + def load_by_name_or_id + @table = is_uuid?(@id) ? Carto::UserTable.where(id: @id).first : nil - @table = Carto::UserTable.where(id: @id).first - # TODO: id should _really_ contain either an id of a user_table or a visualization?? - # Some tests fail if not, and older controller works that way, but... + # INFO: id should _really_ contain either an id of a user_table or a visualization, but for legacy reasons... if @table @visualization = @table.visualization else - @table = Visualization.where(id: @id).first - @visualization = @table - # TODO: refactor load_table duplication - return render(text: 'Visualization does not exist', status: 404) if @visualization.nil? - return render(text: 'Visualization not viewable', status: 403) if !@visualization.is_viewable_by_user?(current_viewer) + load_visualization + @table = @visualization end end