bulk_import_service_spec.rb 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. # frozen_string_literal: true
  2. require 'rails_helper'
  3. RSpec.describe BulkImportService do
  4. subject { described_class.new }
  5. let(:account) { Fabricate(:account) }
  6. let(:import) { Fabricate(:bulk_import, account: account, type: import_type, overwrite: overwrite, state: :in_progress, imported_items: 0, processed_items: 0) }
  7. before do
  8. import.update(total_items: import.rows.count)
  9. end
  10. describe '#call' do
  11. context 'when importing follows' do
  12. let(:import_type) { 'following' }
  13. let(:overwrite) { false }
  14. let!(:rows) do
  15. [
  16. { 'acct' => 'user@foo.bar' },
  17. { 'acct' => 'unknown@unknown.bar' },
  18. ].map { |data| import.rows.create!(data: data) }
  19. end
  20. before { account.follow!(Fabricate(:account)) }
  21. it 'does not immediately change who the account follows, enqueues workers, sends follow requests after worker run' do
  22. expect { subject.call(import) }
  23. .to_not(change { account.reload.active_relationships.to_a })
  24. expect(row_worker_job_args)
  25. .to match_array(rows.map(&:id))
  26. stub_resolve_account_and_drain_workers
  27. expect(FollowRequest.includes(:target_account).where(account: account).map { |follow_request| follow_request.target_account.acct })
  28. .to contain_exactly('user@foo.bar', 'unknown@unknown.bar')
  29. end
  30. end
  31. context 'when importing follows with overwrite' do
  32. let(:import_type) { 'following' }
  33. let(:overwrite) { true }
  34. let!(:followed) { Fabricate(:account, username: 'followed', domain: 'foo.bar', protocol: :activitypub) }
  35. let!(:to_be_unfollowed) { Fabricate(:account, username: 'to_be_unfollowed', domain: 'foo.bar', protocol: :activitypub) }
  36. let!(:rows) do
  37. [
  38. { 'acct' => 'followed@foo.bar', 'show_reblogs' => false, 'notify' => true, 'languages' => ['en'] },
  39. { 'acct' => 'user@foo.bar' },
  40. { 'acct' => 'unknown@unknown.bar' },
  41. ].map { |data| import.rows.create!(data: data) }
  42. end
  43. before do
  44. account.follow!(followed, reblogs: true, notify: false)
  45. account.follow!(to_be_unfollowed)
  46. end
  47. it 'updates the existing follow relationship as expected and unfollows user not on list, enqueues workers, sends follow reqs after worker run' do
  48. expect { subject.call(import) }
  49. .to change { Follow.where(account: account, target_account: followed).pick(:show_reblogs, :notify, :languages) }.from([true, false, nil]).to([false, true, ['en']])
  50. expect(account)
  51. .to_not be_following(to_be_unfollowed)
  52. expect(row_worker_job_args)
  53. .to match_array(rows[1..].map(&:id))
  54. stub_resolve_account_and_drain_workers
  55. expect(FollowRequest.includes(:target_account).where(account: account).map { |follow_request| follow_request.target_account.acct })
  56. .to contain_exactly('user@foo.bar', 'unknown@unknown.bar')
  57. end
  58. end
  59. context 'when importing blocks' do
  60. let(:import_type) { 'blocking' }
  61. let(:overwrite) { false }
  62. let!(:rows) do
  63. [
  64. { 'acct' => 'user@foo.bar' },
  65. { 'acct' => 'unknown@unknown.bar' },
  66. ].map { |data| import.rows.create!(data: data) }
  67. end
  68. before { account.block!(Fabricate(:account, username: 'already_blocked', domain: 'remote.org')) }
  69. it 'does not immediately change who the account blocks, enqueues worker, blocks after run' do
  70. expect { subject.call(import) }
  71. .to_not(change { account.reload.blocking.to_a })
  72. expect(row_worker_job_args)
  73. .to match_array(rows.map(&:id))
  74. stub_resolve_account_and_drain_workers
  75. expect(account.reload.blocking.map(&:acct))
  76. .to contain_exactly('already_blocked@remote.org', 'user@foo.bar', 'unknown@unknown.bar')
  77. end
  78. end
  79. context 'when importing blocks with overwrite' do
  80. let(:import_type) { 'blocking' }
  81. let(:overwrite) { true }
  82. let!(:blocked) { Fabricate(:account, username: 'blocked', domain: 'foo.bar', protocol: :activitypub) }
  83. let!(:to_be_unblocked) { Fabricate(:account, username: 'to_be_unblocked', domain: 'foo.bar', protocol: :activitypub) }
  84. let!(:rows) do
  85. [
  86. { 'acct' => 'blocked@foo.bar' },
  87. { 'acct' => 'user@foo.bar' },
  88. { 'acct' => 'unknown@unknown.bar' },
  89. ].map { |data| import.rows.create!(data: data) }
  90. end
  91. before do
  92. account.block!(blocked)
  93. account.block!(to_be_unblocked)
  94. end
  95. it 'unblocks user not present on list, enqueues worker, requests follow after run' do
  96. subject.call(import)
  97. expect(account.blocking?(to_be_unblocked)).to be false
  98. expect(row_worker_job_args)
  99. .to match_array(rows[1..].map(&:id))
  100. stub_resolve_account_and_drain_workers
  101. expect(account.blocking.map(&:acct))
  102. .to contain_exactly('blocked@foo.bar', 'user@foo.bar', 'unknown@unknown.bar')
  103. end
  104. end
  105. context 'when importing mutes' do
  106. let(:import_type) { 'muting' }
  107. let(:overwrite) { false }
  108. let!(:rows) do
  109. [
  110. { 'acct' => 'user@foo.bar' },
  111. { 'acct' => 'unknown@unknown.bar' },
  112. ].map { |data| import.rows.create!(data: data) }
  113. end
  114. before { account.mute!(Fabricate(:account, username: 'already_muted', domain: 'remote.org')) }
  115. it 'does not immediately change who the account blocks, enqueures worker, mutes users after worker run' do
  116. expect { subject.call(import) }
  117. .to_not(change { account.reload.muting.to_a })
  118. expect(row_worker_job_args)
  119. .to match_array(rows.map(&:id))
  120. stub_resolve_account_and_drain_workers
  121. expect(account.reload.muting.map(&:acct))
  122. .to contain_exactly('already_muted@remote.org', 'user@foo.bar', 'unknown@unknown.bar')
  123. end
  124. end
  125. context 'when importing mutes with overwrite' do
  126. let(:import_type) { 'muting' }
  127. let(:overwrite) { true }
  128. let!(:muted) { Fabricate(:account, username: 'muted', domain: 'foo.bar', protocol: :activitypub) }
  129. let!(:to_be_unmuted) { Fabricate(:account, username: 'to_be_unmuted', domain: 'foo.bar', protocol: :activitypub) }
  130. let!(:rows) do
  131. [
  132. { 'acct' => 'muted@foo.bar', 'hide_notifications' => true },
  133. { 'acct' => 'user@foo.bar' },
  134. { 'acct' => 'unknown@unknown.bar' },
  135. ].map { |data| import.rows.create!(data: data) }
  136. end
  137. before do
  138. account.mute!(muted, notifications: false)
  139. account.mute!(to_be_unmuted)
  140. end
  141. it 'updates the existing mute as expected and unblocks user not on list, and enqueues worker, and requests follow after worker run' do
  142. expect { subject.call(import) }
  143. .to change { Mute.where(account: account, target_account: muted).pick(:hide_notifications) }.from(false).to(true)
  144. expect(account.muting?(to_be_unmuted)).to be false
  145. expect(row_worker_job_args)
  146. .to match_array(rows[1..].map(&:id))
  147. stub_resolve_account_and_drain_workers
  148. expect(account.muting.map(&:acct))
  149. .to contain_exactly('muted@foo.bar', 'user@foo.bar', 'unknown@unknown.bar')
  150. end
  151. end
  152. context 'when importing domain blocks' do
  153. let(:import_type) { 'domain_blocking' }
  154. let(:overwrite) { false }
  155. let(:rows) do
  156. [
  157. { 'domain' => 'blocked.com' },
  158. { 'domain' => 'to-block.com' },
  159. ]
  160. end
  161. before do
  162. rows.each { |data| import.rows.create!(data: data) }
  163. account.block_domain!('alreadyblocked.com')
  164. account.block_domain!('blocked.com')
  165. end
  166. it 'blocks all the new domains and marks import finished' do
  167. subject.call(import)
  168. expect(account.domain_blocks.pluck(:domain))
  169. .to contain_exactly('alreadyblocked.com', 'blocked.com', 'to-block.com')
  170. expect(import.reload.state_finished?).to be true
  171. end
  172. end
  173. context 'when importing domain blocks with overwrite' do
  174. let(:import_type) { 'domain_blocking' }
  175. let(:overwrite) { true }
  176. let(:rows) do
  177. [
  178. { 'domain' => 'blocked.com' },
  179. { 'domain' => 'to-block.com' },
  180. ]
  181. end
  182. before do
  183. rows.each { |data| import.rows.create!(data: data) }
  184. account.block_domain!('alreadyblocked.com')
  185. account.block_domain!('blocked.com')
  186. end
  187. it 'blocks all the new domains and marks import finished' do
  188. subject.call(import)
  189. expect(account.domain_blocks.pluck(:domain))
  190. .to contain_exactly('blocked.com', 'to-block.com')
  191. expect(import.reload.state_finished?)
  192. .to be true
  193. end
  194. end
  195. context 'when importing bookmarks' do
  196. let(:import_type) { 'bookmarks' }
  197. let(:overwrite) { false }
  198. let!(:already_bookmarked) { Fabricate(:status, uri: 'https://already.bookmarked/1') }
  199. let!(:status) { Fabricate(:status, uri: 'https://foo.bar/posts/1') }
  200. let!(:inaccessible_status) { Fabricate(:status, uri: 'https://foo.bar/posts/inaccessible', visibility: :direct) }
  201. let!(:bookmarked) { Fabricate(:status, uri: 'https://foo.bar/posts/already-bookmarked') }
  202. let!(:rows) do
  203. [
  204. { 'uri' => status.uri },
  205. { 'uri' => inaccessible_status.uri },
  206. { 'uri' => bookmarked.uri },
  207. { 'uri' => 'https://domain.unknown/foo' },
  208. { 'uri' => 'https://domain.unknown/private' },
  209. ].map { |data| import.rows.create!(data: data) }
  210. end
  211. before do
  212. account.bookmarks.create!(status: already_bookmarked)
  213. account.bookmarks.create!(status: bookmarked)
  214. end
  215. it 'enqueues workers for the expected rows and updates bookmarks after worker run' do
  216. subject.call(import)
  217. expect(row_worker_job_args)
  218. .to match_array(rows.map(&:id))
  219. stub_fetch_remote_and_drain_workers
  220. expect(account.bookmarks.map { |bookmark| bookmark.status.uri })
  221. .to contain_exactly(already_bookmarked.uri, status.uri, bookmarked.uri, 'https://domain.unknown/foo')
  222. end
  223. end
  224. context 'when importing bookmarks with overwrite' do
  225. let(:import_type) { 'bookmarks' }
  226. let(:overwrite) { true }
  227. let!(:already_bookmarked) { Fabricate(:status, uri: 'https://already.bookmarked/1') }
  228. let!(:status) { Fabricate(:status, uri: 'https://foo.bar/posts/1') }
  229. let!(:inaccessible_status) { Fabricate(:status, uri: 'https://foo.bar/posts/inaccessible', visibility: :direct) }
  230. let!(:bookmarked) { Fabricate(:status, uri: 'https://foo.bar/posts/already-bookmarked') }
  231. let!(:rows) do
  232. [
  233. { 'uri' => status.uri },
  234. { 'uri' => inaccessible_status.uri },
  235. { 'uri' => bookmarked.uri },
  236. { 'uri' => 'https://domain.unknown/foo' },
  237. { 'uri' => 'https://domain.unknown/private' },
  238. ].map { |data| import.rows.create!(data: data) }
  239. end
  240. before do
  241. account.bookmarks.create!(status: already_bookmarked)
  242. account.bookmarks.create!(status: bookmarked)
  243. end
  244. it 'enqueues workers for the expected rows and updates bookmarks' do
  245. subject.call(import)
  246. expect(row_worker_job_args)
  247. .to match_array(rows.map(&:id))
  248. stub_fetch_remote_and_drain_workers
  249. expect(account.bookmarks.map { |bookmark| bookmark.status.uri })
  250. .to contain_exactly(status.uri, bookmarked.uri, 'https://domain.unknown/foo')
  251. end
  252. end
  253. def row_worker_job_args
  254. Import::RowWorker
  255. .jobs
  256. .pluck('args')
  257. .flatten
  258. end
  259. def stub_resolve_account_and_drain_workers
  260. resolve_account_service_double = instance_double(ResolveAccountService)
  261. allow(ResolveAccountService)
  262. .to receive(:new)
  263. .and_return(resolve_account_service_double)
  264. allow(resolve_account_service_double)
  265. .to receive(:call)
  266. .with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
  267. allow(resolve_account_service_double)
  268. .to receive(:call)
  269. .with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
  270. Import::RowWorker.drain
  271. end
  272. def stub_fetch_remote_and_drain_workers
  273. service_double = instance_double(ActivityPub::FetchRemoteStatusService)
  274. allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service_double)
  275. allow(service_double).to receive(:call).with('https://domain.unknown/foo') { Fabricate(:status, uri: 'https://domain.unknown/foo') }
  276. allow(service_double).to receive(:call).with('https://domain.unknown/private') { Fabricate(:status, uri: 'https://domain.unknown/private', visibility: :direct) }
  277. Import::RowWorker.drain
  278. end
  279. end
  280. end