# frozen_string_literal: true require 'rails_helper' describe CommentsController do subject { response } let(:member) { FactoryBot.create(:member) } before do sign_in member controller.stub(:current_member) { member } end def valid_attributes @post = FactoryBot.create(:post) { post_id: @post.id, body: "some text" } end describe "GET RSS feed" do let!(:first_comment) { FactoryBot.create(:comment, created_at: 10.days.ago) } let!(:last_comment) { FactoryBot.create(:comment, created_at: 4.minutes.ago) } describe "returns an RSS feed" do before { get :index, format: "rss" } it { is_expected.to be_successful } it { is_expected.to render_template("comments/index") } it { expect(response.content_type).to eq("application/rss+xml; charset=utf-8") } it { expect(assigns(:comments)).to eq([last_comment, first_comment]) } end end # Shared examples for commentable controllers RSpec.shared_examples "a commentable controller" do |commentable_factory_name, commentable_param_key| let(:commentable_owner) { FactoryBot.create(:member) } let(:comment_author) { FactoryBot.create(:member) } let(:admin_user) { FactoryBot.create(:member, :admin) } let!(:commentable) do if [:post].include?(commentable_factory_name) FactoryBot.create(commentable_factory_name, author: commentable_owner) else FactoryBot.create(commentable_factory_name, owner: commentable_owner) end end describe "GET #new" do context "when not logged in" do before { sign_out member } it "redirects to login" do get :new, params: { commentable_param_key => commentable.id } expect(response).to redirect_to(new_member_session_path) end end context "when logged in" do before { sign_in comment_author } it "assigns @commentable and new @comment" do get :new, params: { commentable_param_key => commentable.id } expect(assigns(:commentable)).to eq(commentable) expect(assigns(:comment)).to be_a_new(Comment) expect(response).to render_template(:new) end it "redirects if commentable not found" do get :new, params: { commentable_param_key => -1 } expect(response).to redirect_to(request.referer || root_url) expect(flash[:alert]).to match(/Cannot add a comment to a non-existent or unspecified item/) end end end describe "POST #create" do let(:valid_comment_params) { { body: "This is a great comment." } } let(:invalid_comment_params) { { body: "" } } context "when not logged in" do before { sign_out member } it "redirects to login" do post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params } expect(response).to redirect_to(new_member_session_path) end end context "when logged in" do before { sign_in comment_author } context "with valid params" do it "creates a new Comment" do expect { post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params } }.to change(Comment, :count).by(1) end it "assigns the comment's author to current_member" do post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params } expect(Comment.last.author).to eq(comment_author) end it "redirects to the commentable's show page" do post :create, params: { commentable_param_key => commentable.id, comment: valid_comment_params } expect(response).to redirect_to(commentable) end end context "with invalid params" do it "does not create a comment" do expect { post :create, params: { commentable_param_key => commentable.id, comment: invalid_comment_params } }.not_to change(Comment, :count) end it "re-renders the 'new' template (or commentable show with errors)" do # The controller currently redirects if commentable is not found in create, # but for invalid comment params, it should re-render or show errors. # The current controller's create action saves and then responds. # If save fails, it typically re-renders the form via respond_with. # For this test, we'll assume it re-renders 'new' if save fails. # A more precise test would check the response if @comment.save fails. # For now, we'll check that it doesn't redirect to the commentable if save fails. post :create, params: { commentable_param_key => commentable.id, comment: invalid_comment_params } # Depending on how `respond_with` handles failure for new comment on commentable, # it might render the commentable's show page or the comments/new template. # The key is that it shouldn't be a successful redirect to the commentable. # And @comment.errors should be present. expect(assigns(:comment).errors).not_to be_empty # Check for re-render of new or specific error handling view # For now, checking that it's not a successful redirect if save fails. # This might need adjustment based on actual controller error flow. # A common pattern is to render the 'new' template again or the parent's show page. # The `respond_with @comment, location: @comment.commentable` will try to redirect if valid. # If invalid, it should re-render the action that led to the form. # For `create` failing, it's often the `new` view or the parent resource's view. # The controller has `respond_with @comment, location: @comment.commentable`. # If `@comment` is not persisted, `respond_with` might render the `new` template by convention, # or the template of the action (`create.js.erb` or `create.html.erb` if they exist). # Given the setup, it's likely to re-render 'new' or the controller action's default. # Let's assume for now the form is on the `new` page. # This is a weak assertion. A better one would be to check for `render_template(:new)` # if the controller is set up to do that on failure. # However, `respond_with` is tricky. It might also render the `commentable` show page with errors. # The `new` action in controller renders `respond_with(@comment)`. # The `create` action has `respond_with @comment, location: @comment.commentable`. # If `@comment.save` fails, `respond_with` will typically render the `new` template by default # if `create.html.haml` doesn't exist, or it might try to render `commentable` show page # with errors displayed by the form partial. # Given the form is rendered via `comments/new`, let's assume it re-renders new. # This part of the test may need refinement based on actual error rendering flow. # A simple check: ensure it doesn't redirect to the commentable. expect(response).not_to redirect_to(commentable_path(commentable)) # And that @commentable is still assigned for the form. expect(assigns(:commentable)).to eq(commentable) end end it "redirects if commentable not found" do post :create, params: { commentable_param_key => -1, comment: valid_comment_params } expect(response).to redirect_to(request.referer || root_url) expect(flash[:alert]).to match(/Cannot create comment for a non-existent or unspecified item/) end end end describe "GET #edit" do let!(:comment) { FactoryBot.create(:comment, commentable: commentable, author: comment_author) } context "when not logged in" do before { sign_out member } it "redirects to login" do get :edit, params: { id: comment.id } expect(response).to redirect_to(new_member_session_path) end end context "as comment author" do before { sign_in comment_author } it "assigns @comment and renders edit" do get :edit, params: { id: comment.id } expect(assigns(:comment)).to eq(comment) expect(response).to render_template(:edit) end end context "as admin" do before { sign_in admin_user } it "assigns @comment and renders edit" do get :edit, params: { id: comment.id } expect(assigns(:comment)).to eq(comment) expect(response).to render_template(:edit) end end context "as unauthorized user" do let(:other_user) { FactoryBot.create(:member) } before { sign_in other_user } it "redirects or shows error" do get :edit, params: { id: comment.id } expect(response).to redirect_to(root_url) # Or some other unauthorized path expect(flash[:alert]).to match(/You are not authorized to access this page./) end end end describe "PUT #update" do let!(:comment) { FactoryBot.create(:comment, commentable: commentable, author: comment_author, body: "Original body") } let(:updated_body) { "Updated comment body." } context "when not logged in" do before { sign_out member } it "redirects to login" do put :update, params: { id: comment.id, comment: { body: updated_body } } expect(response).to redirect_to(new_member_session_path) end end context "as comment author" do before { sign_in comment_author } context "with valid params" do it "updates the comment" do put :update, params: { id: comment.id, comment: { body: updated_body } } comment.reload expect(comment.body).to eq(updated_body) end it "redirects to commentable show page" do put :update, params: { id: comment.id, comment: { body: updated_body } } expect(response).to redirect_to(commentable_path(commentable)) end end context "with invalid params (empty body)" do it "does not update the comment and re-renders edit" do put :update, params: { id: comment.id, comment: { body: "" } } comment.reload expect(comment.body).to eq("Original body") # Should not change expect(response).to render_template(:edit) end end end context "as admin" do before { sign_in admin_user } it "updates the comment" do put :update, params: { id: comment.id, comment: { body: updated_body } } comment.reload expect(comment.body).to eq(updated_body) expect(response).to redirect_to(commentable_path(commentable)) end end context "as unauthorized user" do let(:other_user) { FactoryBot.create(:member) } before { sign_in other_user } it "redirects or shows error" do put :update, params: { id: comment.id, comment: { body: updated_body } } expect(response).to redirect_to(root_url) expect(flash[:alert]).to match(/You are not authorized to access this page./) end end end describe "DELETE #destroy" do let!(:comment_to_delete) { FactoryBot.create(:comment, commentable: commentable, author: comment_author) } context "when not logged in" do before { sign_out member } it "redirects to login" do delete :destroy, params: { id: comment_to_delete.id } expect(response).to redirect_to(new_member_session_path) end end context "as comment author" do before { sign_in comment_author } it "deletes the comment" do expect { delete :destroy, params: { id: comment_to_delete.id } }.to change(Comment, :count).by(-1) end it "redirects to commentable show page" do delete :destroy, params: { id: comment_to_delete.id } expect(response).to redirect_to(commentable_path(commentable)) end end context "as commentable owner" do before { sign_in commentable_owner } it "deletes the comment" do # Ensure comment_author is not the same as commentable_owner for this test case expect(comment_to_delete.author).not_to eq(commentable_owner) expect { delete :destroy, params: { id: comment_to_delete.id } }.to change(Comment, :count).by(-1) end it "redirects to commentable show page" do delete :destroy, params: { id: comment_to_delete.id } expect(response).to redirect_to(commentable_path(commentable)) end end context "as admin" do before { sign_in admin_user } it "deletes the comment" do expect { delete :destroy, params: { id: comment_to_delete.id } }.to change(Comment, :count).by(-1) end it "redirects to commentable show page" do delete :destroy, params: { id: comment_to_delete.id } expect(response).to redirect_to(commentable_path(commentable)) end end context "as unauthorized user" do let(:other_user) { FactoryBot.create(:member) } before { sign_in other_user } it "does not delete the comment and redirects or shows error" do expect { delete :destroy, params: { id: comment_to_delete.id } }.not_to change(Comment, :count) expect(response).to redirect_to(root_url) expect(flash[:alert]).to match(/You are not authorized to access this page./) end end end end # Apply shared examples for each commentable type context "for Post" do it_behaves_like "a commentable controller", :post, :post_id end context "for Photo" do it_behaves_like "a commentable controller", :photo, :photo_id end context "for Planting" do it_behaves_like "a commentable controller", :planting, :planting_id end context "for Harvest" do it_behaves_like "a commentable controller", :harvest, :harvest_id end context "for Activity" do it_behaves_like "a commentable controller", :activity, :activity_id end end