From f5ad0022f72ca1391dd02d04d1aa8ef4e31d05c6 Mon Sep 17 00:00:00 2001 From: OsamaSayegh <asooomaasoooma90@gmail.com> Date: Fri, 8 Jun 2018 07:42:06 +0300 Subject: [PATCH] REFACTOR: admin users controller specs to requests (#5946) --- .../admin/users_controller_spec.rb | 840 ------------------ spec/requests/admin/users_controller_spec.rb | 802 +++++++++++++++++ 2 files changed, 802 insertions(+), 840 deletions(-) delete mode 100644 spec/controllers/admin/users_controller_spec.rb diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb deleted file mode 100644 index 8288d07675e..00000000000 --- a/spec/controllers/admin/users_controller_spec.rb +++ /dev/null @@ -1,840 +0,0 @@ -require 'rails_helper' -require_dependency 'single_sign_on' - -describe Admin::UsersController do - - it 'is a subclass of AdminController' do - expect(Admin::UsersController < Admin::AdminController).to eq(true) - end - - context 'while logged in as an admin' do - before do - @user = log_in(:admin) - end - - context '#index' do - it 'returns success' do - get :index, format: :json - expect(response.status).to eq(200) - end - - it 'returns JSON' do - get :index, format: :json - expect(::JSON.parse(response.body)).to be_present - end - - context 'when showing emails' do - - it "returns email for all the users" do - get :index, params: { show_emails: "true" }, format: :json - data = ::JSON.parse(response.body) - data.each do |user| - expect(user["email"]).to be_present - end - end - - it "logs only 1 enty" do - expect(UserHistory.where(action: UserHistory.actions[:check_email], acting_user_id: @user.id).count).to eq(0) - - get :index, params: { show_emails: "true" }, format: :json - - expect(UserHistory.where(action: UserHistory.actions[:check_email], acting_user_id: @user.id).count).to eq(1) - end - - end - end - - describe '#show' do - context 'an existing user' do - it 'returns success' do - get :show, params: { id: @user.id }, format: :json - expect(response.status).to eq(200) - end - end - - context 'an existing user' do - it 'returns success' do - get :show, params: { id: 0 }, format: :json - expect(response).not_to be_successful - end - end - end - - context '#approve_bulk' do - - let(:evil_trout) { Fabricate(:evil_trout) } - - it "does nothing without uesrs" do - User.any_instance.expects(:approve).never - put :approve_bulk, format: :json - end - - it "won't approve the user when not allowed" do - Guardian.any_instance.expects(:can_approve?).with(evil_trout).returns(false) - User.any_instance.expects(:approve).never - put :approve_bulk, params: { users: [evil_trout.id] }, format: :json - end - - it "approves the user when permitted" do - Guardian.any_instance.expects(:can_approve?).with(evil_trout).returns(true) - User.any_instance.expects(:approve).once - put :approve_bulk, params: { users: [evil_trout.id] }, format: :json - end - - end - - context '#generate_api_key' do - let(:evil_trout) { Fabricate(:evil_trout) } - - it 'calls generate_api_key' do - User.any_instance.expects(:generate_api_key).with(@user) - post :generate_api_key, params: { user_id: evil_trout.id }, format: :json - end - end - - context '#revoke_api_key' do - - let(:evil_trout) { Fabricate(:evil_trout) } - - it 'calls revoke_api_key' do - User.any_instance.expects(:revoke_api_key) - delete :revoke_api_key, params: { user_id: evil_trout.id }, format: :json - end - - end - - context '#approve' do - - let(:evil_trout) { Fabricate(:evil_trout) } - - it "raises an error when the user doesn't have permission" do - Guardian.any_instance.expects(:can_approve?).with(evil_trout).returns(false) - put :approve, params: { user_id: evil_trout.id }, format: :json - expect(response).to be_forbidden - end - - it 'calls approve' do - User.any_instance.expects(:approve).with(@user) - put :approve, params: { user_id: evil_trout.id }, format: :json - end - - end - - context '#suspend' do - let(:user) { Fabricate(:evil_trout) } - - it "works properly" do - Fabricate(:api_key, user: user) - expect(user).not_to be_suspended - put( - :suspend, - params: { - user_id: user.id, - suspend_until: 5.hours.from_now, - reason: "because I said so", - format: :json - } - ) - expect(response.status).to eq(200) - - user.reload - expect(user).to be_suspended - expect(user.suspended_at).to be_present - expect(user.suspended_till).to be_present - expect(ApiKey.where(user_id: user.id).count).to eq(0) - - log = UserHistory.where(target_user_id: user.id).order('id desc').first - expect(log).to be_present - expect(log.details).to match(/because I said so/) - end - - context "with an associated post" do - let(:post) { Fabricate(:post) } - let(:suspend_params) do - { user_id: user.id, - suspend_until: 5.hours.from_now, - reason: "because of this post", - post_id: post.id, - format: :json } - end - - it "can have an associated post" do - put(:suspend, params: suspend_params) - expect(response.status).to eq(200) - - log = UserHistory.where(target_user_id: user.id).order('id desc').first - expect(log).to be_present - expect(log.post_id).to eq(post.id) - end - - it "can delete an associated post" do - put(:suspend, params: suspend_params.merge(post_action: 'delete')) - post.reload - expect(post.deleted_at).to be_present - expect(response.status).to eq(200) - end - - it "can edit an associated post" do - put(:suspend, params: suspend_params.merge( - post_action: 'edit', - post_edit: 'this is the edited content' - )) - post.reload - expect(post.deleted_at).to be_blank - expect(post.raw).to eq("this is the edited content") - expect(response.status).to eq(200) - end - end - - it "can send a message to the user" do - Jobs.expects(:enqueue).with( - :critical_user_email, - has_entries( - type: :account_suspended, - user_id: user.id - ) - ) - - put( - :suspend, - params: { - user_id: user.id, - suspend_until: 10.days.from_now, - reason: "short reason", - message: "long reason", - format: :json - } - ) - expect(response.status).to eq(200) - - log = UserHistory.where(target_user_id: user.id).order('id desc').first - expect(log).to be_present - expect(log.details).to match(/short reason/) - expect(log.details).to match(/long reason/) - end - - it "also revoke any api keys" do - User.any_instance.expects(:revoke_api_key) - put :suspend, params: { user_id: user.id }, format: :json - end - - end - - context '#revoke_admin' do - before do - @another_admin = Fabricate(:admin) - end - - it 'raises an error unless the user can revoke access' do - Guardian.any_instance.expects(:can_revoke_admin?).with(@another_admin).returns(false) - put :revoke_admin, params: { user_id: @another_admin.id }, format: :json - expect(response).to be_forbidden - end - - it 'updates the admin flag' do - put :revoke_admin, params: { user_id: @another_admin.id }, format: :json - @another_admin.reload - expect(@another_admin).not_to be_admin - end - end - - context '#grant_admin' do - before do - @another_user = Fabricate(:coding_horror) - end - - after do - $redis.flushall - end - - it "raises an error when the user doesn't have permission" do - Guardian.any_instance.expects(:can_grant_admin?).with(@another_user).returns(false) - put :grant_admin, params: { user_id: @another_user.id }, format: :json - expect(response).to be_forbidden - end - - it "returns a 404 if the username doesn't exist" do - put :grant_admin, params: { user_id: 123123 }, format: :json - expect(response).to be_forbidden - end - - it 'updates the admin flag' do - expect(AdminConfirmation.exists_for?(@another_user.id)).to eq(false) - put :grant_admin, params: { user_id: @another_user.id }, format: :json - expect(AdminConfirmation.exists_for?(@another_user.id)).to eq(true) - end - end - - context '#add_group' do - let(:user) { Fabricate(:user) } - let(:group) { Fabricate(:group) } - - it 'adds the user to the group' do - post :add_group, params: { - group_id: group.id, user_id: user.id - }, format: :json - - expect(response.status).to eq(200) - expect(GroupUser.where(user_id: user.id, group_id: group.id).exists?).to eq(true) - - group_history = GroupHistory.last - - expect(group_history.action).to eq(GroupHistory.actions[:add_user_to_group]) - expect(group_history.acting_user).to eq(@user) - expect(group_history.target_user).to eq(user) - - # Doing it again doesn't raise an error - post :add_group, params: { - group_id: group.id, user_id: user.id - }, format: :json - - expect(response.status).to eq(200) - end - end - - context '#primary_group' do - let(:group) { Fabricate(:group) } - - before do - @another_user = Fabricate(:coding_horror) - end - - it "raises an error when the user doesn't have permission" do - Guardian.any_instance.expects(:can_change_primary_group?).with(@another_user).returns(false) - put :primary_group, params: { - user_id: @another_user.id - }, format: :json - - expect(response).to be_forbidden - end - - it "returns a 404 if the user doesn't exist" do - put :primary_group, params: { - user_id: 123123 - }, format: :json - - expect(response).to be_forbidden - end - - it "changes the user's primary group" do - group.add(@another_user) - put :primary_group, params: { - user_id: @another_user.id, primary_group_id: group.id - }, format: :json - - @another_user.reload - expect(@another_user.primary_group_id).to eq(group.id) - end - - it "doesn't change primary group if they aren't a member of the group" do - put :primary_group, params: { - user_id: @another_user.id, primary_group_id: group.id - }, format: :json - - @another_user.reload - expect(@another_user.primary_group_id).to be_nil - end - - it "remove user's primary group" do - group.add(@another_user) - - put :primary_group, params: { - user_id: @another_user.id, primary_group_id: "" - }, format: :json - - @another_user.reload - expect(@another_user.primary_group_id).to be(nil) - end - end - - context '#trust_level' do - before do - @another_user = Fabricate(:coding_horror, created_at: 1.month.ago) - end - - it "raises an error when the user doesn't have permission" do - Guardian.any_instance.expects(:can_change_trust_level?).with(@another_user).returns(false) - put :trust_level, params: { - user_id: @another_user.id - }, format: :json - - expect(response).not_to be_successful - end - - it "returns a 404 if the username doesn't exist" do - put :trust_level, params: { - user_id: 123123 - }, format: :json - - expect(response).not_to be_successful - end - - it "upgrades the user's trust level" do - StaffActionLogger.any_instance.expects(:log_trust_level_change).with(@another_user, @another_user.trust_level, 2).once - - put :trust_level, params: { - user_id: @another_user.id, level: 2 - }, format: :json - - @another_user.reload - expect(@another_user.trust_level).to eq(2) - expect(response.status).to eq(200) - end - - it "raises no error when demoting a user below their current trust level (locks trust level)" do - stat = @another_user.user_stat - stat.topics_entered = SiteSetting.tl1_requires_topics_entered + 1 - stat.posts_read_count = SiteSetting.tl1_requires_read_posts + 1 - stat.time_read = SiteSetting.tl1_requires_time_spent_mins * 60 - stat.save! - @another_user.update_attributes(trust_level: TrustLevel[1]) - - put :trust_level, params: { - user_id: @another_user.id, - level: TrustLevel[0] - }, format: :json - - expect(response.status).to eq(200) - @another_user.reload - expect(@another_user.trust_level).to eq(TrustLevel[0]) - expect(@another_user.manual_locked_trust_level).to eq(TrustLevel[0]) - end - end - - describe '#revoke_moderation' do - before do - @moderator = Fabricate(:moderator) - end - - it 'raises an error unless the user can revoke access' do - Guardian.any_instance.expects(:can_revoke_moderation?).with(@moderator).returns(false) - put :revoke_moderation, params: { - user_id: @moderator.id - }, format: :json - - expect(response).to be_forbidden - end - - it 'updates the moderator flag' do - put :revoke_moderation, params: { - user_id: @moderator.id - }, format: :json - - @moderator.reload - expect(@moderator.moderator).not_to eq(true) - end - end - - context '#grant_moderation' do - before do - @another_user = Fabricate(:coding_horror) - end - - it "raises an error when the user doesn't have permission" do - Guardian.any_instance.expects(:can_grant_moderation?).with(@another_user).returns(false) - put :grant_moderation, params: { user_id: @another_user.id }, format: :json - expect(response).to be_forbidden - end - - it "returns a 404 if the username doesn't exist" do - put :grant_moderation, params: { user_id: 123123 }, format: :json - expect(response).to be_forbidden - end - - it 'updates the moderator flag' do - put :grant_moderation, params: { user_id: @another_user.id }, format: :json - @another_user.reload - expect(@another_user.moderator).to eq(true) - end - end - - context '#reject_bulk' do - let(:reject_me) { Fabricate(:user) } - let(:reject_me_too) { Fabricate(:user) } - - it 'does nothing without users' do - UserDestroyer.any_instance.expects(:destroy).never - delete :reject_bulk, format: :json - end - - it "won't delete users if not allowed" do - Guardian.any_instance.stubs(:can_delete_user?).returns(false) - UserDestroyer.any_instance.expects(:destroy).never - - delete :reject_bulk, params: { - users: [reject_me.id] - }, format: :json - end - - it "reports successes" do - Guardian.any_instance.stubs(:can_delete_user?).returns(true) - UserDestroyer.any_instance.stubs(:destroy).returns(true) - - delete :reject_bulk, params: { - users: [reject_me.id, reject_me_too.id] - }, format: :json - - expect(response.status).to eq(200) - json = ::JSON.parse(response.body) - expect(json['success'].to_i).to eq(2) - expect(json['failed'].to_i).to eq(0) - end - - context 'failures' do - before do - Guardian.any_instance.stubs(:can_delete_user?).returns(true) - end - - it 'can handle some successes and some failures' do - UserDestroyer.any_instance.stubs(:destroy).with(reject_me, anything).returns(false) - UserDestroyer.any_instance.stubs(:destroy).with(reject_me_too, anything).returns(true) - - delete :reject_bulk, params: { - users: [reject_me.id, reject_me_too.id] - }, format: :json - - expect(response.status).to eq(200) - json = ::JSON.parse(response.body) - expect(json['success'].to_i).to eq(1) - expect(json['failed'].to_i).to eq(1) - end - - it 'reports failure due to a user still having posts' do - UserDestroyer.any_instance.expects(:destroy).with(reject_me, anything).raises(UserDestroyer::PostsExistError) - - delete :reject_bulk, params: { - users: [reject_me.id] - }, format: :json - - expect(response.status).to eq(200) - json = ::JSON.parse(response.body) - expect(json['success'].to_i).to eq(0) - expect(json['failed'].to_i).to eq(1) - end - end - end - - context '#destroy' do - let(:delete_me) { Fabricate(:user) } - - it "returns a 403 if the user doesn't exist" do - delete :destroy, params: { id: 123123 }, format: :json - expect(response).to be_forbidden - end - - context "user has post" do - let(:topic) { create_topic(user: delete_me) } - - before do - _post = create_post(topic: topic, user: delete_me) - end - - it "returns an api response that the user can't be deleted because it has posts" do - delete :destroy, params: { id: delete_me.id }, format: :json - expect(response).to be_forbidden - json = ::JSON.parse(response.body) - expect(json['deleted']).to eq(false) - end - - it "doesn't return an error if delete_posts == true" do - delete :destroy, params: { id: delete_me.id, delete_posts: true }, format: :json - expect(response.status).to eq(200) - end - end - - it "deletes the user record" do - UserDestroyer.any_instance.expects(:destroy).returns(true) - delete :destroy, params: { id: delete_me.id }, format: :json - end - end - - context 'activate' do - before do - @reg_user = Fabricate(:inactive_user) - end - - it "returns success" do - put :activate, params: { user_id: @reg_user.id }, format: :json - expect(response.status).to eq(200) - json = ::JSON.parse(response.body) - expect(json['success']).to eq("OK") - end - - it "should confirm email even when the tokens are expired" do - @reg_user.email_tokens.update_all(confirmed: false, expired: true) - - @reg_user.reload - expect(@reg_user.email_confirmed?).to eq(false) - - put :activate, params: { user_id: @reg_user.id }, format: :json - expect(response.status).to eq(200) - - @reg_user.reload - expect(@reg_user.email_confirmed?).to eq(true) - end - end - - context 'log_out' do - before do - @reg_user = Fabricate(:user) - end - - it "returns success" do - put :log_out, params: { user_id: @reg_user.id }, format: :json - expect(response.status).to eq(200) - json = ::JSON.parse(response.body) - expect(json['success']).to eq("OK") - end - - it "returns 404 when user_id does not exist" do - put :log_out, params: { user_id: 123123 }, format: :json - expect(response).not_to be_successful - end - end - - context 'silence' do - before do - @reg_user = Fabricate(:user) - end - - it "raises an error when the user doesn't have permission" do - Guardian.any_instance.expects(:can_silence_user?).with(@reg_user).returns(false) - put :silence, params: { user_id: @reg_user.id }, format: :json - expect(response).to be_forbidden - @reg_user.reload - expect(@reg_user).not_to be_silenced - end - - it "returns a 403 if the user doesn't exist" do - put :silence, params: { user_id: 123123 }, format: :json - expect(response).to be_forbidden - end - - it "punishes the user for spamming" do - put :silence, params: { user_id: @reg_user.id }, format: :json - expect(response.status).to eq(200) - @reg_user.reload - expect(@reg_user).to be_silenced - end - - it "can have an associated post" do - silence_post = Fabricate(:post, user: @reg_user) - - put :silence, params: { - user_id: @reg_user.id, - post_id: silence_post.id, - post_action: 'edit', - post_edit: "this is the new contents for the post" - }, format: :json - expect(response.status).to eq(200) - - silence_post.reload - expect(silence_post.raw).to eq("this is the new contents for the post") - - log = UserHistory.where( - target_user_id: @reg_user.id, - action: UserHistory.actions[:silence_user] - ).first - expect(log).to be_present - expect(log.post_id).to eq(silence_post.id) - - @reg_user.reload - expect(@reg_user).to be_silenced - end - - it "will set a length of time if provided" do - future_date = 1.month.from_now.to_date - put( - :silence, - params: { - user_id: @reg_user.id, - silenced_till: future_date - }, - format: :json - ) - @reg_user.reload - expect(@reg_user.silenced_till).to eq(future_date) - end - - it "will send a message if provided" do - Jobs.stubs(:enqueue) - Jobs.expects(:enqueue).with( - :critical_user_email, - has_entries( - type: :account_silenced, - user_id: @reg_user.id - ) - ) - - put( - :silence, - params: { - user_id: @reg_user.id, - message: "Email this to the user" - }, - format: :json - ) - end - end - - context 'unsilence' do - before do - @reg_user = Fabricate(:user) - end - - it "raises an error when the user doesn't have permission" do - Guardian.any_instance.expects(:can_unsilence_user?).with(@reg_user).returns(false) - put :unsilence, params: { user_id: @reg_user.id }, format: :json - expect(response).to be_forbidden - end - - it "returns a 403 if the user doesn't exist" do - put :unsilence, params: { user_id: 123123 }, format: :json - expect(response).to be_forbidden - end - - it "punishes the user for spamming" do - UserSilencer.expects(:unsilence).with(@reg_user, @user, anything) - put :unsilence, params: { user_id: @reg_user.id }, format: :json - end - end - - context 'ip-info' do - - it "uses ipinfo.io webservice to retrieve the info" do - Excon.expects(:get).with("https://ipinfo.io/123.123.123.123/json", read_timeout: 10, connect_timeout: 10) - get :ip_info, params: { ip: "123.123.123.123" }, format: :json - end - - end - - context "delete_other_accounts_with_same_ip" do - - it "works" do - Fabricate(:user, ip_address: "42.42.42.42") - Fabricate(:user, ip_address: "42.42.42.42") - - UserDestroyer.any_instance.expects(:destroy).twice - - delete :delete_other_accounts_with_same_ip, params: { - ip: "42.42.42.42", exclude: -1, order: "trust_level DESC" - }, format: :json - end - - end - - context ".invite_admin" do - it "doesn't work when not via API" do - controller.stubs(:is_api?).returns(false) - - post :invite_admin, params: { - name: 'Bill', username: 'bill22', email: 'bill@bill.com' - }, format: :json - - expect(response).not_to be_successful - end - - it 'should invite admin' do - controller.stubs(:is_api?).returns(true) - Jobs.expects(:enqueue).with(:critical_user_email, anything).returns(true) - - post :invite_admin, params: { - name: 'Bill', username: 'bill22', email: 'bill@bill.com' - }, format: :json - - expect(response.status).to eq(200) - - u = User.find_by_email('bill@bill.com') - expect(u.name).to eq("Bill") - expect(u.username).to eq("bill22") - expect(u.admin).to eq(true) - end - - it "doesn't send the email with send_email falsy" do - controller.stubs(:is_api?).returns(true) - Jobs.expects(:enqueue).with(:user_email, anything).never - - post :invite_admin, params: { - name: 'Bill', username: 'bill22', email: 'bill@bill.com', send_email: '0' - }, format: :json - - expect(response.status).to eq(200) - json = ::JSON.parse(response.body) - expect(json["password_url"]).to be_present - end - end - - context 'remove_group' do - it "also clears the user's primary group" do - g = Fabricate(:group) - u = Fabricate(:user, primary_group: g) - delete :remove_group, params: { group_id: g.id, user_id: u.id }, format: :json - - expect(u.reload.primary_group).to be_nil - end - end - end - - context '#sync_sso' do - let(:sso) { SingleSignOn.new } - let(:sso_secret) { "sso secret" } - - before do - log_in(:admin) - - SiteSetting.email_editable = false - SiteSetting.sso_url = "https://www.example.com/sso" - SiteSetting.enable_sso = true - SiteSetting.sso_overrides_email = true - SiteSetting.sso_overrides_name = true - SiteSetting.sso_overrides_username = true - SiteSetting.sso_secret = sso_secret - sso.sso_secret = sso_secret - end - - it 'can sync up with the sso' do - sso.name = "Bob The Bob" - sso.username = "bob" - sso.email = "bob@bob.com" - sso.external_id = "1" - - user = DiscourseSingleSignOn.parse(sso.payload) - .lookup_or_create_user - - sso.name = "Bill" - sso.username = "Hokli$$!!" - sso.email = "bob2@bob.com" - - post :sync_sso, params: Rack::Utils.parse_query(sso.payload), format: :json - expect(response.status).to eq(200) - - user.reload - expect(user.email).to eq("bob2@bob.com") - expect(user.name).to eq("Bill") - expect(user.username).to eq("Hokli") - end - - it 'should create new users' do - sso.name = "Dr. Claw" - sso.username = "dr_claw" - sso.email = "dr@claw.com" - sso.external_id = "2" - post :sync_sso, params: Rack::Utils.parse_query(sso.payload), format: :json - expect(response.status).to eq(200) - - user = User.find_by_email('dr@claw.com') - expect(user).to be_present - expect(user.ip_address).to be_blank - end - - it 'should return the right message if the record is invalid' do - sso.email = "" - sso.name = "" - sso.external_id = "1" - - post :sync_sso, params: Rack::Utils.parse_query(sso.payload), format: :json - expect(response.status).to eq(403) - expect(JSON.parse(response.body)["message"]).to include("Primary email can't be blank") - end - end -end diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb index a89e92f9cf7..67cf0ee5b90 100644 --- a/spec/requests/admin/users_controller_spec.rb +++ b/spec/requests/admin/users_controller_spec.rb @@ -4,6 +4,808 @@ RSpec.describe Admin::UsersController do let(:admin) { Fabricate(:admin) } let(:user) { Fabricate(:user) } + it 'is a subclass of AdminController' do + expect(Admin::UsersController < Admin::AdminController).to eq(true) + end + + before do + sign_in(admin) + end + + describe '#index' do + it 'returns success with JSON' do + get "/admin/users/list.json" + expect(response.status).to eq(200) + expect(JSON.parse(response.body)).to be_present + end + + context 'when showing emails' do + it "returns email for all the users" do + get "/admin/users/list.json", params: { show_emails: "true" } + expect(response.status).to eq(200) + data = ::JSON.parse(response.body) + data.each do |user| + expect(user["email"]).to be_present + end + end + + it "logs only 1 enty" do + expect do + get "/admin/users/list.json", params: { show_emails: "true" } + end.to change { UserHistory.where(action: UserHistory.actions[:check_email], acting_user_id: admin.id).count }.by(1) + expect(response.status).to eq(200) + end + end + end + + describe '#show' do + context 'an existing user' do + it 'returns success' do + get "/admin/users/#{user.id}.json" + expect(response.status).to eq(200) + end + end + + context 'a non-existing user' do + it 'returns 404 error' do + get "/admin/users/0.json" + expect(response.status).to eq(404) + end + end + end + + describe '#approve' do + let(:evil_trout) { Fabricate(:evil_trout, approved: false) } + + it "raises an error when the user doesn't have permission" do + sign_in(user) + put "/admin/users/#{evil_trout.id}/approve.json" + expect(response.status).to eq(404) + evil_trout.reload + expect(evil_trout.approved).to eq(false) + end + + it 'calls approve' do + put "/admin/users/#{evil_trout.id}/approve.json" + expect(response.status).to eq(200) + evil_trout.reload + expect(evil_trout.approved).to eq(true) + end + end + + describe '#approve_bulk' do + let(:evil_trout) { Fabricate(:evil_trout, approved: false) } + + it "does nothing without uesrs" do + put "/admin/users/approve-bulk.json" + evil_trout.reload + expect(response.status).to eq(200) + expect(evil_trout.approved).to eq(false) + end + + it "won't approve the user when not allowed" do + sign_in(user) + put "/admin/users/approve-bulk.json", params: { users: [evil_trout.id] } + expect(response.status).to eq(404) + evil_trout.reload + expect(evil_trout.approved).to eq(false) + end + + it "approves the user when permitted" do + put "/admin/users/approve-bulk.json", params: { users: [evil_trout.id] } + expect(response.status).to eq(200) + evil_trout.reload + expect(evil_trout.approved).to eq(true) + end + end + + describe '#generate_api_key' do + it 'calls generate_api_key' do + post "/admin/users/#{user.id}/generate_api_key.json" + expect(response.status).to eq(200) + json = JSON.parse(response.body) + expect(json["api_key"]["user"]["id"]).to eq(user.id) + expect(json["api_key"]["key"]).to be_present + end + end + + describe '#revoke_api_key' do + it 'calls revoke_api_key' do + ApiKey.create!(user: user, key: SecureRandom.hex) + delete "/admin/users/#{user.id}/revoke_api_key.json" + expect(response.status).to eq(200) + expect(ApiKey.where(user: user).count).to eq(0) + end + end + + describe '#suspend' do + let(:post) { Fabricate(:post) } + let(:suspend_params) do + { suspend_until: 5.hours.from_now, + reason: "because of this post", + post_id: post.id } + end + + it "works properly" do + expect(user).not_to be_suspended + put "/admin/users/#{user.id}/suspend.json", params: { + suspend_until: 5.hours.from_now, + reason: "because I said so" + } + + expect(response.status).to eq(200) + + user.reload + expect(user).to be_suspended + expect(user.suspended_at).to be_present + expect(user.suspended_till).to be_present + + log = UserHistory.where(target_user_id: user.id).order('id desc').first + expect(log.details).to match(/because I said so/) + end + + context "with an associated post" do + it "can have an associated post" do + put "/admin/users/#{user.id}/suspend.json", params: suspend_params + + expect(response.status).to eq(200) + + log = UserHistory.where(target_user_id: user.id).order('id desc').first + expect(log.post_id).to eq(post.id) + end + + it "can delete an associated post" do + put "/admin/users/#{user.id}/suspend.json", params: suspend_params.merge(post_action: 'delete') + post.reload + expect(post.deleted_at).to be_present + expect(response.status).to eq(200) + end + + it "can edit an associated post" do + put "/admin/users/#{user.id}/suspend.json", params: suspend_params.merge( + post_action: 'edit', + post_edit: 'this is the edited content' + ) + + expect(response.status).to eq(200) + post.reload + expect(post.deleted_at).to be_blank + expect(post.raw).to eq("this is the edited content") + expect(response.status).to eq(200) + end + end + + it "can send a message to the user" do + put "/admin/users/#{user.id}/suspend.json", params: { + suspend_until: 10.days.from_now, + reason: "short reason", + message: "long reason" + } + + expect(response.status).to eq(200) + + expect(Jobs::CriticalUserEmail.jobs.size).to eq(1) + job_args = Jobs::CriticalUserEmail.jobs.first["args"].first + expect(job_args["type"]).to eq("account_suspended") + expect(job_args["user_id"]).to eq(user.id) + + log = UserHistory.where(target_user_id: user.id).order('id desc').first + expect(log).to be_present + expect(log.details).to match(/short reason/) + expect(log.details).to match(/long reason/) + end + + it "also revokes any api keys" do + Fabricate(:api_key, user: user) + put "/admin/users/#{user.id}/suspend.json", params: suspend_params + + expect(response.status).to eq(200) + user.reload + + expect(user).to be_suspended + expect(ApiKey.where(user_id: user.id).count).to eq(0) + end + end + + describe '#revoke_admin' do + let(:another_admin) { Fabricate(:admin) } + + it 'raises an error unless the user can revoke access' do + sign_in(user) + put "/admin/users/#{another_admin.id}/revoke_admin.json" + expect(response.status).to eq(404) + another_admin.reload + expect(another_admin.admin).to eq(true) + end + + it 'updates the admin flag' do + put "/admin/users/#{another_admin.id}/revoke_admin.json" + expect(response.status).to eq(200) + another_admin.reload + expect(another_admin.admin).to eq(false) + end + end + + describe '#grant_admin' do + let(:another_user) { Fabricate(:coding_horror) } + + after do + $redis.flushall + end + + it "raises an error when the user doesn't have permission" do + sign_in(user) + put "/admin/users/#{another_user.id}/grant_admin.json" + expect(response.status).to eq(404) + expect(AdminConfirmation.exists_for?(another_user.id)).to eq(false) + end + + it "returns a 403 if the username doesn't exist" do + put "/admin/users/123123/grant_admin.json" + expect(response.status).to eq(403) + end + + it 'updates the admin flag' do + expect(AdminConfirmation.exists_for?(another_user.id)).to eq(false) + put "/admin/users/#{another_user.id}/grant_admin.json" + expect(response.status).to eq(200) + expect(AdminConfirmation.exists_for?(another_user.id)).to eq(true) + end + end + + describe '#add_group' do + let(:group) { Fabricate(:group) } + + it 'adds the user to the group' do + post "/admin/users/#{user.id}/groups.json", params: { + group_id: group.id + } + + expect(response.status).to eq(200) + expect(GroupUser.where(user_id: user.id, group_id: group.id).exists?).to eq(true) + + group_history = GroupHistory.last + + expect(group_history.action).to eq(GroupHistory.actions[:add_user_to_group]) + expect(group_history.acting_user).to eq(admin) + expect(group_history.target_user).to eq(user) + + # Doing it again doesn't raise an error + post "/admin/users/#{user.id}/groups.json", params: { + group_id: group.id + } + + expect(response.status).to eq(200) + end + end + + describe '#remove_group' do + it "also clears the user's primary group" do + g = Fabricate(:group) + u = Fabricate(:user, primary_group: g) + delete "/admin/users/#{u.id}/groups/#{g.id}.json" + + expect(response.status).to eq(200) + expect(u.reload.primary_group).to eq(nil) + end + end + + describe '#trust_level' do + let(:another_user) { Fabricate(:coding_horror, created_at: 1.month.ago) } + + it "raises an error when the user doesn't have permission" do + sign_in(user) + put "/admin/users/#{another_user.id}/trust_level.json" + expect(response.status).to eq(404) + end + + it "returns a 422 if the username doesn't exist" do + put "/admin/users/123123/trust_level.json" + expect(response.status).to eq(422) + end + + it "upgrades the user's trust level" do + put "/admin/users/#{another_user.id}/trust_level.json", params: { level: 2 } + + expect(response.status).to eq(200) + another_user.reload + expect(another_user.trust_level).to eq(2) + + expect(UserHistory.where( + target_user: another_user, + acting_user: admin, + action: UserHistory.actions[:change_trust_level] + ).count).to eq(1) + end + + it "raises no error when demoting a user below their current trust level (locks trust level)" do + stat = another_user.user_stat + stat.topics_entered = SiteSetting.tl1_requires_topics_entered + 1 + stat.posts_read_count = SiteSetting.tl1_requires_read_posts + 1 + stat.time_read = SiteSetting.tl1_requires_time_spent_mins * 60 + stat.save! + another_user.update_attributes(trust_level: TrustLevel[1]) + + put "/admin/users/#{another_user.id}/trust_level.json", params: { + level: TrustLevel[0] + } + + expect(response.status).to eq(200) + another_user.reload + expect(another_user.trust_level).to eq(TrustLevel[0]) + expect(another_user.manual_locked_trust_level).to eq(TrustLevel[0]) + end + end + + describe '#grant_moderation' do + let(:another_user) { Fabricate(:coding_horror) } + + it "raises an error when the user doesn't have permission" do + sign_in(user) + put "/admin/users/#{another_user.id}/grant_moderation.json" + expect(response.status).to eq(404) + end + + it "returns a 403 if the username doesn't exist" do + put "/admin/users/123123/grant_moderation.json" + expect(response.status).to eq(403) + end + + it 'updates the moderator flag' do + put "/admin/users/#{another_user.id}/grant_moderation.json" + expect(response.status).to eq(200) + another_user.reload + expect(another_user.moderator).to eq(true) + end + end + + describe '#revoke_moderation' do + let(:moderator) { Fabricate(:moderator) } + + it 'raises an error unless the user can revoke access' do + sign_in(user) + put "/admin/users/#{moderator.id}/revoke_moderation.json" + expect(response.status).to eq(404) + moderator.reload + expect(moderator.moderator).to eq(true) + end + + it 'updates the moderator flag' do + put "/admin/users/#{moderator.id}/revoke_moderation.json" + expect(response.status).to eq(200) + moderator.reload + expect(moderator.moderator).to eq(false) + end + end + + describe '#primary_group' do + let(:group) { Fabricate(:group) } + let(:another_user) { Fabricate(:coding_horror) } + + it "raises an error when the user doesn't have permission" do + sign_in(user) + put "/admin/users/#{another_user.id}/primary_group.json" + expect(response.status).to eq(404) + another_user.reload + expect(another_user.primary_group_id).to eq(nil) + end + + it "returns a 404 if the user doesn't exist" do + put "/admin/users/123123/primary_group.json" + expect(response.status).to eq(403) + end + + it "changes the user's primary group" do + group.add(another_user) + put "/admin/users/#{another_user.id}/primary_group.json", params: { + primary_group_id: group.id + } + + expect(response.status).to eq(200) + another_user.reload + expect(another_user.primary_group_id).to eq(group.id) + end + + it "doesn't change primary group if they aren't a member of the group" do + put "/admin/users/#{another_user.id}/primary_group.json", params: { + primary_group_id: group.id + } + + expect(response.status).to eq(200) + another_user.reload + expect(another_user.primary_group_id).to eq(nil) + end + + it "remove user's primary group" do + group.add(another_user) + + put "/admin/users/#{another_user.id}/primary_group.json", params: { + primary_group_id: "" + } + + expect(response.status).to eq(200) + another_user.reload + expect(another_user.primary_group_id).to eq(nil) + end + end + + describe '#destroy' do + let(:delete_me) { Fabricate(:user) } + + it "returns a 403 if the user doesn't exist" do + delete "/admin/users/123123drink.json" + expect(response.status).to eq(403) + end + + context "user has post" do + let(:topic) { Fabricate(:topic, user: delete_me) } + let!(:post) { Fabricate(:post, topic: topic, user: delete_me) } + + it "returns an api response that the user can't be deleted because it has posts" do + delete "/admin/users/#{delete_me.id}.json" + expect(response.status).to eq(403) + json = ::JSON.parse(response.body) + expect(json['deleted']).to eq(false) + end + + it "doesn't return an error if delete_posts == true" do + delete "/admin/users/#{delete_me.id}.json", params: { delete_posts: true } + expect(response.status).to eq(200) + expect(Post.where(id: post.id).count).to eq(0) + expect(Topic.where(id: topic.id).count).to eq(0) + expect(User.where(id: delete_me.id).count).to eq(0) + end + end + + it "deletes the user record" do + delete "/admin/users/#{delete_me.id}.json" + expect(response.status).to eq(200) + expect(User.where(id: delete_me.id).count).to eq(0) + end + end + + describe '#activate' do + let(:reg_user) { Fabricate(:inactive_user) } + + it "returns success" do + put "/admin/users/#{reg_user.id}/activate.json" + expect(response.status).to eq(200) + json = ::JSON.parse(response.body) + expect(json['success']).to eq("OK") + reg_user.reload + expect(reg_user.active).to eq(true) + end + + it "should confirm email even when the tokens are expired" do + reg_user.email_tokens.update_all(confirmed: false, expired: true) + + reg_user.reload + expect(reg_user.email_confirmed?).to eq(false) + + put "/admin/users/#{reg_user.id}/activate.json" + expect(response.status).to eq(200) + + reg_user.reload + expect(reg_user.email_confirmed?).to eq(true) + end + end + + describe '#log_out' do + let(:reg_user) { Fabricate(:user) } + + it "returns success" do + post "/admin/users/#{reg_user.id}/log_out.json" + expect(response.status).to eq(200) + json = ::JSON.parse(response.body) + expect(json['success']).to eq("OK") + end + + it "returns 404 when user_id does not exist" do + post "/admin/users/123123drink/log_out.json" + expect(response.status).to eq(404) + end + end + + describe '#silence' do + let(:reg_user) { Fabricate(:user) } + + it "raises an error when the user doesn't have permission" do + sign_in(user) + put "/admin/users/#{reg_user.id}/silence.json" + expect(response.status).to eq(404) + reg_user.reload + expect(reg_user).not_to be_silenced + end + + it "returns a 403 if the user doesn't exist" do + put "/admin/users/123123/silence.json" + expect(response.status).to eq(403) + end + + it "punishes the user for spamming" do + put "/admin/users/#{reg_user.id}/silence.json" + expect(response.status).to eq(200) + reg_user.reload + expect(reg_user).to be_silenced + end + + it "can have an associated post" do + silence_post = Fabricate(:post, user: reg_user) + + put "/admin/users/#{reg_user.id}/silence.json", params: { + post_id: silence_post.id, + post_action: 'edit', + post_edit: "this is the new contents for the post" + } + expect(response.status).to eq(200) + + silence_post.reload + expect(silence_post.raw).to eq("this is the new contents for the post") + + log = UserHistory.where( + target_user_id: reg_user.id, + action: UserHistory.actions[:silence_user] + ).first + expect(log).to be_present + expect(log.post_id).to eq(silence_post.id) + + reg_user.reload + expect(reg_user).to be_silenced + end + + it "will set a length of time if provided" do + future_date = 1.month.from_now.to_date + put "/admin/users/#{reg_user.id}/silence.json", params: { + silenced_till: future_date + } + + expect(response.status).to eq(200) + reg_user.reload + expect(reg_user).to be_silenced + expect(reg_user.silenced_till).to eq(future_date) + end + + it "will send a message if provided" do + expect do + put "/admin/users/#{reg_user.id}/silence.json", params: { + message: "Email this to the user" + } + end.to change { Jobs::CriticalUserEmail.jobs.size }.by(1) + + expect(response.status).to eq(200) + reg_user.reload + expect(reg_user).to be_silenced + end + end + + describe '#unsilence' do + let(:reg_user) { Fabricate(:user, silenced_till: 10.years.from_now) } + + it "raises an error when the user doesn't have permission" do + sign_in(user) + put "/admin/users/#{reg_user.id}/unsilence.json" + expect(response.status).to eq(404) + end + + it "returns a 403 if the user doesn't exist" do + put "/admin/users/123123/unsilence.json" + expect(response.status).to eq(403) + end + + it "unsilences the user" do + put "/admin/users/#{reg_user.id}/unsilence.json" + expect(response.status).to eq(200) + reg_user.reload + expect(reg_user.silenced?).to eq(false) + log = UserHistory.where( + target_user_id: reg_user.id, + action: UserHistory.actions[:unsilence_user] + ).first + expect(log).to be_present + end + end + + describe '#reject_bulk' do + let(:reject_me) { Fabricate(:user) } + let(:reject_me_too) { Fabricate(:user) } + + it 'does nothing without users' do + delete "/admin/users/reject-bulk.json" + expect(response.status).to eq(200) + expect(User.where(id: reject_me.id).count).to eq(1) + expect(User.where(id: reject_me_too.id).count).to eq(1) + end + + it "won't delete users if not allowed" do + sign_in(user) + delete "/admin/users/reject-bulk.json", params: { + users: [reject_me.id] + } + expect(response.status).to eq(404) + expect(User.where(id: reject_me.id).count).to eq(1) + end + + it "reports successes" do + delete "/admin/users/reject-bulk.json", params: { + users: [reject_me.id, reject_me_too.id] + } + + expect(response.status).to eq(200) + json = ::JSON.parse(response.body) + expect(json['success'].to_i).to eq(2) + expect(json['failed'].to_i).to eq(0) + expect(User.where(id: reject_me.id).count).to eq(0) + expect(User.where(id: reject_me_too.id).count).to eq(0) + end + + context 'failures' do + it 'can handle some successes and some failures' do + stat = reject_me_too.user_stat + stat.first_post_created_at = (SiteSetting.delete_user_max_post_age.to_i + 1).days.ago + stat.post_count = 10 + stat.save! + + delete "/admin/users/reject-bulk.json", params: { + users: [reject_me.id, reject_me_too.id] + } + + expect(response.status).to eq(200) + json = ::JSON.parse(response.body) + expect(json['success'].to_i).to eq(1) + expect(json['failed'].to_i).to eq(1) + expect(User.where(id: reject_me.id).count).to eq(0) + expect(User.where(id: reject_me_too.id).count).to eq(1) + end + + it 'reports failure due to a user still having posts' do + Fabricate(:post, user: reject_me) + + delete "/admin/users/reject-bulk.json", params: { + users: [reject_me.id] + } + + expect(response.status).to eq(200) + json = ::JSON.parse(response.body) + expect(json['success'].to_i).to eq(0) + expect(json['failed'].to_i).to eq(1) + expect(User.where(id: reject_me.id).count).to eq(1) + end + end + end + + describe '#ip_info' do + it "uses ipinfo.io webservice to retrieve the info" do + ip = "192.168.1.1" + ip_data = { + city: "Jeddah", + country: "SA", + ip: ip + } + url = "https://ipinfo.io/#{ip}/json" + + stub_request(:get, url).to_return(status: 200, body: ip_data.to_json) + get "/admin/users/ip-info.json", params: { ip: ip } + expect(response.status).to eq(200) + expect(JSON.parse(response.body).symbolize_keys).to eq(ip_data) + end + end + + describe '#delete_other_accounts_with_same_ip' do + it "works" do + user_a = Fabricate(:user, ip_address: "42.42.42.42") + user_b = Fabricate(:user, ip_address: "42.42.42.42") + + delete "/admin/users/delete-others-with-same-ip.json", params: { + ip: "42.42.42.42", exclude: -1, order: "trust_level DESC" + } + expect(response.status).to eq(200) + expect(User.where(id: user_a.id).count).to eq(0) + expect(User.where(id: user_b.id).count).to eq(0) + end + end + + describe '#invite_admin' do + let(:api_key) { Fabricate(:api_key, user: admin, key: SecureRandom.hex) } + let(:api_params) do + { api_key: api_key.key, api_username: admin.username } + end + + it "doesn't work when not via API" do + post "/admin/users/invite_admin.json", params: { + name: 'Bill', username: 'bill22', email: 'bill@bill.com' + } + + expect(response.status).to eq(403) + end + + it 'should invite admin' do + expect do + post "/admin/users/invite_admin.json", params: api_params.merge( + name: 'Bill', username: 'bill22', email: 'bill@bill.com' + ) + end.to change { Jobs::CriticalUserEmail.jobs.size }.by(1) + + expect(response.status).to eq(200) + + u = User.find_by_email('bill@bill.com') + expect(u.name).to eq("Bill") + expect(u.username).to eq("bill22") + expect(u.admin).to eq(true) + end + + it "doesn't send the email with send_email falsey" do + expect do + post "/admin/users/invite_admin.json", params: api_params.merge( + name: 'Bill', username: 'bill22', email: 'bill@bill.com', send_email: '0' + ) + end.to change { Jobs::CriticalUserEmail.jobs.size }.by(0) + + expect(response.status).to eq(200) + json = ::JSON.parse(response.body) + expect(json["password_url"]).to be_present + end + end + + describe '#sync_sso' do + let(:sso) { SingleSignOn.new } + let(:sso_secret) { "sso secret" } + + before do + SiteSetting.email_editable = false + SiteSetting.sso_url = "https://www.example.com/sso" + SiteSetting.enable_sso = true + SiteSetting.sso_overrides_email = true + SiteSetting.sso_overrides_name = true + SiteSetting.sso_overrides_username = true + SiteSetting.sso_secret = sso_secret + sso.sso_secret = sso_secret + end + + it 'can sync up with the sso' do + sso.name = "Bob The Bob" + sso.username = "bob" + sso.email = "bob@bob.com" + sso.external_id = "1" + + user = DiscourseSingleSignOn.parse(sso.payload).lookup_or_create_user + + sso.name = "Bill" + sso.username = "Hokli$$!!" + sso.email = "bob2@bob.com" + + post "/admin/users/sync_sso.json", params: Rack::Utils.parse_query(sso.payload) + expect(response.status).to eq(200) + + user.reload + expect(user.email).to eq("bob2@bob.com") + expect(user.name).to eq("Bill") + expect(user.username).to eq("Hokli") + end + + it 'should create new users' do + sso.name = "Dr. Claw" + sso.username = "dr_claw" + sso.email = "dr@claw.com" + sso.external_id = "2" + post "/admin/users/sync_sso.json", params: Rack::Utils.parse_query(sso.payload) + expect(response.status).to eq(200) + + user = User.find_by_email('dr@claw.com') + expect(user).to be_present + expect(user.ip_address).to be_blank + end + + it 'should return the right message if the record is invalid' do + sso.email = "" + sso.name = "" + sso.external_id = "1" + + post "/admin/users/sync_sso.json", params: Rack::Utils.parse_query(sso.payload) + expect(response.status).to eq(403) + expect(JSON.parse(response.body)["message"]).to include("Primary email can't be blank") + end + end + describe '#disable_second_factor' do let(:second_factor) { user.create_totp }