bulk_import_service_spec.rb 17 KB

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