304 lines
12 KiB
Ruby
304 lines
12 KiB
Ruby
|
require_relative '../../../spec/spec_helper.rb'
|
||
|
require_relative '../import_user'
|
||
|
require_relative '../export_user'
|
||
|
require_relative '../../../lib/carto/ghost_tables_manager'
|
||
|
|
||
|
RSpec.configure do |c|
|
||
|
c.include Helpers
|
||
|
end
|
||
|
describe CartoDB::DataMover::ExportJob do
|
||
|
def stub_api_keys
|
||
|
CartoDB::DataMover::ImportJob.any_instance.stubs(:create_org_api_key_roles)
|
||
|
CartoDB::DataMover::ImportJob.any_instance.stubs(:create_user_api_key_roles)
|
||
|
CartoDB::DataMover::ImportJob.any_instance.stubs(:grant_org_api_key_roles)
|
||
|
CartoDB::DataMover::ImportJob.any_instance.stubs(:grant_user_api_key_roles)
|
||
|
CartoDB::DataMover::ImportJob.any_instance.stubs(:create_user_oauth_app_user_roles)
|
||
|
CartoDB::DataMover::ImportJob.any_instance.stubs(:grant_user_oauth_app_user_roles)
|
||
|
end
|
||
|
|
||
|
before :each do
|
||
|
bypass_named_maps
|
||
|
@tmp_path = Dir.mktmpdir("mover-test") + '/'
|
||
|
end
|
||
|
|
||
|
let(:first_user) do
|
||
|
create_user(
|
||
|
quota_in_bytes: 100.megabyte,
|
||
|
private_tables_enabled: true,
|
||
|
database_timeout: 123450,
|
||
|
user_timeout: 456780,
|
||
|
table_quota: nil
|
||
|
)
|
||
|
end
|
||
|
|
||
|
shared_examples "a migrated user" do
|
||
|
it "has expected statement_timeout" do
|
||
|
expect(subject.in_database['SHOW statement_timeout'].to_a[0][:statement_timeout]).to eq("456780ms")
|
||
|
expect(subject.in_database(as: :public_db_user)['SHOW statement_timeout']
|
||
|
.to_a[0][:statement_timeout]).to eq("123450ms")
|
||
|
end
|
||
|
|
||
|
it "keeps proper table privacy" do
|
||
|
check_tables(subject)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe "a migrated user" do
|
||
|
before(:each) do
|
||
|
stub_api_keys
|
||
|
end
|
||
|
|
||
|
subject do
|
||
|
create_tables(first_user)
|
||
|
first_user.save
|
||
|
first_user.reload
|
||
|
carto_user = Carto::User.find(first_user.id)
|
||
|
Carto::UserNotification.create!(user: carto_user, notifications: { builder: { onboarding: true } })
|
||
|
move_user(first_user)
|
||
|
end
|
||
|
|
||
|
it_behaves_like "a migrated user"
|
||
|
it "matches old and new user" do
|
||
|
expect((first_user.as_json.reject { |x| [:updated_at, :database_host].include?(x.to_sym) }))
|
||
|
.to eq((subject.as_json.reject { |x| [:updated_at, :database_host].include?(x.to_sym) }))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe "a standalone user which has moved to an organization" do
|
||
|
before(:all) do
|
||
|
@org = create_user_mover_test_organization
|
||
|
end
|
||
|
|
||
|
subject do
|
||
|
create_tables(first_user)
|
||
|
first_user.db_service.move_to_own_schema
|
||
|
|
||
|
CartoDB::DataMover::ExportJob.new(id: first_user.username, path: @tmp_path, schema_mode: true)
|
||
|
CartoDB::UserModule::DBService.terminate_database_connections(first_user.database_name, first_user.database_host)
|
||
|
CartoDB::DataMover::ImportJob.new(
|
||
|
file: @tmp_path + "user_#{first_user.id}.json", mode: :rollback, drop_database: true, drop_roles: true).run!
|
||
|
CartoDB::DataMover::ImportJob.new(
|
||
|
file: @tmp_path + "user_#{first_user.id}.json", mode: :import, host: '127.0.0.2', target_org: @org.name).run!
|
||
|
|
||
|
moved_user = ::User[first_user.id]
|
||
|
moved_user.reload # Clean user sequel cache
|
||
|
|
||
|
Carto::GhostTablesManager.new(moved_user.id).link_ghost_tables_synchronously
|
||
|
moved_user
|
||
|
end
|
||
|
|
||
|
it_behaves_like "a migrated user"
|
||
|
|
||
|
it "matches old and new user except database_name and database_host" do
|
||
|
IGNORED_KEYS = [:updated_at, :database_name, :database_host, :organization_id, :database_schema].freeze
|
||
|
expect(first_user.as_json.reject { |x| IGNORED_KEYS.include? x.to_sym })
|
||
|
.to eq(subject.as_json.reject { |x| IGNORED_KEYS.include? x.to_sym })
|
||
|
expect(subject.database_name).to eq(@org.owner.database_name)
|
||
|
expect(subject.database_schema).to eq(subject.username)
|
||
|
expect(subject.database_host).to eq('127.0.0.2')
|
||
|
expect(subject.organization_id).to eq(@org.id)
|
||
|
end
|
||
|
|
||
|
it "has granted the org role" do
|
||
|
authed_roles = subject
|
||
|
.in_database["select s.rolname from pg_roles r
|
||
|
join pg_catalog.pg_auth_members m on r.oid=m.member join pg_catalog.pg_roles
|
||
|
s on m.roleid=s.oid where r.rolname='#{subject.database_username}'"].to_a
|
||
|
authed_roles.should be_any { |m| m[:rolname] == subject.db_service.organization_member_group_role_member_name }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it "should move a user from an organization to its own account" do
|
||
|
org = create_user_mover_test_organization
|
||
|
user = create_user(
|
||
|
quota_in_bytes: 100.megabyte, table_quota: 400, organization: org, account_type: 'ORGANIZATION USER'
|
||
|
)
|
||
|
org.reload
|
||
|
create_tables(user)
|
||
|
|
||
|
CartoDB::DataMover::ExportJob.new(id: user.username, path: @tmp_path, schema_mode: true)
|
||
|
CartoDB::UserModule::DBService.terminate_database_connections(user.database_name, user.database_host)
|
||
|
CartoDB::DataMover::ImportJob.new(file: @tmp_path + "user_#{user.id}.json", mode: :rollback).run!
|
||
|
CartoDB::DataMover::ImportJob.new(file: @tmp_path + "user_#{user.id}.json", mode: :import, host: '127.0.0.2').run!
|
||
|
|
||
|
moved_user = ::User.find(username: user.username)
|
||
|
moved_user.reload
|
||
|
Carto::GhostTablesManager.new(moved_user.id).link_ghost_tables_synchronously
|
||
|
check_tables(moved_user)
|
||
|
moved_user.organization_id.should eq nil
|
||
|
end
|
||
|
|
||
|
# Skipping this test (TODO: fix it)
|
||
|
xit "should move a whole organization" do
|
||
|
port = find_available_port
|
||
|
run_test_server(port)
|
||
|
|
||
|
test_config = Cartodb.get_config(:org_metadata_api).merge('host' => '127.0.0.1', 'port' => port)
|
||
|
Cartodb.with_config(org_metadata_api: test_config) do
|
||
|
org = create_user_mover_test_organization
|
||
|
user = create_user(
|
||
|
quota_in_bytes: 100.megabyte, table_quota: 400, organization: org, account_type: 'ORGANIZATION USER'
|
||
|
)
|
||
|
user.save
|
||
|
org.reload
|
||
|
|
||
|
create_tables(org.owner)
|
||
|
share_tables(org.owner, user)
|
||
|
share_tables(user, org.owner)
|
||
|
create_tables(user)
|
||
|
|
||
|
carto_org = Carto::Organization.find(org.id)
|
||
|
group_1 = carto_org.create_group(String.random(5))
|
||
|
group_2 = carto_org.create_group(String.random(5))
|
||
|
|
||
|
group_1.add_user(org.owner.username)
|
||
|
group_2.add_user(org.owner.username)
|
||
|
group_1.add_user(user.username)
|
||
|
group_2.add_user(user.username)
|
||
|
|
||
|
share_group_tables(org.owner, group_1)
|
||
|
share_group_tables(user, group_2)
|
||
|
|
||
|
CartoDB::DataMover::ExportJob.new(organization_name: org.name, path: @tmp_path, split_user_schemas: false)
|
||
|
CartoDB::UserModule::DBService.terminate_database_connections(org.owner.database_name, org.owner.database_host)
|
||
|
CartoDB::DataMover::ImportJob.new(file: @tmp_path + "org_#{org.id}.json", mode: :rollback, drop_database: true, drop_roles: true).run!
|
||
|
CartoDB::DataMover::ImportJob.new(file: @tmp_path + "org_#{org.id}.json", mode: :import, host: '127.0.0.2').run!
|
||
|
|
||
|
moved_user = ::User.find(username: user.username)
|
||
|
moved_user.reload
|
||
|
moved_user.database_host.should eq '127.0.0.2'
|
||
|
Carto::GhostTablesManager.new(moved_user.id).link_ghost_tables_synchronously
|
||
|
check_tables(moved_user)
|
||
|
moved_user.in_database['SELECT * FROM cdb_tablemetadata']
|
||
|
moved_user.organization_id.should_not eq nil
|
||
|
end
|
||
|
|
||
|
@server_thread.terminate
|
||
|
end
|
||
|
|
||
|
it "should move a whole organization without splitting user schemas" do
|
||
|
|
||
|
org = create_user_mover_test_organization
|
||
|
user = create_user(
|
||
|
quota_in_bytes: 100.megabyte, table_quota: 400, organization: org, account_type: 'ORGANIZATION USER'
|
||
|
)
|
||
|
user.save
|
||
|
org.reload
|
||
|
|
||
|
create_tables(org.owner)
|
||
|
share_tables(org.owner, user)
|
||
|
share_tables(user, org.owner)
|
||
|
create_tables(user)
|
||
|
|
||
|
CartoDB::DataMover::ExportJob.new(organization_name: org.name, path: @tmp_path)
|
||
|
CartoDB::UserModule::DBService.terminate_database_connections(org.owner.database_name, org.owner.database_host)
|
||
|
CartoDB::DataMover::ImportJob.new(file: @tmp_path + "org_#{org.id}.json", mode: :rollback, drop_database: true, drop_roles: true).run!
|
||
|
CartoDB::DataMover::ImportJob.new(file: @tmp_path + "org_#{org.id}.json", mode: :import, host: '127.0.0.2').run!
|
||
|
|
||
|
moved_user = ::User.find(username: user.username)
|
||
|
moved_user.reload
|
||
|
moved_user.database_host.should eq '127.0.0.2'
|
||
|
Carto::GhostTablesManager.new(moved_user.id).link_ghost_tables_synchronously
|
||
|
check_tables(moved_user)
|
||
|
moved_user.in_database['SELECT * FROM cdb_tablemetadata']
|
||
|
moved_user.organization_id.should_not eq nil
|
||
|
|
||
|
end
|
||
|
|
||
|
it "should not touch an user metadata nor update its oids when update_metadata is not set" do
|
||
|
stub_api_keys
|
||
|
user = create_user(
|
||
|
quota_in_bytes: 100.megabyte, table_quota: 50, private_tables_enabled: true, sync_tables_enabled: true
|
||
|
)
|
||
|
create_tables(user)
|
||
|
|
||
|
old_user_data = user.as_json
|
||
|
old_user_tables = user.real_tables
|
||
|
old_user_oids = user.tables.map(&:table_id).sort
|
||
|
CartoDB::DataMover::ExportJob.new(id: user.username, path: @tmp_path)
|
||
|
CartoDB::UserModule::DBService.terminate_database_connections(user.database_name, user.database_host)
|
||
|
CartoDB::DataMover::ImportJob.new(
|
||
|
file: @tmp_path + "user_#{user.id}.json", mode: :rollback, host: '127.0.0.2',
|
||
|
drop_database: true, drop_roles: true).run!
|
||
|
CartoDB::DataMover::ImportJob.new(file: @tmp_path + "user_#{user.id}.json", mode: :import, host: '127.0.0.2',
|
||
|
update_metadata: false).run!
|
||
|
|
||
|
new_user = ::User.find(username: user.username)
|
||
|
|
||
|
new_user.as_json.should eq old_user_data
|
||
|
new_user.real_tables.should_not eq old_user_tables
|
||
|
new_user.tables.map(&:table_id).sort.should eq old_user_oids
|
||
|
end
|
||
|
end
|
||
|
|
||
|
module Helpers
|
||
|
def move_user(user)
|
||
|
CartoDB::DataMover::ExportJob.new(id: user.username, path: @tmp_path)
|
||
|
CartoDB::UserModule::DBService.terminate_database_connections(user.database_name, user.database_host)
|
||
|
CartoDB::DataMover::ImportJob.new(
|
||
|
file: @tmp_path + "user_#{user.id}.json", mode: :rollback,
|
||
|
drop_database: true, drop_roles: true).run!
|
||
|
CartoDB::DataMover::ImportJob.new(file: @tmp_path + "user_#{user.id}.json", mode: :import, host: '127.0.0.2').run!
|
||
|
::User.find(username: user.username)
|
||
|
end
|
||
|
|
||
|
def create_tables(user)
|
||
|
create_table(user_id: user.id, name: "with_link_table", privacy: UserTable::PRIVACY_LINK)
|
||
|
create_table(user_id: user.id, name: "public_table", privacy: UserTable::PRIVACY_PUBLIC)
|
||
|
create_table(user_id: user.id, name: "private_table", privacy: UserTable::PRIVACY_PRIVATE)
|
||
|
end
|
||
|
|
||
|
def share_tables(user1, user2)
|
||
|
table_ro = create_table(user_id: user1.id, name: "shared_ro_by_#{user1.username}_to_#{user2.id}",
|
||
|
privacy: UserTable::PRIVACY_PRIVATE)
|
||
|
give_permission(table_ro.table_visualization, user2, CartoDB::Permission::ACCESS_READONLY)
|
||
|
table_rw = create_table(user_id: user1.id, name: "shared_rw_by_#{user1.username}_to_#{user2.id}",
|
||
|
privacy: UserTable::PRIVACY_PRIVATE)
|
||
|
give_permission(table_rw.table_visualization, user2, CartoDB::Permission::ACCESS_READWRITE)
|
||
|
end
|
||
|
|
||
|
def share_group_tables(user, group)
|
||
|
table_ro = create_table(user_id: user.id, name: "shared_ro_by_#{user.username}_to_#{group.name}", privacy: UserTable::PRIVACY_PRIVATE)
|
||
|
group.grant_db_permission(table_ro, CartoDB::Permission::ACCESS_READONLY)
|
||
|
table_rw = create_table(user_id: user.id, name: "shared_rw_by_#{user.username}_to_#{group.name}", privacy: UserTable::PRIVACY_PRIVATE)
|
||
|
group.grant_db_permission(table_rw, CartoDB::Permission::ACCESS_READWRITE)
|
||
|
end
|
||
|
|
||
|
def check_tables(moved_user)
|
||
|
Table.new(user_table: moved_user.tables.where(name: "private_table").first).privacy.should eq UserTable::PRIVACY_PRIVATE
|
||
|
Table.new(user_table: moved_user.tables.where(name: "public_table").first).privacy.should eq UserTable::PRIVACY_PUBLIC
|
||
|
Table.new(user_table: moved_user.tables.where(name: "with_link_table").first).privacy.should eq UserTable::PRIVACY_LINK
|
||
|
end
|
||
|
|
||
|
def create_user_mover_test_organization
|
||
|
org = create_organization(name: String.random(5).downcase, quota_in_bytes: 2500.megabytes)
|
||
|
|
||
|
owner = create_user(quota_in_bytes: 500.megabytes, table_quota: 200, private_tables_enabled: true)
|
||
|
uo = CartoDB::UserOrganization.new(org.id, owner.id)
|
||
|
uo.promote_user_to_admin
|
||
|
owner.db_service.setup_organization_owner
|
||
|
org.reload
|
||
|
org
|
||
|
end
|
||
|
|
||
|
def give_permission(vis, user, access)
|
||
|
per = ::Permission[vis.permission.id]
|
||
|
per.set_user_permission(user, access)
|
||
|
per.save
|
||
|
per.reload
|
||
|
end
|
||
|
|
||
|
def run_test_server(port)
|
||
|
@server_thread = Thread.new do
|
||
|
Rack::Handler::Thin.run Rails.application, Port: port
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def find_available_port
|
||
|
server = TCPServer.new('127.0.0.1', 0)
|
||
|
server.addr[1]
|
||
|
ensure
|
||
|
server.close if server
|
||
|
end
|
||
|
end
|