process_account_service_spec.rb 7.5 KB

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