process_account_service_spec.rb 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. # frozen_string_literal: true
  2. require 'rails_helper'
  3. RSpec.describe ActivityPub::ProcessAccountService do
  4. subject { described_class.new }
  5. context 'with property values, an avatar, and a profile header' do
  6. let(:payload) do
  7. {
  8. id: 'https://foo.test',
  9. type: 'Actor',
  10. inbox: 'https://foo.test/inbox',
  11. attachment: [
  12. { type: 'PropertyValue', name: 'Pronouns', value: 'They/them' },
  13. { type: 'PropertyValue', name: 'Occupation', value: 'Unit test' },
  14. { type: 'PropertyValue', name: 'non-string', value: %w(foo bar) },
  15. ],
  16. image: {
  17. type: 'Image',
  18. mediaType: 'image/png',
  19. url: 'https://foo.test/image.png',
  20. },
  21. icon: {
  22. type: 'Image',
  23. url: [
  24. {
  25. mediaType: 'image/png',
  26. href: 'https://foo.test/icon.png',
  27. },
  28. ],
  29. },
  30. }.with_indifferent_access
  31. end
  32. before do
  33. stub_request(:get, 'https://foo.test/image.png').to_return(request_fixture('avatar.txt'))
  34. stub_request(:get, 'https://foo.test/icon.png').to_return(request_fixture('avatar.txt'))
  35. end
  36. it 'parses property values, avatar and profile header as expected' do
  37. account = subject.call('alice', 'example.com', payload)
  38. expect(account.fields)
  39. .to be_an(Array)
  40. .and have_attributes(size: 2)
  41. expect(account.fields.first)
  42. .to be_an(Account::Field)
  43. .and have_attributes(
  44. name: eq('Pronouns'),
  45. value: eq('They/them')
  46. )
  47. expect(account.fields.last)
  48. .to be_an(Account::Field)
  49. .and have_attributes(
  50. name: eq('Occupation'),
  51. value: eq('Unit test')
  52. )
  53. expect(account).to have_attributes(
  54. avatar_remote_url: 'https://foo.test/icon.png',
  55. header_remote_url: 'https://foo.test/image.png'
  56. )
  57. end
  58. end
  59. context 'with attribution domains' do
  60. let(:payload) do
  61. {
  62. id: 'https://foo.test',
  63. type: 'Actor',
  64. inbox: 'https://foo.test/inbox',
  65. attributionDomains: [
  66. 'example.com',
  67. ],
  68. }.with_indifferent_access
  69. end
  70. it 'parses attribution domains' do
  71. account = subject.call('alice', 'example.com', payload)
  72. expect(account.attribution_domains)
  73. .to match_array(%w(example.com))
  74. end
  75. end
  76. context 'when account is not suspended' do
  77. subject { described_class.new.call(account.username, account.domain, payload) }
  78. let!(:account) { Fabricate(:account, username: 'alice', domain: 'example.com') }
  79. let(:payload) do
  80. {
  81. id: 'https://foo.test',
  82. type: 'Actor',
  83. inbox: 'https://foo.test/inbox',
  84. suspended: true,
  85. }.with_indifferent_access
  86. end
  87. before do
  88. allow(Admin::SuspensionWorker).to receive(:perform_async)
  89. end
  90. it 'suspends account remotely' do
  91. expect(subject.suspended?).to be true
  92. expect(subject.suspension_origin_remote?).to be true
  93. end
  94. it 'queues suspension worker' do
  95. subject
  96. expect(Admin::SuspensionWorker).to have_received(:perform_async)
  97. end
  98. end
  99. context 'when account is suspended' do
  100. subject { described_class.new.call('alice', 'example.com', payload) }
  101. let!(:account) { Fabricate(:account, username: 'alice', domain: 'example.com', display_name: '') }
  102. let(:payload) do
  103. {
  104. id: 'https://foo.test',
  105. type: 'Actor',
  106. inbox: 'https://foo.test/inbox',
  107. suspended: false,
  108. name: 'Hoge',
  109. }.with_indifferent_access
  110. end
  111. before do
  112. allow(Admin::UnsuspensionWorker).to receive(:perform_async)
  113. account.suspend!(origin: suspension_origin)
  114. end
  115. context 'when locally' do
  116. let(:suspension_origin) { :local }
  117. it 'does not unsuspend it' do
  118. expect(subject.suspended?).to be true
  119. end
  120. it 'does not update any attributes' do
  121. expect(subject.display_name).to_not eq 'Hoge'
  122. end
  123. end
  124. context 'when remotely' do
  125. let(:suspension_origin) { :remote }
  126. it 'unsuspends it' do
  127. expect(subject.suspended?).to be false
  128. end
  129. it 'queues unsuspension worker' do
  130. subject
  131. expect(Admin::UnsuspensionWorker).to have_received(:perform_async)
  132. end
  133. it 'updates attributes' do
  134. expect(subject.display_name).to eq 'Hoge'
  135. end
  136. end
  137. end
  138. context 'when discovering many subdomains in a short timeframe' do
  139. subject do
  140. 8.times do |i|
  141. domain = "test#{i}.testdomain.com"
  142. json = {
  143. id: "https://#{domain}/users/1",
  144. type: 'Actor',
  145. inbox: "https://#{domain}/inbox",
  146. }.with_indifferent_access
  147. described_class.new.call('alice', domain, json)
  148. end
  149. end
  150. before do
  151. stub_const 'ActivityPub::ProcessAccountService::SUBDOMAINS_RATELIMIT', 5
  152. end
  153. it 'creates accounts without exceeding rate limit' do
  154. expect { subject }
  155. .to create_some_remote_accounts
  156. .and create_fewer_than_rate_limit_accounts
  157. end
  158. end
  159. context 'when Accounts referencing other accounts' do
  160. let(:payload) do
  161. {
  162. '@context': ['https://www.w3.org/ns/activitystreams'],
  163. id: 'https://foo.test/users/1',
  164. type: 'Person',
  165. inbox: 'https://foo.test/inbox',
  166. featured: 'https://foo.test/users/1/featured',
  167. preferredUsername: 'user1',
  168. }.with_indifferent_access
  169. end
  170. before do
  171. stub_const 'ActivityPub::ProcessAccountService::DISCOVERIES_PER_REQUEST', 5
  172. 8.times do |i|
  173. actor_json = {
  174. '@context': ['https://www.w3.org/ns/activitystreams'],
  175. id: "https://foo.test/users/#{i}",
  176. type: 'Person',
  177. inbox: 'https://foo.test/inbox',
  178. featured: "https://foo.test/users/#{i}/featured",
  179. preferredUsername: "user#{i}",
  180. }.with_indifferent_access
  181. status_json = {
  182. '@context': ['https://www.w3.org/ns/activitystreams'],
  183. id: "https://foo.test/users/#{i}/status",
  184. attributedTo: "https://foo.test/users/#{i}",
  185. type: 'Note',
  186. content: "@user#{i + 1} test",
  187. tag: [
  188. {
  189. type: 'Mention',
  190. href: "https://foo.test/users/#{i + 1}",
  191. name: "@user#{i + 1}",
  192. },
  193. ],
  194. to: ['as:Public', "https://foo.test/users/#{i + 1}"],
  195. }.with_indifferent_access
  196. featured_json = {
  197. '@context': ['https://www.w3.org/ns/activitystreams'],
  198. id: "https://foo.test/users/#{i}/featured",
  199. type: 'OrderedCollection',
  200. totalItems: 1,
  201. orderedItems: [status_json],
  202. }.with_indifferent_access
  203. webfinger = {
  204. subject: "acct:user#{i}@foo.test",
  205. links: [{ rel: 'self', href: "https://foo.test/users/#{i}", type: 'application/activity+json' }],
  206. }.with_indifferent_access
  207. stub_request(:get, "https://foo.test/users/#{i}").to_return(status: 200, body: actor_json.to_json, headers: { 'Content-Type': 'application/activity+json' })
  208. stub_request(:get, "https://foo.test/users/#{i}/featured").to_return(status: 200, body: featured_json.to_json, headers: { 'Content-Type': 'application/activity+json' })
  209. stub_request(:get, "https://foo.test/users/#{i}/status").to_return(status: 200, body: status_json.to_json, headers: { 'Content-Type': 'application/activity+json' })
  210. stub_request(:get, "https://foo.test/.well-known/webfinger?resource=acct:user#{i}@foo.test").to_return(body: webfinger.to_json, headers: { 'Content-Type': 'application/jrd+json' })
  211. end
  212. end
  213. it 'creates accounts without exceeding rate limit', :inline_jobs do
  214. expect { subject.call('user1', 'foo.test', payload) }
  215. .to create_some_remote_accounts
  216. .and create_fewer_than_rate_limit_accounts
  217. end
  218. end
  219. private
  220. def create_some_remote_accounts
  221. change(Account.remote, :count).by_at_least(2)
  222. end
  223. def create_fewer_than_rate_limit_accounts
  224. change(Account.remote, :count).by_at_most(5)
  225. end
  226. end