cartodb-4.42/spec/requests/carto/api/analyses_controller_spec.rb
2024-04-06 05:25:13 +00:00

481 lines
18 KiB
Ruby

require 'spec_helper_min'
require 'support/helpers'
require 'factories/carto_visualizations'
require_dependency 'carto/uuidhelper'
describe Carto::Api::AnalysesController do
include Carto::Factories::Visualizations
include HelperMethods
before(:all) do
create(:feature_flag, name: 'editor-3', restricted: false)
@user = FactoryGirl.create(:carto_user, builder_enabled: true)
@user2 = FactoryGirl.create(:carto_user, builder_enabled: true)
@map, @table, @table_visualization, @visualization = create_full_visualization(@user)
bypass_named_maps
@analysis = FactoryGirl.create(:source_analysis, visualization_id: @visualization.id, user_id: @user.id)
end
after(:all) do
Carto::FeatureFlag.destroy_all
destroy_full_visualization(@map, @table, @table_visualization, @visualization)
# This avoids connection leaking.
::User[@user.id].destroy
::User[@user2.id].destroy
@analysis.destroy
end
def viz_analysis_url(user, visualization, analysis_id)
analysis_url(
user_domain: user.username,
api_key: user.api_key,
visualization_id: visualization.id,
id: analysis_id)
end
def clean_analysis_definition(analysis_definition)
# Remove options[:style_history] from all nested nodes for comparison
definition_node = Carto::AnalysisNode.new(analysis_definition.deep_symbolize_keys)
definition_node.descendants.each do |n|
n.definition[:options].delete(:style_history) if n.definition[:options].present?
n.definition.delete(:options) if n.definition[:options] == {}
end
definition_node.definition
end
describe '#show' do
it 'returns 403 if user does not own the visualization' do
get_json viz_analysis_url(@user2, @visualization, @analysis.id) do |response|
response.status.should eq 403
end
end
def verify_analysis_response_body(response_body, analysis)
response_body[:id].should eq analysis.id
analysis_definition = response_body[:analysis_definition]
clean_analysis_definition(analysis_definition).should eq clean_analysis_definition(analysis.analysis_definition)
analysis_definition[:id].should eq analysis.natural_id
end
it 'returns existing analysis by uuid' do
get_json viz_analysis_url(@user, @visualization, @analysis.id) do |response|
response.status.should eq 200
verify_analysis_response_body(response[:body], @analysis)
end
end
it 'returns 404 for nonexisting analysis' do
get_json viz_analysis_url(@user, @visualization, 'wadus') do |response|
response.status.should eq 404
end
end
it 'returns existing analysis by json first id' do
get_json viz_analysis_url(@user, @visualization, @analysis.natural_id) do |response|
response.status.should eq 200
verify_analysis_response_body(response[:body], @analysis)
end
end
it 'returns existing analysis by json first id with uuid ids' do
bypass_named_maps
analysis2 = FactoryGirl.create(
:source_analysis,
visualization_id: @visualization.id,
user_id: @user.id,
analysis_definition: { id: Carto::UUIDHelper.random_uuid }
)
get_json viz_analysis_url(@user, @visualization, analysis2.natural_id) do |response|
response.status.should eq 200
verify_analysis_response_body(response[:body], analysis2)
end
analysis2.destroy
end
end
let(:natural_id) { 'a1' }
let(:payload) { { analysis_definition: { id: natural_id } } }
describe '#create' do
def create_analysis_url(user, visualization)
analyses_url(
user_domain: user.username,
api_key: user.api_key,
visualization_id: visualization.id)
end
it 'creates new analysis' do
bypass_named_maps
post_json create_analysis_url(@user, @visualization), payload do |response|
response.status.should eq 201
response.body[:id].should_not be_nil
analysis_definition = clean_analysis_definition(response.body[:analysis_definition])
analysis_definition.should eq payload[:analysis_definition]
a = Carto::Analysis.find_by_natural_id(@visualization.id, natural_id)
a.should_not eq nil
a.user_id.should eq @user.id
a.visualization_id.should eq @visualization.id
clean_analysis_definition(a.analysis_definition).should eq payload[:analysis_definition].deep_symbolize_keys
a.destroy
end
end
it 'registers table dependencies when creating analysis' do
bypass_named_maps
# Twice because of destroy
Carto::Layer.any_instance.expects(:register_table_dependencies).times(@visualization.data_layers.count * 2)
post_json create_analysis_url(@user, @visualization), payload do |response|
response.status.should eq 201
a = Carto::Analysis.find(response.body[:id])
a.destroy
end
end
it 'overrides old analysis in the same visualization if they have the same natural id' do
bypass_named_maps
Carto::Analysis.where(visualization_id: @visualization.id).count.should eq 1
updated_analysis = { analysis_definition: @analysis.analysis_definition }
updated_analysis[:analysis_definition][:params] = { query: 'select * from whatever_overrided' }
post_json create_analysis_url(@user, @visualization), updated_analysis do |response|
response.status.should eq 201
# Check that update worked
response_body = response.body
response_body[:id].should eq @analysis.id
response_body[:analysis_definition][:id].should eq @analysis.natural_id
clean_analysis_definition(response_body[:analysis_definition]).should eq clean_analysis_definition(updated_analysis[:analysis_definition])
end
# Check that no analysis is _added_
analyses = Carto::Analysis.where(visualization_id: @visualization.id).all
analyses.count.should eq 1
new_analysis = analyses.first
new_analysis.analysis_definition.should eq updated_analysis[:analysis_definition]
new_analysis.updated_at.should be > @analysis.updated_at
# Check that the old analysis was updated
@analysis.reload
new_analysis.created_at.should eq @analysis.created_at
new_analysis.updated_at.should eq @analysis.updated_at
end
it 'returns 422 if payload visualization or user id do not match with url' do
post_json create_analysis_url(@user, @visualization), payload.merge(visualization_id: 'x') do |response|
response.status.should eq 422
end
end
it 'returns 422 if payload is not valid json' do
post_json create_analysis_url(@user, @visualization), nil do |response|
response.status.should eq 422
end
post_json create_analysis_url(@user, @visualization), "" do |response|
response.status.should eq 422
end
post_json create_analysis_url(@user, @visualization), "wadus" do |response|
response.status.should eq 422
end
post_json create_analysis_url(@user, @visualization), "wadus: 1" do |response|
response.status.should eq 422
end
end
it 'returns 422 if payload is empty json' do
post_json create_analysis_url(@user, @visualization), {} do |response|
response.status.should eq 422
end
post_json create_analysis_url(@user, @visualization), [] do |response|
response.status.should eq 422
end
end
it 'returns 422 if analysis definition is not valid json' do
post_json create_analysis_url(@user, @visualization), analysis_definition: nil do |response|
response.status.should eq 422
end
post_json create_analysis_url(@user, @visualization), analysis_definition: "" do |response|
response.status.should eq 422
end
post_json create_analysis_url(@user, @visualization), analysis_definition: "wadus" do |response|
response.status.should eq 422
end
post_json create_analysis_url(@user, @visualization), analysis_definition: "wadus: 1" do |response|
response.status.should eq 422
end
end
it 'returns 422 if analysis_definition is empty json' do
post_json create_analysis_url(@user, @visualization), analysis_definition: {} do |response|
response.status.should eq 422
end
post_json create_analysis_url(@user, @visualization), analysis_definition: [] do |response|
response.status.should eq 422
end
end
it 'returns 403 if user does not own the visualization' do
post_json create_analysis_url(@user2, @visualization), payload do |response|
response.status.should eq 403
end
end
end
describe '#update' do
let(:new_natural_id) { "#{natural_id}_2" }
let(:new_key) { :whatever }
let(:new_payload) do
payload.delete(:id)
payload.merge(whatever: 'really?')
payload[:analysis_definition][:id] = new_natural_id
payload[:analysis_definition][new_key] = 'really'
payload
end
it 'updates existing analysis' do
@analysis.reload
@analysis.analysis_definition[:id].should_not eq new_payload[:analysis_definition][:id]
@analysis.analysis_definition[new_key].should be_nil
bypass_named_maps
put_json viz_analysis_url(@user, @visualization, @analysis), new_payload do |response|
response.status.should eq 200
clean_analysis_definition(response.body[:analysis_definition]).should eq clean_analysis_definition(new_payload[:analysis_definition])
a = Carto::Analysis.find(@analysis.id)
a.analysis_definition[:id].should eq new_payload[:analysis_definition][:id]
a.analysis_definition[new_key].should eq new_payload[:analysis_definition][new_key]
a.analysis_definition.deep_symbolize_keys.should eq new_payload[:analysis_definition].deep_symbolize_keys
end
end
it 'registers table dependencies when updating existing analysis' do
bypass_named_maps
Carto::Layer.any_instance.expects(:register_table_dependencies).times(@visualization.data_layers.count)
put_json viz_analysis_url(@user, @visualization, @analysis), new_payload do |response|
response.status.should eq 200
end
end
it 'returns 422 if payload visualization_id or id do not match' do
put_json viz_analysis_url(@user, @visualization, @analysis),
new_payload.merge(visualization_id: 'x') do |response|
response.status.should eq 422
end
put_json viz_analysis_url(@user, @visualization, @analysis), new_payload.merge(id: 'x') do |response|
response.status.should eq 422
end
end
it 'returns 403 if user does not own the visualization' do
put_json viz_analysis_url(@user2, @visualization, @analysis), new_payload do |response|
response.status.should eq 403
end
end
it 'returns 422 if payload is not valid json' do
put_json viz_analysis_url(@user, @visualization, @analysis), nil do |response|
response.status.should eq 422
end
put_json viz_analysis_url(@user, @visualization, @analysis), "" do |response|
response.status.should eq 422
end
put_json viz_analysis_url(@user, @visualization, @analysis), "wadus" do |response|
response.status.should eq 422
end
put_json viz_analysis_url(@user, @visualization, @analysis), "wadus: 1" do |response|
response.status.should eq 422
end
end
it 'returns 422 if payload is empty json' do
put_json viz_analysis_url(@user, @visualization, @analysis), {} do |response|
response.status.should eq 422
end
put_json viz_analysis_url(@user, @visualization, @analysis), [] do |response|
response.status.should eq 422
end
end
it 'returns 422 if analysis definition is not valid json' do
put_json viz_analysis_url(@user, @visualization, @analysis), analysis_definition: nil do |response|
response.status.should eq 422
end
put_json viz_analysis_url(@user, @visualization, @analysis), analysis_definition: "" do |response|
response.status.should eq 422
end
put_json viz_analysis_url(@user, @visualization, @analysis), analysis_definition: "wadus" do |response|
response.status.should eq 422
end
put_json viz_analysis_url(@user, @visualization, @analysis), analysis_definition: "wadus: 1" do |response|
response.status.should eq 422
end
end
it 'returns 422 if analysis_definition is empty json' do
put_json viz_analysis_url(@user, @visualization, @analysis), analysis_definition: {} do |response|
response.status.should eq 422
end
put_json viz_analysis_url(@user, @visualization, @analysis), analysis_definition: [] do |response|
response.status.should eq 422
end
end
end
describe '#destroy' do
it 'destroys existing analysis' do
bypass_named_maps
delete_json viz_analysis_url(@user, @visualization, @analysis) do |response|
response.status.should eq 200
Carto::Analysis.where(id: @analysis.id).first.should be_nil
end
end
it 'registers table dependencies when destroying existing analysis' do
bypass_named_maps
analysis = FactoryGirl.create(:source_analysis, visualization_id: @visualization.id, user_id: @user.id)
Carto::Layer.any_instance.expects(:register_table_dependencies).times(@visualization.data_layers.count)
delete_json viz_analysis_url(@user, @visualization, analysis) do |response|
response.status.should eq 200
Carto::Analysis.where(id: analysis.id).first.should be_nil
end
end
it 'returns 403 if user does not own the visualization' do
delete_json viz_analysis_url(@user2, @visualization, @analysis) do |response|
response.status.should eq 403
end
end
end
describe '#LayerNodeStyle cache' do
before(:all) do
@styled_analysis = FactoryGirl.create(:analysis_point_in_polygon, visualization_id: @visualization.id, user_id: @user.id)
@layer_id = @visualization.data_layers.first.id
end
before(:each) do
@styled_analysis.analysis_node.descendants.map(&:id).each do |node_id|
Carto::LayerNodeStyle.create(
layer_id: @layer_id,
source_id: node_id,
options: {
tile_style: 'wadus'
},
infowindow: {},
tooltip: {}
)
end
end
after(:each) do
Carto::LayerNodeStyle.where(layer_id: @layer_id).delete_all
end
it '#show returns tile styles' do
get_json viz_analysis_url(@user, @visualization, @styled_analysis.id) do |response|
response.status.should eq 200
Carto::AnalysisNode.new(response[:body][:analysis_definition].deep_symbolize_keys).descendants.each do |node|
node.options[:style_history][@layer_id.to_sym][:options][:tile_style].should eq 'wadus'
end
end
end
it '#show returns tile styles for torque layers' do
Carto::Layer.any_instance.stubs(:kind).returns('torque')
get_json viz_analysis_url(@user, @visualization, @styled_analysis.id) do |response|
response.status.should eq 200
Carto::AnalysisNode.new(response[:body][:analysis_definition].deep_symbolize_keys).descendants.each do |node|
node.options[:style_history][@layer_id.to_sym][:options][:tile_style].should eq 'wadus'
end
end
end
it '#update invalidates the affected node' do
@styled_analysis.analysis_node.params[:dummy] = 'yes'
new_payload = {
id: @styled_analysis.id,
analysis_definition: @styled_analysis.analysis_definition
}
@styled_analysis.reload
put_json viz_analysis_url(@user, @visualization, @styled_analysis), new_payload do |response|
response.status.should eq 200
end
# Should only invalidate the parent
updated_ids = [@styled_analysis.natural_id]
@styled_analysis.analysis_node.descendants.map(&:id).each do |node_id|
modified = updated_ids.include?(node_id)
Carto::LayerNodeStyle.from_visualization_and_source(@visualization, node_id).any?.should eq !modified
end
end
it '#update invalidates the affected node and its parent' do
first_child = @styled_analysis.analysis_node.children.first
first_child.params[:dummy] = 'yes'
new_payload = {
id: @styled_analysis.id,
analysis_definition: @styled_analysis.analysis_definition
}
@styled_analysis.reload
put_json viz_analysis_url(@user, @visualization, @styled_analysis), new_payload do |response|
response.status.should eq 200
end
# Should invalidate the first child and the parent
updated_ids = [@styled_analysis.natural_id, first_child.id]
@styled_analysis.analysis_node.descendants.map(&:id).each do |node_id|
modified = updated_ids.include?(node_id)
Carto::LayerNodeStyle.from_visualization_and_source(@visualization, node_id).any?.should eq !modified
end
end
it '#update does not invalidate upon options change' do
@styled_analysis.reload
@styled_analysis.analysis_node.options[:dummy] = 'yes'
new_payload = {
id: @styled_analysis.id,
analysis_definition: @styled_analysis.analysis_definition
}
@styled_analysis.reload
put_json viz_analysis_url(@user, @visualization, @styled_analysis), new_payload do |response|
response.status.should eq 200
end
# Should not invalidate anything
@styled_analysis.analysis_node.descendants.map(&:id).each do |node_id|
Carto::LayerNodeStyle.from_visualization_and_source(@visualization, node_id).any?.should be_true
end
end
it '#update does not invalidate upon children node_id change' do
@styled_analysis.reload
first_child = @styled_analysis.analysis_node.children.first
first_child.definition[:id] = 'bogus'
new_payload = {
id: @styled_analysis.id,
analysis_definition: @styled_analysis.analysis_definition
}
@styled_analysis.reload
put_json viz_analysis_url(@user, @visualization, @styled_analysis), new_payload do |response|
response.status.should eq 200
end
# Should not invalidate anything
@styled_analysis.analysis_node.descendants.map(&:id).each do |node_id|
Carto::LayerNodeStyle.from_visualization_and_source(@visualization, node_id).any?.should be_true
end
end
end
end