From b73eb1b847bad88b2df174de361bacf42846c3d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KMY=EF=BC=88=E9=9B=AA=E3=81=82=E3=81=99=E3=81=8B=EF=BC=89?= Date: Wed, 18 Oct 2023 10:34:53 +0900 Subject: [PATCH] =?UTF-8?q?Test:=20API=E5=91=BC=E3=81=B3=E5=87=BA=E3=81=97?= =?UTF-8?q?=E3=81=AB=E3=82=88=E3=81=A3=E3=81=A6=E3=82=B9=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=83=97=E3=82=92=E3=81=A4=E3=81=91=E3=82=8B=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=20(#139)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Test: API呼び出しによってスタンプをつけるテスト * Fix: スタンプ削除APIで`emoji`パラメータを指定していないときに投稿が自由閲覧できる問題 * Test: さっきの問題のテストを追加 --- .../v1/statuses/emoji_reactions_controller.rb | 6 +- app/models/concerns/account_interactions.rb | 12 +- .../api/v1/statuses/emoji_reactions_spec.rb | 271 ++++++++++++++++++ 3 files changed, 285 insertions(+), 4 deletions(-) create mode 100644 spec/requests/api/v1/statuses/emoji_reactions_spec.rb diff --git a/app/controllers/api/v1/statuses/emoji_reactions_controller.rb b/app/controllers/api/v1/statuses/emoji_reactions_controller.rb index f437576d1b8abc..d7ca4f2da7f014 100644 --- a/app/controllers/api/v1/statuses/emoji_reactions_controller.rb +++ b/app/controllers/api/v1/statuses/emoji_reactions_controller.rb @@ -3,9 +3,9 @@ class Api::V1::Statuses::EmojiReactionsController < Api::BaseController include Authorization - before_action -> { doorkeeper_authorize! :write, :'write:emoji_reactions' } + before_action -> { doorkeeper_authorize! :write, :'write:favourites' } before_action :require_user! - before_action :set_status, only: %i(create update destroy) + before_action :set_status, only: %i(create update) before_action :set_status_without_authorize, only: [:destroy] def create @@ -28,6 +28,8 @@ def destroy authorize @status, :show? if emoji_reaction.nil? UnEmojiReactService.new.call(current_account.id, @status.id, emoji_reaction) if emoji_reaction.present? + else + authorize @status, :show? end render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new( diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index 8c0a635d02e451..143e91feb85a65 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -247,8 +247,16 @@ def favourited?(status) status.proper.favourites.where(account: self).exists? end - def emoji_reactioned?(status) - status.proper.emoji_reactions.where(account: self).exists? + def emoji_reacted?(status, shortcode = nil, domain = nil, domain_force = false) # rubocop:disable Style/OptionalBooleanParameter + if shortcode.present? + if domain.present? || domain_force + status.proper.emoji_reactions.joins(:custom_emoji).where(account: self, name: shortcode, custom_emoji: { domain: domain }).exists? + else + status.proper.emoji_reactions.where(account: self, name: shortcode).exists? + end + else + status.proper.emoji_reactions.where(account: self).exists? + end end def bookmarked?(status) diff --git a/spec/requests/api/v1/statuses/emoji_reactions_spec.rb b/spec/requests/api/v1/statuses/emoji_reactions_spec.rb new file mode 100644 index 00000000000000..e85404379d1472 --- /dev/null +++ b/spec/requests/api/v1/statuses/emoji_reactions_spec.rb @@ -0,0 +1,271 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'EmojiReactions' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'write:favourites' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'POST /api/v1/statuses/:status_id/emoji_reactions' do + subject do + post "/api/v1/statuses/#{status.id}/emoji_reactions", headers: headers, params: { emoji: emoji } + end + + let(:status) { Fabricate(:status) } + let(:emoji) { '😀' } + + it_behaves_like 'forbidden for wrong scope', 'read read:favourites' + + context 'with public status' do + it 'reacts the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.emoji_reacted?(status, emoji)).to be true + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, emoji_reactions_count: 1) + ) + end + end + + context 'with private status of not-followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'with private status of followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + before do + user.account.follow!(status.account) + end + + it 'reacts the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.emoji_reacted?(status)).to be true + end + end + + context 'when local custom emoji' do + before { Fabricate(:custom_emoji, shortcode: 'ohagi') } + + let(:emoji) { 'ohagi' } + + it 'reacts the status succeessfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.emoji_reacted?(status, 'ohagi')).to be true + end + end + + context 'when remote custom emoji' do + let!(:custom_emoji) { Fabricate(:custom_emoji, shortcode: 'ohagi', domain: 'foo.bar', uri: 'https://foo.bar/emoji') } + let(:emoji) { 'ohagi@foo.bar' } + + before { Fabricate(:emoji_reaction, status: status, name: 'ohagi', custom_emoji: custom_emoji) } + + it 'reacts the status succeessfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.emoji_reacted?(status, 'ohagi', 'foo.bar')).to be true + end + end + + context 'when not existing custom emoji' do + let(:emoji) { 'ohagi' } + + it 'reacts the status succeessfully', :aggregate_failures do + subject + + expect(response).to have_http_status(422) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end + + describe 'POST /api/v1/statuses/:status_id/emoji_unreaction' do + subject do + post "/api/v1/statuses/#{status.id}/emoji_unreaction", headers: headers, params: { emoji: emoji } + end + + let(:status) { Fabricate(:status) } + let(:emoji) { '😀' } + + it_behaves_like 'forbidden for wrong scope', 'read read:favourites' + + context 'with public status' do + before do + EmojiReactService.new.call(user.account, status, emoji) + end + + it 'unreacts the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.emoji_reacted?(status)).to be false + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, emoji_reactions_count: 0) + ) + end + end + + context 'when the requesting user was blocked by the status author' do + before do + EmojiReactService.new.call(user.account, status, emoji) + status.account.block!(user.account) + end + + it 'unreacts the status successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.emoji_reacted?(status)).to be false + end + + it 'returns json with updated attributes' do + subject + + expect(body_as_json).to match( + a_hash_including(id: status.id.to_s, emoji_reactions_count: 0) + ) + end + end + + context 'when status is not reacted' do + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + + context 'with private status that was not reacted' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'with private status that was not reacted without emoji parameter' do + let(:status) { Fabricate(:status, visibility: :private) } + let(:emoji) { nil } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'when local custom emoji' do + before do + Fabricate(:custom_emoji, shortcode: 'ohagi') + EmojiReactService.new.call(user.account, status, emoji) + end + + let(:emoji) { 'ohagi' } + + it 'reacts the status succeessfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.emoji_reacted?(status)).to be false + end + end + + context 'when remote custom emoji' do + let(:emoji) { 'ohagi@foo.bar' } + + before do + custom_emoji = Fabricate(:custom_emoji, shortcode: 'ohagi', domain: 'foo.bar', uri: 'https://foo.bar/emoji') + Fabricate(:emoji_reaction, name: 'ohagi', status: status, custom_emoji: custom_emoji) + EmojiReactService.new.call(user.account, status, emoji) + end + + it 'reacts the status succeessfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.emoji_reacted?(status)).to be false + end + end + + context 'when remote custom emoji but not specified domain' do + let(:emoji) { 'ohagi' } + + before do + custom_emoji = Fabricate(:custom_emoji, shortcode: 'ohagi', domain: 'foo.bar', uri: 'https://foo.bar/emoji') + Fabricate(:emoji_reaction, name: 'ohagi', status: status, custom_emoji: custom_emoji) + EmojiReactService.new.call(user.account, status, 'ohagi@foo.bar') + end + + it 'reacts the status succeessfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.emoji_reacted?(status)).to be true + end + end + + context 'without specified domain and reacted same name multiple domains' do + let(:emoji) { 'ohagi' } + + before do + Fabricate(:custom_emoji, shortcode: 'ohagi', domain: 'foo.bar', uri: 'https://foo.bar/emoji') + Fabricate(:custom_emoji, shortcode: 'ohagi') + EmojiReactService.new.call(user.account, status, 'ohagi') + EmojiReactService.new.call(user.account, status, 'ohagi@foo.bar') + end + + it 'reacts the status succeessfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(user.account.emoji_reacted?(status)).to be false + end + end + + context 'when not existing custom emoji' do + let(:emoji) { 'ohagi' } + + it 'reacts the status succeessfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + end + end + end +end