Initial commit
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user