525 lines
19 KiB
Ruby
525 lines
19 KiB
Ruby
require_relative '../../../spec_helper_min'
|
|
require 'support/helpers'
|
|
require_relative '../../../../app/controllers/carto/api/users_controller'
|
|
|
|
describe Carto::Api::UsersController do
|
|
include_context 'organization with users helper'
|
|
include Rack::Test::Methods
|
|
include Warden::Test::Helpers
|
|
include HelperMethods
|
|
|
|
before(:all) do
|
|
@headers = { 'CONTENT_TYPE' => 'application/json' }
|
|
FactoryGirl.create(:notification, organization: @carto_organization)
|
|
end
|
|
|
|
before(:each) do
|
|
::User.any_instance.stubs(:create_in_central).returns(true)
|
|
::User.any_instance.stubs(:update_in_central).returns(true)
|
|
FactoryGirl.create(:carto_visualization, user: @carto_org_user_owner, privacy: Carto::Visualization::PRIVACY_PUBLIC)
|
|
FactoryGirl.create(:carto_visualization, user: @carto_org_user_owner,
|
|
privacy: Carto::Visualization::PRIVACY_PRIVATE)
|
|
FactoryGirl.create(:carto_visualization, user: @carto_org_user_owner, privacy: Carto::Visualization::PRIVACY_LINK)
|
|
FactoryGirl.create(:carto_visualization, user: @carto_org_user_owner, privacy: Carto::Visualization::PRIVACY_LINK)
|
|
FactoryGirl.create(:carto_visualization, user: @carto_org_user_owner,
|
|
privacy: Carto::Visualization::PRIVACY_PROTECTED, password: 'a')
|
|
FactoryGirl.create(:carto_visualization, user: @carto_org_user_owner,
|
|
privacy: Carto::Visualization::PRIVACY_PROTECTED, password: 'a')
|
|
FactoryGirl.create(:carto_visualization, user: @carto_org_user_owner,
|
|
privacy: Carto::Visualization::PRIVACY_PROTECTED, password: 'a')
|
|
end
|
|
|
|
describe 'me' do
|
|
it 'contains hubspot_form_ids in config' do
|
|
CartoDB::Hubspot.any_instance.stubs(:enabled?).returns(true)
|
|
CartoDB::Hubspot.any_instance.stubs(:token).returns('something')
|
|
|
|
params = { user_domain: @org_user_1.username, api_key: @org_user_1.api_key }
|
|
|
|
get_json api_v3_users_me_url(params), @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
expect(response.body).to have_key(:config)
|
|
expect(response.body[:config]).to have_key(:hubspot_form_ids)
|
|
end
|
|
CartoDB::Hubspot.any_instance.unstub(:enabled?)
|
|
CartoDB::Hubspot.any_instance.unstub(:token)
|
|
end
|
|
|
|
it 'returns the user info even when locked' do
|
|
@org_user_1.update(state: 'locked')
|
|
params = { user_domain: @org_user_1.username, api_key: @org_user_1.api_key }
|
|
|
|
get_json api_v3_users_me_url(params), @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.body[:user_data][:username]).to eq(@org_user_1.username)
|
|
end
|
|
|
|
@org_user_1.update(state: 'active')
|
|
end
|
|
|
|
it 'returns a hash with current user info' do
|
|
params = { user_domain: @carto_org_user_owner.username, api_key: @carto_org_user_owner.api_key }
|
|
|
|
get_json api_v3_users_me_url(params), @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.body[:default_fallback_basemap].with_indifferent_access).to eq(@carto_org_user_owner.default_basemap)
|
|
|
|
dashboard_notifications = @carto_org_user_owner.notifications_for_category(:dashboard)
|
|
|
|
expect(response.body[:dashboard_notifications]).to eq(dashboard_notifications)
|
|
expect(response.body[:organization_notifications].count).to eq(1)
|
|
expect(response.body[:organization_notifications].first[:icon]).to eq(
|
|
@carto_org_user_owner.received_notifications.unread.first.icon
|
|
)
|
|
expect(response.body[:can_change_email]).to eq(@carto_org_user_owner.can_change_email?)
|
|
expect(response.body[:auth_username_password_enabled]).to eq(true)
|
|
expect(response.body[:can_change_password]).to eq(true)
|
|
expect(response.body[:plan_name]).to eq('ORGANIZATION USER')
|
|
expect(response.body[:services]).to eq(@carto_org_user_owner.get_oauth_services.map(&:symbolize_keys))
|
|
expect(response.body[:google_sign_in]).to eq(@carto_org_user_owner.google_sign_in)
|
|
expect(response.body[:user_data][:public_privacy_map_count]).to eq 1
|
|
expect(response.body[:user_data][:link_privacy_map_count]).to eq 2
|
|
expect(response.body[:user_data][:password_privacy_map_count]).to eq 3
|
|
expect(response.body[:user_data][:private_privacy_map_count]).to eq 1
|
|
end
|
|
end
|
|
|
|
it 'returns a hash with only config if there is no authenticated user' do
|
|
get_json api_v3_users_me_url, @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
expect(response.body).to have_key(:config)
|
|
expect(response.body[:user_frontend_version]).to eq(CartoDB::Application.frontend_version)
|
|
end
|
|
end
|
|
|
|
context 'license_expiration field' do
|
|
before(:each) do
|
|
@expiration_date = Time.parse("2020-11-05T00:00:00.000+00:00")
|
|
end
|
|
|
|
after(:each) do
|
|
Carto::Api::UsersController.any_instance.unstub(:license_expiration_date)
|
|
end
|
|
|
|
it 'is nil for cloud' do
|
|
Carto::Api::UsersController.any_instance.stubs(:license_expiration_date).returns(@expiration_date)
|
|
|
|
Cartodb.with_config(cartodb_com_hosted: false) do
|
|
get_json api_v3_users_me_url, @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
expect(response.body[:license_expiration]).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'is nil if the license_expiration_date does not exist (gear not loaded)' do
|
|
Cartodb.with_config(cartodb_com_hosted: true) do
|
|
get_json api_v3_users_me_url, @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
expect(response.body[:license_expiration]).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'gets the date from the license' do
|
|
Carto::Api::UsersController.any_instance.stubs(:license_expiration_date).returns(@expiration_date)
|
|
|
|
params = { user_domain: @org_user_1.username, api_key: @org_user_1.api_key }
|
|
|
|
Cartodb.with_config(cartodb_com_hosted: true) do
|
|
get_json api_v3_users_me_url(params), @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
expect(response.body[:license_expiration]).to eq "2020-11-05T00:00:00.000+00:00"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'is_enterprise field' do
|
|
before(:all) do
|
|
@params = { user_domain: @org_user_1.username, api_key: @org_user_1.api_key }
|
|
end
|
|
|
|
after(:each) do
|
|
User.any_instance.unstub(:account_type)
|
|
end
|
|
|
|
it 'returns false for Individual plan' do
|
|
Carto::User.any_instance.stubs(account_type: 'Individual')
|
|
|
|
get_json api_v3_users_me_url(@params), @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
expect(response.body[:user_data][:is_enterprise]).to eq(false)
|
|
end
|
|
end
|
|
|
|
it 'returns true for an enterprise plan' do
|
|
Carto::User.any_instance.stubs(account_type: 'ENTERPRISE LUMP-SUM')
|
|
|
|
get_json api_v3_users_me_url(@params), @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
expect(response.body[:user_data][:is_enterprise]).to eq(true)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'update_me' do
|
|
context 'account updates' do
|
|
before(:each) do
|
|
@user = FactoryGirl.create(:user, password: 'foobarbaz', password_confirmation: 'foobarbaz')
|
|
end
|
|
|
|
after(:each) do
|
|
@user.destroy
|
|
end
|
|
|
|
let(:url_options) do
|
|
{
|
|
user_domain: @user.username,
|
|
user_id: @user.id,
|
|
api_key: @user.api_key
|
|
}
|
|
end
|
|
|
|
it 'gives an error if password is the same as password_confirmation' do
|
|
last_change = @user.last_password_change_date
|
|
payload = {
|
|
user: {
|
|
email: 'foo@bar.baz',
|
|
password_confirmation: 'foobarbaz',
|
|
new_password: 'foobarbaz',
|
|
confirm_password: 'foobarbaz'
|
|
}
|
|
}
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(400)
|
|
|
|
expect(response.body[:errors]).to have_key(:new_password)
|
|
expect(response.body[:errors][:new_password]).to eq ['New password cannot be the same as old password']
|
|
@user.reload
|
|
expect(@user.last_password_change_date).to eq(last_change)
|
|
end
|
|
end
|
|
|
|
it 'gives a status code 200 if payload is empty' do
|
|
put_json api_v3_users_update_me_url(url_options), {}, @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
end
|
|
end
|
|
|
|
it 'gives an error if there is no old password' do
|
|
payload = { user: { email: 'foo1@bar.baz' } }
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(403)
|
|
expect(response.body[:message]).to eq("Error updating your account details")
|
|
expect(response.body[:errors]).to have_key(:password)
|
|
end
|
|
end
|
|
|
|
it 'updates account if old password is correct' do
|
|
payload = { user: { email: 'foo1@bar.baz',
|
|
password_confirmation: 'foobarbaz' } }
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
|
|
@user.refresh
|
|
expect(@user.email).to eq('foo1@bar.baz')
|
|
end
|
|
end
|
|
|
|
it 'gives an error if email is invalid' do
|
|
payload = { user: { email: 'foo@',
|
|
password_confirmation: 'foobarbaz' } }
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(400)
|
|
expect(response.body[:message]).to eq("Error updating your account details")
|
|
expect(response.body[:errors]).to have_key(:email)
|
|
end
|
|
end
|
|
|
|
it 'gives an error if old password is invalid' do
|
|
payload = { user: { password_confirmation: 'idontknow', new_password: 'barbaz', confirm_password: 'barbaz' } }
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(403)
|
|
expect(response.body[:message]).to eq("Error updating your account details")
|
|
expect(response.body[:errors]).to have_key(:password)
|
|
end
|
|
end
|
|
|
|
it 'gives an error if new password and confirmation are not the same' do
|
|
payload = { user: { password_confirmation: 'foobarbaz', new_password: 'foofoo', confirm_password: 'barbar' } }
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(400)
|
|
expect(response.body[:message]).to eq("Error updating your account details")
|
|
expect(response.body[:errors]).to have_key(:new_password)
|
|
end
|
|
end
|
|
|
|
it 'returns 401 if user is not logged in' do
|
|
put_json api_v3_users_update_me_url(url_options.except(:api_key)), @headers do |response|
|
|
expect(response.status).to eq(401)
|
|
end
|
|
end
|
|
|
|
it 'updates account data for the given user' do
|
|
last_change = @user.last_password_change_date
|
|
payload = {
|
|
user: {
|
|
email: 'foo@bar.baz',
|
|
password_confirmation: 'foobarbaz',
|
|
new_password: 'bazbarfoo',
|
|
confirm_password: 'bazbarfoo'
|
|
}
|
|
}
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
|
|
@user.refresh
|
|
expect(@user.email).to eq('foo@bar.baz')
|
|
expect(@user.last_password_change_date).to_not eq(last_change)
|
|
end
|
|
end
|
|
|
|
context 'multifactor authentication' do
|
|
it 'creates a multifactor authentication' do
|
|
payload = { user: { password_confirmation: 'foobarbaz', mfa: true } }
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers
|
|
|
|
@user.reload.user_multifactor_auths.should_not be_empty
|
|
end
|
|
|
|
it 'removes the multifactor authentications' do
|
|
FactoryGirl.create(:totp, user_id: @user.id)
|
|
payload = { user: { password_confirmation: 'foobarbaz', mfa: false } }
|
|
|
|
@user.reload.user_multifactor_auths.should_not be_empty
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers
|
|
|
|
last_response.status.should eq 200
|
|
@user.reload.user_multifactor_auths.should be_empty
|
|
end
|
|
|
|
it 'does not update the user multifactor authentications if the user saving operation fails' do
|
|
User.any_instance.stubs(:save).raises(Sequel::ValidationFailed.new('error!'))
|
|
payload = { user: { password_confirmation: 'foobarbaz', mfa: false } }
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers
|
|
|
|
last_response.status.should eq 400
|
|
@user.reload.user_multifactor_auths.should be_empty
|
|
end
|
|
|
|
it 'does not save the user if the multifactor authentication updating operation fails' do
|
|
mfa = Carto::UserMultifactorAuth.new
|
|
Carto::UserMultifactorAuth.stubs(:create!).raises(ActiveRecord::RecordInvalid.new(mfa))
|
|
payload = { user: { password_confirmation: 'foobarbaz', mfa: true } }
|
|
|
|
@user.expects(:save).never
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers
|
|
|
|
last_response.status.should eq 400
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'profile updates' do
|
|
before(:each) do
|
|
@user = FactoryGirl.create(:user, password: 'foobarbaz', password_confirmation: 'foobarbaz')
|
|
end
|
|
|
|
after(:each) do
|
|
@user.destroy
|
|
end
|
|
|
|
let(:url_options) do
|
|
{
|
|
user_domain: @user.username,
|
|
user_id: @user.id,
|
|
api_key: @user.api_key
|
|
}
|
|
end
|
|
|
|
it 'updates profile data for the given user' do
|
|
payload = {
|
|
user: {
|
|
name: 'Foo',
|
|
last_name: 'Bar',
|
|
website: 'https://carto.rocks',
|
|
description: 'Foo Bar Baz',
|
|
location: 'Anywhere',
|
|
twitter_username: 'carto',
|
|
disqus_shortname: 'carto',
|
|
avatar_url: 'http://carto.rocks/avatar.jpg',
|
|
password_confirmation: 'foobarbaz'
|
|
}
|
|
}
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.body[:name]).to eq('Foo')
|
|
expect(response.body[:last_name]).to eq('Bar')
|
|
expect(response.body[:website]).to eq('https://carto.rocks')
|
|
expect(response.body[:description]).to eq('Foo Bar Baz')
|
|
expect(response.body[:location]).to eq('Anywhere')
|
|
expect(response.body[:twitter_username]).to eq('carto')
|
|
expect(response.body[:disqus_shortname]).to eq('carto')
|
|
expect(response.body[:avatar_url]).to eq('http://carto.rocks/avatar.jpg')
|
|
end
|
|
end
|
|
|
|
it 'does not update profile data if old password is wrong' do
|
|
payload = {
|
|
user: {
|
|
name: 'Foo2',
|
|
password_confirmation: 'prapra'
|
|
}
|
|
}
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(403)
|
|
|
|
@user.reload
|
|
@user.username.should_not eq 'Foo2'
|
|
end
|
|
end
|
|
|
|
it 'does not update profile data if password_confirmation is missing' do
|
|
payload = {
|
|
user: {
|
|
name: 'Foo2'
|
|
}
|
|
}
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(403)
|
|
|
|
@user.reload
|
|
@user.username.should_not eq 'Foo2'
|
|
end
|
|
end
|
|
|
|
it 'does not update fields not present in the user hash' do
|
|
payload = {
|
|
user: {
|
|
name: 'Foo',
|
|
last_name: 'Bar',
|
|
website: 'https://carto.rocks',
|
|
password_confirmation: 'foobarbaz'
|
|
}
|
|
}
|
|
old_description = @user.description
|
|
old_location = @user.location
|
|
old_twitter_username = @user.twitter_username
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
|
|
expect(response.body[:name]).to eq('Foo')
|
|
expect(response.body[:last_name]).to eq('Bar')
|
|
expect(response.body[:website]).to eq('https://carto.rocks')
|
|
expect(response.body[:description]).to eq(old_description)
|
|
expect(response.body[:location]).to eq(old_location)
|
|
expect(response.body[:twitter_username]).to eq(old_twitter_username)
|
|
end
|
|
end
|
|
|
|
it 'sets field to nil if key is present in the hash with a nil value' do
|
|
fields_to_check = [
|
|
:name, :last_name, :website, :description, :location, :twitter_username,
|
|
:disqus_shortname, :available_for_hire
|
|
]
|
|
|
|
fields_to_check.each do |field|
|
|
payload = { user: { field => nil,
|
|
password_confirmation: 'foobarbaz' } }
|
|
|
|
put_json api_v3_users_update_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
@user.refresh
|
|
expect(@user.values[field]).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'returns 401 if user is not logged in' do
|
|
payload = { user: { name: 'Foo',
|
|
password_confirmation: 'foobarbaz' } }
|
|
|
|
put_json api_v3_users_update_me_url(url_options.except(:api_key)), payload, @headers do |response|
|
|
expect(response.status).to eq(401)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'delete_me' do
|
|
before(:each) do
|
|
@user = FactoryGirl.create(:user, password: 'foobarbaz', password_confirmation: 'foobarbaz')
|
|
User.any_instance.stubs(:delete_in_central)
|
|
end
|
|
|
|
let(:url_options) do
|
|
{
|
|
user_domain: @user.username,
|
|
api_key: @user.api_key
|
|
}
|
|
end
|
|
|
|
it 'deletes the authenticated user' do
|
|
payload = { deletion_password_confirmation: 'foobarbaz' }
|
|
|
|
delete_json api_v3_users_delete_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
expect(Carto::User.exists?(@user.id)).to be_false
|
|
end
|
|
end
|
|
|
|
it 'deletes the authenticated user even when locked' do
|
|
@user.update(state: 'locked')
|
|
|
|
payload = { deletion_password_confirmation: 'foobarbaz' }
|
|
|
|
delete_json api_v3_users_delete_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(200)
|
|
expect(Carto::User.exists?(@user.id)).to be_false
|
|
end
|
|
end
|
|
|
|
context 'failures in deletion' do
|
|
after(:each) do
|
|
@user.destroy
|
|
end
|
|
|
|
it 'gives an error if deletion password confirmation is invalid' do
|
|
payload = { deletion_password_confirmation: 'idontknow' }
|
|
|
|
delete_json api_v3_users_delete_me_url(url_options), payload, @headers do |response|
|
|
expect(response.status).to eq(400)
|
|
expect(response.body[:message]).to eq("Error deleting user: Password does not match")
|
|
end
|
|
end
|
|
|
|
it 'returns 401 if user is not logged in' do
|
|
delete_json api_v3_users_delete_me_url(url_options.except(:api_key)), @headers do |response|
|
|
expect(response.status).to eq(401)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|