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
@@ -0,0 +1,136 @@
require 'spec_helper_min'
require 'carto_api/json_client'
require 'carto/data_library_service'
describe Carto::DataLibraryService do
include_context 'users helper'
let(:visualization_json) do
"{\"id\":\"b7aa989a-d7bb-11e4-a982-0e43f3deba5a\",\"name\":\"meta_dataset\",\"display_name\":null," +
"\"attributions\":null,\"source\":\"\",\"license\":\"\",\"type\":\"table\",\"tags\":[\"production\"]" +
",\"description\":\"This must be PUBLIC for \\\"Public Library\\\" to work correctly\"," +
"\"created_at\":\"2015-03-31T15:36:36+00:00\",\"updated_at\":\"2015-05-29T14:49:45+00:00\"," +
"\"title\":\"\",\"kind\":\"geom\",\"privacy\":\"PUBLIC\",\"likes\":0,\"liked\":false," +
"\"permission\":{\"id\":\"da9b55a8-65d2-42a7-b30a-9847805fcb99\"," +
"\"owner\":{\"id\":\"891c95dc-b1f3-47d2-ace0-12651259b811\",\"username\":\"common-data\"," +
"\"name\":\"\",\"last_name\":null," +
"\"avatar_url\":\"//wadus.net/cartodbui/assets/unversioned/images/avatars/avatar_mountain_green.png\"," +
"\"base_url\":\"https://common-data.carto.com\",\"google_maps_query_string\":\"\"," +
"\"disqus_shortname\":\"\",\"viewer\":false,\"org_admin\":false,\"org_user\":false,\"remove_logo\":true}}," +
"\"stats\":{\"2018-02-08\":0,\"2018-02-09\":0,\"2018-02-10\":0,\"2018-02-11\":0,\"2018-02-12\":0," +
"\"2018-02-13\":0,\"2018-02-14\":0,\"2018-02-15\":0,\"2018-02-16\":0,\"2018-02-17\":0,\"2018-02-18\":0," +
"\"2018-02-19\":0,\"2018-02-20\":0,\"2018-02-21\":0,\"2018-02-22\":0,\"2018-02-23\":0,\"2018-02-24\":0," +
"\"2018-02-25\":0,\"2018-02-26\":1.0,\"2018-02-27\":0,\"2018-02-28\":0,\"2018-03-01\":0,\"2018-03-02\":0," +
"\"2018-03-03\":0,\"2018-03-04\":0,\"2018-03-05\":0,\"2018-03-06\":0,\"2018-03-07\":0,\"2018-03-08\":0," +
"\"2018-03-09\":0},\"auth_tokens\":[],\"table\":{\"id\":\"bf8fa9a0-2000-4682-8f32-2334f9882ca3\"," +
"\"name\":\"\\\"public\\\".meta_dataset\",\"permission\":{\"id\":\"da9b55a8-65d2-42a7-b30a-9847805fcb99\"," +
"\"owner\":{\"id\":\"891c95dc-b1f3-47d2-ace0-12651259b811\",\"username\":\"common-data\",\"name\":\"\"," +
"\"last_name\":null,\"avatar_url\":\"//wadus.net/cartodbui/assets/images/avatars/avatar_mountain_green.png\"," +
"\"base_url\":\"https://common-data.carto.com\",\"google_maps_query_string\":\"\"," +
"\"disqus_shortname\":\"\",\"viewer\":false,\"org_admin\":false,\"org_user\":false,\"remove_logo\":true}}," +
"\"geometry_types\":[\"ST_MultiPolygon\"],\"privacy\":\"PUBLIC\",\"updated_at\":\"2015-05-14T10:30:31+00:00\", " +
"\"size\":53248,\"row_count\":124}}"
end
describe '#load_dataset!' do
it 'loads a remote dataset into a Data Library' do
client_p = { scheme: 'https', base_domain: 'waduscarto.com', port: 666 }
client = CartoAPI::JsonClient.new(**client_p)
mocked_get_visualization_v1_response = JSON.parse(visualization_json, symbolize_names: true)
source_dataset = mocked_get_visualization_v1_response[:name]
params = {
source_dataset: source_dataset,
source_username: 'wadus-username',
source_api_key: 'rewadus-api_key',
target_username: @carto_user1.username,
granted_api_key: 'wadus-api_key',
format: 'gpkg'
}
client.expects(:get_visualization_v1)
.with(username: params[:source_username],
name: params[:source_dataset],
params: { api_key: params[:source_api_key] })
.returns(mocked_get_visualization_v1_response)
visualization = @carto_user1.visualizations
.where(type: Carto::Visualization::TYPE_REMOTE)
.where(name: source_dataset)
.first
visualization.should_not be
Carto::DataLibraryService.new.load_dataset!(client, **params)
visualization = @carto_user1.visualizations
.where(type: Carto::Visualization::TYPE_REMOTE)
.where(name: source_dataset)
.first
visualization.should be
visualization.display_name.should eq visualization.name # The example doesn't have display_name, so name is used
visualization.tags.should eq ['production']
external_source = visualization.external_source
external_source.should be
external_source.visualization.should be
import_url = "#{client_p[:scheme]}://#{params[:source_username]}.#{client_p[:base_domain]}"
external_source.import_url.should start_with import_url
/format=gpkg/.match(external_source.import_url).should be
external_source.geometry_types.should eq ['ST_MultiPolygon']
external_source.username.should eq params[:source_username]
visualization.destroy
end
end
let(:api_keys_response) do
{
total: 1,
count: 1,
result: [
{
name: "test_ro_untitled_table_39",
user: { username: "juanignaciosl" },
type: "regular",
token: "z17KQqbcdbDfIYcD4FTw",
grants: [
{ type: "apis", apis: ["maps", "sql"] },
{ type: "database", tables: [
{ schema: "juanignaciosl", name: "untitled_table_30", permissions: ["select"] }
] }
],
created_at: "2018-03-23 14:45:00 +0000",
updated_at: "2018-03-23 14:45:00 +0000",
_links: { :self => "https://juanignaciosl.wcarto.com/api/v3/api_keys/test_ro_untitled_table_39" }
}
],
_links: {
first: { :href => "https://juanignaciosl.wcarto.com/api/v3/api_keys?order=updated_at&page=1&per_page=100000" },
last: { :href => "https://juanignaciosl.wcarto.com/api/v3/api_keys?order=updated_at&page=1&per_page=100000" }
}
}
end
describe '#load_datasets!' do
it 'loads remote datasets from an API key into a Data Library' do
client_p = { scheme: 'https', base_domain: 'wcarto.com', port: 666 }
client = CartoAPI::JsonClient.new(**client_p)
api_key_at_response = api_keys_response[:result][0]
source_dataset = api_key_at_response[:grants][1][:tables][0][:name]
params = {
source_username: 'wadus-username',
source_api_key: 'rewadus-api_key',
target_username: @carto_user1.username,
granted_api_key: api_key_at_response[:token],
format: 'gpkg'
}
client.expects(:get_api_keys_v3)
.with(username: params[:source_username],
params: { api_key: params[:granted_api_key] })
.returns(api_keys_response)
service = Carto::DataLibraryService.new
params_load_one = { source_dataset: source_dataset }.merge(params)
service.expects(:load_dataset!).once.with(client, **params_load_one)
service.load_datasets!(client, **params)
end
end
end
@@ -0,0 +1,419 @@
require 'spec_helper_min'
class DbPermissionServiceMock < Carto::DbPermissionService
def self.user_ids(type, id)
if type == 'user'
[id]
else
["1", "2", "3", "4"]
end
end
end
describe Carto::DbPermissionService do
describe '#revokes_by_user_diff' do
it 'should be empty with grants' do
old_acl = []
new_acl = [{ "type": "user", "id": "1", "access": "r" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
old_acl = []
new_acl = [{ "type": "user", "id": "1", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
old_acl = [{ "type": "user", "id": "1", "access": "r" }]
new_acl = [{ "type": "user", "id": "1", "access": "r" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
old_acl = [{ "type": "user", "id": "1", "access": "r" }]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
old_acl = []
new_acl = [{ "type": "user", "id": "1", "access": "r" }, { "type": "user", "id": "2", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
old_acl = [{ "type": "user", "id": "1", "access": "r" }, { "type": "org", "id": "1", "access": "r" }]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "org", "id": "1", "access": "r" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'should not be empty with revokes by type' do
old_acl = [{ "type": "user", "id": "1", "access": "r" }]
new_acl = []
expected = { "1" => "r" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
old_acl = [{ "type": "user", "id": "1", "access": "rw" }]
new_acl = [{ "type": "user", "id": "1", "access": "r" }]
expected = { "1" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
old_acl = [{ "type": "user", "id": "1", "access": "rw" }]
new_acl = []
expected = { "1" => "rw" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
old_acl = [{ "type": "user", "id": "1", "access": "r" }, { "type": "user", "id": "2", "access": "rw" }]
new_acl = []
expected = { "1" => "r", "2" => "rw" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
old_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "org", "id": "1", "access": "r" }]
new_acl = [{ "type": "user", "id": "1", "access": "r" }, { "type": "org", "id": "1", "access": "r" }]
expected = { "1" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
describe '#orgs and groups' do
it 'org: rw ---> r' do
old_acl = [{ "type": "org", "id": "1", "access": "rw" }]
new_acl = [{ "type": "org", "id": "1", "access": "r" }]
expected = { "1" => "w", "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'org: rw ---> none' do
old_acl = [{ "type": "org", "id": "1", "access": "rw" }]
new_acl = []
expected = { "1" => "rw", "2" => "rw", "3" => "rw", "4" => "rw" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'org: r ---> none' do
old_acl = [{ "type": "org", "id": "1", "access": "r" }]
new_acl = []
expected = { "1" => "r", "2" => "r", "3" => "r", "4" => "r" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'org: rw ---> none; group: rw ---> r' do
old_acl = [{ "type": "group", "id": "1", "access": "rw" }, { "type": "org", "id": "1", "access": "rw" }]
new_acl = [{ "type": "group", "id": "1", "access": "r" }]
expected = { "1" => "w", "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'org: rw ---> r; group: rw ---> none' do
old_acl = [{ "type": "org", "id": "1", "access": "rw" }, { "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "org", "id": "1", "access": "r" }]
expected = { "1" => "w", "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'org: rw; group: rw ---> none' do
old_acl = [{ "type": "org", "id": "1", "access": "rw" }, { "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "org", "id": "1", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'group: rw; org: rw ---> none' do
old_acl = [{ "type": "group", "id": "1", "access": "rw" }, { "type": "org", "id": "1", "access": "rw" }]
new_acl = [{ "type": "group", "id": "1", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
end
describe '#several orgs or groups' do
it 'org1: rw; org2: rw ---> none' do
old_acl = [{ "type": "org", "id": "1", "access": "rw" }, { "type": "org", "id": "2", "access": "rw" }]
new_acl = [{ "type": "org", "id": "1", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'org1: rw; org2: rw ---> r' do
old_acl = [{ "type": "org", "id": "1", "access": "rw" }, { "type": "org", "id": "2", "access": "rw" }]
new_acl = [{ "type": "org", "id": "1", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'org1: rw ---> r; org2: rw ---> none' do
old_acl = [{ "type": "org", "id": "1", "access": "rw" }, { "type": "org", "id": "2", "access": "rw" }]
new_acl = [{ "type": "org", "id": "1", "access": "r" }]
expected = { "1" => "w", "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'group1: rw; group2: rw ---> none' do
old_acl = [{ "type": "group", "id": "1", "access": "rw" }, { "type": "group", "id": "2", "access": "rw" }]
new_acl = [{ "type": "group", "id": "1", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'group1: rw; group2: rw ---> r' do
old_acl = [{ "type": "group", "id": "1", "access": "rw" }, { "type": "group", "id": "2", "access": "rw" }]
new_acl = [{ "type": "group", "id": "1", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'group1: rw ---> r; group2: rw ---> none' do
old_acl = [{ "type": "group", "id": "1", "access": "rw" }, { "type": "group", "id": "2", "access": "rw" }]
new_acl = [{ "type": "group", "id": "1", "access": "r" }]
expected = { "1" => "w", "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: rw; group1: rw ---> r; group2: rw ---> none' do
old_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "group", "id": "1", "access": "rw" },
{ "type": "group", "id": "1", "access": "rw" }
]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "group", "id": "1", "access": "r" }]
expected = { "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: r; group1: rw ---> r; group2: rw ---> none' do
old_acl = [{ "type": "group", "id": "1", "access": "rw" }, { "type": "group", "id": "2", "access": "rw" }]
new_acl = [{ "type": "group", "id": "1", "access": "r" }]
expected = { "1" => "w", "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
end
describe '#users and orgs or groups' do
it 'user1: rw, user2: r, org: rw ---> r' do
old_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "user", "id": "2", "access": "r" },
{ "type": "org", "id": "1", "access": "rw" }
]
new_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "user", "id": "2", "access": "r" },
{ "type": "org", "id": "1", "access": "r" }
]
expected = { "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: rw, user2: r, org: r ---> none' do
old_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "user", "id": "2", "access": "r" },
{ "type": "org", "id": "1", "access": "r" }
]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "user", "id": "2", "access": "r" }]
expected = { "3" => "r", "4" => "r" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: rw, user2: r, org: rw ---> none' do
old_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "user", "id": "2", "access": "r" },
{ "type": "org", "id": "1", "access": "rw" }
]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "user", "id": "2", "access": "r" }]
expected = { "2" => "w", "3" => "rw", "4" => "rw" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
end
describe '#users, orgs and groups' do
it 'user1: rw; org: rw ---> none; group: rw ---> r' do
old_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "group", "id": "1", "access": "rw" },
{ "type": "org", "id": "1", "access": "rw" }
]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "group", "id": "1", "access": "r" }]
expected = { "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: rw; user2: r; org: r ---> none; group: rw ---> r' do
old_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "user", "id": "2", "access": "r" },
{ "type": "group", "id": "1", "access": "rw" },
{ "type": "org", "id": "1", "access": "rw" }
]
new_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "user", "id": "2", "access": "r" },
{ "type": "group", "id": "1", "access": "r" }
]
expected = { "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: rw; user2: r; org: r ---> none; group: rw ---> none' do
old_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "user", "id": "2", "access": "r" },
{ "type": "org", "id": "1", "access": "r" },
{ "type": "group", "id": "1", "access": "rw" }
]
new_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "user", "id": "2", "access": "r" }
]
expected = { "2" => "w", "3" => "rw", "4" => "rw" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: rw; user2: rw ---> none; user3: rw ---> r; org: rw; group: rw' do
old_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "user", "id": "2", "access": "rw" },
{ "type": "user", "id": "3", "access": "rw" },
{ "type": "group", "id": "1", "access": "rw" },
{ "type": "org", "id": "1", "access": "rw" }
]
new_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "user", "id": "3", "access": "r" },
{ "type": "group", "id": "1", "access": "rw" },
{ "type": "org", "id": "1", "access": "rw" }
]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'user1: rw; user2: rw ---> none; org: rw ---> none; group: rw ---> r' do
old_acl = [
{ "type": "user", "id": "1", "access": "rw" },
{ "type": "user", "id": "2", "access": "rw" },
{ "type": "group", "id": "1", "access": "rw" },
{ "type": "org", "id": "1", "access": "rw" }
]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "group", "id": "1", "access": "r" }]
expected = { "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
end
describe '#grants and revokes' do
it 'group1: rw ---> none; group2: none ---> rw;' do
old_acl = [{ "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "group", "id": "2", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'group1: rw ---> none; org1: none ---> rw;' do
old_acl = [{ "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "org", "id": "1", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'group1: rw ---> r; group2: none ---> rw;' do
old_acl = [{ "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "group", "id": "1", "access": "r" }, { "type": "group", "id": "2", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'group1: rw ---> r; org1: none ---> rw;' do
old_acl = [{ "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "group", "id": "1", "access": "r" }, { "type": "org", "id": "1", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'group1: rw ---> none; group2: none ---> r;' do
old_acl = [{ "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "group", "id": "2", "access": "r" }]
expected = { "1" => "w", "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: rw; group1: rw ---> none; group2: none ---> rw;' do
old_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "group", "id": "2", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'user1: r; group1: rw ---> none; group2: none ---> rw;' do
old_acl = [{ "type": "user", "id": "1", "access": "r" }, { "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "user", "id": "1", "access": "r" }, { "type": "group", "id": "2", "access": "rw" }]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
it 'user1: rw; group1: rw ---> none; group2: none ---> r;' do
old_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "group", "id": "2", "access": "r" }]
expected = { "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: r ---> rw; group1: rw ---> none;' do
old_acl = [{ "type": "user", "id": "1", "access": "r" }, { "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }]
expected = { "2" => "rw", "3" => "rw", "4" => "rw" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: r ---> rw; group1: rw ---> r;' do
old_acl = [{ "type": "user", "id": "1", "access": "r" }, { "type": "group", "id": "1", "access": "rw" }]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }, { "type": "group", "id": "1", "access": "r" }]
expected = { "2" => "w", "3" => "w", "4" => "w" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: none ---> rw; group1: r ---> none;' do
old_acl = [{ "type": "group", "id": "1", "access": "r" }]
new_acl = [{ "type": "user", "id": "1", "access": "rw" }]
expected = { "2" => "r", "3" => "r", "4" => "r" }
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to eq(expected)
end
it 'user1: none ---> r; group1: r ---> rw; org1: rw ---> r;' do
old_acl = [
{ "type": "group", "id": "1", "access": "r" },
{ "type": "org", "id": "1", "access": "rw" }
]
new_acl = [
{ "type": "user", "id": "1", "access": "r" },
{ "type": "group", "id": "1", "access": "rw" },
{ "type": "org", "id": "1", "access": "r" }
]
diff = DbPermissionServiceMock.revokes_by_user_diff(old_acl, new_acl, 'fake_table_owner_id')
expect(diff).to be_blank
end
end
end
end
@@ -0,0 +1,111 @@
require 'spec_helper_min'
describe Carto::DoLicensingService do
before(:all) do
@user = FactoryGirl.create(:valid_user, username: 'fulano')
@redis_key = "do:fulano:datasets"
@service = Carto::DoLicensingService.new('fulano')
@dataset_id = 'carto.abc.dataset1'
@datasets = [
{ dataset_id: @dataset_id, available_in: ['bq', 'spanner'], price: 100,
expires_at: Time.new(2020, 9, 27, 8, 0, 0) },
{ dataset_id: 'carto.abc.dataset2', available_in: ['spanner'], price: 200,
expires_at: Time.new(2020, 12, 31, 12, 0, 0) }
]
end
after(:all) do
@user.destroy
end
after(:each) do
$users_metadata.del(@redis_key)
end
describe '#subscribe' do
before(:each) do
@central_mock = mock
Cartodb::Central.stubs(:new).returns(@central_mock)
end
after(:each) do
Cartodb::Central.unstub(:new)
$users_metadata.del(@redis_key)
end
it 'calls create_do_datasets from Central with the expected parameters' do
@central_mock.expects(:create_do_datasets).once.with(username: 'fulano', datasets: @datasets)
@service.subscribe(@datasets)
end
it 'stores the metadata in Redis' do
@central_mock.stubs(:create_do_datasets)
bq_datasets = [
{ dataset_id: 'carto.abc.dataset1', expires_at: '2020-09-27 08:00:00 +0000' }
].to_json
spanner_datasets = [
{ dataset_id: 'carto.abc.dataset1', expires_at: '2020-09-27 08:00:00 +0000' },
{ dataset_id: 'carto.abc.dataset2', expires_at: '2020-12-31 12:00:00 +0000' }
].to_json
@service.subscribe(@datasets)
$users_metadata.hget(@redis_key, 'bq').should eq bq_datasets
$users_metadata.hget(@redis_key, 'spanner').should eq spanner_datasets
end
it 'allows to add more data in the same Redis key' do
@central_mock.stubs(:create_do_datasets)
more_datasets = [
{ dataset_id: 'carto.abc.dataset3', available_in: ['bq'], price: 300,
expires_at: '2020-09-27 08:00:00 +0000' }
]
@service.subscribe(@datasets)
@service.subscribe(more_datasets)
bq_datasets = JSON.parse($users_metadata.hget(@redis_key, 'bq'))
spanner_datasets = JSON.parse($users_metadata.hget(@redis_key, 'spanner'))
bq_datasets.count.should eq 2
spanner_datasets.count.should eq 2
end
end
describe '#unsubscribe' do
before(:each) do
@central_mock = mock
Cartodb::Central.stubs(:new).returns(@central_mock)
end
after(:each) do
Cartodb::Central.unstub(:new)
end
it 'calls remove_do_dataset from Central with the expected parameters' do
@central_mock.expects(:remove_do_dataset).once.with(username: 'fulano', id: @dataset_id)
@service.unsubscribe(@dataset_id)
end
it 'removes the metadata from Redis' do
@central_mock.stubs(:create_do_datasets)
@central_mock.stubs(:remove_do_dataset)
bq_datasets = [].to_json
spanner_datasets = [
{ dataset_id: 'carto.abc.dataset2', expires_at: '2020-12-31 12:00:00 +0000' }
].to_json
@service.subscribe(@datasets)
@service.unsubscribe(@dataset_id)
$users_metadata.hget(@redis_key, 'bq').should eq bq_datasets
$users_metadata.hget(@redis_key, 'spanner').should eq spanner_datasets
end
end
end
@@ -0,0 +1,447 @@
require 'spec_helper_min'
require 'factories/carto_visualizations'
describe Carto::OrganizationMetadataExportService do
include NamedMapsHelper
include Carto::Factories::Visualizations
include TableSharing
before(:all) do
@connector_provider = FactoryGirl.create(:connector_provider)
user = FactoryGirl.create(:carto_user)
@oauth_app = FactoryGirl.create(:oauth_app, user: user)
end
after(:all) do
@connector_provider.destroy
end
def create_organization_with_dependencies
@sequel_organization = FactoryGirl.create(:organization_with_users, password_expiration_in_d: 365)
@organization = Carto::Organization.find(@sequel_organization.id)
@owner = @organization.owner
@non_owner = @organization.users.reject(&:organization_owner?).first
@map, @table, @table_visualization, @visualization = create_full_visualization(@non_owner)
share_visualization_with_user(@table_visualization, @owner)
@asset = FactoryGirl.create(:organization_asset, organization: @organization)
@group = FactoryGirl.create(:carto_group, organization: @organization)
@group.add_user(@non_owner.username)
notification = FactoryGirl.create(:notification, organization: @organization)
notification.received_notifications.find_by_user_id(@owner.id).update_attribute(:read_at, DateTime.now)
CartoDB::GeocoderUsageMetrics.new(@owner.username, @organization.name).incr(:geocoder_here, :success_responses)
FactoryGirl.create(:connector_configuration, connector_provider: @connector_provider, organization: @organization)
@organization.reload
end
def destroy_organization
clean_redis
@organization.groups.each(&:destroy)
@organization.groups.clear
Organization[@organization.id].destroy_cascade
end
def clean_redis
gum = CartoDB::GeocoderUsageMetrics.new(@owner.username, @organization.name)
$users_metadata.DEL(gum.send(:user_key_prefix, :geocoder_here, :success_responses, DateTime.now))
$users_metadata.DEL(gum.send(:org_key_prefix, :geocoder_here, :success_responses, DateTime.now))
end
let(:service) { Carto::OrganizationMetadataExportService.new }
describe '#organization export' do
before(:all) do
create_organization_with_dependencies
end
after(:all) do
destroy_organization
end
it 'exports' do
export = service.export_organization_json_hash(@organization)
expect_export_matches_organization(export[:organization], @organization)
end
it 'includes all user model attributes' do
export = service.export_organization_json_hash(@organization)
expect(export[:organization].keys).to include(*@organization.attributes.symbolize_keys.keys)
end
it 'exports notifications and received notifications' do
export = service.export_organization_json_hash(@organization)
exported_notifications = export[:organization][:notifications]
exported_notifications.length.should eq 1
exported_notifications.first[:received_by].length.should eq @organization.users.length
end
end
describe '#organization import' do
it 'imports' do
organization = service.build_organization_from_hash_export(full_export)
expect_export_matches_organization(full_export[:organization], organization)
end
it 'imports 1.0.2 (without oauth_app_organizations)' do
organization = service.build_organization_from_hash_export(full_export_one_zero_two)
expect_export_matches_organization(full_export_one_zero_two[:organization], organization)
expect(organization.oauth_app_organizations).to be_empty
end
it 'imports 1.0.1 (without connector configurations)' do
organization = service.build_organization_from_hash_export(full_export_one_zero_one)
expect_export_matches_organization(full_export_one_zero_one[:organization], organization)
expect(organization.connector_configurations).to be_empty
end
it 'imports 1.0.0 (without password expiration)' do
organization = service.build_organization_from_hash_export(full_export_one_zero_zero)
expect_export_matches_organization(full_export_one_zero_zero[:organization], organization)
expect(organization.password_expiration_in_d).to be_nil
end
end
describe '#full export + import (organization, users and visualizations)' do
it 'export + import organization, users and visualizations' do
Dir.mktmpdir do |path|
create_organization_with_dependencies
service.export_to_directory(@organization, path)
source_organization = @organization.attributes
source_users = @organization.users.map(&:attributes)
source_groups = @organization.groups.map(&:attributes)
source_group_users = @group.users.map(&:id)
source_assets = @organization.assets.map(&:attributes)
expect(source_assets).not_to be_empty
source_notifications = @organization.notifications.map(&:attributes)
source_received_notifications = @organization.notifications.map { |n|
[n.id, n.received_notifications.map(&:attributes)]
}.to_h
# Destroy, keeping the database
clean_redis
Table.any_instance.stubs(:remove_table_from_user_database)
@organization.notifications.each(&:destroy)
@organization.assets.each { |a| a.update_attribute(:storage_info, nil) } # Avoids remote deletion attempt
@organization.assets.each(&:delete)
@organization.assets.clear
@organization.users.flat_map(&:visualizations).each(&:destroy)
@organization.users.each do |u|
sequel_user = ::User[u.id]
sequel_user.client_application.access_tokens.each(&:destroy)
sequel_user.client_application.destroy
u.destroy
end
@organization.groups.each(&:destroy)
@organization.groups.clear
@organization.destroy
imported_organization = service.import_from_directory(path)
service.import_metadata_from_directory(imported_organization, path)
compare_excluding_dates(imported_organization.attributes, source_organization)
expect(imported_organization.users.count).to eq source_users.count
imported_organization.users.zip(source_users).each do |u1, u2|
compare_excluding_dates(u1.attributes, u2)
end
expect(imported_organization.groups.count).to eq source_groups.count
expect_redis_restored(imported_organization)
imported_organization.groups.zip(source_groups).each do |g1, g2|
compare_excluding_fields(g1.attributes, g2, EXCLUDED_ORG_META_DATE_FIELDS + EXCLUDED_ORG_META_ID_FIELDS)
end
expect(imported_organization.groups.first.users.map(&:id)).to eq source_group_users
expect(imported_organization.assets.length).to eq source_assets.length
imported_organization.assets.zip(source_assets).each do |ia, sa|
compare_excluding_fields(ia.attributes, sa, EXCLUDED_ASSETS_FIELDS)
end
expect(imported_organization.notifications.length).to eq source_notifications.length
imported_organization.notifications.zip(source_notifications).each do |i_n, s_n|
compare_excluding_fields(
stringify_fields(i_n.attributes, ['created_at']),
stringify_fields(s_n, ['created_at']),
EXCLUDED_NOTIFICATIONS_FIELDS
)
i_received = i_n.received_notifications.map(&:attributes)
s_received = source_received_notifications[s_n['id']]
i_received.length.should eq s_received.length
i_received.zip(s_received).each do |i_rn, s_rn|
compare_excluding_fields(
stringify_fields(i_rn, ['received_at', 'read_at']),
stringify_fields(s_rn, ['received_at', 'read_at']),
['id', 'notification_id']
)
end
end
end
end
end
EXCLUDED_ORG_META_DATE_FIELDS = ['created_at', 'updated_at', 'period_end_date'].freeze
EXCLUDED_ORG_META_ID_FIELDS = ['id', 'organization_id'].freeze
EXCLUDED_NOTIFICATIONS_FIELDS = ['id'].freeze
EXCLUDED_ASSETS_FIELDS = ['id'].freeze
# DateTime comparisons fail if they're not stringified
def stringify_fields(hash, fields)
hash.merge(Hash[hash.slice(*fields).map { |k, v| [k, v.to_s] }])
end
def compare_excluding_dates(u1, u2)
compare_excluding_fields(u1, u2, EXCLUDED_ORG_META_DATE_FIELDS)
end
def compare_excluding_fields(u1, u2, fields)
filtered1 = u1.reject { |k, _| fields.include?(k) }
filtered2 = u2.reject { |k, _| fields.include?(k) }
expect(filtered1).to eq filtered2
end
def expect_export_matches_organization(export, organization)
Carto::OrganizationMetadataExportService::EXPORTED_ORGANIZATION_ATTRIBUTES.each do |att|
expect(export[att]).to eq organization.attributes[att.to_s]
end
expect(export[:assets].count).to eq organization.assets.size
export[:assets].zip(organization.assets).each do |exported_asset, asset|
expect_export_matches_asset(exported_asset, asset)
end
expect(export[:groups].count).to eq organization.groups.size
export[:groups].zip(organization.groups).each do |exported_group, group|
expect_export_matches_group(exported_group, group)
end
expect(export[:notifications].count).to eq organization.notifications.size
export[:notifications].zip(organization.notifications).each do |exported_notification, notification|
expect_export_matches_notification(exported_notification, notification)
end
if export[:connector_configurations]
expect(export[:connector_configurations].count).to eq organization.connector_configurations.size
export[:connector_configurations].zip(organization.connector_configurations).each do |exported_cc, cc|
expect_export_matches_connector_configuration(exported_cc, cc)
end
else
expect(organization.connector_configurations).to be_empty
end
if export[:oauth_app_organizations]
expect(export[:oauth_app_organizations].count).to eq organization.oauth_app_organizations.size
export[:oauth_app_organizations].zip(organization.oauth_app_organizations).each do |exported_oao, oao|
expect_export_matches_oauth_app_organization(exported_oao, oao)
end
else
expect(organization.oauth_app_organizations).to be_empty
end
end
def expect_export_matches_asset(exported_asset, asset)
expect(exported_asset[:public_url]).to eq asset.public_url
expect(exported_asset[:kind]).to eq asset.kind
expect(exported_asset[:storage_info]).to eq asset.storage_info
end
def expect_export_matches_group(exported_group, group)
expect(exported_group[:id]).to eq group.id
expect(exported_group[:name]).to eq group.name
expect(exported_group[:display_name]).to eq group.display_name
expect(exported_group[:database_role]).to eq group.database_role
expect(exported_group[:auth_token]).to eq group.auth_token
end
def expect_export_matches_notification(exported_notification, notification)
expect(exported_notification[:icon]).to eq notification.icon
expect(exported_notification[:recipients]).to eq notification.recipients
expect(exported_notification[:body]).to eq notification.body
expect(exported_notification[:created_at]).to eq notification.created_at
# This check forces test data to have notification receptions, so they will be tested
expect(exported_notification[:received_by].length).to be > 0
expect(exported_notification[:received_by].length).to eq notification.received_notifications.length
exported_notification[:received_by].each do |exported_received_notification|
received_notification = notification.received_notifications.find do |rn|
rn.user_id == exported_received_notification[:user_id]
end
expect(exported_received_notification[:received_at]).to eq received_notification.received_at
expect(exported_received_notification[:read_at]).to eq received_notification.read_at
end
end
def expect_export_matches_received_notification(exported_received_notification, received_notification)
expect(exported_received_notification[:user_id]).to eq received_notification.user_id
expect(exported_received_notification[:received_at]).to eq received_notification.received_at
expect(exported_received_notification[:read_at]).to eq received_notification.read_at
end
def expect_export_matches_connector_configuration(exported_cc, cc)
expect(exported_cc[:enabled]).to eq cc.enabled
expect(exported_cc[:max_rows]).to eq cc.max_rows
expect(exported_cc[:created_at]).to eq cc.created_at
expect(exported_cc[:updated_at]).to eq cc.updated_at
expect(exported_cc[:provider_name]).to eq cc.connector_provider.name
end
def expect_export_matches_oauth_app_organization(exported_oao, oao)
expect(exported_oao[:oauth_app_id]).to eq oao.oauth_app_id
expect(exported_oao[:seats]).to eq oao.seats
expect(exported_oao[:oauth_app_id]).to eq oao.oauth_app_id
expect(exported_oao[:oauth_app_id]).to eq oao.oauth_app_id
fake_oao = Carto::OauthAppOrganization.new(
created_at: exported_oao[:created_at],
updated_at: exported_oao[:updated_at]
)
expect(fake_oao.created_at).to eq oao.created_at
expect(fake_oao.updated_at).to eq oao.updated_at
end
def expect_redis_restored(org)
expect(CartoDB::GeocoderUsageMetrics.new(org.owner.username, org.name).get(:geocoder_here, :success_responses)).to eq(1)
expect(CartoDB::GeocoderUsageMetrics.new(org.owner.username).get(:geocoder_here, :success_responses)).to eq(1)
end
let(:full_export) do
{
version: "1.0.3",
organization: {
id: "189d642c-c7da-40aa-bffd-517aa0eb7999",
seats: 100,
quota_in_bytes: 99999999997,
created_at: DateTime.now,
updated_at: DateTime.now,
name: "worg",
avatar_url: nil,
owner_id: "06b974db-de0d-49b7-ae9b-76c63af8c122",
website: "",
description: "",
display_name: "",
discus_shortname: "",
twitter_organizationname: nil,
geocoding_quota: 0,
map_view_quota: nil,
auth_token: "pgYcd8XnAn46HlczpvQcIw",
geocoding_block_price: nil,
map_view_block_price: nil,
twitter_datasource_enabled: true,
twitter_datasource_block_price: 100,
twitter_datasource_block_size: 1000,
twitter_datasource_quota: 1,
google_maps_key: "key=AIzaEa12DxNYEyM257DwzuieArJ_pinDIPyYVts",
google_maps_private_key: nil,
color: "#FF9900",
default_quota_in_bytes: 209715200,
whitelisted_email_domains: ["carto.com", "worg.com"],
admin_email: "worg1@worg.com",
auth_username_password_enabled: nil,
auth_google_enabled: true,
location: nil,
here_isolines_quota: 0,
here_isolines_block_price: nil,
strong_passwords_enabled: false,
obs_snapshot_quota: 0,
obs_snapshot_block_price: nil,
obs_general_quota: 999999999,
obs_general_block_price: nil,
salesforce_datasource_enabled: false,
viewer_seats: 100,
geocoder_provider: "heremaps",
isolines_provider: "heremaps",
routing_provider: "mapzen",
auth_github_enabled: true,
engine_enabled: true,
mapzen_routing_quota: nil,
mapzen_routing_block_price: nil,
builder_enabled: true,
auth_saml_configuration: {},
no_map_logo: false,
password_expiration_in_d: 365,
assets: [{
public_url: "http://localhost.lan:3000/uploads/organization_assets/189d642c-c7da-40aa-bffd-517aa0eb7999/asset_download_148430456220170113-20961-67b7r0",
kind: "organization_asset",
storage_info: {
type: "local",
location: "organization_assets",
identifier: "public/uploads/organization_assets/189d642c-c7da-40aa-bffd-517aa0eb7999/asset_download_148430456220170113-20961-67b7r0"
}
}],
groups: [
{
name: 'g_group',
display_name: '#group',
database_role: 'a98f3bc6391fadfe4d1487e2b6912d24_g_g_group',
auth_token: 'TE7rg6_4RU8vAeTeEeITIQ',
user_ids: []
}
],
notifications: [
{
icon: "alert",
recipients: "all",
body: "Empty body",
created_at: DateTime.now,
received_by: [
{
user_id: '06b974db-de0d-49b7-ae9b-76c63af8c122',
received_at: DateTime.now,
read_at: nil
}
]
}
],
connector_configurations: [
{
created_at: DateTime.now,
updated_at: DateTime.now,
enabled: true,
max_rows: 100000,
provider_name: @connector_provider.name
}
],
oauth_app_organizations: [
{
oauth_app_id: @oauth_app.id,
seats: 5,
created_at: "2018-11-16T14:31:46+00:00",
updated_at: "2018-11-17T16:41:56+00:00"
}
]
}
}
end
let(:full_export_one_zero_two) do
organizations_hash = full_export[:organization].except!(:oauth_app_organizations)
full_export[:organization] = organizations_hash
full_export
end
let(:full_export_one_zero_one) do
organizations_hash = full_export_one_zero_two[:organization].except!(:connector_configurations)
full_export[:organization] = organizations_hash
full_export
end
let(:full_export_one_zero_zero) do
organizations_hash = full_export_one_zero_one[:organization].except!(:password_expiration_in_d)
full_export[:organization] = organizations_hash
full_export
end
end
@@ -0,0 +1,100 @@
require 'spec_helper_min'
describe 'Carto::OverquotaUsersService' do
before(:all) do
@user = FactoryGirl.create(:carto_user, account_type: 'NOT_FREE')
@user2 = FactoryGirl.create(:carto_user, account_type: 'FREE')
end
after(:all) do
@user.destroy
@user2.destroy
end
# Filter overquota users to only those created by this spec
def overquota(delta = 0)
service = Carto::OverquotaUsersService.new
$users_metadata.del(service.send(:date_key))
service.store_overquota_users(delta)
users = service.get_stored_overquota_users.map { |u| u['id'] }.select { |uid| [@user.id, @user2.id].include?(uid) }
users
end
it "should return users near their geocoding quota" do
::User.any_instance.stubs(:get_api_calls).returns([0])
::User.any_instance.stubs(:map_view_quota).returns(120)
::User.any_instance.stubs(:get_geocoding_calls).returns(81)
::User.any_instance.stubs(:geocoding_quota).returns(100)
overquota.should be_empty
overquota(0.20).should include(@user.id, @user2.id)
overquota(0.20).size.should == 2
overquota(0.10).should be_empty
end
it "should return users near their here isolines quota" do
::User.any_instance.stubs(:get_api_calls).returns([0])
::User.any_instance.stubs(:map_view_quota).returns(120)
::User.any_instance.stubs(:get_geocoding_calls).returns(0)
::User.any_instance.stubs(:geocoding_quota).returns(100)
::User.any_instance.stubs(:get_here_isolines_calls).returns(81)
::User.any_instance.stubs(:here_isolines_quota).returns(100)
overquota.should be_empty
overquota(0.20).should include(@user.id, @user2.id)
overquota(0.20).size.should == 2
overquota(0.10).should be_empty
end
it "should return users near their data observatory snapshot quota" do
::User.any_instance.stubs(:get_api_calls).returns([0])
::User.any_instance.stubs(:map_view_quota).returns(120)
::User.any_instance.stubs(:get_geocoding_calls).returns(0)
::User.any_instance.stubs(:geocoding_quota).returns(100)
::User.any_instance.stubs(:get_here_isolines_calls).returns(0)
::User.any_instance.stubs(:here_isolines_quota).returns(100)
::User.any_instance.stubs(:get_obs_general_calls).returns(0)
::User.any_instance.stubs(:obs_general_quota).returns(100)
::User.any_instance.stubs(:get_obs_snapshot_calls).returns(81)
::User.any_instance.stubs(:obs_snapshot_quota).returns(100)
overquota.should be_empty
overquota(0.20).should include(@user.id, @user2.id)
overquota(0.20).size.should == 2
overquota(0.10).should be_empty
end
it "should return users near their data observatory general quota" do
::User.any_instance.stubs(:get_api_calls).returns([0])
::User.any_instance.stubs(:map_view_quota).returns(120)
::User.any_instance.stubs(:get_geocoding_calls).returns(0)
::User.any_instance.stubs(:geocoding_quota).returns(100)
::User.any_instance.stubs(:get_here_isolines_calls).returns(0)
::User.any_instance.stubs(:here_isolines_quota).returns(100)
::User.any_instance.stubs(:get_obs_snapshot_calls).returns(0)
::User.any_instance.stubs(:obs_snapshot_quota).returns(100)
::User.any_instance.stubs(:get_obs_general_calls).returns(81)
::User.any_instance.stubs(:obs_general_quota).returns(100)
overquota.should be_empty
overquota(0.20).should include(@user.id, @user2.id)
overquota(0.20).size.should == 2
overquota(0.10).should be_empty
end
it "should return users near their twitter quota" do
::User.any_instance.stubs(:get_api_calls).returns([0])
::User.any_instance.stubs(:map_view_quota).returns(120)
::User.any_instance.stubs(:get_geocoding_calls).returns(0)
::User.any_instance.stubs(:geocoding_quota).returns(100)
::User.any_instance.stubs(:get_twitter_imports_count).returns(81)
::User.any_instance.stubs(:get_twitter_datasource_calls).returns(81)
::User.any_instance.stubs(:twitter_datasource_quota).returns(100)
overquota.should be_empty
overquota(0.20).should include(@user.id, @user2.id)
overquota(0.20).size.should == 2
overquota(0.10).should be_empty
end
it "should not return organization users" do
::User.any_instance.stubs(:organization_id).returns("organization-id")
::User.any_instance.stubs(:organization).returns(Organization.new)
overquota.should be_empty
end
end
@@ -0,0 +1,128 @@
require 'spec_helper_min'
require 'factories/carto_visualizations'
describe Carto::RedisExportService do
include Carto::Factories::Visualizations
before(:all) do
sequel_organization = FactoryGirl.create(:organization)
@organization = Carto::Organization.find(sequel_organization.id)
@user = FactoryGirl.create(:carto_user)
end
after(:all) do
@organization.destroy
@user.destroy
end
def with_redis_keys(prefix)
$users_metadata.set("#{prefix}:string", 'something')
$users_metadata.zincrby("#{prefix}:set", 5, 'set_key')
$users_metadata.hset("#{prefix}:hash", 'hash_key', 'qwerty')
yield
ensure
$users_metadata.del("#{prefix}:string", "#{prefix}:set", "#{prefix}:hash")
end
def with_visualization
map, table, table_visualization, visualization = create_full_visualization(@user)
map.save!
table.save!
table_visualization.save!
visualization.save!
yield(visualization)
ensure
map.destroy
table.destroy
table_visualization.destroy
visualization.destroy
end
def with_named_maps(visualization)
$tables_metadata.hset("map_tpl|#{visualization.user.username}", "tpl_#{ visualization.id.tr('-','_') }", { c: 'rand' } )
$tables_metadata.hset("map_tpl|#{visualization.user.username}", "custom_named", { c: 'custom' } )
yield
ensure
$tables_metadata.del("map_tpl|#{visualization.user.username}")
end
def check_export(export, prefix)
expect(export[:redis][:users_metadata].keys.sort).to eq(["#{prefix}:hash", "#{prefix}:set", "#{prefix}:string"])
end
def check_named_maps(export, visualization)
expect(export[:redis][:tables_metadata]["map_tpl|#{visualization.user.username}"].keys).to eq(['custom_named'])
expect(export[:redis][:tables_metadata]["map_tpl|#{visualization.user.username}"]['custom_named']).to eq(Base64.encode64("{:c=>\"custom\"}"))
end
def check_redis(prefix)
expect($users_metadata.get("#{prefix}:string")).to eq('something')
expect($users_metadata.zrange("#{prefix}:set", 0, -1, withscores: true)).to eq([['set_key', 5.0]])
expect($users_metadata.hgetall("#{prefix}:hash")).to eq('hash_key' => 'qwerty')
end
def check_redis_named_maps
expect($tables_metadata.hgetall("map_tpl|#{@user.username}")). to eq("custom_named" => "{:c=>\"custom\"}")
end
let(:service) { Carto::RedisExportService.new }
describe '#export' do
it 'exports an empty dictionary' do
export = service.export_user_json_hash(@user)
expect(export[:redis][:users_metadata]).to eq({})
expect(export[:redis][:tables_metadata]).to eq({})
end
it 'includes all keys under org:name for organizations' do
prefix = "org:#{@organization.name}"
with_redis_keys(prefix) do
export = service.export_organization_json_hash(@organization)
check_export(export, prefix)
end
end
it 'includes all keys under user:username for users' do
prefix = "user:#{@user.username}"
with_redis_keys(prefix) do
export = service.export_user_json_hash(@user)
check_export(export, prefix)
end
end
it 'includes non-cartodb-managed named maps' do
with_visualization do |visualization|
with_named_maps(visualization) do
export = service.export_user_json_hash(@user)
check_named_maps(export, visualization)
end
end
end
end
describe '#export + import' do
it 'copies all keys under org:name for organizations' do
prefix = "org:#{@organization.name}"
export = with_redis_keys(prefix) { service.export_organization_json_hash(@organization) }
service.restore_redis_from_hash_export(export)
check_redis(prefix)
end
it 'copies all keys under user:username for users' do
with_visualization do |visualization|
prefix = "user:#{@user.username}"
export = with_redis_keys(prefix) do
with_named_maps(visualization) do
service.export_user_json_hash(@user)
end
end
service.restore_redis_from_hash_export(export)
check_redis(prefix)
check_redis_named_maps
end
end
end
end
@@ -0,0 +1,83 @@
require 'spec_helper_min'
require 'carto/user_authenticator'
require 'cartodb-common'
describe Carto::UserAuthenticator do
include Carto::UserAuthenticator
before(:all) do
@user_password = 'admin123'
@user = FactoryGirl.create(:carto_user, password: @user_password)
end
after(:all) do
@user.delete
end
it "should authenticate if given email and password are correct" do
response_user = authenticate(@user.email, @user_password)
response_user.id.should eq @user.id
response_user.email.should eq @user.email
authenticate(@user.email, @user_password + 'no').should be_nil
authenticate('', '').should be_nil
end
it "should authenticate with case-insensitive email and username" do
response_user = authenticate(@user.email, @user_password)
response_user.id.should eq @user.id
response_user.email.should eq @user.email
response_user2 = authenticate(@user.email.upcase, @user_password)
response_user2.id.should eq @user.id
response_user2.email.should eq @user.email
response_user3 = authenticate(@user.username, @user_password)
response_user3.id.should eq @user.id
response_user3.email.should eq @user.email
response_user4 = authenticate(@user.username.upcase, @user_password)
response_user4.id.should eq @user.id
response_user4.email.should eq @user.email
end
context "password reencryption" do
after(:each) do
@user.password = @user_password
@user.save
end
it "reencrypts the password if it is correct and not saved with argon2" do
::User.any_instance.stubs(:update_in_central).returns(true)
@user.crypted_password = Carto::Common::EncryptionService.encrypt(password: @user_password,
sha_class: Digest::SHA1)
@user.save
@user.crypted_password.length.should eql 40
authenticate(@user.email, @user_password)
@user.reload.crypted_password.should =~ /^\$argon2/
end
it "does not reencrypt the password if the password is not correct" do
@user.crypted_password = Carto::Common::EncryptionService.encrypt(password: @user_password,
sha_class: Digest::SHA1)
@user.save
initial_crypted_password = @user.crypted_password
initial_crypted_password.length.should eql 40
authenticate(@user.email, "wrong pass")
@user.reload.crypted_password.should eql initial_crypted_password
end
it "does not reencrypt the password if it was already with argon2" do
initial_crypted_password = @user.crypted_password
initial_crypted_password.should =~ /^\$argon2/
authenticate(@user.email, @user_password)
@user.reload.crypted_password.should eql initial_crypted_password
end
end
end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,47 @@
require 'spec_helper_min'
describe Carto::UserMultifactorAuthUpdateService do
before(:all) do
@user = FactoryGirl.create(:carto_user)
@service = Carto::UserMultifactorAuthUpdateService.new(user_id: @user.id)
end
after(:all) do
@user.destroy
end
describe '#update' do
after(:each) do
@user.user_multifactor_auths.each(&:destroy)
end
context 'with enabled = true' do
it 'creates a totp multifactor auth for the user' do
expect { @service.update(enabled: true) }.to change { @user.user_multifactor_auths.count }.by(1)
@user.user_multifactor_auths.first.type.should eql 'totp'
end
it 'does nothing if the user already has one multifactor auth' do
@service.update(enabled: true)
expect { @service.update(enabled: true) }.to change { @user.user_multifactor_auths.count }.by(0)
end
end
context 'with enabled = false' do
it 'removes all the multifactor auths of the user' do
@service.update(enabled: true)
@user.reload.user_multifactor_auths.should_not be_empty
@service.update(enabled: false)
@user.reload.user_multifactor_auths.should be_empty
end
it 'does nothing if the user does not have multifactor auths' do
expect { @service.update(enabled: false) }.to change { @user.user_multifactor_auths.count }.by(0)
end
end
end
end
@@ -0,0 +1,309 @@
require 'spec_helper_min'
describe Carto::UserTableIndexService do
include NamedMapsHelper
include Carto::Factories::Visualizations
before(:all) do
bypass_named_maps
@user = FactoryGirl.create(:carto_user)
@map1, @table1, @table_visualization1, @visualization1 = create_full_visualization(@user)
@map2, @table2, @table_visualization2, @visualization2 = create_full_visualization(@user)
@map12 = FactoryGirl.create(:carto_map, user_id: @user.id)
FactoryGirl.create(:carto_tiled_layer, maps: [@map12])
FactoryGirl.create(:carto_layer_with_sql, maps: [@map12], table_name: @table1.name)
FactoryGirl.create(:carto_layer_with_sql, maps: [@map12], table_name: @table2.name)
@map12.reload
@visualization12 = FactoryGirl.create(:carto_visualization, user: @user, map: @map12)
# Register table dependencies
[@map1, @map2, @map12].each do |map|
map.data_layers.each do |layer|
::Layer[layer.id].register_table_dependencies
end
end
# Create analyses
@analysis1 = FactoryGirl.create(:source_analysis,
visualization: @visualization1,
user: @user,
source_table: @table1.name)
@analysis2 = FactoryGirl.create(:analysis_with_source,
visualization: @visualization2,
user: @user,
source_table: @table2.name)
@analysis12_1 = FactoryGirl.create(:source_analysis,
visualization: @visualization12,
user: @user,
source_table: @table1.name)
@analysis12_2 = FactoryGirl.create(:source_analysis,
visualization: @visualization12,
user: @user,
source_table: @table2.name)
end
after(:all) do
bypass_named_maps
@visualization12.destroy if @visualization12
@map12.destroy if @map12
destroy_full_visualization(@map2, @table2, @table_visualization2, @visualization2)
destroy_full_visualization(@map1, @table1, @table_visualization1, @visualization1)
# This avoids connection leaking.
::User[@user.id].destroy
end
describe '#table_widgets' do
before(:all) do
@widget1 = create_widget(@analysis1)
@widget2_analysis = create_widget(@analysis2)
@widget2_source = create_widget(@analysis2, child: true)
@widget12_1 = create_widget(@analysis12_1)
@widget12_2 = create_widget(@analysis12_2)
end
after(:all) do
Carto::Widget.all.map(&:destroy)
end
it 'retrieves all widgets related to the table' do
service = Carto::UserTableIndexService.new(@table1)
widgets = service.send(:table_widgets)
widgets.sort.should eq [@widget1, @widget12_1].sort
end
it 'does not retrieve widgets that operate on an analysis' do
service = Carto::UserTableIndexService.new(@table2)
widgets = service.send(:table_widgets)
widgets.sort.should eq [@widget2_source, @widget12_2].sort
widgets.should_not include @widget2_analysis
end
end
describe '#generate_indices' do
before(:each) do
bypass_named_maps
Carto::Widget.all.map(&:destroy)
@table1.reload
Carto::UserTableIndexService.any_instance.stubs(:indexable_column?).returns(true)
end
it 'creates indices for all widgets' do
@table1.service.stubs(:pg_indexes).returns([])
@table1.service.stubs(:estimated_row_count).returns(100000)
create_widget(@analysis1, column: 'number')
create_widget(@analysis12_1, column: 'date', type: 'time-series')
stub_create_index('number').once
stub_create_index('date').once
Carto::UserTableIndexService.new(@table1).send(:generate_indices)
end
it 'does not create indices for small tables' do
@table1.service.stubs(:pg_indexes).returns([])
@table1.service.stubs(:estimated_row_count).returns(100)
create_widget(@analysis1, column: 'number')
create_widget(@analysis12_1, column: 'date', type: 'time-series')
stub_create_index('number').never
stub_create_index('date').never
Carto::UserTableIndexService.new(@table1).send(:generate_indices)
end
it 'does not create indices for formula widgets' do
@table1.service.stubs(:pg_indexes).returns([])
@table1.service.stubs(:estimated_row_count).returns(100000)
create_widget(@analysis1, column: 'number', type: 'formula')
stub_create_index('number').never
Carto::UserTableIndexService.new(@table1).send(:generate_indices)
end
it 'does not create indices for indexed columns' do
@table1.service.stubs(:pg_indexes).returns([{ name: 'wadus', column: 'number', valid: true }])
@table1.service.stubs(:estimated_row_count).returns(100000)
create_widget(@analysis1, column: 'number')
create_widget(@analysis12_1, column: 'date', type: 'time-series')
stub_create_index('number').never
stub_create_index('date').once
Carto::UserTableIndexService.new(@table1).send(:generate_indices)
end
it 'does not create indices for indexed columns (in multi-column indexes)' do
@table1.service.stubs(:pg_indexes).returns([{ name: 'idx', column: 'number', valid: true },
{ name: 'idx', column: 'date', valid: true }])
@table1.service.stubs(:estimated_row_count).returns(100000)
create_widget(@analysis1, column: 'number')
create_widget(@analysis12_1, column: 'date', type: 'time-series')
stub_create_index('number').never
stub_create_index('date').never
Carto::UserTableIndexService.new(@table1).send(:generate_indices)
end
it 'drops unneeded indices' do
@table1.service.stubs(:pg_indexes).returns([automatic_index_record(@table1, 'number')])
@table1.service.stubs(:estimated_row_count).returns(100000)
stub_drop_index('number').once
Carto::UserTableIndexService.new(@table1).send(:generate_indices)
end
it 'drops indices for small tables' do
@table1.service.stubs(:pg_indexes).returns([automatic_index_record(@table1, 'number')])
@table1.service.stubs(:estimated_row_count).returns(100)
create_widget(@analysis1, column: 'number')
stub_drop_index('number').once
Carto::UserTableIndexService.new(@table1).send(:generate_indices)
end
it 'does not drop manual indices' do
@table1.service.stubs(:pg_indexes).returns([{ name: 'idx', column: 'number', valid: true },
{ name: 'idx', column: 'date', valid: false }])
@table1.service.stubs(:estimated_row_count).returns(100)
create_widget(@analysis1, column: 'number')
@table1.service.stubs(:drop_index).never
Carto::UserTableIndexService.new(@table1).send(:generate_indices)
end
it 'drops and recreates invalid auto indices' do
@table1.service.stubs(:pg_indexes).returns([automatic_index_record(@table1, 'number', valid: false)])
@table1.service.stubs(:estimated_row_count).returns(100000)
create_widget(@analysis1, column: 'number')
stub_drop_index('number').once
stub_create_index('number').once
Carto::UserTableIndexService.new(@table1).send(:generate_indices)
end
end
describe '#indexable_column?' do
before(:all) do
@service = Carto::UserTableIndexService.new(@table1)
end
it 'returns true for a random numbers column' do
@service.stubs(:pg_stats_by_column).returns(
'col' => {
null_frac: 0,
n_distinct: -1,
most_common_vals: [],
most_common_freqs: [],
histogram_bounds: [4, 10, 20, 50, 100],
correlation: 0.4
}
)
@service.send(:indexable_column?, 'col').should be_true
end
it 'returns true for a column with several categories' do
@service.stubs(:pg_stats_by_column).returns(
'col' => {
null_frac: 0,
n_distinct: 20,
most_common_vals: ['a', 'b', 'c', 'd', 'e', 'f'],
most_common_freqs: [0.2, 0.1, 0.1, 0.05, 0.05, 0.02],
histogram_bounds: ['a', 'c', 'e', 'f'],
correlation: 0.4
}
)
@service.send(:indexable_column?, 'col').should be_true
end
it 'returns true for a column with several and no histogram' do
@service.stubs(:pg_stats_by_column).returns(
'col' => {
null_frac: 0,
n_distinct: 20,
most_common_vals: nil,
most_common_freqs: nil,
histogram_bounds: nil,
correlation: 0.4
}
)
@service.send(:indexable_column?, 'col').should be_true
end
it 'returns false for a balance boolean column' do
@service.stubs(:pg_stats_by_column).returns(
'col' => {
null_frac: 0,
n_distinct: 2,
most_common_vals: [true, false],
most_common_freqs: [0.51, 0.49],
histogram_bounds: nil,
correlation: 0.502
}
)
@service.send(:indexable_column?, 'col').should be_false
end
it 'returns true for an unbalance boolean column' do
@service.stubs(:pg_stats_by_column).returns(
'col' => {
null_frac: 0,
n_distinct: 2,
most_common_vals: [true, false],
most_common_freqs: [0.89, 0.11],
histogram_bounds: nil,
correlation: 0.602
}
)
@service.send(:indexable_column?, 'col').should be_true
end
it 'returns true for a boolean column used as cluster (table sorted by it)' do
@service.stubs(:pg_stats_by_column).returns(
'col' => {
null_frac: 0,
n_distinct: 2,
most_common_vals: [true, false],
most_common_freqs: [0.51, 0.49],
histogram_bounds: nil,
correlation: -1
}
)
@service.send(:indexable_column?, 'col').should be_true
end
it 'returns false if no stats can be gathered' do
@service.stubs(:pg_stats_by_column).returns({})
@service.send(:indexable_column?, 'col').should be_false
end
end
private
def automatic_index_record(table, column, valid: true)
{
name: table.service.send(:index_name, column, Carto::UserTableIndexService::AUTO_INDEX_PREFIX),
column: column,
valid: valid
}
end
def stub_create_index(column)
@table1.service.stubs(:create_index).with(column, Carto::UserTableIndexService::AUTO_INDEX_PREFIX, concurrent: true)
end
def stub_drop_index(column)
@table1.service.stubs(:drop_index).with(column, Carto::UserTableIndexService::AUTO_INDEX_PREFIX, concurrent: true)
end
def create_widget(analysis, child: false, column: 'col', type: 'histogram')
root_node = analysis.analysis_node
child_node = root_node.children.first
widget_node = child ? child_node : root_node
# Locate the layer corresponding to this analysis (matches visualization and table name from source node)
source_node = child_node || root_node
layer = analysis.visualization.data_layers.find do |l|
l.user_tables.any? { |t| t.name == source_node.options[:table_name] }
end
FactoryGirl.create(:widget, layer: layer, source_id: widget_node.id, column_name: column, type: type)
end
end
@@ -0,0 +1,112 @@
require 'spec_helper_min'
describe Carto::VisualizationBackupService do
include Carto::Factories::Visualizations
include Carto::VisualizationBackupService
include_context 'user helper'
describe '#create_visualization_backup' do
before(:all) do
@map = FactoryGirl.create(:carto_map_with_layers, user: @carto_user)
end
before(:each) do
Carto::VisualizationBackup.all.map(&:destroy)
end
after(:each) do
Carto::VisualizationBackup.all.map(&:destroy)
end
after(:all) do
@map.destroy
end
it 'creates a backup with create_visualization_backup' do
visualization = FactoryGirl.create(:carto_visualization, user: @carto_user, map: @map)
Carto::VisualizationBackup.all.count.should eq 0
create_visualization_backup(
visualization: visualization,
category: Carto::VisualizationBackup::CATEGORY_VISUALIZATION,
with_mapcaps: true,
with_password: true
)
Carto::VisualizationBackup.all.count.should eq 1
backup = Carto::VisualizationBackup.where(visualization_id: visualization.id).first
backup.should_not eq nil
backup.user_id.should eq @carto_user.id
backup.created_at.should_not eq nil
backup.category.should eq Carto::VisualizationBackup::CATEGORY_VISUALIZATION
backup.export.should_not be_empty
backup.destroy
visualization.destroy
end
it 'fails backup creation without visualization but not raises' do
create_visualization_backup(
visualization: nil,
category: Carto::VisualizationBackup::CATEGORY_VISUALIZATION,
with_mapcaps: true,
with_password: true
)
Carto::VisualizationBackup.all.count.should eq 0
end
it 'fails backup creation without category but not raises' do
visualization = FactoryGirl.create(:carto_visualization, user: @carto_user, map: @map)
create_visualization_backup(
visualization: visualization,
category: nil,
with_mapcaps: true,
with_password: true
)
Carto::VisualizationBackup.all.count.should eq 0
visualization.destroy
end
end
describe '#create_visualization_backup' do
before(:all) do
@map = FactoryGirl.create(:carto_map_with_layers, user: @carto_user)
Carto::VisualizationBackup.all.map(&:destroy)
@visualization = FactoryGirl.create(:carto_visualization, user: @carto_user, map: @map)
@visualization.destroy # creates a Visualization backup
end
after(:all) do
@map.destroy
Carto::VisualizationBackup.all.map(&:destroy)
end
it 'restores a visualization backup' do
Carto::Visualization.where(user: @carto_user).count.should eq 0
backup = Carto::VisualizationBackup.first
visualization = restore_visualization_backup(backup.id)
visualization.should_not eq nil
visualization.id.should eq @visualization.id
visualization.name.should eq @visualization.name
visualization.type.should eq @visualization.type
Carto::Visualization.where(user: @carto_user).count.should eq 1
end
it 'throws if the visualization already exists' do
backup = Carto::VisualizationBackup.first
restore_visualization_backup(backup.id)
expect {
restore_visualization_backup(backup.id)
}.to raise_error('A visualization with the same id as the backup one already exists')
end
end
end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,199 @@
require_relative '../../spec_helper_min'
describe CartoDB::Visualization::CommonDataService do
before(:all) do
@user = FactoryGirl.create(:valid_user)
end
after(:all) do
@user.destroy
end
before(:each) do
remote_visualizations(@user).each(&:destroy)
end
def service
CartoDB::Visualization::CommonDataService.new
end
def remote_visualizations(user)
Carto::Visualization.where(user_id: user.id, type: Carto::Visualization::TYPE_REMOTE)
end
def dataset(name, description: 'A very creative description')
{
name: name,
description: description,
tags: ['Awesome', 'Data'],
license: '',
source: 'Myself',
attributions: 'Be nice to each other',
display_name: name,
url: 'http://example.org',
geometry_types: '{ST_MultiPolygon}',
rows: 1000,
size: 1000000
}.stringify_keys
end
def stub_datasets(datasets)
CommonDataSingleton.instance.stubs(:datasets).returns(datasets)
end
it 'should import common data datasets' do
stub_datasets([dataset('ds1')])
added, updated, not_modified, deleted, failed = service.load_common_data_for_user(@user, 'some_url')
expect(added).to eq 1
expect(updated).to eq 0
expect(not_modified).to eq 0
expect(deleted).to eq 0
expect(failed).to eq 0
expect(remote_visualizations(@user).count).to eq 1
remote_visualization = remote_visualizations(@user).first
expect(remote_visualization.name).to eq 'ds1'
expect(remote_visualization.external_source.geometry_types).to eq ["ST_MultiPolygon"]
end
it 'should import common data datasets within an ActiveRecord transaction (see #12488)' do
# This would trigger an exception because of data integrity
expect {
ActiveRecord::Base.transaction do
stub_datasets([dataset('ds1')])
service.load_common_data_for_user(@user, 'some_url')
end
}.not_to raise_error
end
it 'should update common data datasets' do
stub_datasets([dataset('ds1')])
service.load_common_data_for_user(@user, 'some_url')
stub_datasets([dataset('ds1', description: 'desc')])
added, updated, not_modified, deleted, failed = service.load_common_data_for_user(@user, 'some_url')
expect(added).to eq 0
expect(updated).to eq 1
expect(not_modified).to eq 0
expect(deleted).to eq 0
expect(failed).to eq 0
expect(remote_visualizations(@user).count).to eq 1
expect(remote_visualizations(@user).first.name).to eq 'ds1'
expect(remote_visualizations(@user).first.description).to eq 'desc'
end
it 'should not touch unmodified common data datasets' do
stub_datasets([dataset('ds1')])
service.load_common_data_for_user(@user, 'some_url')
added, updated, not_modified, deleted, failed = service.load_common_data_for_user(@user, 'some_url')
expect(added).to eq 0
expect(updated).to eq 0
expect(not_modified).to eq 1
expect(deleted).to eq 0
expect(failed).to eq 0
expect(remote_visualizations(@user).count).to eq 1
expect(remote_visualizations(@user).first.name).to eq 'ds1'
end
it 'should delete removed common data datasets' do
stub_datasets([dataset('ds1'), dataset('ds2')])
service.load_common_data_for_user(@user, 'some_url')
expect(remote_visualizations(@user).count).to eq 2
Carto::ExternalSource.count.should eq 2
stub_datasets([dataset('ds1')])
added, updated, not_modified, deleted, failed = service.load_common_data_for_user(@user, 'some_url')
expect(added).to eq 0
expect(updated).to eq 0
expect(not_modified).to eq 1
expect(deleted).to eq 1
expect(failed).to eq 0
Carto::ExternalSource.count.should eq 1
expect(remote_visualizations(@user).count).to eq 1
expect(remote_visualizations(@user).first.name).to eq 'ds1'
end
it 'should not delete anything if common data datasets fetching fails or it\'s empty' do
stub_datasets([dataset('ds1'), dataset('ds2')])
service.load_common_data_for_user(@user, 'some_url')
expect(remote_visualizations(@user).count).to eq 2
Carto::ExternalSource.count.should eq 2
stub_datasets([])
service.load_common_data_for_user(@user, 'some_url').should be_nil
Carto::ExternalSource.count.should eq 2
expect(remote_visualizations(@user).count).to eq 2
common_data_singleton_mock = mock
common_data_singleton_mock.stubs(:datasets).raises("error!")
CommonDataSingleton.stubs(:instance).returns(common_data_singleton_mock)
service.load_common_data_for_user(@user, 'some_url').should be_nil
Carto::ExternalSource.count.should eq 2
expect(remote_visualizations(@user).count).to eq 2
end
it 'should fail when missing some fields, but still import the rest' do
stub_datasets([dataset('ds1'), {}])
added, updated, not_modified, deleted, failed = service.load_common_data_for_user(@user, 'some_url')
expect(added).to eq 1
expect(updated).to eq 0
expect(not_modified).to eq 0
expect(deleted).to eq 0
expect(failed).to eq 1
expect(remote_visualizations(@user).count).to eq 1
expect(remote_visualizations(@user).first.name).to eq 'ds1'
end
describe 'destroying common data remote visualizations' do
it 'delete remote visualizations that have been imported but keep the import' do
viz = FactoryGirl.create(:carto_table_visualization, user_id: @user.id)
data_import = FactoryGirl.create(:data_import, user_id: @user.id, visualization_id: viz.id)
external_data_import = FactoryGirl.create(:external_data_import_with_external_source, data_import: data_import)
visualization = external_data_import.external_source.visualization
service.send(:delete_remote_visualization, visualization).should be_true
Carto::Visualization.where(id: visualization.id).should be_empty
Carto::ExternalSource.where(id: external_data_import.external_source.id).should be_empty
Carto::ExternalDataImport.where(id: external_data_import.id).should be_empty
Carto::DataImport.where(id: data_import.id).first.should_not be_nil
Carto::Visualization.where(id: viz.id).first.should_not be_nil
end
it 'delete remote visualizations that have been synced and the sync but keep the import' do
viz = FactoryGirl.create(:carto_table_visualization, user_id: @user.id)
data_import = FactoryGirl.create(:data_import, user_id: @user.id, visualization_id: viz.id)
sync = FactoryGirl.create(:carto_synchronization, user_id: @user.id, visualization_id: viz.id)
external_data_import = FactoryGirl.create(:external_data_import_with_external_source,
data_import: data_import,
synchronization: sync)
visualization = external_data_import.external_source.visualization
service.send(:delete_remote_visualization, visualization).should be_true
Carto::Visualization.where(id: visualization.id).should be_empty
Carto::ExternalSource.where(id: external_data_import.external_source.id).should be_empty
Carto::ExternalDataImport.where(id: external_data_import.id).should be_empty
Carto::Synchronization.where(id: sync.id).should be_empty
Carto::DataImport.where(id: data_import.id).first.should_not be_nil
Carto::Visualization.where(id: viz.id).first.should_not be_nil
end
end
end