From d6915ff5d08842d78903928e819f4d61ab4d130f Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Mon, 15 Oct 2018 10:36:34 -0400 Subject: [PATCH] Likes API endpoint complete --- app/controllers/api/v1/base_controller.rb | 2 + app/controllers/api/v1/likes_controller.rb | 34 +++++- app/presenters/likes_presenter.rb | 10 ++ config/locales/diaspora/en.yml | 9 ++ config/routes.rb | 2 +- spec/integration/api/likes_controller_spec.rb | 102 +++++++++++++++++- spec/presenters/likes_presenter_spec.rb | 32 ++++++ 7 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 app/presenters/likes_presenter.rb create mode 100644 spec/presenters/likes_presenter_spec.rb diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index 4461e4e0d..8f60537c7 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -5,6 +5,8 @@ module Api class BaseController < ApplicationController include Api::OpenidConnect::ProtectedResourceEndpoint + protect_from_forgery unless: -> { request.format.json? } + protected rescue_from Exception do |e| diff --git a/app/controllers/api/v1/likes_controller.rb b/app/controllers/api/v1/likes_controller.rb index c4549d18f..9e7b8d0b1 100644 --- a/app/controllers/api/v1/likes_controller.rb +++ b/app/controllers/api/v1/likes_controller.rb @@ -3,31 +3,55 @@ module Api module V1 class LikesController < Api::V1::BaseController + before_action only: %i[show] do + require_access_token %w[read] + end + before_action only: %i[create destroy] do require_access_token %w[write] end rescue_from ActiveRecord::RecordNotFound do - render json: I18n.t("likes.not_found"), status: :not_found + render json: I18n.t("api.endpoint_errors.posts.post_not_found"), status: :not_found end rescue_from ActiveRecord::RecordInvalid do - render json: I18n.t("likes.create.fail"), status: :not_found + render json: I18n.t("api.endpoint_errors.likes.user_not_allowed_to_like"), status: :not_found + end + + def show + likes = like_service.find_for_post(params[:post_id]) + render json: likes.map {|x| like_json(x) } end def create like_service.create(params[:post_id]) - head :no_content, status: 204 + rescue ActiveRecord::RecordInvalid => e + return render json: I18n.t("api.endpoint_errors.likes.like_exists"), status: :unprocessable_entity if + e.message == "Validation failed: Target has already been taken" + raise + else + head :no_content end def destroy - like_service.unlike_post(params[:post_id]) - head :no_content, status: 204 + success = like_service.unlike_post(params[:post_id]) + if success + head :no_content + else + render json: I18n.t("api.endpoint_errors.likes.no_like"), status: :not_found + end end def like_service @like_service ||= LikeService.new(current_user) end + + private + + def like_json(like) + LikesPresenter.new(like).as_api_json + end end end end diff --git a/app/presenters/likes_presenter.rb b/app/presenters/likes_presenter.rb new file mode 100644 index 000000000..26f532724 --- /dev/null +++ b/app/presenters/likes_presenter.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class LikesPresenter < BasePresenter + def as_api_json + { + guid: @presentable.guid, + author: PersonPresenter.new(@presentable.author).as_api_json + } + end +end diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index 971af8495..b257a9a1c 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -950,6 +950,13 @@ en: contact_developer: "You should contact the developer of the application and include the following detailed error message:" login_required: "You must first login before you can authorize this application" could_not_authorize: "The application could not be authorized" + endpoint_errors: + likes: + user_not_allowed_to_like: "User is not allowed to like" + like_exists: "Like already exists" + no_like: "Like doesn’t exist" + posts: + post_not_found: "Post with provided guid could not be found" error: not_found: "No record found for given id." @@ -1360,3 +1367,5 @@ en: disabled: "Not available" open: "Open" closed: "Closed" + + diff --git a/config/routes.rb b/config/routes.rb index aacc7089c..34fcc239a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -226,7 +226,7 @@ Rails.application.routes.draw do resources :comments, only: %i[create index destroy] do post "report" => "comments#report" end - resource :likes, only: %i[create destroy] + resource :likes, only: %i[show create destroy] end resources :conversations, only: %i[show index create destroy] do resources :messages, only: %i[index create] diff --git a/spec/integration/api/likes_controller_spec.rb b/spec/integration/api/likes_controller_spec.rb index 148070ed3..02c4c6cf4 100644 --- a/spec/integration/api/likes_controller_spec.rb +++ b/spec/integration/api/likes_controller_spec.rb @@ -15,6 +15,47 @@ describe Api::V1::LikesController do ) end + describe "#show" do + context "with right post id" do + it "succeeds in getting empty likes" do + get( + api_v1_post_likes_path(post_id: @status.guid), + params: {access_token: access_token} + ) + expect(response.status).to eq(200) + likes = response_body(response) + expect(likes.length).to eq(0) + end + + it "succeeds in getting post with likes" do + like_service(bob).create(@status.guid) + like_service(auth.user).create(@status.guid) + like_service(alice).create(@status.guid) + get( + api_v1_post_likes_path(post_id: @status.guid), + params: {access_token: access_token} + ) + expect(response.status).to eq(200) + likes = response_body(response) + expect(likes.length).to eq(3) + confirm_like_format(likes, alice) + confirm_like_format(likes, bob) + confirm_like_format(likes, auth.user) + end + end + + context "with wrong post id" do + it "fails at getting likes" do + get( + api_v1_post_likes_path(post_id: "badguid"), + params: {access_token: access_token} + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) + end + end + end + describe "#create" do context "with right post id" do it "succeeeds in liking post" do @@ -27,6 +68,25 @@ describe Api::V1::LikesController do expect(likes.length).to eq(1) expect(likes[0].author.id).to eq(auth.user.person.id) end + + it "fails in liking already liked post" do + post( + api_v1_post_likes_path(post_id: @status.guid), + params: {access_token: access_token} + ) + expect(response.status).to eq(204) + + post( + api_v1_post_likes_path(post_id: @status.guid), + params: {access_token: access_token} + ) + expect(response.status).to eq(422) + expect(response.body).to eq(I18n.t("api.endpoint_errors.likes.like_exists")) + + likes = like_service.find_for_post(@status.guid) + expect(likes.length).to eq(1) + expect(likes[0].author.id).to eq(auth.user.person.id) + end end context "with wrong post id" do @@ -36,11 +96,12 @@ describe Api::V1::LikesController do params: {access_token: access_token} ) expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) end end end - describe "#create" do + describe "#delete" do before do post( api_v1_post_likes_path(post_id: @status.guid), @@ -58,6 +119,24 @@ describe Api::V1::LikesController do likes = like_service.find_for_post(@status.guid) expect(likes.length).to eq(0) end + + it "fails at unliking post user didn't like" do + delete( + api_v1_post_likes_path(post_id: @status.guid), + params: {access_token: access_token} + ) + expect(response.status).to eq(204) + + delete( + api_v1_post_likes_path(post_id: @status.guid), + params: {access_token: access_token} + ) + expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.likes.no_like")) + + likes = like_service.find_for_post(@status.guid) + expect(likes.length).to eq(0) + end end context "with wrong post id" do @@ -67,11 +146,28 @@ describe Api::V1::LikesController do params: {access_token: access_token} ) expect(response.status).to eq(404) + expect(response.body).to eq(I18n.t("api.endpoint_errors.posts.post_not_found")) end end end - def like_service - LikeService.new(auth.user) + private + + # rubocop:disable Metrics/AbcSize + def confirm_like_format(likes, user) + like = likes.find {|like_element| like_element["author"]["guid"] == user.guid } + author = like["author"] + expect(author["diaspora_id"]).to eq(user.diaspora_handle) + expect(author["name"]).to eq(user.name) + expect(author["avatar"]).to eq(user.profile.image_url) + end + # rubocop:enable Metrics/AbcSize + + def like_service(user=auth.user) + LikeService.new(user) + end + + def response_body(response) + JSON.parse(response.body) end end diff --git a/spec/presenters/likes_presenter_spec.rb b/spec/presenters/likes_presenter_spec.rb new file mode 100644 index 000000000..8b5c93493 --- /dev/null +++ b/spec/presenters/likes_presenter_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +describe LikesPresenter do + before do + @status = alice.post( + :status_message, + text: "This is a status message from alice", + public: true, + to: "all" + ) + bobs_like_service = LikeService.new(bob) + like = bobs_like_service.create(@status.guid) + @presenter = LikesPresenter.new(like, bob) + end + + describe "#as_api_json" do + it "works" do + expect(@presenter.as_api_json).to be_present + end + + it "confirm API V1 compliance" do + like = @presenter.as_api_json + expect(like.has_key?(:guid)).to be_truthy + author = like[:author] + expect(author).not_to be_nil + expect(author).to include(guid: bob.guid) + expect(author).to include(diaspora_id: bob.diaspora_handle) + expect(author).to include(name: bob.name) + expect(author).to include(avatar: bob.profile.image_url) + end + end +end