From bcbcf6bce32698681d885b7e482c678cb7b864e5 Mon Sep 17 00:00:00 2001 From: Frank Rousseau Date: Fri, 5 Jan 2018 04:26:37 +0100 Subject: [PATCH] Make the comment API match the API specs --- app/controllers/api/v1/base_controller.rb | 1 + app/controllers/api/v1/comments_controller.rb | 43 +++-- app/presenters/comment_presenter.rb | 9 ++ app/services/comment_service.rb | 19 ++- config/routes.rb | 4 +- .../api/comments_controller_spec.rb | 151 +++++++++++++----- spec/services/comment_service_spec.rb | 41 +++++ 7 files changed, 215 insertions(+), 53 deletions(-) diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index 58610b002..4f1d887b7 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -6,6 +6,7 @@ module Api include Api::OpenidConnect::ProtectedResourceEndpoint protected + rescue_from Exception do |e| logger.error e.message logger.error e.backtrace.join("\n") diff --git a/app/controllers/api/v1/comments_controller.rb b/app/controllers/api/v1/comments_controller.rb index 0b493ab16..32aa9618f 100644 --- a/app/controllers/api/v1/comments_controller.rb +++ b/app/controllers/api/v1/comments_controller.rb @@ -3,34 +3,53 @@ module Api module V1 class CommentsController < Api::V1::BaseController + before_action only: %i[index report] do + require_access_token %w[read] + end + before_action only: %i[create destroy] do require_access_token %w[read write] end - rescue_from ActiveRecord::RecordNotFound do - render json: I18n.t("comments.not_found"), status: 404 - end - - rescue_from ActiveRecord::RecordInvalid do - render json: I18n.t("comments.create.fail"), status: 404 - end - def create - @comment = comment_service.create(params[:post_id], params[:text]) - render json: CommentPresenter.new(@comment), status: 201 + @comment = comment_service.create(params[:post_id], params[:body]) + comment = comment_as_json(@comment) + render json: comment, status: 201 + end + + def index + comments = comment_service.find_for_post(params[:post_id]) + render json: comments.map {|x| comment_as_json(x) } end def destroy - if comment_service.destroy(params[:id]) + comment_service.destroy!(params[:id]) + head :no_content + end + + def report + comment_guid = params.require(:comment_id) + reason = params.require(:reason) + comment = comment_service.find!(comment_guid) + report = current_user.reports.new( + item_id: comment.id, + item_type: "Comment", + text: reason + ) + if report.save head :no_content else - render json: I18n.t("comments.destroy.fail"), status: 403 + render json: {error: I18n.t("report.status.failed")}, status: 500 end end def comment_service @comment_service ||= CommentService.new(current_user) end + + def comment_as_json(comment) + CommentPresenter.new(comment).as_api_response + end end end end diff --git a/app/presenters/comment_presenter.rb b/app/presenters/comment_presenter.rb index c77f64a34..6eae62ece 100644 --- a/app/presenters/comment_presenter.rb +++ b/app/presenters/comment_presenter.rb @@ -15,4 +15,13 @@ class CommentPresenter < BasePresenter mentioned_people: @comment.mentioned_people.as_api_response(:backbone) } end + + def as_api_response + { + guid: @comment.guid, + body: @comment.message.plain_text_for_json, + author: @comment.author.as_api_response(:backbone), + created_at: @comment.created_at + } + end end diff --git a/app/services/comment_service.rb b/app/services/comment_service.rb index 4872d9068..5f5588e1b 100644 --- a/app/services/comment_service.rb +++ b/app/services/comment_service.rb @@ -10,6 +10,14 @@ class CommentService user.comment!(post, text) end + def find_for_post(post_id) + post_service.find!(post_id).comments.for_a_stream + end + + def find!(comment_guid) + Comment.find_by!(guid: comment_guid) + end + def destroy(comment_id) comment = Comment.find(comment_id) if user.owns?(comment) || user.owns?(comment.parent) @@ -20,8 +28,15 @@ class CommentService end end - def find_for_post(post_id) - post_service.find!(post_id).comments.for_a_stream + def destroy!(comment_guid) + comment = find!(comment_guid) + if user.owns?(comment) + user.retract(comment) + elsif user.owns?(comment.parent) + user.retract(comment) + else + raise ActiveRecord::RecordNotFound + end end private diff --git a/config/routes.rb b/config/routes.rb index 3194915e0..1d931528e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -224,7 +224,9 @@ Rails.application.routes.draw do api_version(module: "Api::V1", path: {value: "api/v1"}) do match "user", to: "users#show", via: %i[get post] resources :posts, only: %i[show create destroy] do - resources :comments, only: %i[create destroy] + resources :comments, only: %i[create index destroy] do + post "report" => "comments#report" + end resource :likes, only: %i[create destroy] end resources :conversations, only: %i[show index create destroy] do diff --git a/spec/integration/api/comments_controller_spec.rb b/spec/integration/api/comments_controller_spec.rb index 5e73cc8e9..a3fdf5423 100644 --- a/spec/integration/api/comments_controller_spec.rb +++ b/spec/integration/api/comments_controller_spec.rb @@ -18,66 +18,141 @@ describe Api::V1::CommentsController do describe "#create" do context "valid post ID" do - it "succeeds" do - post( - api_v1_post_comments_path(post_id: @status.id), - params: {text: "This is a comment", access_token: access_token} - ) - expect(JSON.parse(response.body)["text"]).to eq("This is a comment") + it "succeeds in adding a comment" do + create_comment(@status.guid, "This is a comment") + expect(response.status).to eq(201) + comment = response_body(response) + expect(comment["body"]).to eq("This is a comment") + expect(comment_service.find!(comment["guid"])).to_not be_nil end end - context "comment too long" do - before do - post( - api_v1_post_comments_path(post_id: @status.id), - params: { - text: "This is a long comment" * 99_999, - access_token: access_token - } - ) + context "wrong post id" do + it "fails at adding a comment" do + create_comment("999_999_999", "This is a comment") + expect(response.status).to eq(404) end + end + end - it "fails with appropriate error message" do - expect(response.body).to eq("Comment creation has failed") + describe "#read" do + before do + create_comment(@status.guid, "This is a comment") + create_comment(@status.guid, "This is a comment 2") + end + + context "valid post ID" do + it "retrieves related comments" do + get( + api_v1_post_comments_path(post_id: @status.guid), + params: {access_token: access_token} + ) + expect(response.status).to eq(200) + expect(response_body(response).length).to eq(2) + end + end + + context "wrong post id" do + it "fails at retrieving comments" do + get( + api_v1_post_comments_path(post_id: "999_999_999"), + params: {access_token: access_token} + ) + expect(response.status).to eq(404) end end end describe "#delete" do - context "valid comment ID" do - before do - post( - api_v1_post_comments_path(post_id: @status.id), - params: {text: "This is a comment", access_token: access_token} - ) - end + before do + create_comment(@status.guid, "This is a comment") + @comment_guid = response_body(response)["guid"] + end - it "succeeds" do - first_comment_id = JSON.parse(response.body)["id"] + context "valid comment ID" do + it "succeeds in deleting comment" do delete( - api_v1_post_comment_path(id: first_comment_id), + api_v1_post_comment_path( + post_id: @status.guid, + id: @comment_guid + ), params: {access_token: access_token} ) - expect(response).to be_success + expect(response.status).to eq(204) + expect { comment_service.find!(@comment_guid) }.to( + raise_error(ActiveRecord::RecordNotFound) + ) end end context "invalid comment ID" do - before do - post( - api_v1_post_comments_path(post_id: @status.id), - params: {text: "This is a comment", access_token: access_token} - ) - end - - it "fails to delete" do + it "fails at deleting comment" do delete( - api_v1_post_comment_path(id: 1_234_567), + api_v1_post_comment_path( + post_id: @status.guid, + id: "1_234_567" + ), params: {access_token: access_token} ) - expect(response.body).to eq("Post or comment not found") + expect(response.status).to eq(404) end end end + + describe "#report" do + before do + create_comment(@status.guid, "This is a comment") + @comment_guid = response_body(response)["guid"] + end + + context "valid comment ID" do + it "succeeds in reporting comment" do + post( + api_v1_post_comment_report_path( + post_id: @status.guid, + comment_id: @comment_guid + ), + params: { + reason: "bad comment", + access_token: access_token + } + ) + expect(response.status).to eq(204) + report = Report.first + expect(report.item_type).to eq("Comment") + expect(report.text).to eq("bad comment") + end + end + + context "invalid comment ID" do + it "fails at reporting comment" do + post( + api_v1_post_comment_report_path( + post_id: @status.guid, + comment_id: "1_234_567" + ), + params: { + reason: "bad comment", + access_token: access_token + } + ) + expect(response.status).to eq(404) + end + end + end + + def comment_service + CommentService.new(auth.user) + end + + def create_comment(post_guid, text) + post( + api_v1_post_comments_path(post_id: post_guid), + params: {body: text, access_token: access_token} + ) + end + + def response_body(response) + JSON.parse(response.body) + end end diff --git a/spec/services/comment_service_spec.rb b/spec/services/comment_service_spec.rb index c04002cdc..e5e6a59ca 100644 --- a/spec/services/comment_service_spec.rb +++ b/spec/services/comment_service_spec.rb @@ -33,6 +33,21 @@ describe CommentService do end end + describe "#find!" do + let(:comment) { CommentService.new(bob).create(post.id, "hi") } + + it "returns comment" do + result = CommentService.new(bob).find!(comment.guid) + expect(result.id).to eq(comment.id) + end + + it "raises exception the comment does not exist" do + expect { + CommentService.new(bob).find!("unknown id") + }.to raise_error ActiveRecord::RecordNotFound + end + end + describe "#destroy" do let(:comment) { CommentService.new(bob).create(post.id, "hi") } @@ -58,6 +73,32 @@ describe CommentService do end end + describe "#destroy!" do + let(:comment) { CommentService.new(bob).create(post.id, "hi") } + + it "lets the user destroy his own comment" do + result = CommentService.new(bob).destroy!(comment.guid) + expect(result).to be_truthy + end + + it "lets the parent author destroy others comment" do + result = CommentService.new(alice).destroy!(comment.guid) + expect(result).to be_truthy + end + + it "does not let someone destroy others comment" do + expect { + CommentService.new(eve).destroy!(comment.guid) + }.to raise_error ActiveRecord::RecordNotFound + end + + it "raises exception the comment does not exist" do + expect { + CommentService.new(bob).destroy!("unknown id") + }.to raise_error ActiveRecord::RecordNotFound + end + end + describe "#find_for_post" do context "with user" do it "returns comments for a public post" do