require_relative '../../../spec_helper' require_dependency 'carto/uuidhelper' include Carto::UUIDHelper shared_context 'layer hierarchy' do before(:all) do @user1 = FactoryGirl.create(:valid_user, private_tables_enabled: true, private_maps_enabled: true) @user2 = FactoryGirl.create(:valid_user, private_tables_enabled: true) @map = FactoryGirl.create(:carto_map_with_layers, user_id: @user1.id) @layer = @map.layers.first @visualization = FactoryGirl.create(:carto_visualization, map: @map, privacy: Carto::Visualization::PRIVACY_PRIVATE, user_id: @user1.id) end before(:each) do @widget = FactoryGirl.create(:widget, layer: @layer) end after(:each) do Carto::Widget.destroy_all end after(:all) do @visualization.destroy if @visualization end def response_widget_should_match_widget(response_widget, widget) response_widget[:id].should == widget.id response_widget[:order].should == widget.order response_widget[:type].should == widget.type response_widget[:title].should == widget.title response_widget[:layer_id].should == widget.layer.id response_widget[:options].should == widget.options.symbolize_keys response_widget[:style].should == widget.style.symbolize_keys if widget.source_id.present? response_widget[:source][:id].should eq widget.source_id else response_widget[:source].should be_nil end end def response_widget_should_match_payload(response_widget, payload) response_widget[:layer_id].should == payload[:layer_id] response_widget[:type].should == payload[:type] response_widget[:title].should == payload[:title] response_widget[:options].should == payload[:options].symbolize_keys if payload[:style].present? response_widget[:style].should == payload[:style].symbolize_keys else response_widget[:style].blank?.should be_true end if payload[:source].present? response_widget[:source][:id].should == payload[:source][:id] else response_widget[:source].should be_nil end if payload[:order].present? response_widget[:order].should == payload[:order] else @visualization.reload response_widget[:order].should == @visualization.widgets.count - 1 end end def widget_payload( layer_id: @layer.id, type: 'formula', title: 'the title', options: { 'a field' => 'first', 'another field' => 'second' }, order: nil, source: { id: 'a0' }, style: { 'widget_style': { 'fill': 'wadus' } }) payload = { layer_id: layer_id, type: type, title: title, options: options, style: style } payload[:order] = order if order payload[:source] = source if source payload end end describe Carto::Api::WidgetsController do include_context 'layer hierarchy' let(:random_map_id) { Carto::UUIDHelper.random_uuid } let(:random_layer_id) { Carto::UUIDHelper.random_uuid } let(:random_widget_id) { Carto::UUIDHelper.random_uuid } describe '#show' do it 'returns 401 for non-authenticated requests' do get_json widget_url(user_domain: @user1.username, map_id: random_map_id, map_layer_id: random_layer_id, id: random_widget_id), {}, http_json_headers do |response| response.status.should == 401 end end it 'returns 404 for requests without matching map, layer or widget' do map_id = @map.id get_json widget_url(user_domain: @user1.username, map_id: random_map_id, map_layer_id: @layer.id, id: @widget.id, api_key: @user1.api_key), {}, http_json_headers do |response| response.status.should == 404 end get_json widget_url(user_domain: @user1.username, map_id: map_id, map_layer_id: random_layer_id, id: @widget.id, api_key: @user1.api_key), {}, http_json_headers do |response| response.status.should == 404 end get_json widget_url(user_domain: @user1.username, map_id: map_id, map_layer_id: @widget.layer_id, id: random_widget_id, api_key: @user1.api_key), {}, http_json_headers do |response| response.status.should == 404 end end it 'returns the source widget content' do get_json widget_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user1.api_key), {}, http_json_headers do |response| response.status.should == 200 response_widget_should_match_widget(response.body, @widget) end end it 'returns options as json' do get widget_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user1.api_key), {}, http_json_headers response.status.should == 200 JSON.parse(response.body).class.should == Hash JSON.parse(response.body)['options'].class.should == Hash end it 'returns 403 if visualization is private and current user is not the owner' do get_json widget_url(user_domain: @user2.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user2.api_key), {}, http_json_headers do |response| response.status.should == 403 end end it 'returns 403 if visualization is public and current user is not the owner' do Carto::Visualization.stubs(:privacy).returns('public') get_json widget_url(user_domain: @user2.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user2.api_key), {}, http_json_headers do |response| response.status.should == 403 end end end describe '#create' do it 'creates a new widget' do payload = widget_payload post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user1.api_key), payload, http_json_headers do |response| response.status.should == 201 response_widget = response.body response_widget_should_match_payload(response_widget, payload) widget = Carto::Widget.find(response_widget[:id]) response_widget_should_match_widget(response_widget, widget) widget.destroy end end it 'creates a new widget with order' do payload = widget_payload(order: 7) post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user1.api_key), payload, http_json_headers do |response| response.status.should == 201 response_widget = response.body response_widget_should_match_payload(response_widget, payload) widget = Carto::Widget.find(response_widget[:id]) response_widget_should_match_widget(response_widget, widget) widget.destroy end end it 'creates a new widget without style' do payload = widget_payload.reject { |p| p == :style } post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user1.api_key), payload, http_json_headers do |response| response.status.should == 201 response_widget = response.body response_widget_should_match_payload(response_widget, payload) widget = Carto::Widget.find(response_widget[:id]) response_widget_should_match_widget(response_widget, widget) widget.destroy end end it 'creates a new widget with source_id' do analysis = FactoryGirl.create(:analysis, visualization: @visualization, user_id: @user1.id) payload = widget_payload.merge(source: { id: analysis.natural_id }) url = widgets_url( user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user1.api_key) post_json url, payload, http_json_headers do |response| response.status.should eq 201 response_widget = response.body response_widget[:source][:id].should eq analysis.natural_id widget = Carto::Widget.find(response_widget[:id]) widget.source_id.should eq analysis.natural_id widget.destroy end analysis.destroy end it 'returns 404 for unknown map id' do post_json widgets_url(user_domain: @user1.username, map_id: random_uuid, map_layer_id: @widget.layer_id, api_key: @user1.api_key), widget_payload, http_json_headers do |response| response.status.should == 404 end end it 'returns 422 if layer id do not match' do post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: random_uuid, api_key: @user1.api_key), widget_payload, http_json_headers do |response| response.status.should == 422 end end it 'returns 422 if payload layer id do not match' do post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user1.api_key), widget_payload(layer_id: random_uuid), http_json_headers do |response| response.status.should == 422 end end it 'returns 422 if layer id do not match map' do other_map = FactoryGirl.create(:carto_map_with_layers, user_id: @user1.id) other_layer = other_map.data_layers.first other_layer.should_not be_nil post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: other_layer.id, api_key: @user1.api_key), widget_payload(layer_id: other_layer.id), http_json_headers do |response| response.status.should == 422 end other_map.destroy end it 'returns 422 if missing source' do post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user1.api_key), widget_payload(source: nil), http_json_headers do |response| response.status.should == 422 end end it 'returns 403 if visualization is private and current user is not the owner' do post_json widgets_url(user_domain: @user2.username, map_id: @map.id, map_layer_id: @widget.layer_id, api_key: @user2.api_key), widget_payload, http_json_headers do |response| response.status.should == 403 end end it 'assigns consecutive orders for widgets for the same visualization' do # Note: First widget is already created in the layer hierarchy context @layer.widgets.reload.each(&:destroy) payload = widget_payload(layer_id: @layer.id) post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @layer.id, api_key: @user1.api_key), payload, http_json_headers do |response| response.status.should == 201 response.body[:order].should == 0 end post_json widgets_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @layer.id, api_key: @user1.api_key), payload, http_json_headers do |response| response.status.should == 201 response.body[:order].should == 1 end Carto::Widget.where(layer_id: @layer.id).destroy_all end end describe '#update' do it 'returns 422 if layer id does not match in url and payload' do put_json widget_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user1.api_key), widget_payload(layer_id: random_uuid), http_json_headers do |response| response.status.should == 422 end end it 'ignores payload id' do put_json widget_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user1.api_key), widget_payload.merge(id: random_uuid), http_json_headers do |response| response.status.should == 200 end end it 'returns 200 and updates the model' do analysis = FactoryGirl.create(:analysis, visualization: @visualization, user_id: @user1.id) new_order = @widget.order + 1 new_type = "new #{@widget.type}" new_title = "new #{@widget.title}" new_options = @widget.options.merge(new: 'whatever') payload = widget_payload( order: new_order, type: new_type, title: new_title, options: new_options, source: { id: analysis.natural_id } ) url = widget_url( user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user1.api_key) put_json url, payload, http_json_headers do |response| response.status.should eq 200 response_widget_should_match_payload(response.body, payload) Carto::Widget.find(response.body[:id]) response_widget_should_match_widget(response.body, Carto::Widget.find(response.body[:id])) end analysis.destroy end end describe '#update_many' do it 'updates many' do widget2 = FactoryGirl.create(:widget, layer: @layer) payload = [serialize_widget(@widget).merge(title: 'wadus'), serialize_widget(widget2).merge(title: 'wadus2')] url = api_v3_maps_layers_update_many_widgets_url(user_domain: @user1.username, map_id: @map.id, api_key: @user1.api_key) put_json url, payload, http_json_headers do |response| response.status.should == 200 response.body[0]['title'].should eq 'wadus' Carto::Widget.find(response.body[0]['id']).title.should eq 'wadus' response.body[1]['title'].should eq 'wadus2' Carto::Widget.find(response.body[1]['id']).title.should eq 'wadus2' end end it 'fails with 404 if widget is not found' do payload = [serialize_widget(@widget).merge(title: 'wadus')] @widget.destroy url = api_v3_maps_layers_update_many_widgets_url(user_domain: @user1.username, map_id: @map.id, api_key: @user1.api_key) put_json url, payload, http_json_headers do |response| response.status.should == 404 end end it 'fails with 404 if widget does not belong to map' do payload = [serialize_widget(@widget).merge(title: 'wadus')] Carto::Widget.any_instance.stubs(:belongs_to_map?).with(@map.id).returns(false) url = api_v3_maps_layers_update_many_widgets_url(user_domain: @user1.username, map_id: @map.id, api_key: @user1.api_key) put_json url, payload, http_json_headers do |response| response.status.should == 404 @widget.reload.title.should_not eq 'wadus' end end it 'fails with 404 if not writable by user' do payload = [serialize_widget(@widget).merge(title: 'wadus')] Carto::Widget.any_instance.stubs(:writable_by_user?).returns(false) url = api_v3_maps_layers_update_many_widgets_url(user_domain: @user1.username, map_id: @map.id, api_key: @user1.api_key) put_json url, payload, http_json_headers do |response| response.status.should == 403 @widget.reload.title.should_not eq 'wadus' end end it 'fails if any of the widgets fails and doesn\'t update any' do widget2 = FactoryGirl.create(:widget, layer: @layer) Carto::Widget.stubs(:find).with(@widget.id).returns(@widget) Carto::Widget.stubs(:find).with(widget2.id).raises(ActiveRecord::RecordNotFound.new) payload = [serialize_widget(@widget).merge(title: 'wadus'), serialize_widget(widget2).merge(title: 'wadus2')] url = api_v3_maps_layers_update_many_widgets_url(user_domain: @user1.username, map_id: @map.id, api_key: @user1.api_key) put_json url, payload, http_json_headers do |response| response.status.should == 404 Carto::Widget.unstub(:find) @widget.reload.title.should_not eq 'wadus' widget2.reload.title.should_not eq 'wadus2' end end def serialize_widget(w) payload = { id: w.id, layer_id: w.layer_id, type: w.type, title: w.title, options: w.options, style: w.style } payload[:order] = w.order if w.order payload[:source] = { id: w.source_id } if w.source_id payload end end describe '#delete' do it 'returns 200 and deletes the widget' do delete_json widget_url(user_domain: @user1.username, map_id: @map.id, map_layer_id: @widget.layer_id, id: @widget.id, api_key: @user1.api_key), {}, http_json_headers do |response| response.status.should == 200 Carto::Widget.where(id: @widget.id).first.should be_nil end end end end