replies_controller_spec.rb 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. # frozen_string_literal: true
  2. require 'rails_helper'
  3. RSpec.describe ActivityPub::RepliesController, type: :controller do
  4. let(:status) { Fabricate(:status, visibility: parent_visibility) }
  5. let(:remote_account) { Fabricate(:account, domain: 'foobar.com') }
  6. let(:remote_reply_id) { 'https://foobar.com/statuses/1234' }
  7. let(:remote_querier) { nil }
  8. shared_examples 'cacheable response' do
  9. it 'does not set cookies' do
  10. expect(response.cookies).to be_empty
  11. expect(response.headers['Set-Cookies']).to be nil
  12. end
  13. it 'does not set sessions' do
  14. response
  15. expect(session).to be_empty
  16. end
  17. it 'returns public Cache-Control header' do
  18. expect(response.headers['Cache-Control']).to include 'public'
  19. end
  20. end
  21. shared_examples 'common behavior' do
  22. context 'when status is private' do
  23. let(:parent_visibility) { :private }
  24. it 'returns http not found' do
  25. expect(response).to have_http_status(404)
  26. end
  27. end
  28. context 'when status is direct' do
  29. let(:parent_visibility) { :direct }
  30. it 'returns http not found' do
  31. expect(response).to have_http_status(404)
  32. end
  33. end
  34. end
  35. shared_examples 'disallowed access' do
  36. context 'when status is public' do
  37. let(:parent_visibility) { :public }
  38. it 'returns http not found' do
  39. expect(response).to have_http_status(404)
  40. end
  41. end
  42. it_behaves_like 'common behavior'
  43. end
  44. shared_examples 'allowed access' do
  45. context 'when account is permanently suspended' do
  46. let(:parent_visibility) { :public }
  47. before do
  48. status.account.suspend!
  49. status.account.deletion_request.destroy
  50. end
  51. it 'returns http gone' do
  52. expect(response).to have_http_status(410)
  53. end
  54. end
  55. context 'when account is temporarily suspended' do
  56. let(:parent_visibility) { :public }
  57. before do
  58. status.account.suspend!
  59. end
  60. it 'returns http forbidden' do
  61. expect(response).to have_http_status(403)
  62. end
  63. end
  64. context 'when status is public' do
  65. let(:parent_visibility) { :public }
  66. let(:json) { body_as_json }
  67. let(:page_json) { json[:first] }
  68. it 'returns http success' do
  69. expect(response).to have_http_status(200)
  70. end
  71. it 'returns application/activity+json' do
  72. expect(response.media_type).to eq 'application/activity+json'
  73. end
  74. it_behaves_like 'cacheable response'
  75. context 'without only_other_accounts' do
  76. it "returns items with thread author's replies" do
  77. expect(page_json).to be_a Hash
  78. expect(page_json[:items]).to be_an Array
  79. expect(page_json[:items].size).to eq 1
  80. expect(page_json[:items].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
  81. end
  82. context 'when there are few self-replies' do
  83. it 'points next to replies from other people' do
  84. expect(page_json).to be_a Hash
  85. expect(Addressable::URI.parse(page_json[:next]).query.split('&')).to include('only_other_accounts=true', 'page=true')
  86. end
  87. end
  88. context 'when there are many self-replies' do
  89. before do
  90. 10.times { Fabricate(:status, account: status.account, thread: status, visibility: :public) }
  91. end
  92. it 'points next to other self-replies' do
  93. expect(page_json).to be_a Hash
  94. expect(Addressable::URI.parse(page_json[:next]).query.split('&')).to include('only_other_accounts=false', 'page=true')
  95. end
  96. end
  97. end
  98. context 'with only_other_accounts' do
  99. let(:only_other_accounts) { 'true' }
  100. it 'returns items with other public or unlisted replies' do
  101. expect(page_json).to be_a Hash
  102. expect(page_json[:items]).to be_an Array
  103. expect(page_json[:items].size).to eq 3
  104. end
  105. it 'only inlines items that are local and public or unlisted replies' do
  106. inlined_replies = page_json[:items].select { |x| x.is_a?(Hash) }
  107. public_collection = ActivityPub::TagManager::COLLECTIONS[:public]
  108. expect(inlined_replies.all? { |item| item[:to].include?(public_collection) || item[:cc].include?(public_collection) }).to be true
  109. expect(inlined_replies.all? { |item| ActivityPub::TagManager.instance.local_uri?(item[:id]) }).to be true
  110. end
  111. it 'uses ids for remote toots' do
  112. remote_replies = page_json[:items].select { |x| !x.is_a?(Hash) }
  113. expect(remote_replies.all? { |item| item.is_a?(String) && !ActivityPub::TagManager.instance.local_uri?(item) }).to be true
  114. end
  115. context 'when there are few replies' do
  116. it 'does not have a next page' do
  117. expect(page_json).to be_a Hash
  118. expect(page_json[:next]).to be_nil
  119. end
  120. end
  121. context 'when there are many replies' do
  122. before do
  123. 10.times { Fabricate(:status, thread: status, visibility: :public) }
  124. end
  125. it 'points next to other replies' do
  126. expect(page_json).to be_a Hash
  127. expect(Addressable::URI.parse(page_json[:next]).query.split('&')).to include('only_other_accounts=true', 'page=true')
  128. end
  129. end
  130. end
  131. end
  132. it_behaves_like 'common behavior'
  133. end
  134. before do
  135. stub_const 'ActivityPub::RepliesController::DESCENDANTS_LIMIT', 5
  136. allow(controller).to receive(:signed_request_actor).and_return(remote_querier)
  137. Fabricate(:status, thread: status, visibility: :public)
  138. Fabricate(:status, thread: status, visibility: :public)
  139. Fabricate(:status, thread: status, visibility: :private)
  140. Fabricate(:status, account: status.account, thread: status, visibility: :public)
  141. Fabricate(:status, account: status.account, thread: status, visibility: :private)
  142. Fabricate(:status, account: remote_account, thread: status, visibility: :public, uri: remote_reply_id)
  143. end
  144. describe 'GET #index' do
  145. subject(:response) { get :index, params: { account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts } }
  146. let(:only_other_accounts) { nil }
  147. context 'with no signature' do
  148. it_behaves_like 'allowed access'
  149. end
  150. context 'with signature' do
  151. let(:remote_querier) { Fabricate(:account, domain: 'example.com') }
  152. it_behaves_like 'allowed access'
  153. context 'when signed request account is blocked' do
  154. before do
  155. status.account.block!(remote_querier)
  156. end
  157. it_behaves_like 'disallowed access'
  158. end
  159. context 'when signed request account is domain blocked' do
  160. before do
  161. status.account.block_domain!(remote_querier.domain)
  162. end
  163. it_behaves_like 'disallowed access'
  164. end
  165. end
  166. end
  167. end