~cytrogen/masto-fe

ref: 68b4e36c82344fba7c5a01e9f8dc9ddbaaf4e3ff masto-fe/spec/models/form/import_spec.rb -rw-r--r-- 14.8 KiB
68b4e36c — Eugen Rochko Fix `#hashtag` matching non-hashtagged posts in search (#26781) 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Form::Import do
  subject { described_class.new(current_account: account, type: import_type, mode: import_mode, data: data) }

  let(:account)     { Fabricate(:account) }
  let(:data)        { fixture_file_upload(import_file) }
  let(:import_mode) { 'merge' }

  describe 'validations' do
    shared_examples 'incompatible import type' do |type, file|
      let(:import_file) { file }
      let(:import_type) { type }

      it 'has errors' do
        subject.validate
        expect(subject.errors[:data]).to include(I18n.t('imports.errors.incompatible_type'))
      end
    end

    shared_examples 'too many CSV rows' do |type, file, allowed_rows|
      let(:import_file) { file }
      let(:import_type) { type }

      before do
        stub_const 'Form::Import::ROWS_PROCESSING_LIMIT', allowed_rows
      end

      it 'has errors' do
        subject.validate
        expect(subject.errors[:data]).to include(I18n.t('imports.errors.over_rows_processing_limit', count: Form::Import::ROWS_PROCESSING_LIMIT))
      end
    end

    shared_examples 'valid import' do |type, file|
      let(:import_file) { file }
      let(:import_type) { type }

      it 'passes validation' do
        expect(subject).to be_valid
      end
    end

    context 'when the file too large' do
      let(:import_type) { 'following' }
      let(:import_file) { 'imports.txt' }

      before do
        stub_const 'Form::Import::FILE_SIZE_LIMIT', 5
      end

      it 'has errors' do
        subject.validate
        expect(subject.errors[:data]).to include(I18n.t('imports.errors.too_large'))
      end
    end

    context 'when the CSV file is malformed CSV' do
      let(:import_type) { 'following' }
      let(:import_file) { 'boop.ogg' }

      it 'has errors' do
        # NOTE: not testing more specific error because we don't know the string to match
        expect(subject).to model_have_error_on_field(:data)
      end
    end

    context 'when importing more follows than allowed' do
      let(:import_type) { 'following' }
      let(:import_file) { 'imports.txt' }

      before do
        allow(FollowLimitValidator).to receive(:limit_for_account).with(account).and_return(1)
      end

      it 'has errors' do
        subject.validate
        expect(subject.errors[:data]).to include(I18n.t('users.follow_limit_reached', limit: 1))
      end
    end

    it_behaves_like 'too many CSV rows', 'following', 'imports.txt', 1
    it_behaves_like 'too many CSV rows', 'blocking', 'imports.txt', 1
    it_behaves_like 'too many CSV rows', 'muting', 'imports.txt', 1
    it_behaves_like 'too many CSV rows', 'domain_blocking', 'domain_blocks.csv', 2
    it_behaves_like 'too many CSV rows', 'bookmarks', 'bookmark-imports.txt', 3
    it_behaves_like 'too many CSV rows', 'lists', 'lists.csv', 2

    # Importing list of addresses with no headers into various types
    it_behaves_like 'valid import', 'following', 'imports.txt'
    it_behaves_like 'valid import', 'blocking', 'imports.txt'
    it_behaves_like 'valid import', 'muting', 'imports.txt'

    # Importing domain blocks with headers into expected type
    it_behaves_like 'valid import', 'domain_blocking', 'domain_blocks.csv'

    # Importing bookmarks list with no headers into expected type
    it_behaves_like 'valid import', 'bookmarks', 'bookmark-imports.txt'

    # Importing lists with no headers into expected type
    it_behaves_like 'valid import', 'lists', 'lists.csv'

    # Importing followed accounts with headers into various compatible types
    it_behaves_like 'valid import', 'following', 'following_accounts.csv'
    it_behaves_like 'valid import', 'blocking', 'following_accounts.csv'
    it_behaves_like 'valid import', 'muting', 'following_accounts.csv'

    # Importing domain blocks with headers into incompatible types
    it_behaves_like 'incompatible import type', 'following', 'domain_blocks.csv'
    it_behaves_like 'incompatible import type', 'blocking', 'domain_blocks.csv'
    it_behaves_like 'incompatible import type', 'muting', 'domain_blocks.csv'
    it_behaves_like 'incompatible import type', 'bookmarks', 'domain_blocks.csv'

    # Importing followed accounts with headers into incompatible types
    it_behaves_like 'incompatible import type', 'domain_blocking', 'following_accounts.csv'
    it_behaves_like 'incompatible import type', 'bookmarks', 'following_accounts.csv'
  end

  describe '#guessed_type' do
    shared_examples 'with enough information' do |type, file, original_filename, expected_guess|
      let(:import_file) { file }
      let(:import_type) { type }

      before do
        allow(data).to receive(:original_filename).and_return(original_filename)
      end

      it 'guesses the expected type' do
        expect(subject.guessed_type).to eq expected_guess
      end
    end

    context 'when the headers are enough to disambiguate' do
      it_behaves_like 'with enough information', 'following', 'following_accounts.csv', 'import.csv', :following
      it_behaves_like 'with enough information', 'blocking', 'following_accounts.csv', 'import.csv', :following
      it_behaves_like 'with enough information', 'muting', 'following_accounts.csv', 'import.csv', :following

      it_behaves_like 'with enough information', 'following', 'muted_accounts.csv', 'imports.csv', :muting
      it_behaves_like 'with enough information', 'blocking', 'muted_accounts.csv', 'imports.csv', :muting
      it_behaves_like 'with enough information', 'muting', 'muted_accounts.csv', 'imports.csv', :muting
    end

    context 'when the file name is enough to disambiguate' do
      it_behaves_like 'with enough information', 'following', 'imports.txt', 'following_accounts.csv', :following
      it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'following_accounts.csv', :following
      it_behaves_like 'with enough information', 'muting', 'imports.txt', 'following_accounts.csv', :following

      it_behaves_like 'with enough information', 'following', 'imports.txt', 'follows.csv', :following
      it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'follows.csv', :following
      it_behaves_like 'with enough information', 'muting', 'imports.txt', 'follows.csv', :following

      it_behaves_like 'with enough information', 'following', 'imports.txt', 'blocked_accounts.csv', :blocking
      it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'blocked_accounts.csv', :blocking
      it_behaves_like 'with enough information', 'muting', 'imports.txt', 'blocked_accounts.csv', :blocking

      it_behaves_like 'with enough information', 'following', 'imports.txt', 'blocks.csv', :blocking
      it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'blocks.csv', :blocking
      it_behaves_like 'with enough information', 'muting', 'imports.txt', 'blocks.csv', :blocking

      it_behaves_like 'with enough information', 'following', 'imports.txt', 'muted_accounts.csv', :muting
      it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'muted_accounts.csv', :muting
      it_behaves_like 'with enough information', 'muting', 'imports.txt', 'muted_accounts.csv', :muting

      it_behaves_like 'with enough information', 'following', 'imports.txt', 'mutes.csv', :muting
      it_behaves_like 'with enough information', 'blocking', 'imports.txt', 'mutes.csv', :muting
      it_behaves_like 'with enough information', 'muting', 'imports.txt', 'mutes.csv', :muting
    end
  end

  describe '#likely_mismatched?' do
    shared_examples 'with matching types' do |type, file, original_filename = nil|
      let(:import_file) { file }
      let(:import_type) { type }

      before do
        allow(data).to receive(:original_filename).and_return(original_filename) if original_filename.present?
      end

      it 'returns false' do
        expect(subject.likely_mismatched?).to be false
      end
    end

    shared_examples 'with mismatching types' do |type, file, original_filename = nil|
      let(:import_file) { file }
      let(:import_type) { type }

      before do
        allow(data).to receive(:original_filename).and_return(original_filename) if original_filename.present?
      end

      it 'returns true' do
        expect(subject.likely_mismatched?).to be true
      end
    end

    it_behaves_like 'with matching types', 'following', 'following_accounts.csv'
    it_behaves_like 'with matching types', 'following', 'following_accounts.csv', 'imports.txt'
    it_behaves_like 'with matching types', 'following', 'imports.txt'
    it_behaves_like 'with matching types', 'blocking', 'imports.txt', 'blocks.csv'
    it_behaves_like 'with matching types', 'blocking', 'imports.txt'
    it_behaves_like 'with matching types', 'muting', 'muted_accounts.csv'
    it_behaves_like 'with matching types', 'muting', 'muted_accounts.csv', 'imports.txt'
    it_behaves_like 'with matching types', 'muting', 'imports.txt'
    it_behaves_like 'with matching types', 'domain_blocking', 'domain_blocks.csv'
    it_behaves_like 'with matching types', 'domain_blocking', 'domain_blocks.csv', 'imports.txt'
    it_behaves_like 'with matching types', 'bookmarks', 'bookmark-imports.txt'
    it_behaves_like 'with matching types', 'bookmarks', 'bookmark-imports.txt', 'imports.txt'

    it_behaves_like 'with mismatching types', 'following', 'imports.txt', 'blocks.csv'
    it_behaves_like 'with mismatching types', 'following', 'imports.txt', 'blocked_accounts.csv'
    it_behaves_like 'with mismatching types', 'following', 'imports.txt', 'mutes.csv'
    it_behaves_like 'with mismatching types', 'following', 'imports.txt', 'muted_accounts.csv'
    it_behaves_like 'with mismatching types', 'following', 'muted_accounts.csv'
    it_behaves_like 'with mismatching types', 'following', 'muted_accounts.csv', 'imports.txt'
    it_behaves_like 'with mismatching types', 'blocking', 'following_accounts.csv'
    it_behaves_like 'with mismatching types', 'blocking', 'following_accounts.csv', 'imports.txt'
    it_behaves_like 'with mismatching types', 'blocking', 'muted_accounts.csv'
    it_behaves_like 'with mismatching types', 'blocking', 'muted_accounts.csv', 'imports.txt'
    it_behaves_like 'with mismatching types', 'blocking', 'imports.txt', 'follows.csv'
    it_behaves_like 'with mismatching types', 'blocking', 'imports.txt', 'following_accounts.csv'
    it_behaves_like 'with mismatching types', 'blocking', 'imports.txt', 'mutes.csv'
    it_behaves_like 'with mismatching types', 'blocking', 'imports.txt', 'muted_accounts.csv'
    it_behaves_like 'with mismatching types', 'muting', 'following_accounts.csv'
    it_behaves_like 'with mismatching types', 'muting', 'following_accounts.csv', 'imports.txt'
    it_behaves_like 'with mismatching types', 'muting', 'imports.txt', 'follows.csv'
    it_behaves_like 'with mismatching types', 'muting', 'imports.txt', 'following_accounts.csv'
    it_behaves_like 'with mismatching types', 'muting', 'imports.txt', 'blocks.csv'
    it_behaves_like 'with mismatching types', 'muting', 'imports.txt', 'blocked_accounts.csv'
  end

  describe 'save' do
    shared_examples 'on successful import' do |type, mode, file, expected_rows|
      let(:import_type) { type }
      let(:import_file) { file }
      let(:import_mode) { mode }

      before do
        subject.save
      end

      it 'creates the expected rows' do
        expect(account.bulk_imports.first.rows.pluck(:data)).to match_array(expected_rows)
      end

      context 'with a BulkImport' do
        let(:bulk_import) { account.bulk_imports.first }

        it 'creates a non-nil bulk import' do
          expect(bulk_import).to_not be_nil
        end

        it 'matches the subjects type' do
          expect(bulk_import.type.to_sym).to eq subject.type.to_sym
        end

        it 'matches the subjects original filename' do
          expect(bulk_import.original_filename).to eq subject.data.original_filename
        end

        it 'matches the subjects likely_mismatched? value' do
          expect(bulk_import.likely_mismatched?).to eq subject.likely_mismatched?
        end

        it 'matches the subject overwrite value' do
          expect(bulk_import.overwrite?).to eq !!subject.overwrite # rubocop:disable Style/DoubleNegation
        end

        it 'has zero processed items' do
          expect(bulk_import.processed_items).to eq 0
        end

        it 'has zero imported items' do
          expect(bulk_import.imported_items).to eq 0
        end

        it 'has a correct total_items value' do
          expect(bulk_import.total_items).to eq bulk_import.rows.count
        end

        it 'defaults to unconfirmed true' do
          expect(bulk_import.unconfirmed?).to be true
        end
      end
    end

    it_behaves_like 'on successful import', 'following', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
    it_behaves_like 'on successful import', 'following', 'overwrite', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
    it_behaves_like 'on successful import', 'blocking', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
    it_behaves_like 'on successful import', 'blocking', 'overwrite', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
    it_behaves_like 'on successful import', 'muting', 'merge', 'imports.txt', (%w(user@example.com user@test.com).map { |acct| { 'acct' => acct } })
    it_behaves_like 'on successful import', 'domain_blocking', 'merge', 'domain_blocks.csv', (%w(bad.domain worse.domain reject.media).map { |domain| { 'domain' => domain } })
    it_behaves_like 'on successful import', 'bookmarks', 'merge', 'bookmark-imports.txt', (%w(https://example.com/statuses/1312 https://local.com/users/foo/statuses/42 https://unknown-remote.com/users/bar/statuses/1 https://example.com/statuses/direct).map { |uri| { 'uri' => uri } })

    it_behaves_like 'on successful import', 'following', 'merge', 'following_accounts.csv', [
      { 'acct' => 'user@example.com', 'show_reblogs' => true, 'notify' => false, 'languages' => nil },
      { 'acct' => 'user@test.com', 'show_reblogs' => true, 'notify' => true, 'languages' => ['en', 'fr'] },
    ]

    it_behaves_like 'on successful import', 'muting', 'merge', 'muted_accounts.csv', [
      { 'acct' => 'user@example.com', 'hide_notifications' => true },
      { 'acct' => 'user@test.com', 'hide_notifications' => false },
    ]

    it_behaves_like 'on successful import', 'lists', 'merge', 'lists.csv', [
      { 'acct' => 'gargron@example.com', 'list_name' => 'Mastodon project' },
      { 'acct' => 'mastodon@example.com', 'list_name' => 'Mastodon project' },
      { 'acct' => 'foo@example.com', 'list_name' => 'test' },
    ]

    # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users
    #
    # https://github.com/mastodon/mastodon/issues/20571
    it_behaves_like 'on successful import', 'following', 'merge', 'utf8-followers.txt', [{ 'acct' => 'nare@թութ.հայ' }]
  end
end