diff --git a/app/controllers/ajax/question_controller.rb b/app/controllers/ajax/question_controller.rb index acdc3109..81ee5ef7 100644 --- a/app/controllers/ajax/question_controller.rb +++ b/app/controllers/ajax/question_controller.rb @@ -27,9 +27,11 @@ class Ajax::QuestionController < AjaxController params.require :anonymousQuestion params.require :rcpt + is_never_anonymous = user_signed_in? && (params[:rcpt].start_with?('grp:') || params[:rcpt] == 'followers') + begin question = Question.create!(content: params[:question], - author_is_anonymous: params[:anonymousQuestion], + author_is_anonymous: is_never_anonymous ? false : params[:anonymousQuestion], user: current_user) rescue ActiveRecord::RecordInvalid => e NewRelic::Agent.notice_error(e) @@ -38,6 +40,11 @@ class Ajax::QuestionController < AjaxController return end + if !user_signed_in? && !question.author_is_anonymous + question.delete + return + end + unless current_user.nil? current_user.increment! :asked_count unless params[:anonymousQuestion] == 'true' end @@ -53,19 +60,27 @@ class Ajax::QuestionController < AjaxController QuestionWorker.perform_async params[:rcpt], current_user.id, question.id rescue ActiveRecord::RecordNotFound => e NewRelic::Agent.notice_error(e) + question.delete @response[:status] = :not_found @response[:message] = I18n.t('messages.question.create.not_found') return end end else - if User.find(params[:rcpt]).nil? + u = User.find_by_id(params[:rcpt]) + if u.nil? @response[:status] = :not_found @response[:message] = I18n.t('messages.question.create.not_found') + question.delete return end - Inbox.create!(user_id: params[:rcpt], question_id: question.id, new: true) + if !u.privacy_allow_anonymous_questions && question.author_is_anonymous + question.delete + return + end + + Inbox.create!(user_id: u.id, question_id: question.id, new: true) end @response[:status] = :okay diff --git a/spec/controllers/ajax/question_controller_spec.rb b/spec/controllers/ajax/question_controller_spec.rb new file mode 100644 index 00000000..5a4b6727 --- /dev/null +++ b/spec/controllers/ajax/question_controller_spec.rb @@ -0,0 +1,401 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe Ajax::QuestionController, :ajax_controller, type: :controller do + let(:user) { FactoryBot.create(:user) } + + describe "#create" do + shared_examples "creates the question" do |check_for_inbox = true| + it "creates the question" do + expect { subject }.to(change { Question.count }.by(1)) + expect(Question.last.content).to eq(question_content) + expect(Question.last.author_is_anonymous).to be(expected_question_anonymous) + expect(Question.last.user).to eq(expected_question_user) + end + + if check_for_inbox + it "adds the question to the target users' inbox" do + expect { subject }.to(change { target_user.inboxes.count }.by(1)) + expect(target_user.inboxes.last.question.content).to eq(question_content) + end + end + + include_examples "returns the expected response" + end + + shared_examples "does not create the question" do |check_for_inbox = true| + it "does not create the question" do + expect { subject }.not_to(change { Question.count }) + end + + if check_for_inbox + it "does not add the question to the target users' inbox" do + expect { subject }.not_to(change { target_user.inboxes.count }) + end + end + + include_examples "returns the expected response" + end + + shared_examples "enqueues a QuestionWorker job" do |expected_rcpt| + it "enqueues a QuestionWorker job" do + allow(QuestionWorker).to receive(:perform_async) + subject + expect(QuestionWorker).to have_received(:perform_async).with(expected_rcpt, user.id, Question.last.id) + end + + include_examples "returns the expected response" + end + + shared_examples "does not enqueue a QuestionWorker job" do + it "does not enqueue a QuestionWorker job" do + allow(QuestionWorker).to receive(:perform_async) + subject + expect(QuestionWorker).not_to have_received(:perform_async) + end + + include_examples "returns the expected response" + end + + let(:target_user) { FactoryBot.create(:user, privacy_allow_anonymous_questions: user_allows_anonymous_questions) } + let(:params) do + { + question: question_content, + anonymousQuestion: anonymous_question, + rcpt: rcpt + } + end + + subject { post(:create, params: params) } + + context "when user is signed in" do + let(:question_content) { "Was letzte Preis?" } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + let(:expected_question_user) { user } + + before(:each) { sign_in(user) } + + context "when rcpt is a valid user" do + let(:rcpt) { target_user.id } + + context "when user allows anonymous questions" do + let(:user_allows_anonymous_questions) { true } + + context "when anonymousQuestion is true" do + let(:anonymous_question) { "true" } + let(:expected_question_anonymous) { true } + + include_examples "creates the question" + end + + context "when anonymousQuestion is false" do + let(:anonymous_question) { "false" } + let(:expected_question_anonymous) { false } + + include_examples "creates the question" + end + end + + context "when user does not allow anonymous questions" do + let(:user_allows_anonymous_questions) { false } + + context "when anonymousQuestion is true" do + let(:anonymous_question) { "true" } + let(:expected_response) do + { + "success" => false, + "status" => "unknown", + "message" => anything + } + end + + include_examples "does not create the question" + end + + context "when anonymousQuestion is false" do + let(:anonymous_question) { "false" } + let(:expected_question_anonymous) { false } + + include_examples "creates the question" + end + end + end + + context "when rcpt is followers" do + let(:rcpt) { "followers" } + + context "when anonymousQuestion is true" do + let(:anonymous_question) { "true" } + let(:expected_question_anonymous) { false } + + include_examples "creates the question", false + include_examples "enqueues a QuestionWorker job", "followers" + end + + context "when anonymousQuestion is false" do + let(:anonymous_question) { "false" } + let(:expected_question_anonymous) { false } + + include_examples "creates the question", false + include_examples "enqueues a QuestionWorker job", "followers" + end + end + + context "when rcpt is a group" do + let(:rcpt) { "grp:foobar" } + + context "when group exists" do + let(:group) { FactoryBot.create(:group, display_name: "FooBar", user: user) } + before { group } + + context "when anonymousQuestion is true" do + let(:anonymous_question) { "true" } + let(:expected_question_anonymous) { false } + + include_examples "creates the question", false + include_examples "enqueues a QuestionWorker job", "grp:foobar" + end + + context "when anonymousQuestion is false" do + let(:anonymous_question) { "false" } + let(:expected_question_anonymous) { false } + + include_examples "creates the question", false + include_examples "enqueues a QuestionWorker job", "grp:foobar" + end + end + + context "when group does not exist" do + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + context "when anonymousQuestion is true" do + let(:anonymous_question) { "true" } + + include_examples "does not create the question", false + include_examples "does not enqueue a QuestionWorker job" + end + + context "when anonymousQuestion is false" do + let(:anonymous_question) { "false" } + + include_examples "does not create the question", false + include_examples "does not enqueue a QuestionWorker job" + end + end + end + + context "when rcpt is a non-existent user" do + let(:rcpt) { "tripmeister_eder" } + let(:anonymous_question) { "false" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + include_examples "does not create the question", false + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:target_user) { FactoryBot.create(:user, privacy_allow_anonymous_questions: user_allows_anonymous_questions) } + let(:question_content) { "Was letzte Preis?" } + let(:anonymous_question) { "true" } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + let(:expected_question_anonymous) { true } + let(:expected_question_user) { nil } + + context "when rcpt is a valid user" do + let(:rcpt) { target_user.id } + + context "when user allows anonymous questions" do + let(:user_allows_anonymous_questions) { true } + + include_examples "creates the question" + + context "when anonymousQuestion is false" do + let(:anonymous_question) { "false" } + let(:expected_response) do + { + "success" => false, + "status" => "unknown", + "message" => anything + } + end + + include_examples "does not create the question" + end + end + + context "when user does not allow anonymous questions" do + let(:user_allows_anonymous_questions) { false } + let(:expected_response) do + { + "success" => false, + "status" => "unknown", + "message" => anything + } + end + + include_examples "does not create the question" + + context "when anonymousQuestion is false" do + let(:anonymous_question) { "false" } + + include_examples "does not create the question" + end + end + end + + context "when rcpt is followers" do + let(:rcpt) { "followers" } + + include_examples "does not enqueue a QuestionWorker job" + end + + context "when rcpt is a group" do + let(:rcpt) { "grp:foobar" } + + include_examples "does not enqueue a QuestionWorker job" + end + + context "when rcpt is a non-existent user" do + let(:rcpt) { "tripmeister_eder" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + include_examples "does not create the question", false + end + end + end + + describe "#destroy" do + shared_examples "does not delete the question" do |expected_status| + let(:expected_response) do + { + "success" => false, + "status" => expected_status, + "message" => anything + } + end + + it "does not delete the question" do + question # ensure we already have it in the db + expect { subject }.not_to(change { Question.count }) + end + + include_examples "returns the expected response" + end + + let(:question_user) { user } + let(:question) { FactoryBot.create(:question, user: question_user) } + let(:question_id) { question.id } + + let(:params) do + { + question: question_id + } + end + + subject { delete(:destroy, params: params) } + + context "when user is signed in" do + shared_examples "deletes the question" do + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + it "deletes the question" do + question # ensure we already have it in the db + expect { subject }.to(change { Question.count }.by(-1)) + end + + include_examples "returns the expected response" + end + + before(:each) { sign_in(user) } + + context "when the question exists and was made by the current user" do + include_examples "deletes the question" + end + + context "when the question exists and was not made by the current user" do + let(:question_user) { FactoryBot.create(:user) } + + include_examples "does not delete the question", "not_authorized" + + %i[moderator administrator].each do |privileged_role| + context "when the current user is a #{privileged_role}" do + around do |example| + user.add_role privileged_role + example.run + user.remove_role privileged_role + end + + include_examples "deletes the question" + end + end + end + + context "when the question exists and was not made by any registered user" do + let(:question_user) { nil } + + include_examples "does not delete the question", "not_authorized" + + %i[moderator administrator].each do |privileged_role| + context "when the current user is a #{privileged_role}" do + around do |example| + user.add_role privileged_role + example.run + user.remove_role privileged_role + end + + include_examples "deletes the question" + end + end + end + + context "when the question does not exist" do + let(:question_id) { "sonic_the_hedgehog" } + + include_examples "does not delete the question", "not_found" + end + end + + context "when user is not signed in" do + include_examples "does not delete the question", "err" + end + end +end diff --git a/spec/factories/group.rb b/spec/factories/group.rb new file mode 100644 index 00000000..8b9afdde --- /dev/null +++ b/spec/factories/group.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :group do + sequence(:display_name) { |i| "#{Faker::Internet.username(specifier: 0..12, separators: %w[_])}#{i}" } + user { FactoryBot.build(:user) } + end +end