Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
require 'spec_helper'
describe "Assets API" do
before(:each) do
@user = FactoryGirl.create(:valid_user)
delete_user_data @user
host! "#{@user.username}.localhost.lan"
end
after(:each) do
@user.destroy
end
let(:params) { { :api_key => @user.api_key } }
it 'creates a new asset' do
Asset.any_instance.stubs('use_s3?').returns(false)
file_path = Rails.root.join('spec', 'support', 'data', 'cartofante_blue.png')
if File.exist?(file_path) && File.file?(file_path)
uploaded_file = Rack::Test::UploadedFile.new(file_path, 'image/png')
post_json(
api_v1_users_assets_create_url(user_id: @user),
params.merge(kind: 'wadus', filename: uploaded_file.path)
) do |response|
response.status.should be_success
response.body[:public_url].should =~ /\/test\/#{@user.username}\/assets\/\d+cartofante_blue/
URI(response.body[:public_url]).should be_absolute
response.body[:kind].should == 'wadus'
@user.reload
@user.assets.count.should == 1
asset = @user.assets.first
asset.public_url.should =~ /\/test\/#{@user.username}\/assets\/\d+cartofante_blue/
asset.kind.should == 'wadus'
end
else
pending("Required spec asset '#{file_path}' not found, test can't properly execute")
end
end
it 'creates a new asset with spaces in name' do
Asset.any_instance.stubs('use_s3?').returns(false)
file_path = Rails.root.join('spec', 'support', 'data', 'cartofante blue.png')
if File.exist?(file_path) && File.file?(file_path)
uploaded_file = Rack::Test::UploadedFile.new(file_path, 'image/png')
post_json(
api_v1_users_assets_create_url(user_id: @user),
params.merge(kind: 'wadus', filename: uploaded_file.path)
) do |response|
response.status.should be_success
response.body[:public_url].should =~ /\/test\/#{@user.username}\/assets\/\d+cartofante%20blue/
URI(response.body[:public_url]).should be_absolute
response.body[:kind].should == 'wadus'
@user.reload
@user.assets.count.should == 1
asset = @user.assets.first
asset.public_url.should =~ /\/test\/#{@user.username}\/assets\/\d+cartofante%20blue/
asset.kind.should == 'wadus'
end
else
pending("Required spec asset '#{file_path}' not found, test can't properly execute")
end
end
it "returns some error message when the asset creation fails" do
pending "Fix this test, sometimes fails"
Asset.any_instance.stubs(:s3_bucket).raises("AWS exception")
post_json(api_v1_users_assets_create_url(user_id: @user), params.merge(
:filename => Rack::Test::UploadedFile.new(Rails.root.join('spec/support/data/cartofante_blue.png'), 'image/png').path)
) do |response|
response.status.should == 400
response.body[:error].should == ["AWS exception"]
end
end
it "finds image file extension" do
asset = FactoryGirl.create(:asset, user_id: @user.id)
Asset::VALID_EXTENSIONS.each do |extension|
asset_name = "cartofante" + extension
asset.stubs(:asset_file).returns(asset_name)
asset.asset_file_extension.should == extension
end
end
it "detects incorrect image file extension" do
asset = FactoryGirl.create(:asset, user_id: @user.id)
asset.stubs(:asset_file).returns("cartofante.gifv")
asset.asset_file_extension.should == nil
end
it "deletes an asset" do
Asset.any_instance.stubs('use_s3?').returns(false)
FactoryGirl.create(:asset, user_id: @user.id)
@user.reload
delete_json(api_v1_users_assets_destroy_url(user_id: @user.id, id: @user.assets.first.id), params) do |response|
response.body.should == {}
response.status.should be_success
@user.reload
@user.assets.count.should == 0
end
end
end
+162
View File
@@ -0,0 +1,162 @@
require 'spec_helper'
describe "Geocodings API" do
before(:all) do
@user = FactoryGirl.create(:valid_user, table_quota: 50)
delete_user_data @user
host! "#{@user.username}.localhost.lan"
end
after(:all) do
@user.destroy
end
before(:each) do
bypass_named_maps
end
let(:params) { { :api_key => @user.api_key } }
describe 'POST /api/v1/geocodings' do
let(:table) { create_table(user_id: @user.id) }
it 'creates a new geocoding' do
Geocoding.any_instance.stubs("run!").returns(true)
payload = params.merge table_name: table.name, formatter: 'name, description', kind: 'high-resolution'
post_json api_v1_geocodings_create_url(payload) do |response|
response.status.should eq 200
response.body[:id].should_not be_nil
response.body[:user_id].should eq @user.id
response.body[:table_id].should eq table.id
response.body[:formatter].should eq 'name, description'
response.body[:created_at].should_not be_nil
response.body[:updated_at].should_not be_nil
end
end
it 'uses column_name instead of formatter if present' do
Geocoding.any_instance.stubs("run!").returns(true)
payload = params.merge table_name: table.name, column_name: 'name', kind: 'high-resolution'
post_json api_v1_geocodings_create_url(payload) do |response|
response.status.should eq 200
response.body[:id].should_not be_nil
response.body[:formatter].should eq '{name}'
end
end
describe 'namedplace geocodings' do
it "should set the country_code when the name of the country is present" do
Geocoding.any_instance.stubs("run!").returns(true)
payload = params.merge \
table_name: table.name,
column_name: 'name',
text: true,
location: 'spain',
kind: 'namedplace'
post_json api_v1_geocodings_create_url(payload) do |response|
response.status.should eq 200
response.body[:id].should_not be_nil
response.body[:country_code].should eq "'spain'"
response.body[:country_column].should be_nil
end
end
it "should set country_code and country_column when a country column has been selected" do
table.add_column!(name: "country", type: "text")
table.insert_row!(country: "spain")
table.insert_row!(country: "spain")
table.insert_row!(country: "us")
table.reload
Geocoding.any_instance.stubs("run!").returns(true)
payload = params.merge \
table_name: table.name,
column_name: 'name',
text: false,
location: 'country',
kind: 'namedplace'
post_json api_v1_geocodings_create_url, payload do |response|
response.status.should eq 200
response.body[:id].should_not be_nil
response.body[:country_code].split(',').sort.should eq ["'spain'", "'us'"]
response.body[:country_column].should eq "country"
end
end
it "should set the region_code when the name of the region is present" do
Geocoding.any_instance.stubs("run!").returns(true)
payload = params.merge \
table_name: table.name,
column_name: 'name',
text: true,
location: 'spain',
region_text: true,
region: 'madrid',
kind: 'namedplace'
post_json api_v1_geocodings_create_url(payload) do |response|
response.status.should eq 200
response.body[:id].should_not be_nil
response.body[:region_code].should eq "'madrid'"
response.body[:region_column].should be_nil
end
end
it "should set region_code and region_column when a region column has been selected" do
table.add_column!(name: "country", type: "text")
table.add_column!(name: "region", type: "text")
table.insert_row!(country: "spain", region: "madrid")
table.insert_row!(country: "spain", region: "madrid")
table.insert_row!(country: "us", region: "minnesota")
table.reload
Geocoding.any_instance.stubs("run!").returns(true)
payload = params.merge \
table_name: table.name,
column_name: 'name',
text: false,
location: 'country',
region_text: false,
region: 'region',
kind: 'namedplace'
post_json api_v1_geocodings_create_url, payload do |response|
response.status.should eq 200
response.body[:id].should_not be_nil
response.body[:region_code].split(',').sort.should eq ["'madrid'", "'minnesota'"]
response.body[:region_column].should eq "region"
end
end
end
it 'responds with 422 on bad payload' do
payload = params.merge(table_name: '', formatter: '', kind: 'high-resolution')
post_json api_v1_geocodings_create_url(payload) do |response|
response.status.should eq 422
response.body[:description].should eq "formatter is not present"
end
end
it 'responds with 500 on failure' do
payload = params.merge(table_name: '', formatter: '', kind: 'high-resolution')
Geocoding.any_instance.stubs(:save).raises(RuntimeError.new)
post_json api_v1_geocodings_create_url(payload) do |response|
response.status.should eq 500
response.body[:description].should eq "RuntimeError"
end
end
end
describe 'PUT /api/v1/geocodings/:id' do
it 'fails gracefully on job cancel failure' do
geocoding = FactoryGirl.create(:geocoding, table_id: UUIDTools::UUID.timestamp_create.to_s, formatter: 'b', user: @user)
Geocoding.any_instance.stubs(:cancel).raises('wadus')
put_json api_v1_geocodings_update_url(params.merge(id: geocoding.id)), { state: 'cancelled' } do |response|
response.status.should eq 400
response.body.should eq errors: "wadus"
end
end
end
end
+247
View File
@@ -0,0 +1,247 @@
require 'spec_helper'
describe "Imports API" do
before(:all) do
@user = FactoryGirl.create(:valid_user)
end
before(:each) do
bypass_named_maps
delete_user_data @user
host! "#{@user.username}.localhost.lan"
end
after(:all) do
@user.destroy
end
let(:params) { { :api_key => @user.api_key } }
it 'performs asynchronous imports' do
f = upload_file('db/fake_data/column_number_to_boolean.csv', 'text/csv')
post api_v1_imports_create_url(params.merge(table_name: "wadus")), filename: f
response.code.should be == '200'
response_json = JSON.parse(response.body)
last_import = DataImport[response_json['item_queue_id']]
last_import.state.should be == 'complete'
table = UserTable[last_import.table_id]
table.name.should == "column_number_to_boolean"
table.map.data_layers.first.options["table_name"].should == "column_number_to_boolean"
end
it 'performs asynchronous imports (file parameter, used in API docs)' do
f = upload_file('db/fake_data/column_number_to_boolean.csv', 'text/csv')
post api_v1_imports_create_url(params.merge(table_name: "wadus")), file: f
response.code.should be == '200'
response_json = JSON.parse(response.body)
last_import = DataImport[response_json['item_queue_id']]
last_import.state.should be == 'complete'
table = UserTable[last_import.table_id]
table.name.should == "column_number_to_boolean"
table.map.data_layers.first.options["table_name"].should == "column_number_to_boolean"
end
it 'performs asynchronous url imports' do
CartoDB::Importer2::Downloader.any_instance.stubs(:validate_url!).returns(true)
serve_file Rails.root.join('db/fake_data/clubbing.csv') do |url|
post api_v1_imports_create_url(params.merge(:url => url,
:table_name => "wadus"))
end
response.code.should be == '200'
response_json = JSON.parse(response.body)
last_import = DataImport[response_json['item_queue_id']]
last_import.state.should be == 'complete'
end
pending 'appends data to an existing table' do
@table = FactoryGirl.create(:table, :user_id => @user.id)
f = upload_file('db/fake_data/column_number_to_boolean.csv', 'text/csv')
post api_v1_imports_create_url(params.merge(table_id: @table.id, append: true)),
f.read.force_encoding('UTF-8'),
filename: f
response.code.should be == '200'
response_json = JSON.parse(response.body)
last_import = DataImport[response_json['item_queue_id']]
last_import.state.should be == 'complete'
@table.reload.rows_counted.should be == 4
end
it 'duplicates a table' do
post api_v1_imports_create_url,
params.merge(:filename => upload_file('spec/support/data/csv_with_lat_lon.csv', 'application/octet-stream'))
@table_from_import = UserTable.all.last.service
post api_v1_imports_create_url(params.merge(:table_name => 'wadus_copy__copy',
:table_copy => @table_from_import.name))
response.code.should be == '200'
response_json = JSON.parse(response.body)
last_import = DataImport[response_json['item_queue_id']]
last_import.state.should be == 'complete'
import_table = UserTable.all.last.service
import_table.rows_counted.should be == @table_from_import.rows_counted
import_table.should have_required_indexes_and_triggers
end
it 'detects lat/long columns and produces a the_geom column from them' do
post api_v1_imports_create_url,
params.merge(:filename => upload_file('spec/support/data/csv_with_lat_lon.csv', 'application/octet-stream'))
@table_from_import = UserTable.all.last.service
@table_from_import.geometry_types.should == ["ST_Point"]
@table_from_import.record(1)[:the_geom].should == '{"type":"Point","coordinates":[16.5607329,48.1199611]}'
end
it 'duplicates a table without geometries' do
post api_v1_imports_create_url,
params.merge(:filename => upload_file('spec/support/data/csv_with_number_columns.csv', 'application/octet-stream'))
@table_from_import = UserTable.all.last.service
post api_v1_imports_create_url(params.merge(:table_name => 'wadus_copy__copy',
:table_copy => @table_from_import.name))
response.code.should be == '200'
response_json = JSON.parse(response.body)
last_import = DataImport[response_json['item_queue_id']]
last_import.state.should be == 'complete'
import_table = UserTable.all.last.service
import_table.rows_counted.should be == @table_from_import.rows_counted
import_table.should have_required_indexes_and_triggers
end
it 'imports all the sample data' do
@user.update table_quota: 10
CartoDB::Importer2::Downloader.any_instance.stubs(:validate_url!).returns(true)
serve_file(Rails.root.join('spec/support/data/TM_WORLD_BORDERS_SIMPL-0.3.zip')) do |url|
post api_v1_imports_create_url(params.merge(url: url, table_name: "wadus"))
response.code.should be == '200'
response_json = JSON.parse(response.body)
last_import = DataImport[response_json['item_queue_id']]
last_import.state.should be == 'complete'
table = UserTable[last_import.table_id].service
table.should have_required_indexes_and_triggers
table.should have_no_invalid_the_geom
table.geometry_types.should_not be_blank
table.map.should be
end
end
it 'raises an error if the user attempts to import tables when being over quota' do
@user.update table_quota: 1
# This file contains 6 data sources
CartoDB::Importer2::Downloader.any_instance.stubs(:validate_url!).returns(true)
serve_file(Rails.root.join('spec/support/data/ESP_adm.zip')) do |url|
post api_v1_imports_create_url, params.merge(:url => url,
:table_name => "wadus")
end
response.code.should be == '200'
last_import = DataImport.order(Sequel.desc(:updated_at)).first
last_import.tables_created_count.should be_nil
last_import.state.should be == 'failure'
last_import.error_code.should be == 8002
last_import.log.entries.should include('Results would set overquota')
@user.reload.tables.count.should == 0
end
it 'raises an error if the user attempts to import tables when being over disk quota' do
@user.update quota_in_bytes: 1000, table_quota: 200
CartoDB::Importer2::Downloader.any_instance.stubs(:validate_url!).returns(true)
serve_file(Rails.root.join('spec/support/data/ESP_adm.zip')) do |url|
post api_v1_imports_create_url, params.merge(:url => url,
:table_name => "wadus")
end
response.code.should be == '200'
last_import = DataImport.order(Sequel.desc(:updated_at)).first
last_import.state.should be == 'failure'
last_import.error_code.should be == 8001
@user.reload.tables.count.should == 0
end
it 'raises an error if the user attempts to duplicate a table when being over quota' do
@user.update table_quota: 1, quota_in_bytes: 100.megabytes
post api_v1_imports_create_url,
params.merge(:filename => upload_file('spec/support/data/_penguins_below_80.zip', 'application/octet-stream'))
@table_from_import = UserTable.all.last.service
last_import = DataImport.order(Sequel.desc(:updated_at)).first
last_import.state.should be == 'complete', "Import failure: #{last_import.log}"
post api_v1_imports_create_url, params.merge(:table_name => 'wadus_copy__copy',
:table_copy => @table_from_import.name)
response.code.should be == '200'
last_import = DataImport.order(Sequel.desc(:updated_at)).first
last_import.state.should be == 'failure'
last_import.error_code.should be == 8002
@user.reload.tables.count.should == 1
end
it 'imports data when the user has infinite tables' do
@user.update table_quota: nil
post api_v1_imports_create_url,
params.merge(:filename => upload_file('spec/support/data/csv_with_lat_lon.csv', 'application/octet-stream'))
@table_from_import = UserTable.all.last.service
response.code.should be == '200'
last_import = DataImport.order(Sequel.desc(:updated_at)).first
last_import.state.should be == 'complete'
@user.reload.tables.count.should == 1
end
it 'updates tables_created_count upon finished import' do
post api_v1_imports_create_url,
params.merge(:filename => upload_file('spec/support/data/zipped_ab.zip', 'application/octet-stream'))
response.code.should be == '200'
last_import = DataImport.order(Sequel.desc(:updated_at)).first
last_import.state.should be == 'complete'
last_import.tables_created_count.should eq 2
last_import.table_names.should eq 'zipped_a zipped_b'
end
it 'properly reports table row count limit' do
old_max_import_row_count = @user.max_import_table_row_count
@user.update max_import_table_row_count: 2
# Internally uses reltuples from pg_class which is an estimation and non-deterministic so...
CartoDB::PlatformLimits::Importer::TableRowCount.any_instance.expects(:get).returns(5)
post api_v1_imports_create_url,
params.merge(:filename => upload_file('spec/support/data/csv_with_lat_lon.csv', 'application/octet-stream'))
response.code.should be == '200'
last_import = DataImport.order(Sequel.desc(:updated_at)).first
last_import.state.should be == 'failure'
last_import.error_code.should be == 6668
@user.update max_import_table_row_count: old_max_import_row_count
end
end
@@ -0,0 +1,7 @@
require 'spec_helper'
shared_examples_for "geocoding controllers" do
# Left for when writes migration happens
end
@@ -0,0 +1,69 @@
require_relative '../../../spec_helper'
require_relative 'geocodings_controller_shared_examples'
require_relative '../../../../app/controllers/api/json/geocodings_controller'
describe Api::Json::GeocodingsController do
it_behaves_like 'geocoding controllers' do
end
include Rack::Test::Methods
include Warden::Test::Helpers
include CacheHelper
describe 'POST api/v1/geocodings' do
before(:all) do
@user = create_user
end
after(:all) do
bypass_named_maps
@user.destroy
end
let(:params) { { api_key: @user.api_key, kind: 'ipaddress', formatter: '{some_column}' } }
before(:each) do
bypass_named_maps
delete_user_data @user
host! "#{@user.username}.localhost.lan"
login_as(@user, scope: @user.username)
end
it 'takes an optional argument force_all_rows and stores it in the model' do
table = create_table(user_id: @user.id)
Resque.expects(:enqueue).once
geocoding_id = nil
post_json api_v1_geocodings_create_url(params.merge({
table_name: table.name,
force_all_rows: true
})) do |response|
response.status.should be_success
response.body[:force_all_rows].should == true
geocoding_id = response.body[:id]
end
Geocoding[geocoding_id].force_all_rows == true
end
it 'sets force_all_rows to nil if not present' do
table = create_table(user_id: @user.id)
Resque.expects(:enqueue).once
geocoding_id = nil
post_json api_v1_geocodings_create_url(params.merge({
table_name: table.name,
})) do |response|
response.status.should be_success
response.body[:force_all_rows].should == false
geocoding_id = response.body[:id]
end
Geocoding[geocoding_id].force_all_rows == false
end
end
end
@@ -0,0 +1,6 @@
require 'spec_helper'
shared_examples_for "imports controllers" do
end
@@ -0,0 +1,8 @@
require_relative '../../../spec_helper'
require_relative 'imports_controller_shared_examples'
require_relative '../../../../app/controllers/api/json/imports_controller'
describe Api::Json::ImportsController do
it_behaves_like 'imports controllers' do
end
end
@@ -0,0 +1,743 @@
shared_examples_for "layer presenters" do |tested_klass, model_klass|
describe '#show legacy tests' do
def set_tested_classes(tested_class, model_class)
@tested_class = tested_class
@model_class = model_class
end
def instance_of_tested_class(*args)
@tested_class.new(*args)
end
# Always uses old models to created data, then battery set one for instantiation
def instance_of_tested_model(creation_model)
@model_class.where(id: creation_model.id).first
end
before(:all) do
set_tested_classes(tested_klass, model_klass)
puts "Testing class #{tested_klass.to_s} with model #{model_klass.to_s}"
@user_1 = FactoryGirl.create(:valid_user)
@user_2 = FactoryGirl.create(:valid_user)
end
after(:all) do
@user_1.destroy
@user_2.destroy
end
before(:each) do
CartoDB::UserModule::DBService.any_instance.stubs(:enable_remote_db_user).returns(true)
end
before(:each) do
bypass_named_maps
delete_user_data @user_1
delete_user_data @user_2
end
it "Tests to_json()" do
layer_1 = Layer.create({
kind: 'tiled'
})
layer_2 = Layer.create({
kind: 'carto',
order: 13,
options: {
'fake' => 'value',
'table_name' => 'test_table'
},
infowindow: { 'fake2' => 'val2' },
tooltip: { 'fake3' => 'val3' }
})
layer_2 = instance_of_tested_model(layer_2)
presenter = instance_of_tested_class(layer_2)
json_data = JSON.parse(presenter.to_json)
json_data['id'].should == layer_2.id
json_data['kind'].should == layer_2.kind
json_data['order'].should == layer_2.order
json_data['options'].should == layer_2.options
json_data['infowindow'].should == layer_2.infowindow
json_data['tooltip'].should == layer_2.tooltip
presenter_options = {
viewer_user: @user_2,
user: @user_1
}
#no changes to layer_2
presenter = instance_of_tested_class(layer_2, presenter_options)
json_data = JSON.parse(presenter.to_json)
# to_json shouldn't change table_name even if viewer/user sent at presenter options
json_data['options'].should == layer_2.options
end
it 'Tests to_poro()' do
table_name = 'test_table'
layer_2 = Layer.create(
kind: 'carto',
order: 13,
options: {
'fake' => 'value',
'table_name' => table_name
},
infowindow: { 'fake2' => 'val2' },
tooltip: { 'fake3' => 'val3' }
)
layer_2 = instance_of_tested_model(layer_2)
expected_poro = {
'id' => layer_2.id,
'kind' => 'carto',
'order' => 13,
'options' => {
'fake' => 'value',
'table_name' => table_name
},
'infowindow' => {
'fake2' => 'val2'
},
'tooltip' => {
'fake3' => 'val3'
}
}
expected_options = expected_poro.delete('options')
poro = instance_of_tested_class(layer_2).to_poro
poro_options = poro.delete('options')
expect(poro).to include(expected_poro)
expect(poro_options).to include(expected_options)
# Now add presenter options to change table_name (new way)
expected_options['table_name'] = "#{@user_1.database_schema}.#{table_name}"
presenter_options = {
viewer_user: @user_2,
# New presenter way of sending a viewer that's different from the owner
user: @user_1
}
poro = instance_of_tested_class(layer_2, presenter_options).to_poro
poro_options = poro.delete('options')
expect(poro).to include(expected_poro)
expect(poro_options).to include(expected_options)
# old way
# change state always with old model to be sure
layer_2 = ::Layer.where(id: layer_2.id).first
layer_2.options = {
'fake' => 'value',
'table_name' => table_name,
# Old presenter way of sending a viewer
'user_name' => @user_1.username
}
layer_2.save
layer_2 = instance_of_tested_model(layer_2)
expected_poro = {
'id' => layer_2.id,
'kind' => 'carto',
'order' => 13,
'options' => {
'fake' => 'value',
'table_name' => "#{@user_1.username}.#{table_name}",
'user_name' => @user_1.username
},
'infowindow' => {
'fake2' => 'val2'
},
'tooltip' => {
'fake3' => 'val3'
}
}
presenter_options = {
viewer_user: @user_2
}
poro = instance_of_tested_class(layer_2, presenter_options).to_poro
# Already changed to fully qualified expected table name, so no need to do it again
poro_options = poro.delete('options')
expected_options = expected_poro.delete('options')
expect(poro).to include(expected_poro)
expect(poro_options).to include(expected_options)
# Finally, don't change if already has a fully qualified table_name
layer_2 = ::Layer.where(id: layer_2.id).first
layer_2.options = {
'fake' => 'value',
'table_name' => "fake.#{table_name}",
# Old presenter way of sending a viewer
'user_name' => @user_1.username
}
layer_2.save
layer_2 = instance_of_tested_model(layer_2)
expected_options['table_name'] = "fake.#{table_name}"
poro = instance_of_tested_class(layer_2, presenter_options).to_poro
poro_options = poro.delete('options')
expect(poro).to include(expected_poro)
expect(poro_options).to include(expected_options)
end
it 'Tests to_poro() with uuid table name' do
table_name = '000cd294-b124-4f82-b569-0f7fe41d2db8'
layer_2 = Layer.create(
kind: 'carto',
order: 13,
options: {
'fake' => 'value',
'table_name' => table_name
},
infowindow: { 'fake2' => 'val2' },
tooltip: { 'fake3' => 'val3' }
)
layer_2 = instance_of_tested_model(layer_2)
expected_poro = {
'id' => layer_2.id,
'kind' => 'carto',
'order' => 13,
'options' => {
'fake' => 'value',
'table_name' => table_name
},
'infowindow' => {
'fake2' => 'val2'
},
'tooltip' => {
'fake3' => 'val3'
}
}
expected_options = expected_poro.delete('options')
poro = instance_of_tested_class(layer_2).to_poro
poro_options = poro.delete('options')
expect(poro).to include(expected_poro)
expect(poro_options).to include(expected_options)
# Now add presenter options to change table_name (new way)
expected_options['table_name'] = "#{@user_1.database_schema}.\"#{table_name}\""
presenter_options = {
viewer_user: @user_2,
# New presenter way of sending a viewer that's different from the owner
user: @user_1
}
poro = instance_of_tested_class(layer_2, presenter_options).to_poro
poro_options = poro.delete('options')
expect(poro).to include(expected_poro)
expect(poro_options).to include(expected_options)
# old way
# change state always with old model to be sure
layer_2 = ::Layer.where(id: layer_2.id).first
layer_2.options = {
'fake' => 'value',
'table_name' => table_name,
# Old presenter way of sending a viewer
'user_name' => @user_1.username
}
layer_2.save
layer_2 = instance_of_tested_model(layer_2)
expected_poro = {
'id' => layer_2.id,
'kind' => 'carto',
'order' => 13,
'options' => {
'fake' => 'value',
'table_name' => "#{@user_1.username}.\"#{table_name}\"",
'user_name' => @user_1.username
},
'infowindow' => {
'fake2' => 'val2'
},
'tooltip' => {
'fake3' => 'val3'
}
}
presenter_options = {
viewer_user: @user_2
}
poro = instance_of_tested_class(layer_2, presenter_options).to_poro
# Already changed to fully qualified expected table name, so no need to do it again
poro_options = poro.delete('options')
expected_options = expected_poro.delete('options')
expect(poro).to include(expected_poro)
expect(poro_options).to include(expected_options)
# Finally, don't change if already has a fully qualified table_name
layer_2 = ::Layer.where(id: layer_2.id).first
layer_2.options = {
'fake' => 'value',
'table_name' => "fake.\"#{table_name}\"",
# Old presenter way of sending a viewer
'user_name' => @user_1.username
}
layer_2.save
layer_2 = instance_of_tested_model(layer_2)
expected_options['table_name'] = "fake.\"#{table_name}\""
poro = instance_of_tested_class(layer_2, presenter_options).to_poro
poro_options = poro.delete('options')
expect(poro).to include(expected_poro)
expect(poro_options).to include(expected_options)
end
it 'Tests to_vizjson_v1()' do
layer_parent = Layer.create({
kind: 'tiled'
})
layer_parent = instance_of_tested_model(layer_parent)
layer = Layer.create({
kind: 'carto',
order: 5,
options: {
'fake' => 'value',
'table_name' => 'my_test_table',
'opt1' => 'val1',
},
infowindow: {
'template' => nil,
'fake2' => 'val2'
},
tooltip: { 'fake3' => 'val3' }
})
layer = instance_of_tested_model(layer)
expected_vizjson = {
id: layer.id,
kind: 'CartoDB',
order: layer.order,
infowindow: {
'template' => nil,
'fake2' => 'val2'
},
options: {
'opt1' => 'val1'
}
}
presenter_options = { }
presenter_configuration = {
layer_opts: {
'public_opts' => {
'whatever' => true,
'opt1' => true
}
}
}
vizjson = instance_of_tested_class(layer, presenter_options, presenter_configuration).to_vizjson_v1
vizjson.should == expected_vizjson
# Now full options
presenter_options = {
# Full options
full: true
}
expected_vizjson[:options] = {
'fake' => 'value',
'table_name' => 'my_test_table',
'opt1' => 'val1',
}
vizjson = instance_of_tested_class(layer, presenter_options, presenter_configuration).to_vizjson_v1
vizjson.should == expected_vizjson
# Now a base layer, which should be a poro with symbolized keys
vizjson = instance_of_tested_class(layer_parent).to_vizjson_v1
vizjson[:id].should == layer_parent.id
vizjson[:kind].should == layer_parent.kind
end
it 'Tests to_vizjson_v2()' do
maps_api_template = "http://{user}.localhost.lan:8181"
sql_api_template = "http://{user}.localhost.lan:8080"
# Modules do not use '.any_instance' for stubbing
ApplicationHelper.stubs(:maps_api_template).returns(maps_api_template)
ApplicationHelper.stubs(:sql_api_template).returns(sql_api_template)
stat_tag = '00000000-0000-0000-0000-000000000000'
layer_parent = Layer.create({
kind: 'tiled'
})
layer_parent = instance_of_tested_model(layer_parent)
# Base, which should be a poro but with symbols
vizjson = instance_of_tested_class(layer_parent).to_vizjson_v2
vizjson[:id].should == layer_parent.id
vizjson[:kind].should == nil
vizjson[:type].should == layer_parent.kind
presenter_options = {
visualization_id: stat_tag
}
# torque layer with very basic options
layer = Layer.create({
kind: 'torque',
options: {
'table_name' => 'my_test_table',
},
})
layer = instance_of_tested_model(layer)
expected_vizjson = {
id: layer.id,
type: layer.kind,
order: layer.order,
legend: nil,
options: {
stat_tag: stat_tag,
maps_api_template: maps_api_template,
sql_api_template: sql_api_template,
tiler_protocol: nil,
tiler_domain: nil,
tiler_port: nil,
sql_api_protocol: nil,
sql_api_domain: nil,
sql_api_endpoint: nil,
sql_api_port: nil,
layer_name: layer.options['table_name'],
'table_name' => layer.options['table_name'],
'query' => "select * from #{layer.options['table_name']}"
}
}
vizjson = instance_of_tested_class(layer, presenter_options).to_vizjson_v2
vizjson.should == expected_vizjson
# torque layer, different viewer
layer = Layer.create({
kind: 'torque',
options: {
'table_name' => 'my_test_table',
# This is only for compatibility with old LayerModule::Presenter, new one checkes in the presenter options
'user_name' => @user_1.database_schema
},
})
layer = instance_of_tested_model(layer)
expected_vizjson[:id] = layer.id
# No special quoting
expected_vizjson[:options]['query'] = "select * from public.#{layer.options['table_name']}"
expected_vizjson[:options]['user_name'] = @user_1.database_schema
presenter_options = {
visualization_id: stat_tag,
viewer_user: @user_2,
# New presenter way of sending a viewer that's different from the owner
user: @user_1
}
vizjson = instance_of_tested_class(layer, presenter_options).to_vizjson_v2
vizjson.should == expected_vizjson
# torque layer, different viewer, UUID table name
layer = Layer.create(
kind: 'torque',
options: {
'table_name' => '000cd294-b124-4f82-b569-0f7fe41d2db8',
# This is only for compatibility with old LayerModule::Presenter, new one checkes in the presenter options
'user_name' => @user_1.database_schema
}
)
layer = instance_of_tested_model(layer)
expected_vizjson[:id] = layer.id
# No special quoting
expected_vizjson[:options][:layer_name] = layer.options['table_name']
expected_vizjson[:options]['table_name'] = layer.options['table_name']
expected_vizjson[:options]['query'] = "select * from public.\"#{layer.options['table_name']}\""
expected_vizjson[:options]['user_name'] = @user_1.database_schema
presenter_options = {
visualization_id: stat_tag,
viewer_user: @user_2,
# New presenter way of sending a viewer that's different from the owner
user: @user_1
}
vizjson = instance_of_tested_class(layer, presenter_options).to_vizjson_v2
vizjson.should == expected_vizjson
# torque layer, custom query
layer = Layer.create({
kind: 'torque',
options: {
'query' => 'SELECT * FROM my_test_table LIMIT 5',
'table_name' => 'my_test_table',
},
})
layer = instance_of_tested_model(layer)
presenter_options = {
visualization_id: stat_tag
}
expected_vizjson[:id] = layer.id
expected_vizjson[:options][:layer_name] = layer.options['table_name']
expected_vizjson[:options]['table_name'] = layer.options['table_name']
expected_vizjson[:options]['query'] = layer.options['query']
expected_vizjson[:options].delete('user_name')
vizjson = instance_of_tested_class(layer, presenter_options).to_vizjson_v2
vizjson.should == expected_vizjson
# torque layer, with wrapping
layer = Layer.create({
kind: 'torque',
options: {
'query' => 'SELECT * FROM my_test_table LIMIT 5',
'table_name' => 'my_test_table',
'query_wrapper' => "select * from (<%= sql %>)",
# This options shouldn't appear as is not listed at presnter TORQUE_ATTRS
'wadus' => 'whatever'
}
})
layer = instance_of_tested_model(layer)
presenter_options = {
visualization_id: stat_tag
}
expected_vizjson[:id] = layer.id
expected_vizjson[:options]['query'] = "select * from (#{layer.options['query']})"
vizjson = instance_of_tested_class(layer, presenter_options).to_vizjson_v2
vizjson.should == expected_vizjson
# CartoDB layer, minimal options
layer = Layer.create({
kind: 'carto',
options: {
'table_name' => 'my_test_table',
'style_version' => '2.1.1',
'interactivity' => 'something'
},
})
layer = instance_of_tested_model(layer)
presenter_options = {
visualization_id: stat_tag
}
expected_vizjson = {
id: layer.id,
type: 'CartoDB',
order: layer.order,
infowindow: nil,
tooltip: nil,
legend: nil,
visible: nil,
options: {
sql: "select * from #{layer.options['table_name']}",
layer_name: layer.options['table_name'],
cartocss: @tested_class::EMPTY_CSS,
cartocss_version: layer.options['style_version'],
interactivity: layer.options['interactivity']
}
}
vizjson = instance_of_tested_class(layer, presenter_options).to_vizjson_v2
vizjson.should == expected_vizjson
# CartoDB layer, non default fields filled. table_name UUID to check quoting
layer = Layer.create(
kind: 'carto',
options: {
'table_name' => '000cd294-b124-4f82-b569-0f7fe41d2db8',
'style_version' => '2.1.1',
'interactivity' => 'something',
'tile_style' => '/* aaa */ #bbb{ marker-width: 12; } ',
'legend' => {
'type' => "custom",
'show_title' => true,
'title' => "xxx",
'template' => "",
'items' => [
{
'name' => "yyy",
'visible' => true,
'value' => "#ccc",
'sync' => true
}
]
},
'visible' => true,
# Shouldn't appear
'wadus' => 'whatever'
},
infowindow: {
'fields' => nil,
'template_name' => "infowindow_light",
'template' => "<div></div>",
'alternative_names' => {},
'width' => 200,
'maxHeight' => 100
},
tooltip: {
'fields' => nil,
'template_name' => "tooltip_light",
'template' => "<div></div>",
'alternative_names' => {},
'maxHeight' => 180
}
)
layer = instance_of_tested_model(layer)
expected_vizjson = {
id: layer.id,
type: 'CartoDB',
order: layer.order,
infowindow: layer.infowindow,
tooltip: layer.tooltip,
legend: layer.legend,
visible: true,
options: {
# yes... this ones come as symbols and not strings as the others (sigh)
sql: "select * from \"#{layer.options['table_name']}\"",
layer_name: layer.options['table_name'],
cartocss: layer.options['tile_style'],
cartocss_version: layer.options['style_version'],
interactivity: layer.options['interactivity']
}
}
vizjson = instance_of_tested_class(layer, presenter_options).to_vizjson_v2
vizjson.should == expected_vizjson
# CartoDB layer, non default fields filled
layer = Layer.create({
kind: 'carto',
options: {
'table_name' => 'my_test_table',
'style_version' => '2.1.1',
'interactivity' => 'something',
'tile_style' => '/* aaa */ #bbb{ marker-width: 12; } ',
'legend' => {
'type' => "custom",
'show_title' => true,
'title' => "xxx",
'template' => "",
'items' => [
{
'name' => "yyy",
'visible' => true,
'value' => "#ccc",
'sync' => true
}
]
},
'visible' => true,
# Shouldn't appear
'wadus' => 'whatever'
},
infowindow: {
'fields' => nil,
'template_name' => "infowindow_light",
'template' => "<div></div>",
'alternative_names' => { },
'width' => 200,
'maxHeight' => 100
},
tooltip: {
'fields' => nil,
'template_name' => "tooltip_light",
'template' => "<div></div>",
'alternative_names' => { },
'maxHeight' => 180
}
})
layer = instance_of_tested_model(layer)
expected_vizjson = {
id: layer.id,
type: 'CartoDB',
order: layer.order,
infowindow: layer.infowindow,
tooltip: layer.tooltip,
legend: layer.legend,
visible: true,
options: {
# yes... this ones come as symbols and not strings as the others (sigh)
sql: "select * from #{layer.options['table_name']}",
layer_name: layer.options['table_name'],
cartocss: layer.options['tile_style'],
cartocss_version: layer.options['style_version'],
interactivity: layer.options['interactivity']
}
}
vizjson = instance_of_tested_class(layer, presenter_options).to_vizjson_v2
vizjson.should == expected_vizjson
# CartoDB layer with `Full` options flag
layer = Layer.create({
kind: 'carto',
options: {
'table_name' => 'my_test_table',
'interactivity' => 'something',
'wadus' => 'whatever'
}
})
layer = instance_of_tested_model(layer)
presenter_options = {
visualization_id: stat_tag,
full: true
}
expected_vizjson = {
id: layer.id,
type: 'CartoDB',
order: layer.order,
infowindow: nil,
tooltip: nil,
legend: nil,
visible: nil,
options: {
'table_name' => layer.options['table_name'],
'interactivity' => layer.options['interactivity'],
'wadus' => 'whatever'
}
}
vizjson = instance_of_tested_class(layer, presenter_options).to_vizjson_v2
puts vizjson.inspect
vizjson.should == expected_vizjson
end
end
end
@@ -0,0 +1,8 @@
require_relative '../../../spec_helper'
require_relative '../../../../app/models/layer/presenter'
require_relative 'layer_presenter_shared_examples'
describe CartoDB::LayerModule::Presenter do
# Old presenter, old model
it_behaves_like 'layer presenters', CartoDB::LayerModule::Presenter, ::Layer
end
@@ -0,0 +1,3 @@
shared_examples_for "overlays controllers" do
end
@@ -0,0 +1,9 @@
require 'sequel'
require 'rack/test'
shared_examples_for 'synchronization controllers' do
include Rack::Test::Methods
include Warden::Test::Helpers
include CacheHelper
end
@@ -0,0 +1,108 @@
require_relative '../../../spec_helper'
require_relative 'synchronizations_controller_shared_examples'
require_relative '../../../../app/controllers/api/json/synchronizations_controller'
describe Api::Json::SynchronizationsController do
include_context 'users helper'
it_behaves_like 'synchronization controllers' do
end
before(:each) do
login(@user1)
@user1.sync_tables_enabled = true
@user1.save
end
describe '#create' do
let(:params) do
{
url: "fake_url",
interval: 3600,
content_guessing: true,
type_guessing: true,
create_vis: false
}
end
describe 'for users without sync tables' do
before(:each) do
@user1.sync_tables_enabled = false
@user1.save
end
it 'returns 401' do
post_json api_v1_synchronizations_create_url(params) do |r|
r.status.should eq 401
end
end
it 'creates a synchronization and enqueues a import job for external sources' do
begin
Resque::ImporterJobs.expects(:perform).once
carto_visualization = FactoryGirl.create(:carto_visualization, user_id: @user1.id)
external_source = FactoryGirl.create(:external_source, visualization: carto_visualization)
remote_id = external_source.visualization_id
expect {
post_json api_v1_synchronizations_create_url(params.merge(remote_visualization_id: remote_id)) do |r|
r.status.should eq 200
Carto::Synchronization.find(r.body[:id]).state.should eq Carto::Synchronization::STATE_QUEUED
end
}.to change { Carto::Synchronization.count }.by 1
ensure
external_source.external_data_imports.each(&:destroy)
external_source.destroy
carto_visualization.destroy
end
end
end
it 'creates a synchronization and enqueues a import job' do
Resque::ImporterJobs.expects(:perform).once
expect {
post_json api_v1_synchronizations_create_url(params) do |r|
r.status.should eq 200
Carto::Synchronization.find(r.body[:id]).state.should eq Carto::Synchronization::STATE_QUEUED
end
}.to change { Carto::Synchronization.count }.by 1
end
end
describe '#sync_now' do
it 'syncs... now :D' do
sync = FactoryGirl.create(:carto_synchronization, user_id: @user1.id)
sync.state.should eq Carto::Synchronization::STATE_SUCCESS
Resque::SynchronizationJobs.expects(:perform).once
put_json api_v1_synchronizations_sync_now_url(id: sync.id) do |r|
r.status.should eq 200
sync.reload
sync.state.should eq Carto::Synchronization::STATE_QUEUED
end
end
end
describe '#update' do
it 'updates the interval' do
sync = FactoryGirl.create(:carto_synchronization, user_id: @user1.id)
new_interval = sync.interval * 2
put_json api_v1_synchronizations_update_url(id: sync.id), interval: new_interval do |r|
r.status.should eq 200
sync.reload
sync.interval.should eq new_interval
end
end
end
describe '#destroy' do
it 'destroys a sync' do
sync = FactoryGirl.create(:carto_synchronization, user_id: @user1.id)
Carto::Synchronization.exists?(sync.id).should be_true
delete_json api_v1_synchronizations_update_url(id: sync.id) do |r|
r.status.should eq 204
Carto::Synchronization.exists?(sync.id).should be_false
end
end
end
end
+75
View File
@@ -0,0 +1,75 @@
require_relative '../../acceptance_helper'
feature "API 1.0 maps management" do
before(:all) do
Capybara.current_driver = :rack_test
@user = create_user({:username => 'test'})
end
before(:each) do
bypass_named_maps
delete_user_data @user
end
after(:all) do
bypass_named_maps
@user.destroy
end
scenario "Create a new map" do
table = create_table(:user_id => @user.id)
post_json v1_maps_url(:host => CartoDB.hostname.sub('http://', ''), :api_key => api_key), { :table_id => table.id } do |response|
response.status.should be_success
table.refresh
table.map.should == Map[response.body[:id]]
end
end
scenario "Create a new google map" do
table = create_table(:user_id => @user.id)
data = { table_id: table.id, center: "[41.68932225997044,-13.4912109375]", zoom: 5, minZoom: 0, maxZoom: 20, provider: "googlemaps", id: 24, user_id: 2,bounding_box_sw: "[0,0]", bounding_box_ne: "[0,0]" }
post_json v1_maps_url(:host => CartoDB.hostname.sub('http://', ''), :api_key => api_key), data do |response|
response.status.should be_success
table.refresh
table.map.should == Map[response.body[:id]]
response.body[:center].should == "[41.68932225997044,-13.4912109375]"
response.body[:zoom].should == 5
response.body[:bounding_box_sw].should == "[0,0]"
response.body[:bounding_box_ne].should == "[0,0]"
end
end
scenario "Can't create a map associated to another user's table" do
another_user = create_user({:username => 'waldo'})
table = create_table(:user_id => another_user.id)
data = { table_id: table.id }
post_json v1_maps_url(:host => CartoDB.hostname.sub('http://', ''), :api_key => api_key), data do |response|
response.status.should_not be_success
end
another_user.destroy
end
scenario "Update a map" do
table = create_table(:user_id => @user.id)
map = create_map(:user_id => @user.id, :table_id => table.id)
put_json v1_map_url(:host => CartoDB.hostname.sub('http://', ''), :api_key => api_key, :id => map.id), {:center => "center_value"} do |response|
response.status.should be_success
response.body[:id].should == map.id
response.body[:center].should == "center_value"
end
end
scenario "Drop a map" do
table = create_table(:user_id => @user.id)
map = create_map(:user_id => @user.id, :table_id => table.id)
delete_json v1_map_url(:host => CartoDB.hostname.sub('http://', ''), :api_key => api_key, :id => map.id) do |response|
response.status.should eql(204)
end
end
end
+64
View File
@@ -0,0 +1,64 @@
require_relative '../../acceptance_helper'
feature "API 1.0 queries management" do
before(:all) do
Capybara.current_driver = :rack_test
@user = create_user({:username => 'test'})
end
before(:each) do
bypass_named_maps
delete_user_data @user
@table = create_table :user_id => @user.id
end
after(:all) do
bypass_named_maps
@user.destroy
end
scenario "Get the results of a SELECT query" do
50.times do |i|
@table.insert_row!({:name => "Row number #{i}", :description => String.random(50), :the_geom => %Q{\{"type":"Point","coordinates":[40.392949,-3.69084]\}}})
end
get_json api_queries_url(:sql => "SELECT * FROM #{@table.name} ORDER BY cartodb_id ASC LIMIT 10", :rows_per_page => 2) do |response|
response.status.should be_success
response.body[:rows].size.should == 2
response.body[:rows][0]["name"].should == "Row number 0"
response.body[:rows][1]["name"].should == "Row number 1"
end
end
scenario "Get the results of a UPDATE query" do
50.times do |i|
@table.insert_row!({:name => "Row number #{i}", :description => String.random(50), :the_geom => %Q{\{"type":"Point","coordinates":[40.392949,-3.69084]\}}})
end
get_json api_queries_url(:sql => "UPDATE #{@table.name} SET description = NULL WHERE cartodb_id < 13", :rows_per_page => 2) do |response|
response.status.should be_success
response.body[:rows].size.should == 0
response.body[:affected_rows].should == 12
end
end
scenario "Get the results of a DELETE query" do
50.times do |i|
@table.insert_row!({:name => "Row number #{i}", :description => String.random(50), :the_geom => %Q{\{"type":"Point","coordinates":[40.392949,-3.69084]\}}})
end
get_json api_queries_url(:sql => "DELETE FROM #{@table.name} WHERE cartodb_id < 13", :rows_per_page => 2) do |response|
response.status.should be_success
response.body[:rows].size.should == 0
response.body[:affected_rows].should == 12
end
end
scenario "Get the results of a query involving system tables" do
get_json api_queries_url(:sql => "SELECT datname FROM pg_database;", :rows_per_page => 2) do |response|
response.status.should == 400
response.body[:errors].should == ["System tables are forbidden"]
end
end
end
@@ -0,0 +1,90 @@
require 'sequel'
require 'rack/test'
require 'json'
require 'uri'
require_relative '../../spec_helper'
require_relative '../../../app/controllers/api/json/synchronizations_controller'
require_relative '../../../services/data-repository/backend/sequel'
include CartoDB
def app
CartoDB::Application.new
end
describe Api::Json::SynchronizationsController do
include Rack::Test::Methods
before(:all) do
@user = create_user(
sync_tables_enabled: true
)
@api_key = @user.api_key
end
before(:each) do
@db = SequelRails.connection
Sequel.extension(:pagination)
CartoDB::Synchronization.repository = DataRepository::Backend::Sequel.new(@db, :synchronizations)
bypass_named_maps
delete_user_data @user
@headers = {
'CONTENT_TYPE' => 'application/json'
}
host! "#{@user.username}.localhost.lan"
end
after(:all) do
bypass_named_maps
@user.destroy
end
describe 'POST /api/v1/synchronizations' do
it 'creates a synchronization' do
payload = {
table_name: 'table_1',
interval: 3600,
url: 'http://www.foo.com'
}
post "/api/v1/synchronizations?api_key=#{@api_key}", payload.to_json, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
response.fetch('id').should_not be_empty
response.fetch('name').should_not be_empty
end
it 'respond error 400 if interval is beneath 15 minutes' do
payload = {
table_name: 'table_1',
interval: 60,
url: 'http://www.foo.com'
}
post "/api/v1/synchronizations?api_key=#{@api_key}", payload.to_json, @headers
last_response.status.should eq 400
last_response.body.to_str.should match /15 minutes/
end
it 'schedules an import' do
payload = {
table_name: 'table_1',
interval: 3600,
url: 'http://www.foo.com'
}
post "/api/v1/synchronizations?api_key=#{@api_key}", payload.to_json, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
response.fetch('data_import').should_not be_empty
end
end
end
+277
View File
@@ -0,0 +1,277 @@
require 'sequel'
require 'rack/test'
require 'json'
require 'uri'
require_relative '../../spec_helper'
require_relative '../../../services/data-repository/backend/sequel'
require 'helpers/unique_names_helper'
describe Carto::Api::VisualizationsController do
include UniqueNamesHelper
include Rack::Test::Methods
include DataRepository
before(:all) do
CartoDB::Varnish.any_instance.stubs(:send_command).returns(true)
@user = create_user(
private_tables_enabled: true,
private_maps_enabled: true
)
@api_key = @user.api_key
end
before(:each) do
CartoDB::Varnish.any_instance.stubs(:send_command).returns(true)
bypass_named_maps_requests
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! "#{@user.username}.localhost.lan"
end
after(:all) do
bypass_named_maps
@user.destroy
end
describe 'POST /api/v1/viz' do
it 'creates a visualization' do
payload = factory.merge(type: 'table')
post "/api/v1/viz?api_key=#{@api_key}", payload.to_json, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
response.fetch('tags') .should == payload.fetch(:tags)
response.fetch('map_id') .should == payload.fetch(:map_id)
response.fetch('description') .should == payload.fetch(:description)
id = response.fetch('id')
get "/api/v1/viz/#{id}?api_key=#{@api_key}", {}, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
response.fetch('name') .should_not == nil
response.fetch('tags') .should_not == payload.fetch(:tags).to_json
response.keys.should_not include 'related'
end
it 'creates a visualization from a source_visualization_id' do
table = table_factory
source_visualization = table.fetch('table_visualization')
payload = { source_visualization_id: source_visualization.fetch('id') }
post "/api/v1/viz?api_key=#{@api_key}", payload.to_json, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
id = response.fetch('id')
CartoDB::Visualization::Member.new(id: id).fetch.derived?.should be_true
end
it 'creates a private visualization from a private table' do
table1 = table_factory(privacy: 0)
source_visualization_id = table1.fetch('table_visualization').fetch('id')
payload = { source_visualization_id: source_visualization_id }
post "/api/v1/viz?api_key=#{@api_key}", payload.to_json, @headers
last_response.status.should == 200
visualization = JSON.parse(last_response.body)
visualization.fetch('privacy').should == 'PRIVATE'
end
it 'creates a private visualization if any table in the list is private' do
table3 = table_factory(privacy: 0)
payload = {
name: 'new visualization',
tables: [table3.fetch('name')]
}
post "/api/v1/viz?api_key=#{@api_key}", payload.to_json, @headers
last_response.status.should == 200
visualization = JSON.parse(last_response.body)
visualization.fetch('privacy').should == 'PRIVATE'
end
it 'creates a private visualization if any table in the list is private' do
table1 = table_factory
table2 = table_factory
table3 = table_factory(privacy: 0)
payload = {
name: 'new visualization',
tables: [
table1.fetch('name'),
table2.fetch('name'),
table3.fetch('name')
],
privacy: 'public'
}
post "/api/v1/viz?api_key=#{@api_key}", payload.to_json, @headers
last_response.status.should == 200
visualization = JSON.parse(last_response.body)
visualization.fetch('privacy').should == 'PRIVATE'
end
it 'assigns a generated name if name taken' do
table = table_factory
visualization = table.fetch('table_visualization')
visualization_name = visualization.fetch('name')
payload = {
source_visualization_id: visualization.fetch('id'),
name: visualization_name
}
post "/api/v1/viz?api_key=#{@api_key}", payload.to_json, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
response.fetch('name').should =~ /#{visualization_name} 1/
end
end # POST /api/v1/viz
describe 'PUT /api/v1/viz/:id' do
it 'updates an existing visualization' do
payload = factory
post "/api/v1/viz?api_key=#{@api_key}", payload.to_json, @headers
response = JSON.parse(last_response.body)
id = response.fetch('id')
response.fetch('tags').should == ['foo', 'bar']
put "/api/v1/viz/#{id}?api_key=#{@api_key}", { name: 'changed', tags: [], id: id }.to_json, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
response.fetch('name').should == 'changed'
response.fetch('tags').should == []
end
it 'updates the table in a table visualization', now: true do
table_attributes = table_factory
id = table_attributes.fetch('table_visualization').fetch('id')
Delorean.jump(1.minute)
put "/api/v1/viz/#{id}?api_key=#{@api_key}", { name: 'changed name', id: id }.to_json, @headers
Delorean.back_to_the_present
last_response.status.should == 200
response = JSON.parse(last_response.body)
response.fetch('table').fetch('updated_at').should_not == table_attributes.fetch('updated_at')
end
it 'returns a downcased name' do
table_attributes = table_factory
id = table_attributes.fetch('table_visualization').fetch('id')
put "/api/v1/viz/#{id}?api_key=#{@api_key}", { name: 'CHANGED_NAME', id: id }.to_json, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
response.fetch('name').should == 'changed_name'
get "/api/v1/viz/#{id}?api_key=#{@api_key}", {}, @headers
response = JSON.parse(last_response.body)
response.fetch('name').should == 'changed_name'
end
it 'returns a sanitized name' do
table_attributes = table_factory
id = table_attributes.fetch('table_visualization').fetch('id')
put "/api/v1/viz/#{id}?api_key=#{@api_key}", { name: 'changed name', id: id }.to_json, @headers
last_response.status.should == 200
response = JSON.parse(last_response.body)
response.fetch('name').should == 'changed_name'
get "/api/v1/viz/#{id}?api_key=#{@api_key}", {}, @headers
response = JSON.parse(last_response.body)
response.fetch('name').should == 'changed_name'
end
end # PUT /api/v1/viz/:id
describe 'DELETE /api/v1/viz/:id' do
it 'deletes the visualization' do
payload = factory
post "/api/v1/viz?api_key=#{@api_key}",
payload.to_json, @headers
id = JSON.parse(last_response.body).fetch('id')
get "/api/v1/viz/#{id}?api_key=#{@api_key}", {}, @headers
last_response.status.should == 200
delete "/api/v1/viz/#{id}?api_key=#{@api_key}", {}, @headers
last_response.status.should == 204
last_response.body.should be_empty
get "/api/v1/viz/#{id}?api_key=#{@api_key}", {}, @headers
last_response.status.should == 404
end
it 'deletes the associated table' do
table_attributes = table_factory
table_id = table_attributes.fetch('id')
get "/api/v1/tables/#{table_id}?api_key=#{@api_key}", {}, @headers
last_response.status.should == 200
table = JSON.parse(last_response.body)
visualization_id = table.fetch('table_visualization').fetch('id')
delete "/api/v1/viz/#{visualization_id}?api_key=#{@api_key}", {}, @headers
last_response.status.should == 204
get "/api/v1/tables/#{table_id}?api_key=#{@api_key}", {}, @headers
last_response.status.should == 404
end
end # DELETE /api/v1/viz/:id
# Visualizations are always created with default_privacy
def factory(attributes={})
{
name: attributes.fetch(:name, unique_name('viz')),
tags: attributes.fetch(:tags, ['foo', 'bar']),
map_id: attributes.fetch(:map_id, ::Map.create(user_id: @user.id).id),
description: attributes.fetch(:description, 'bogus'),
type: attributes.fetch(:type, 'derived'),
privacy: attributes.fetch(:privacy, 'public'),
source_visualization_id: attributes.fetch(:source_visualization_id, nil),
parent_id: attributes.fetch(:parent_id, nil),
locked: attributes.fetch(:locked, false),
prev_id: attributes.fetch(:prev_id, nil),
next_id: attributes.fetch(:next_id, nil)
}
end
def table_factory(options={})
privacy = options.fetch(:privacy, 1)
name = unique_name('table')
payload = {
name: name,
description: "#{name} description"
}
post "/api/v1/tables?api_key=#{@api_key}", payload.to_json, @headers
table_attributes = JSON.parse(last_response.body)
table_id = table_attributes.fetch('table_visualization').fetch("id")
put "/api/v1/viz/#{table_id}?api_key=#{@api_key}", { privacy: privacy }.to_json, @headers
table_attributes
end
end