bulk_import_service_spec.rb 17 KB

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