From 5bb94927a23571c121919be41e0648041b12859b Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 30 Apr 2020 20:12:13 +0200 Subject: [PATCH 01/17] Add codecov --- .github/workflows/retrospring.yml | 1 + Gemfile | 2 +- Gemfile.lock | 9 ++++++--- spec/support/simplecov.rb | 7 +++++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/retrospring.yml b/.github/workflows/retrospring.yml index bf6914fb..fc052a12 100644 --- a/.github/workflows/retrospring.yml +++ b/.github/workflows/retrospring.yml @@ -65,5 +65,6 @@ jobs: - name: Run tests run: bundle exec rake spec env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} REDIS_URL: "redis://localhost:${{ job.services.redis.ports[6379] }}" diff --git a/Gemfile b/Gemfile index 1fe983a4..fb90b52c 100644 --- a/Gemfile +++ b/Gemfile @@ -98,7 +98,7 @@ group :development, :test do gem 'poltergeist' gem 'simplecov', require: false gem 'simplecov-json', require: false - gem 'simplecov-rcov', require: false + gem 'codecov', require: false gem 'database_cleaner' gem 'better_errors' gem 'letter_opener' # Use this just in local test environments diff --git a/Gemfile.lock b/Gemfile.lock index 31414a32..e6d75ba7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,6 +100,10 @@ GEM xpath (~> 3.2) climate_control (0.2.0) cliver (0.3.2) + codecov (0.1.16) + json + simplecov + url coderay (1.1.2) coffee-rails (4.2.2) coffee-script (>= 2.2.0) @@ -448,8 +452,6 @@ GEM simplecov-json (0.2.1) json simplecov - simplecov-rcov (0.2.3) - simplecov (>= 0.4.1) spring (2.1.0) sprockets (3.7.2) concurrent-ruby (~> 1.0) @@ -492,6 +494,7 @@ GEM unicorn (5.5.4) kgio (~> 2.6) raindrops (~> 0.7) + url (0.3.2) warden (1.2.8) rack (>= 2.0.6) web-console (3.7.0) @@ -518,6 +521,7 @@ DEPENDENCIES brakeman byebug capybara + codecov coffee-rails (~> 4.1) colorize database_cleaner @@ -574,7 +578,6 @@ DEPENDENCIES sidekiq (< 6) simplecov simplecov-json - simplecov-rcov spring (~> 2.0) sweetalert-rails timecop diff --git a/spec/support/simplecov.rb b/spec/support/simplecov.rb index 2cc94286..aa93ee94 100644 --- a/spec/support/simplecov.rb +++ b/spec/support/simplecov.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true require "simplecov" -# require "simplecov-rcov" -# SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter SimpleCov.start "rails" + +if ENV.key?("CODECOV_TOKEN") + require "codecov" + SimpleCov.formatter = SimpleCov::Formatter::Codecov +end From 1fe5e1de6f11932a225796c6c47471cfdeb99c84 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 1 May 2020 10:05:30 +0200 Subject: [PATCH 02/17] Use different branch for codecov to better support GitHub Actions --- Gemfile | 2 +- Gemfile.lock | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index fb90b52c..2c428619 100644 --- a/Gemfile +++ b/Gemfile @@ -98,7 +98,7 @@ group :development, :test do gem 'poltergeist' gem 'simplecov', require: false gem 'simplecov-json', require: false - gem 'codecov', require: false + gem 'codecov', require: false, git: "https://github.com/timoschilling/codecov-ruby", ref: "add-github-action-support" # watch this space: https://github.com/codecov/codecov-ruby/pull/52 gem 'database_cleaner' gem 'better_errors' gem 'letter_opener' # Use this just in local test environments diff --git a/Gemfile.lock b/Gemfile.lock index e6d75ba7..86eb7a73 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,6 +16,16 @@ GIT specs: questiongenerator (0.1.0) +GIT + remote: https://github.com/timoschilling/codecov-ruby + revision: d98380b89acfe4c014aafc50c5ec0b075c32ef7a + ref: add-github-action-support + specs: + codecov (0.1.16) + json + simplecov + url + GEM remote: https://rubygems.org/ remote: https://rails-assets.org/ @@ -100,10 +110,6 @@ GEM xpath (~> 3.2) climate_control (0.2.0) cliver (0.3.2) - codecov (0.1.16) - json - simplecov - url coderay (1.1.2) coffee-rails (4.2.2) coffee-script (>= 2.2.0) @@ -521,7 +527,7 @@ DEPENDENCIES brakeman byebug capybara - codecov + codecov! coffee-rails (~> 4.1) colorize database_cleaner From 781a4ba8b45580ea759968c22c5ffcf880330532 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 1 May 2020 10:13:42 +0200 Subject: [PATCH 03/17] Use codecov's bash-based github action --- .github/workflows/retrospring.yml | 5 ++++- Gemfile | 2 +- Gemfile.lock | 15 +++------------ spec/support/simplecov.rb | 6 +++--- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/.github/workflows/retrospring.yml b/.github/workflows/retrospring.yml index fc052a12..b1ee1108 100644 --- a/.github/workflows/retrospring.yml +++ b/.github/workflows/retrospring.yml @@ -65,6 +65,9 @@ jobs: - name: Run tests run: bundle exec rake spec env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} REDIS_URL: "redis://localhost:${{ job.services.redis.ports[6379] }}" + - uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: ./coverage/coverage.xml diff --git a/Gemfile b/Gemfile index 2c428619..f06b049a 100644 --- a/Gemfile +++ b/Gemfile @@ -98,7 +98,7 @@ group :development, :test do gem 'poltergeist' gem 'simplecov', require: false gem 'simplecov-json', require: false - gem 'codecov', require: false, git: "https://github.com/timoschilling/codecov-ruby", ref: "add-github-action-support" # watch this space: https://github.com/codecov/codecov-ruby/pull/52 + gem 'simplecov-cobertura', require: false gem 'database_cleaner' gem 'better_errors' gem 'letter_opener' # Use this just in local test environments diff --git a/Gemfile.lock b/Gemfile.lock index 86eb7a73..bf356d05 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,16 +16,6 @@ GIT specs: questiongenerator (0.1.0) -GIT - remote: https://github.com/timoschilling/codecov-ruby - revision: d98380b89acfe4c014aafc50c5ec0b075c32ef7a - ref: add-github-action-support - specs: - codecov (0.1.16) - json - simplecov - url - GEM remote: https://rubygems.org/ remote: https://rails-assets.org/ @@ -454,6 +444,8 @@ GEM simplecov (0.18.5) docile (~> 1.1) simplecov-html (~> 0.11) + simplecov-cobertura (1.3.1) + simplecov (~> 0.8) simplecov-html (0.12.2) simplecov-json (0.2.1) json @@ -500,7 +492,6 @@ GEM unicorn (5.5.4) kgio (~> 2.6) raindrops (~> 0.7) - url (0.3.2) warden (1.2.8) rack (>= 2.0.6) web-console (3.7.0) @@ -527,7 +518,6 @@ DEPENDENCIES brakeman byebug capybara - codecov! coffee-rails (~> 4.1) colorize database_cleaner @@ -583,6 +573,7 @@ DEPENDENCIES sass-rails (~> 5.0) sidekiq (< 6) simplecov + simplecov-cobertura simplecov-json spring (~> 2.0) sweetalert-rails diff --git a/spec/support/simplecov.rb b/spec/support/simplecov.rb index aa93ee94..c07246da 100644 --- a/spec/support/simplecov.rb +++ b/spec/support/simplecov.rb @@ -3,7 +3,7 @@ require "simplecov" SimpleCov.start "rails" -if ENV.key?("CODECOV_TOKEN") - require "codecov" - SimpleCov.formatter = SimpleCov::Formatter::Codecov +if ENV.key?("GITHUB_ACTIONS") + require "simplecov-cobertura" + SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter end From d492cd34f6af45b18a6b144765abecd624595423 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Wed, 29 Apr 2020 19:54:43 +0200 Subject: [PATCH 04/17] AjaxController: also rescue from StandardError --- app/controllers/ajax_controller.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/controllers/ajax_controller.rb b/app/controllers/ajax_controller.rb index 8331130d..03947c1b 100644 --- a/app/controllers/ajax_controller.rb +++ b/app/controllers/ajax_controller.rb @@ -6,6 +6,18 @@ class AjaxController < ApplicationController respond_to :json + rescue_from(StandardError) do |e| + NewRelic::Agent.notice_error(e) + + @response = { + success: false, + message: "Something went wrong", + status: :err + } + + return_response + end + rescue_from(ActiveRecord::RecordNotFound) do |e| NewRelic::Agent.notice_error(e) From 07f489430c09a399b31955f8d0612cf78e169b88 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Wed, 29 Apr 2020 19:55:05 +0200 Subject: [PATCH 05/17] add spec for Ajax::CommentController --- .../ajax/comment_controller_spec.rb | 233 ++++++++++++++++++ spec/factories/comment.rb | 12 + 2 files changed, 245 insertions(+) create mode 100644 spec/controllers/ajax/comment_controller_spec.rb create mode 100644 spec/factories/comment.rb diff --git a/spec/controllers/ajax/comment_controller_spec.rb b/spec/controllers/ajax/comment_controller_spec.rb new file mode 100644 index 00000000..a91b7fe3 --- /dev/null +++ b/spec/controllers/ajax/comment_controller_spec.rb @@ -0,0 +1,233 @@ +# coding: utf-8 +# frozen_string_literal: true + +require "rails_helper" + +describe Ajax::CommentController, type: :controller do + shared_examples "returns the expected response" do + it "returns the expected response" do + expect(JSON.parse(subject.body)).to match(expected_response) + end + end + + let(:user) { FactoryBot.create(:user) } + let(:answer) { FactoryBot.create(:answer, user: FactoryBot.create(:user)) } + + describe "#create" do + let(:params) do + { + answer: answer_id, + comment: comment + }.compact + end + + subject { post(:create, params: params) } + + context "when user is signed in" do + shared_examples "creates the comment" do + it "creates a comment to the answer" do + expect { subject }.to(change { Comment.count }.by(1)) + expect(answer.reload.comments.ids).to include(Comment.last.id) + end + + include_examples "returns the expected response" + end + + shared_examples "does not create the comment" do + it "does not create a comment" do + expect { subject }.not_to(change { Comment.count }) + end + + include_examples "returns the expected response" + end + + before(:each) { sign_in(user) } + + context "when all parameters are given" do + let(:comment) { "// Here be dragons." } + + context "when answer exists" do + let(:answer_id) { answer.id } + + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything, + "render" => anything, + "count" => 1 + } + end + + include_examples "creates the comment" + + context "when comment is too long" do + let(:comment) { "E" * 621 } + let(:expected_response) do + { + "success" => false, + "status" => "rec_inv", + "message" => anything + } + end + + include_examples "does not create the comment" + end + end + + context "when answer does not exist" do + let(:answer_id) { "nein!" } + + let(:expected_response) do + { + "success" => false, + "status" => anything, + "message" => anything + } + end + + include_examples "does not create the comment" + end + end + + context "when some parameters are missing" do + let(:answer_id) { nil } + let(:comment) { "" } + + let(:expected_response) do + { + "success" => false, + "status" => "parameter_error", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:answer_id) { answer.id } + let(:comment) { "HACKED" } + + let(:expected_response) do + { + "success" => false, + "status" => "err", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + describe "#destroy" do + let(:answer_user) { FactoryBot.create(:user) } + let(:answer) { FactoryBot.create(:answer, user: answer_user) } + let(:comment_user) { user } + let(:comment) { FactoryBot.create(:comment, user: comment_user, answer: answer) } + let(:comment_id) { comment.id } + + let(:params) do + { + comment: comment_id + } + end + + subject { delete(:destroy, params: params) } + + context "when user is signed in" do + shared_examples "deletes the comment" do + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything, + "count" => 0 + } + end + + it "deletes the comment" do + comment # ensure we already have it in the db + expect { subject }.to(change { Comment.count }.by(-1)) + end + + include_examples "returns the expected response" + end + + shared_examples "does not delete the comment" do + let(:expected_response) do + { + "success" => false, + "status" => "nopriv", + "message" => anything + } + end + + it "does not delete the comment" do + comment # ensure we already have it in the db + expect { subject }.not_to(change { Comment.count }) + end + + include_examples "returns the expected response" + end + + before(:each) { sign_in(user) } + + context "when the comment exists and was made by the current user" do + include_examples "deletes the comment" + end + + context "when the comment exists and was not made by the current user" do + let(:comment_user) { FactoryBot.create(:user) } + + include_examples "does not delete the comment" + + context "when the current user created the answer" do + let(:answer_user) { user } + + include_examples "deletes the comment" + end + + %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 comment" + end + end + end + + context "when the comment does not exist" do + let(:comment_id) { "sonic_the_hedgehog" } + + let(:expected_response) do + { + "success" => false, + "status" => anything, + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:expected_response) do + { + "success" => false, + "status" => "nopriv", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end +end diff --git a/spec/factories/comment.rb b/spec/factories/comment.rb new file mode 100644 index 00000000..7ce5a312 --- /dev/null +++ b/spec/factories/comment.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :comment do + transient do + answer_content { Faker::Lorem.sentence } + end + + content { Faker::Lorem.sentence } + answer { FactoryBot.build(:answer, content: answer_content) } + end +end From dd5f718f31f26948e400be43abfef66ffd049bd5 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 30 Apr 2020 18:39:33 +0200 Subject: [PATCH 06/17] Question: user association is optional --- app/models/question.rb | 2 +- spec/controllers/ajax/answer_controller_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/question.rb b/app/models/question.rb index 65daf030..78ea192d 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -1,7 +1,7 @@ class Question < ApplicationRecord include Question::AnswerMethods - belongs_to :user + belongs_to :user, optional: true has_many :answers, dependent: :destroy has_many :inboxes, dependent: :destroy diff --git a/spec/controllers/ajax/answer_controller_spec.rb b/spec/controllers/ajax/answer_controller_spec.rb index 55aa7ad6..d80c5b82 100644 --- a/spec/controllers/ajax/answer_controller_spec.rb +++ b/spec/controllers/ajax/answer_controller_spec.rb @@ -186,7 +186,7 @@ describe Ajax::AnswerController, type: :controller do describe "#destroy" do let(:answer_user) { user } - let(:question) { FactoryBot.create(:question, user: FactoryBot.create(:user)) } + let(:question) { FactoryBot.create(:question) } let(:answer) { FactoryBot.create(:answer, user: answer_user, question: question) } let(:answer_id) { answer.id } From fcdb640b9abb2667a492c49f704fe1f31a6b7dd8 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 30 Apr 2020 19:04:20 +0200 Subject: [PATCH 07/17] add spec for Ajax::SmileController --- .../controllers/ajax/smile_controller_spec.rb | 300 ++++++++++++++++++ spec/factories/comment_smile.rb | 8 + spec/factories/smile.rb | 8 + 3 files changed, 316 insertions(+) create mode 100644 spec/controllers/ajax/smile_controller_spec.rb create mode 100644 spec/factories/comment_smile.rb create mode 100644 spec/factories/smile.rb diff --git a/spec/controllers/ajax/smile_controller_spec.rb b/spec/controllers/ajax/smile_controller_spec.rb new file mode 100644 index 00000000..3b739f24 --- /dev/null +++ b/spec/controllers/ajax/smile_controller_spec.rb @@ -0,0 +1,300 @@ +# coding: utf-8 +# frozen_string_literal: true + +require "rails_helper" + +describe Ajax::SmileController, type: :controller do + shared_examples "returns the expected response" do + it "returns the expected response" do + expect(JSON.parse(subject.body)).to match(expected_response) + end + end + + let(:user) { FactoryBot.create(:user) } + + describe "#create" do + let(:params) do + { + id: answer_id + }.compact + end + let(:answer) { FactoryBot.create(:answer, user: user) } + + subject { post(:create, params: params) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + + context "when answer exists" do + let(:answer_id) { answer.id } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + it "creates a smile to the answer" do + expect { subject }.to(change { Smile.count }.by(1)) + expect(answer.reload.smiles.ids).to include(Smile.last.id) + end + + include_examples "returns the expected response" + end + + context "when answer does not exist" do + let(:answer_id) { "nein!" } + + let(:expected_response) do + { + "success" => false, + "status" => anything, + "message" => anything + } + end + + it "does not create a smile" do + expect { subject }.not_to(change { Smile.count }) + end + + include_examples "returns the expected response" + end + + context "when some parameters are missing" do + let(:answer_id) { nil } + + let(:expected_response) do + { + "success" => false, + "status" => "parameter_error", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:answer_id) { answer.id } + + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + describe "#destroy" do + let(:answer) { FactoryBot.create(:answer, user: user) } + let(:smile) { FactoryBot.create(:smile, user: user, answer: answer) } + let(:answer_id) { answer.id } + + let(:params) do + { + id: answer_id + } + end + + subject { delete(:destroy, params: params) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + + context "when the smile exists" do + # ensure we already have it in the db + before(:each) { smile } + + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + it "deletes the smile" do + expect { subject }.to(change { Smile.count }.by(-1)) + end + + include_examples "returns the expected response" + end + + context "when the smile does not exist" do + let(:answer_id) { "sonic_the_hedgehog" } + + let(:expected_response) do + { + "success" => false, + "status" => anything, + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + describe "#create_comment" do + let(:params) do + { + id: comment_id + }.compact + end + let(:answer) { FactoryBot.create(:answer, user: user) } + let(:comment) { FactoryBot.create(:comment, user: user, answer: answer) } + + subject { post(:create_comment, params: params) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + + context "when comment exists" do + let(:comment_id) { comment.id } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + it "creates a smile to the comment" do + expect { subject }.to(change { CommentSmile.count }.by(1)) + expect(comment.reload.smiles.ids).to include(CommentSmile.last.id) + end + + include_examples "returns the expected response" + end + + context "when comment does not exist" do + let(:comment_id) { "nein!" } + + let(:expected_response) do + { + "success" => false, + "status" => anything, + "message" => anything + } + end + + it "does not create a smile" do + expect { subject }.not_to(change { CommentSmile.count }) + end + + include_examples "returns the expected response" + end + + context "when some parameters are missing" do + let(:comment_id) { nil } + + let(:expected_response) do + { + "success" => false, + "status" => "parameter_error", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:comment_id) { comment.id } + + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + describe "#destroy_comment" do + let(:answer) { FactoryBot.create(:answer, user: user) } + let(:comment) { FactoryBot.create(:comment, user: user, answer: answer) } + let(:comment_smile) { FactoryBot.create(:comment_smile, user: user, comment: comment) } + let(:comment_id) { comment.id } + + let(:params) do + { + id: comment_id + } + end + + subject { delete(:destroy_comment, params: params) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + + context "when the smile exists" do + # ensure we already have it in the db + before(:each) { comment_smile } + + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + it "deletes the smile" do + expect { subject }.to(change { CommentSmile.count }.by(-1)) + end + + include_examples "returns the expected response" + end + + context "when the smile does not exist" do + let(:answer_id) { "sonic_the_hedgehog" } + + let(:expected_response) do + { + "success" => false, + "status" => anything, + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end +end diff --git a/spec/factories/comment_smile.rb b/spec/factories/comment_smile.rb new file mode 100644 index 00000000..3a2b585e --- /dev/null +++ b/spec/factories/comment_smile.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :comment_smile do + user { FactoryBot.build(:user) } + comment { FactoryBot.build(:comment) } + end +end diff --git a/spec/factories/smile.rb b/spec/factories/smile.rb new file mode 100644 index 00000000..1ce23750 --- /dev/null +++ b/spec/factories/smile.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :smile do + user { FactoryBot.build(:user) } + answer { FactoryBot.build(:answer) } + end +end From 6a7e8a30239b217e294ff4d5b6df69643d43db6e Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 30 Apr 2020 19:15:32 +0200 Subject: [PATCH 08/17] spec: bit less code duplication --- spec/controllers/ajax/answer_controller_spec.rb | 8 +------- spec/controllers/ajax/comment_controller_spec.rb | 8 +------- spec/controllers/ajax/smile_controller_spec.rb | 8 +------- spec/shared_examples/ajax_controllers.rb | 13 +++++++++++++ 4 files changed, 16 insertions(+), 21 deletions(-) create mode 100644 spec/shared_examples/ajax_controllers.rb diff --git a/spec/controllers/ajax/answer_controller_spec.rb b/spec/controllers/ajax/answer_controller_spec.rb index d80c5b82..bdc6cef4 100644 --- a/spec/controllers/ajax/answer_controller_spec.rb +++ b/spec/controllers/ajax/answer_controller_spec.rb @@ -3,13 +3,7 @@ require "rails_helper" -describe Ajax::AnswerController, type: :controller do - shared_examples "returns the expected response" do - it "returns the expected response" do - expect(JSON.parse(subject.body)).to match(expected_response) - end - end - +describe Ajax::AnswerController, :ajax_controller, type: :controller do let(:user) { FactoryBot.create(:user) } let(:question) { FactoryBot.create(:question, user: FactoryBot.build(:user, privacy_allow_stranger_answers: asker_allows_strangers)) } let(:asker_allows_strangers) { true } diff --git a/spec/controllers/ajax/comment_controller_spec.rb b/spec/controllers/ajax/comment_controller_spec.rb index a91b7fe3..c071b03f 100644 --- a/spec/controllers/ajax/comment_controller_spec.rb +++ b/spec/controllers/ajax/comment_controller_spec.rb @@ -3,13 +3,7 @@ require "rails_helper" -describe Ajax::CommentController, type: :controller do - shared_examples "returns the expected response" do - it "returns the expected response" do - expect(JSON.parse(subject.body)).to match(expected_response) - end - end - +describe Ajax::CommentController, :ajax_controller, type: :controller do let(:user) { FactoryBot.create(:user) } let(:answer) { FactoryBot.create(:answer, user: FactoryBot.create(:user)) } diff --git a/spec/controllers/ajax/smile_controller_spec.rb b/spec/controllers/ajax/smile_controller_spec.rb index 3b739f24..7f509674 100644 --- a/spec/controllers/ajax/smile_controller_spec.rb +++ b/spec/controllers/ajax/smile_controller_spec.rb @@ -3,13 +3,7 @@ require "rails_helper" -describe Ajax::SmileController, type: :controller do - shared_examples "returns the expected response" do - it "returns the expected response" do - expect(JSON.parse(subject.body)).to match(expected_response) - end - end - +describe Ajax::SmileController, :ajax_controller, type: :controller do let(:user) { FactoryBot.create(:user) } describe "#create" do diff --git a/spec/shared_examples/ajax_controllers.rb b/spec/shared_examples/ajax_controllers.rb new file mode 100644 index 00000000..144e216d --- /dev/null +++ b/spec/shared_examples/ajax_controllers.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +RSpec.shared_context "AjaxController context" do + shared_examples "returns the expected response" do + it "returns the expected response" do + expect(JSON.parse(subject.body)).to match(expected_response) + end + end +end + +RSpec.configure do |c| + c.include_context "AjaxController context", ajax_controller: true +end From 1824fb1c25120f67d16548c9b2637e7358fd1f2b Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 30 Apr 2020 19:57:39 +0200 Subject: [PATCH 09/17] add spec for Ajax::InboxController --- app/controllers/ajax/inbox_controller.rb | 2 + .../controllers/ajax/inbox_controller_spec.rb | 246 ++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 spec/controllers/ajax/inbox_controller_spec.rb diff --git a/app/controllers/ajax/inbox_controller.rb b/app/controllers/ajax/inbox_controller.rb index 0aa99cdc..f0aa994d 100644 --- a/app/controllers/ajax/inbox_controller.rb +++ b/app/controllers/ajax/inbox_controller.rb @@ -46,6 +46,8 @@ class Ajax::InboxController < AjaxController end def remove_all + raise unless user_signed_in? + begin Inbox.where(user: current_user).each { |i| i.remove } rescue => e diff --git a/spec/controllers/ajax/inbox_controller_spec.rb b/spec/controllers/ajax/inbox_controller_spec.rb new file mode 100644 index 00000000..07ca219a --- /dev/null +++ b/spec/controllers/ajax/inbox_controller_spec.rb @@ -0,0 +1,246 @@ +# coding: utf-8 +# frozen_string_literal: true + +require "rails_helper" + +describe Ajax::InboxController, :ajax_controller, type: :controller do + let(:user) { FactoryBot.create(:user) } + + describe "#create" do + subject { post(:create) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything, + "render" => anything + } + end + + it "creates a generated question to the user's inbox" do + allow(QuestionGenerator).to receive(:generate).and_return("Is Mayonnaise an instrument?") + expect { subject }.to(change { user.inboxes.count }.by(1)) + expect(user.inboxes.last.question.author_is_anonymous).to eq(true) + expect(user.inboxes.last.question.author_name).to eq("justask") + expect(user.inboxes.last.question.user).to eq(user) + expect(user.inboxes.last.question.content).to eq("Is Mayonnaise an instrument?") + end + + include_examples "returns the expected response" + end + + context "when user is not signed in" do + let(:expected_response) do + { + "success" => false, + "status" => "noauth", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + describe "#remove" do + let(:params) do + { + id: inbox_entry_id + } + end + + subject { delete(:remove, params: params) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + + context "when inbox entry exists" do + let(:inbox_entry) { FactoryBot.create(:inbox, user: inbox_user) } + let(:inbox_entry_id) { inbox_entry.id } + + # ensure the inbox entry exists + before(:each) { inbox_entry } + + context "when inbox entry belongs to the current user" do + let(:inbox_user) { user } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + it "removes the inbox entry" do + expect { subject }.to(change { user.inboxes.count }.by(-1)) + expect { Inbox.find(inbox_entry.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + + include_examples "returns the expected response" + end + + context "when inbox entry does not belong to the current user" do + let(:inbox_user) { FactoryBot.create(:user) } + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + it "does not remove the inbox entry" do + expect { subject }.not_to(change { Inbox.count }) + expect { Inbox.find(inbox_entry.id) }.not_to raise_error + end + + include_examples "returns the expected response" + end + end + + context "when inbox entry does not exist" do + let(:inbox_entry_id) { "Nein!" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:inbox_entry_id) { "HACKED" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + describe "#remove_all" do + subject { delete(:remove_all) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + include_examples "returns the expected response" + + context "when user has some inbox entries" do + let(:some_other_user) { FactoryBot.create(:user) } + before do + 10.times { FactoryBot.create(:inbox, user: user) } + 10.times { FactoryBot.create(:inbox, user: some_other_user) } + end + + it "deletes all the entries from the user's inbox" do + expect { subject }.to(change { [Inbox.count, user.inboxes.count] }.from([20, 10]).to([10, 0])) + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:expected_response) do + { + "success" => false, + "status" => "err", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + describe "#remove_all_author" do + let(:params) do + { + author: author + } + end + + subject { delete(:remove_all_author, params: params) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + let(:author) { user.screen_name } + + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + include_examples "returns the expected response" + + context "when user has some inbox entries" do + let(:some_other_user) { FactoryBot.create(:user) } + let(:author) { some_other_user.screen_name } + before do + normal_question = FactoryBot.create(:question, user: some_other_user, author_is_anonymous: false) + anon_question = FactoryBot.create(:question, user: some_other_user, author_is_anonymous: true) + + 10.times { FactoryBot.create(:inbox, user: user) } + 3.times { FactoryBot.create(:inbox, user: user, question: normal_question) } + 2.times { FactoryBot.create(:inbox, user: user, question: anon_question) } + end + + it "deletes all the entries asked by some other user which are not anonymous from the user's inbox" do + expect { subject }.to(change { user.inboxes.count }.from(15).to(12)) + end + + include_examples "returns the expected response" + end + + context "when author is unknown" do + let(:author) { "schmarrn" } + let(:expected_response) do + { + "success" => false, + "status" => "err", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:author) { "hackerman1337" } + let(:expected_response) do + { + "success" => false, + "status" => "err", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end +end From 52dfab57d4fdea8a2e6dda61b3b4dcb02ef4aa88 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 30 Apr 2020 20:46:26 +0200 Subject: [PATCH 10/17] add spec for Ajax::FriendController --- .../ajax/friend_controller_spec.rb | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 spec/controllers/ajax/friend_controller_spec.rb diff --git a/spec/controllers/ajax/friend_controller_spec.rb b/spec/controllers/ajax/friend_controller_spec.rb new file mode 100644 index 00000000..13b86ae1 --- /dev/null +++ b/spec/controllers/ajax/friend_controller_spec.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe Ajax::FriendController, :ajax_controller, type: :controller do + let(:user) { FactoryBot.create(:user) } + + describe "#create" do + let(:params) do + { + screen_name: screen_name + } + end + + subject { post(:create, params: params) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + + context "when target user exists" do + let(:target_user) { FactoryBot.create(:user) } + let(:screen_name) { target_user.screen_name } + + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + it "creates a follow relationship" do + expect(user.friends.ids).not_to include(target_user.id) + expect { subject }.to(change { user.friends.count }.by(1)) + expect(user.friends.ids).to include(target_user.id) + end + + include_examples "returns the expected response" + end + + context "when target user does not exist" do + let(:screen_name) { "tripmeister_eder" } + + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + it "does not create a follow relationship" do + expect { subject }.not_to(change { user.friends.count }) + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:screen_name) { "tutenchamun" } + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + describe "#destroy" do + let(:params) do + { + screen_name: screen_name + } + end + + subject { delete(:destroy, params: params) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + + context "when target user exists" do + let(:target_user) { FactoryBot.create(:user) } + let(:screen_name) { target_user.screen_name } + + before(:each) { target_user } + + context "when user follows target" do + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + before(:each) { user.follow target_user } + + it "destroys a follow relationship" do + expect(user.friends.ids).to include(target_user.id) + expect { subject }.to(change { user.friends.count }.by(-1)) + expect(user.friends.ids).not_to include(target_user.id) + end + + include_examples "returns the expected response" + end + + context "when user does not already follow target" do + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + it "does not destroy a follow relationship" do + expect { subject }.not_to(change { user.friends.count }) + end + + include_examples "returns the expected response" + end + end + + context "when target user does not exist" do + let(:screen_name) { "tripmeister_eder" } + + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + it "does not destroy a follow relationship" do + expect { subject }.not_to(change { user.friends.count }) + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:screen_name) { "tutenchamun" } + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end +end From 54532c71e1499d20a1b4923268e16fbe4e403f1b Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 30 Apr 2020 22:49:15 +0200 Subject: [PATCH 11/17] add spec for Ajax::QuestionController also fix some minor annoyances --- app/controllers/ajax/question_controller.rb | 21 +- .../ajax/question_controller_spec.rb | 401 ++++++++++++++++++ spec/factories/group.rb | 8 + 3 files changed, 427 insertions(+), 3 deletions(-) create mode 100644 spec/controllers/ajax/question_controller_spec.rb create mode 100644 spec/factories/group.rb 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 From 137743001f35002cce695c22623365895b32e2d6 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 30 Apr 2020 22:57:00 +0200 Subject: [PATCH 12/17] spec: move user to shared context --- spec/controllers/ajax/answer_controller_spec.rb | 1 - spec/controllers/ajax/comment_controller_spec.rb | 1 - spec/controllers/ajax/friend_controller_spec.rb | 2 -- spec/controllers/ajax/inbox_controller_spec.rb | 2 -- spec/controllers/ajax/question_controller_spec.rb | 2 -- spec/controllers/ajax/smile_controller_spec.rb | 2 -- spec/shared_examples/ajax_controllers.rb | 2 ++ 7 files changed, 2 insertions(+), 10 deletions(-) diff --git a/spec/controllers/ajax/answer_controller_spec.rb b/spec/controllers/ajax/answer_controller_spec.rb index bdc6cef4..b234a41b 100644 --- a/spec/controllers/ajax/answer_controller_spec.rb +++ b/spec/controllers/ajax/answer_controller_spec.rb @@ -4,7 +4,6 @@ require "rails_helper" describe Ajax::AnswerController, :ajax_controller, type: :controller do - let(:user) { FactoryBot.create(:user) } let(:question) { FactoryBot.create(:question, user: FactoryBot.build(:user, privacy_allow_stranger_answers: asker_allows_strangers)) } let(:asker_allows_strangers) { true } diff --git a/spec/controllers/ajax/comment_controller_spec.rb b/spec/controllers/ajax/comment_controller_spec.rb index c071b03f..d1e1b0be 100644 --- a/spec/controllers/ajax/comment_controller_spec.rb +++ b/spec/controllers/ajax/comment_controller_spec.rb @@ -4,7 +4,6 @@ require "rails_helper" describe Ajax::CommentController, :ajax_controller, type: :controller do - let(:user) { FactoryBot.create(:user) } let(:answer) { FactoryBot.create(:answer, user: FactoryBot.create(:user)) } describe "#create" do diff --git a/spec/controllers/ajax/friend_controller_spec.rb b/spec/controllers/ajax/friend_controller_spec.rb index 13b86ae1..89b846cf 100644 --- a/spec/controllers/ajax/friend_controller_spec.rb +++ b/spec/controllers/ajax/friend_controller_spec.rb @@ -3,8 +3,6 @@ require "rails_helper" describe Ajax::FriendController, :ajax_controller, type: :controller do - let(:user) { FactoryBot.create(:user) } - describe "#create" do let(:params) do { diff --git a/spec/controllers/ajax/inbox_controller_spec.rb b/spec/controllers/ajax/inbox_controller_spec.rb index 07ca219a..3d99ffe7 100644 --- a/spec/controllers/ajax/inbox_controller_spec.rb +++ b/spec/controllers/ajax/inbox_controller_spec.rb @@ -4,8 +4,6 @@ require "rails_helper" describe Ajax::InboxController, :ajax_controller, type: :controller do - let(:user) { FactoryBot.create(:user) } - describe "#create" do subject { post(:create) } diff --git a/spec/controllers/ajax/question_controller_spec.rb b/spec/controllers/ajax/question_controller_spec.rb index 5a4b6727..add68805 100644 --- a/spec/controllers/ajax/question_controller_spec.rb +++ b/spec/controllers/ajax/question_controller_spec.rb @@ -3,8 +3,6 @@ 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 diff --git a/spec/controllers/ajax/smile_controller_spec.rb b/spec/controllers/ajax/smile_controller_spec.rb index 7f509674..07a20de4 100644 --- a/spec/controllers/ajax/smile_controller_spec.rb +++ b/spec/controllers/ajax/smile_controller_spec.rb @@ -4,8 +4,6 @@ require "rails_helper" describe Ajax::SmileController, :ajax_controller, type: :controller do - let(:user) { FactoryBot.create(:user) } - describe "#create" do let(:params) do { diff --git a/spec/shared_examples/ajax_controllers.rb b/spec/shared_examples/ajax_controllers.rb index 144e216d..c5907548 100644 --- a/spec/shared_examples/ajax_controllers.rb +++ b/spec/shared_examples/ajax_controllers.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true RSpec.shared_context "AjaxController context" do + let(:user) { FactoryBot.create(:user) } + shared_examples "returns the expected response" do it "returns the expected response" do expect(JSON.parse(subject.body)).to match(expected_response) From aaa60d0197bc8fe5e861e02b706536a9b19296ef Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Thu, 30 Apr 2020 23:16:27 +0200 Subject: [PATCH 13/17] add spec for Ajax::SubscriptionController --- .../ajax/subscription_controller_spec.rb | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 spec/controllers/ajax/subscription_controller_spec.rb diff --git a/spec/controllers/ajax/subscription_controller_spec.rb b/spec/controllers/ajax/subscription_controller_spec.rb new file mode 100644 index 00000000..6c82f400 --- /dev/null +++ b/spec/controllers/ajax/subscription_controller_spec.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe Ajax::SubscriptionController, :ajax_controller, type: :controller do + # need to use a different user here, as after a create the user owning the + # answer is automatically subscribed to it + let(:answer_user) { FactoryBot.create(:user) } + let(:answer) { FactoryBot.create(:answer, user: answer_user) } + + describe "#subscribe" do + let(:params) do + { + answer: answer_id + } + end + + subject { post(:subscribe, params: params) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + + context "when answer exists" do + let(:answer_id) { answer.id } + let(:expected_response) do + { + "success" => true, + "status" => 418, + "message" => anything + } + end + + context "when subscription does not exist" do + it "creates a subscription on the answer" do + expect { subject }.to(change { answer.subscriptions.count }.by(1)) + expect(answer.subscriptions.where(is_active: true).map { |s| s.user.id }.sort).to eq([answer_user.id, user.id].sort) + end + + include_examples "returns the expected response" + end + + context "when subscription already exists" do + before(:each) { Subscription.subscribe(user, answer) } + + it "does not modify the answer's subscriptions" do + expect { subject }.to(change { answer.subscriptions.count }.by(0)) + expect(answer.subscriptions.where(is_active: true).map { |s| s.user.id }.sort).to eq([answer_user.id, user.id].sort) + end + + include_examples "returns the expected response" + end + end + + context "when answer does not exist" do + let(:answer_id) { "Bielefeld" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + it "does not create a new subscription" do + expect { subject }.not_to(change { Subscription.count }) + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:answer_id) { answer.id } + + it "redirects to somewhere else, apparently" do + subject + expect(response).to be_a_redirect + end + end + end + + describe "#unsubscribe" do + let(:params) do + { + answer: answer_id + } + end + + subject { post(:unsubscribe, params: params) } + + context "when user is signed in" do + before(:each) { sign_in(user) } + + context "when answer exists" do + let(:answer_id) { answer.id } + let(:expected_response) do + { + "success" => true, + "status" => 418, + "message" => anything + } + end + + context "when subscription exists" do + before(:each) { Subscription.subscribe(user, answer) } + + it "removes an active subscription from the answer" do + expect { subject }.to(change { answer.subscriptions.where(is_active: true).count }.by(-1)) + expect(answer.subscriptions.where(is_active: true).map { |s| s.user.id }.sort).to eq([answer_user.id].sort) + end + + include_examples "returns the expected response" + end + + context "when subscription does not exist" do + let(:expected_response) do + { + "success" => false, + "status" => 418, + "message" => anything + } + end + + it "does not modify the answer's subscriptions" do + expect { subject }.to(change { answer.subscriptions.count }.by(0)) + expect(answer.subscriptions.where(is_active: true).map { |s| s.user.id }.sort).to eq([answer_user.id].sort) + end + + include_examples "returns the expected response" + end + end + + context "when answer does not exist" do + let(:answer_id) { "Bielefeld" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + it "does not create a new subscription" do + expect { subject }.not_to(change { Subscription.count }) + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:answer_id) { answer.id } + + it "redirects to somewhere else, apparently" do + subject + expect(response).to be_a_redirect + end + end + end +end From 6f3f3afa2f2b40c6acd174913bc5e565b05c45a3 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 1 May 2020 00:05:41 +0200 Subject: [PATCH 14/17] add spec for Ajax::GroupController --- app/controllers/ajax/group_controller.rb | 6 +- .../controllers/ajax/group_controller_spec.rb | 341 ++++++++++++++++++ spec/factories/group.rb | 10 + 3 files changed, 354 insertions(+), 3 deletions(-) create mode 100644 spec/controllers/ajax/group_controller_spec.rb diff --git a/app/controllers/ajax/group_controller.rb b/app/controllers/ajax/group_controller.rb index 8d364136..919340aa 100644 --- a/app/controllers/ajax/group_controller.rb +++ b/app/controllers/ajax/group_controller.rb @@ -19,7 +19,7 @@ class Ajax::GroupController < AjaxController params.require :user begin - target_user = User.find_by_screen_name(params[:user]) + target_user = User.find_by_screen_name!(params[:user]) group = Group.create! user: current_user, display_name: params[:name] rescue ActiveRecord::RecordInvalid => e NewRelic::Agent.notice_error(e) @@ -85,7 +85,7 @@ class Ajax::GroupController < AjaxController add = params[:add] == 'true' begin - group = current_user.groups.find_by_name(params[:group]) + group = current_user.groups.find_by_name!(params[:group]) rescue ActiveRecord::RecordNotFound => e NewRelic::Agent.notice_error(e) @response[:status] = :notfound @@ -93,7 +93,7 @@ class Ajax::GroupController < AjaxController return end - target_user = User.find_by_screen_name(params[:user]) + target_user = User.find_by_screen_name!(params[:user]) if add group.add_member target_user if group.members.find_by_user_id(target_user.id).nil? diff --git a/spec/controllers/ajax/group_controller_spec.rb b/spec/controllers/ajax/group_controller_spec.rb new file mode 100644 index 00000000..dd9e171f --- /dev/null +++ b/spec/controllers/ajax/group_controller_spec.rb @@ -0,0 +1,341 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe Ajax::GroupController, :ajax_controller, type: :controller do + let(:target_user) { FactoryBot.create(:user) } + + describe "#create" do + let(:name) { "I signori della gallassia" } + let(:target_user_param) { target_user.screen_name } + let(:params) do + { + "name" => name, + "user" => target_user_param + } + end + + subject { post(:create, params: params) } + + context "when user is signed in" do + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything, + "render" => anything + } + end + + before(:each) { sign_in(user) } + + it "creates the group" do + expect { subject }.to(change { user.groups.count }.by(1)) + end + + include_examples "returns the expected response" + + context "when name param is missing" do + let(:name) { "" } + let(:expected_response) do + { + "success" => false, + "status" => "toolong", + "message" => anything + } + end + + it "does not create the group" do + expect { subject }.not_to(change { user.groups.count }) + end + + include_examples "returns the expected response" + end + + context "when target user does not exist" do + let(:target_user_param) { "giuseppe-drogo" } + let(:expected_response) do + { + "success" => false, + "status" => "notfound", + "message" => anything + } + end + + it "does not create the group" do + expect { subject }.not_to(change { user.groups.count }) + end + + include_examples "returns the expected response" + end + + context "when group name is invalid for reasons" do + let(:name) { "\u{1f43e}" } + let(:expected_response) do + { + "success" => false, + "status" => "toolong", + "message" => anything + } + end + + it "does not create the group" do + expect { subject }.not_to(change { user.groups.count }) + end + + include_examples "returns the expected response" + end + + context "when group already exists" do + before(:each) { post(:create, params: params) } + let(:expected_response) do + { + "success" => false, + "status" => "exists", + "message" => anything + } + end + + it "does not create the group" do + expect { subject }.not_to(change { user.groups.count }) + end + + include_examples "returns the expected response" + end + + context "when someone else created a group with the same name" do + before(:each) do + FactoryBot.create(:group, user: target_user, display_name: name) + end + + it "creates the group" do + expect { subject }.to(change { user.groups.count }.by(1)) + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:expected_response) do + { + "success" => false, + "status" => "noauth", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + describe "#destroy" do + let(:name) { "I signori della gallassia" } + let(:group) { FactoryBot.create(:group, user: user, display_name: name) } + let(:group_param) { group.name } + let(:params) do + { + "group" => group_param + } + end + + subject { delete(:destroy, params: params) } + + context "when user is signed in" do + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + before(:each) { sign_in(user) } + + it "deletes the group" do + group + expect { subject }.to(change { user.groups.count }.by(-1)) + end + + include_examples "returns the expected response" + + context "when group param is missing" do + let(:group_param) { "" } + let(:expected_response) do + { + "success" => false, + "status" => "parameter_error", + "message" => anything + } + end + + it "does not delete the group" do + expect { subject }.not_to(change { user.groups.count }) + end + + include_examples "returns the expected response" + end + + context "when group does not exist" do + let(:group_param) { "the-foobars-and-the-dingdongs" } + let(:expected_response) do + { + "success" => false, + "status" => "err", + "message" => anything + } + end + + it "does not delete the group" do + expect { subject }.not_to(change { user.groups.count }) + end + + include_examples "returns the expected response" + end + + context "when someone else created a group with the same name" do + before(:each) do + group + FactoryBot.create(:group, user: target_user, display_name: name) + end + + it "deletes the group" do + expect { subject }.to(change { user.groups.count }.by(-1)) + end + + it "does not delete the other users' group" do + expect { subject }.not_to(change { target_user.groups.count }) + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:expected_response) do + { + "success" => false, + "status" => "noauth", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + describe "#membership" do + let(:name) { "The Agency" } + let(:members) { [] } + let(:group) { FactoryBot.create(:group, user: user, display_name: name, members: members) } + let(:group_param) { group.name } + let(:target_user_param) { target_user.screen_name } + let(:params) do + { + "group" => group_param, + "user" => target_user_param, + "add" => add_param + } + end + + subject { post(:membership, params: params) } + + context "when user is signed in" do + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything, + "checked" => expected_checked + } + end + + before(:each) { sign_in(user) } + + context "when add is false" do + let(:add_param) { "false" } + let(:expected_checked) { false } + + it "does not do anything" do + expect { subject }.not_to(change { group.members }) + expect(group.members.map { |gm| gm.user.id }.sort ).to eq([]) + end + + include_examples "returns the expected response" + + context "when the user was already added to the group" do + let(:members) { [target_user] } + + it "removes the user from the group" do + expect { subject }.to(change { group.reload.members.map { |gm| gm.user.id }.sort }.from([target_user.id]).to([])) + end + + include_examples "returns the expected response" + end + end + + context "when add is true" do + let(:add_param) { "true" } + let(:expected_checked) { true } + + it "adds the user to the group" do + expect { subject }.to(change { group.reload.members.map { |gm| gm.user.id }.sort }.from([]).to([target_user.id])) + end + + include_examples "returns the expected response" + + context "when the user was already added to the group" do + let(:members) { [target_user] } + + it "does not add the user to the group again" do + expect { subject }.not_to(change { group.members }) + expect(group.members.map { |gm| gm.user.id }.sort ).to eq([target_user.id]) + end + + include_examples "returns the expected response" + end + end + + context "when group does not exist" do + let(:group_param) { "the-good-agency" } + let(:add_param) { "add" } + let(:expected_response) do + { + "success" => false, + "status" => "notfound", + "message" => anything + } + end + + include_examples "returns the expected response" + end + + context "when target user does not exist" do + let(:target_user_param) { "erwin-proell" } + let(:add_param) { "add" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:add_param) { "whatever" } + let(:expected_response) do + { + "success" => false, + "status" => "noauth", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end +end diff --git a/spec/factories/group.rb b/spec/factories/group.rb index 8b9afdde..6c0ce165 100644 --- a/spec/factories/group.rb +++ b/spec/factories/group.rb @@ -4,5 +4,15 @@ FactoryBot.define do factory :group do sequence(:display_name) { |i| "#{Faker::Internet.username(specifier: 0..12, separators: %w[_])}#{i}" } user { FactoryBot.build(:user) } + + transient do + members { [] } + end + + after(:create) do |group, evaluator| + evaluator.members.each do |member| + GroupMember.create(group_id: group.id, user_id: member.id) + end + end end end From 3e95d5ebebfd02530718c7a0389948c2607ed2d8 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 1 May 2020 09:57:44 +0200 Subject: [PATCH 15/17] add spec for Ajax::ReportController --- app/controllers/ajax/report_controller.rb | 2 +- .../ajax/report_controller_spec.rb | 151 ++++++++++++++++++ 2 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 spec/controllers/ajax/report_controller_spec.rb diff --git a/app/controllers/ajax/report_controller.rb b/app/controllers/ajax/report_controller.rb index c49ec9b4..6e6ef762 100644 --- a/app/controllers/ajax/report_controller.rb +++ b/app/controllers/ajax/report_controller.rb @@ -19,7 +19,7 @@ class Ajax::ReportController < AjaxController object = case obj when 'User' - User.find_by_screen_name params[:id] + User.find_by_screen_name! params[:id] when 'Question' Question.find params[:id] when 'Answer' diff --git a/spec/controllers/ajax/report_controller_spec.rb b/spec/controllers/ajax/report_controller_spec.rb new file mode 100644 index 00000000..dd8c4c10 --- /dev/null +++ b/spec/controllers/ajax/report_controller_spec.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require "rails_helper" + +describe Ajax::ReportController, :ajax_controller, type: :controller do + describe "#create" do + let(:params) do + { + id: id, + type: type, + reason: reason + } + end + subject { post(:create, params: params) } + + context "when user is signed in" do + shared_examples "reporting an item" do |type| + let(:type) { type } + let(:id) { object.id } + + context "when #{type} exists" do + before { object } + + context "when reason is empty" do + let(:reason) { "" } + + it "creates a report of type Reports::#{type.capitalize}" do + report_klass = "Reports::#{type.capitalize}".constantize + expect { subject }.to(change { report_klass.count }.by(1)) + expect(report_klass.last.target).to eq(object) + expect(report_klass.last.reason).to be_blank + end + + include_examples "returns the expected response" + end + + context "when reason is not empty" do + let(:reason) { "I don't like this" } + + it "creates a report of type Reports::#{type.capitalize}" do + report_klass = "Reports::#{type.capitalize}".constantize + expect { subject }.to(change { report_klass.count }.by(1)) + expect(report_klass.last.target).to eq(object) + expect(report_klass.last.reason).to eq(reason) + end + + include_examples "returns the expected response" + end + end + + context "when #{type} does not exist" do + let(:id) { "nonexistent" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything, + } + end + + context "when reason is empty" do + let(:reason) { "" } + + it "does not create a report" do + expect { subject }.not_to(change { Report.count }) + end + + include_examples "returns the expected response" + end + + context "when reason is not empty" do + let(:reason) { "I don't like this" } + + it "does not create a report" do + expect { subject }.not_to(change { Report.count }) + end + + include_examples "returns the expected response" + end + end + end + + let(:target_user) { FactoryBot.create(:user) } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything, + } + end + + before(:each) { sign_in(user) } + + it_behaves_like "reporting an item", "user" do + let(:object) { target_user } + let(:id) { object.screen_name } + end + + it_behaves_like "reporting an item", "question" do + let(:object) { FactoryBot.create(:question, user: target_user) } + end + + it_behaves_like "reporting an item", "answer" do + let(:object) { FactoryBot.create(:answer, user: target_user) } + end + + it_behaves_like "reporting an item", "comment" do + let(:answer) { FactoryBot.create(:answer, user: target_user) } + let(:object) { FactoryBot.create(:comment, user: target_user, answer: answer) } + end + + context "when type is anything else" do + let(:id) { "whatever" } + let(:type) { "whatever" } + let(:reason) { "whatever" } + let(:expected_response) do + { + "success" => false, + "status" => "err", + "message" => anything, + } + end + + it "does not create a report" do + expect { subject }.not_to(change { Report.count }) + end + + include_examples "returns the expected response" + end + end + + context "when user is not signed in" do + let(:id) { "peter_zwegat" } + let(:type) { "user" } + let(:reason) { "I'm broke now thanks to this bloke" } + let(:expected_response) do + { + "success" => false, + "status" => "err", + "message" => anything + } + end + + it "does not create a report" do + expect { subject }.not_to(change { Report.count }) + end + + include_examples "returns the expected response" + end + end +end From 0109322610933598c5dd040b08f77341fea2b6b6 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 1 May 2020 22:41:26 +0200 Subject: [PATCH 16/17] add spec for Ajax::ModerationController --- app/controllers/ajax/moderation_controller.rb | 4 +- .../ajax/moderation_controller_spec.rb | 650 ++++++++++++++++++ 2 files changed, 652 insertions(+), 2 deletions(-) create mode 100644 spec/controllers/ajax/moderation_controller_spec.rb diff --git a/app/controllers/ajax/moderation_controller.rb b/app/controllers/ajax/moderation_controller.rb index 850c3564..b2f25ad8 100644 --- a/app/controllers/ajax/moderation_controller.rb +++ b/app/controllers/ajax/moderation_controller.rb @@ -111,7 +111,7 @@ class Ajax::ModerationController < AjaxController params.require :permaban reason = params[:reason] - target = User.find_by_screen_name(params[:user]) + target = User.find_by_screen_name!(params[:user]) unban = params[:ban] == "0" perma = params[:permaban] == "1" @@ -149,7 +149,7 @@ class Ajax::ModerationController < AjaxController status = params[:status] == 'true' - target_user = User.find_by_screen_name(params[:user]) + target_user = User.find_by_screen_name!(params[:user]) @response[:message] = I18n.t('messages.moderation.privilege.nope') return unless %w(moderator admin).include? params[:type].downcase diff --git a/spec/controllers/ajax/moderation_controller_spec.rb b/spec/controllers/ajax/moderation_controller_spec.rb new file mode 100644 index 00000000..013bdd25 --- /dev/null +++ b/spec/controllers/ajax/moderation_controller_spec.rb @@ -0,0 +1,650 @@ +# coding: utf-8 +# frozen_string_literal: true + +require "rails_helper" + +describe Ajax::ModerationController, :ajax_controller, type: :controller do + shared_examples "fails when report does not exist" do + let(:report_id) { "Burgenland" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + include_examples "returns the expected response" + end + + let(:target_user) { FactoryBot.create(:user) } + let(:report) do + Reports::User.create!( + user: user, + target_id: target_user.id + ) + end + let(:user_role) { :moderator } + + before do + user.add_role user_role if user_role + sign_in(user) + end + + describe "#vote" do + let(:params) do + { + id: report_id, + upvote: upvote + } + end + + subject { post(:vote, params: params) } + + context "when report exists" do + let(:report_id) { report.id } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything, + "count" => expected_count + } + end + + context "when upvote is true" do + let(:upvote) { "true" } + let(:expected_count) { 1 } + + it "creates a moderation vote" do + expect { subject }.to(change { ModerationVote.count }.by(1)) + expect(report.moderation_votes.last.user).to eq(user) + expect(report.moderation_votes.last.upvote).to eq(true) + end + + include_examples "returns the expected response" + + context "when moderation vote already exists" do + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + before { post(:vote, params: params) } + + it "does not create a new moderation vote" do + expect { subject }.to_not(change { ModerationVote.count }) + end + + include_examples "returns the expected response" + end + end + + context "when upvote is false" do + let(:upvote) { "false" } + let(:expected_count) { 0 } + + it "creates a moderation vote" do + expect { subject }.to(change { ModerationVote.count }.by(1)) + expect(report.moderation_votes.last.user).to eq(user) + expect(report.moderation_votes.last.upvote).to eq(false) + end + + context "when moderation vote already exists" do + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + before { post(:vote, params: params) } + + it "does not create a new moderation vote" do + expect { subject }.to_not(change { ModerationVote.count }) + end + + include_examples "returns the expected response" + end + end + end + + it_behaves_like "fails when report does not exist" do + let(:upvote) { "true" } + + it "does not create a moderation vote" do + expect { subject }.to_not(change { ModerationVote.count }) + end + end + end + + describe "#destroy_vote" do + let(:params) do + { + id: report_id + } + end + + subject { post(:destroy_vote, params: params) } + + context "when report exists" do + let(:report_id) { report.id } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything, + "count" => expected_count + } + end + + context "when the user already voted" do + let(:expected_count) { 0 } + + before { post(:vote, params: params.merge("upvote" => true)) } + + it "removes a moderation vote" do + expect { subject }.to(change { ModerationVote.count }.by(-1)) + end + + include_examples "returns the expected response" + end + + context "when the user has not voted yet" do + let(:expected_count) { 0 } + let(:expected_response) do + { + "success" => false, + "status" => "fail", + "message" => anything + } + end + + it "does not create a new moderation vote" do + expect { subject }.to_not(change { ModerationVote.count }) + end + + include_examples "returns the expected response" + end + end + + it_behaves_like "fails when report does not exist" do + it "does not create a moderation vote" do + expect { subject }.to_not(change { ModerationVote.count }) + end + end + end + + describe "#destroy_report" do + let(:params) do + { + id: report_id + } + end + + subject { post(:destroy_report, params: params) } + + context "when report exists" do + let(:report_id) { report.id } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + before { report } + + it "does not actually destroy the report" do + expect { subject }.to_not(change { Report.count }) + end + + it "only marks the report as deleted" do + expect { subject }.to(change { report.reload.deleted }.from(false).to(true)) + end + + include_examples "returns the expected response" + end + + it_behaves_like "fails when report does not exist" + end + + describe "#create_comment" do + let(:params) do + { + id: report_id, + comment: comment + } + end + let(:comment) { "ZEFIX NUAMOI!" } + + subject { post(:create_comment, params: params) } + + context "when report exists" do + let(:report_id) { report.id } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything, + "render" => anything, + "count" => 1 + } + end + + it "creates a moderation comment" do + expect { subject }.to(change { ModerationComment.count }.by(1)) + expect(report.moderation_comments.last.user).to eq(user) + expect(report.moderation_comments.last.content).to eq(comment) + end + + include_examples "returns the expected response" + + context "when comment is blank" do + let(:comment) { "" } + let(:expected_response) do + { + "success" => false, + "status" => "parameter_error", + "message" => anything + } + end + + it "does not create a moderation comment" do + expect { subject }.to_not(change { ModerationComment.count }) + end + + include_examples "returns the expected response" + end + + context "when comment is the letter E 621 times" do + let(:comment) { "E" * 621 } + let(:expected_response) do + { + "success" => false, + "status" => "rec_inv", + "message" => anything + } + end + + it "does not create a moderation comment" do + expect { subject }.to_not(change { ModerationComment.count }) + end + + include_examples "returns the expected response" + end + end + + it_behaves_like "fails when report does not exist" do + it "does not create a moderation comment" do + expect { subject }.to_not(change { ModerationComment.count }) + end + end + end + + describe "#destroy_comment" do + let(:comment) { ModerationComment.create!(user: comment_user, report: report, content: "sigh") } + let(:params) do + { + comment: comment_id + } + end + + subject { post(:destroy_comment, params: params) } + + context "when comment exists" do + let(:comment_id) { comment.id } + + before { comment } + + context "when comment was made by the current user" do + let(:comment_user) { user } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + it "destroys the comment" do + expect { subject }.to(change { ModerationComment.count }.by(-1)) + expect { comment.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + + include_examples "returns the expected response" + end + + context "when comment was made by someone else" do + let(:comment_user) { FactoryBot.create(:user) } + let(:expected_response) do + { + "success" => false, + "status" => "nopriv", + "message" => anything + } + end + + it "does not destroy the comment" do + expect { subject }.not_to(change { ModerationComment.count }) + expect { comment.reload }.not_to raise_error + end + + include_examples "returns the expected response" + + context "when current user is an administrator" do + let(:user_role) { :administrator } + + it "does not destroy the comment" do + expect { subject }.not_to(change { ModerationComment.count }) + expect { comment.reload }.not_to raise_error + end + + include_examples "returns the expected response" + end + end + end + + context "when comment does not exist" do + let(:comment_id) { "Rügenwalder" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + it "does not destroy any comment" do + expect { subject }.not_to(change { ModerationComment.count }) + end + + include_examples "returns the expected response" + end + end + + describe "#ban" do + let(:params) do + { + user: user_param, + ban: ban, + permaban: permaban, + reason: "just a prank, bro", + until: wrongly_formatted_date_ugh + } + end + + subject { post(:ban, params: params) } + + context "when user exists" do + shared_examples "does not ban administrators" do + let(:expected_response) do + { + "success" => false, + "status" => "nopriv", + "message" => anything + } + end + + before { target_user.add_role :administrator } + + it "does not ban the target user" do + subject + expect(target_user).not_to be_banned + end + + include_examples "returns the expected response" + end + + let(:user_param) { target_user.screen_name } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything + } + end + + before { target_user } + + context "when ban = 0" do + let(:ban) { "0" } + let(:wrongly_formatted_date_ugh) { nil } + + "01".each_char do |pb| + context "when permaban = #{pb}" do + let(:permaban) { pb } + + context "when user is already banned" do + before { target_user.ban } + + it "unbans the user" do + expect { subject }.to(change { target_user.reload.banned? }.from(true).to(false)) + end + + include_examples "returns the expected response" + end + + context "when user is not yet banned" do + it "does not change the status of the ban" do + expect { subject }.not_to(change { target_user.reload.banned? }) + end + + include_examples "returns the expected response" + end + end + end + end + + context "when ban = 1" do + let(:ban) { "1" } + let(:wrongly_formatted_date_ugh) { "4/20/2420 12:00 AM" } + + context "when permaban = 0" do + let(:permaban) { "0" } + + it "bans the user until 2420-04-20" do + expect { subject }.to(change { target_user.reload.banned? }.from(false).to(true)) + expect(target_user).not_to be_permanently_banned + expect(target_user.ban_reason).to eq("just a prank, bro") + expect(target_user.banned_until).to eq(DateTime.strptime(wrongly_formatted_date_ugh, "%m/%d/%Y %I:%M %p")) + end + + include_examples "returns the expected response" + + it_behaves_like "does not ban administrators" + end + + context "when permaban = 1" do + let(:permaban) { "1" } + + it "bans the user for all eternity" do + expect { subject }.to(change { target_user.reload.banned? }.from(false).to(true)) + expect(target_user).to be_permanently_banned + expect(target_user.ban_reason).to eq("just a prank, bro") + expect(target_user.banned_until).to be_nil + end + + include_examples "returns the expected response" + + it_behaves_like "does not ban administrators" + end + end + end + + context "when user does not exist" do + let(:user_param) { "fritz-fantom" } + let(:ban) { "1" } + let(:permaban) { "1" } + let(:wrongly_formatted_date_ugh) { "4/20/2420 12:00 AM" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end + + describe "#privilege" do + valid_role_pairs = { + moderator: :moderator, + admin: :administrator + }.freeze + + let(:params) do + { + user: user_param, + type: type, + status: status + } + end + + subject { post(:privilege, params: params) } + + context "when user exists" do + let(:user_param) { target_user.screen_name } + before { target_user } + + { + nil => "has no extra roles", + :moderator => "is a moderator" + }.each do |u_role, context_desc| + context "when the current user #{context_desc}" do + let(:user_role) { u_role } + let(:expected_response) do + { + "success" => false, + "status" => "nopriv", + "message" => anything + } + end + + valid_role_pairs.each do |type, role_name| + context "when type is #{type}" do + let(:type) { type } + + context "when status is true" do + let(:status) { "true" } + + it "does not modify the roles on the target user" do + expect { subject }.not_to(change { target_user.reload.roles.to_a }) + end + + include_examples "returns the expected response" + end + + context "when status is false" do + let(:status) { "true" } + + before { target_user.add_role role_name } + + it "does not modify the roles on the target user" do + expect { subject }.not_to(change { target_user.reload.roles.to_a }) + end + + include_examples "returns the expected response" + end + end + end + end + end + + context "when the current user is an administrator" do + let(:user_role) { :administrator } + + valid_role_pairs.each do |type, role_name| + context "when type is #{type}" do + let(:type) { type } + + context "when status is true" do + let(:status) { "true" } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything, + "checked" => true + } + end + + it "adds the #{role_name} role to the target user" do + expect { subject }.to(change { target_user.roles.reload.to_a }) + expect(target_user).to have_role(role_name) + end + + include_examples "returns the expected response" + end + + context "when status is false" do + let(:status) { "false" } + let(:expected_response) do + { + "success" => true, + "status" => "okay", + "message" => anything, + "checked" => false + } + end + + before { target_user.add_role role_name } + + it "removes the #{role_name} role from the target user" do + expect { subject }.to(change { target_user.reload.roles.to_a }) + expect(target_user).not_to have_role(role_name) + end + + include_examples "returns the expected response" + end + end + end + + context "when type is some bogus value" do + let(:type) { "some bogus value" } + let(:expected_response) do + { + "success" => false, + "status" => "err", + "message" => anything + } + end + + %w[true false].each do |s| + context "when status is #{s}" do + let(:status) { s } + + it "does not modify the roles on the target user" do + expect { subject }.not_to(change { target_user.reload.roles.to_a }) + end + + include_examples "returns the expected response" + end + end + end + end + end + + context "when user does not exist" do + let(:user_param) { "fritz-fantom" } + let(:type) { "admin" } + let(:status) { "true" } + let(:expected_response) do + { + "success" => false, + "status" => "not_found", + "message" => anything + } + end + + include_examples "returns the expected response" + end + end +end From 0504e4a5d4b80b9428157bdd30a92785b95a8e49 Mon Sep 17 00:00:00 2001 From: Georg Gadinger Date: Fri, 1 May 2020 22:43:08 +0200 Subject: [PATCH 17/17] require rspec-sidekiq only in specs --- Gemfile | 2 +- spec/rails_helper.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index f06b049a..1211bc58 100644 --- a/Gemfile +++ b/Gemfile @@ -91,7 +91,7 @@ group :development, :test do gem 'puma' gem 'rspec-rails', '~> 3.9' gem 'rspec-its', '~> 1.3' - gem "rspec-sidekiq", "~> 3.0" + gem "rspec-sidekiq", "~> 3.0", require: false gem 'factory_bot_rails', require: false gem 'faker' gem 'capybara' diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 5fc46546..742cffa8 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -12,6 +12,7 @@ require "rspec/its" require "devise" require "capybara/rails" require "capybara/rspec" +require "rspec-sidekiq" # Requires supporting ruby files with custom matchers and macros, etc, in # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are