このコミットが含まれているのは:
@@ -0,0 +1,133 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe PostImportRunner do
|
||||
let(:actor) { create(:user, :member) }
|
||||
|
||||
def row(source_row: 1, url: 'https://example.com/post')
|
||||
{
|
||||
sourceRow: source_row,
|
||||
url:,
|
||||
attributes: { title: 'title', tags: '' },
|
||||
provenance: { url: 'manual', title: 'manual', tags: 'manual' },
|
||||
tagSources: { automatic: '', manual: '' }
|
||||
}
|
||||
end
|
||||
|
||||
def preview(source_row: 1, errors: { }, skip_reason: nil, existing_post_id: nil)
|
||||
{
|
||||
source_row:,
|
||||
attributes: { 'title' => 'title', 'tags' => '' },
|
||||
validation_errors: errors,
|
||||
skip_reason:,
|
||||
existing_post_id:
|
||||
}
|
||||
end
|
||||
|
||||
it 'previews the whole batch once before processing individual rows' do
|
||||
rows = [row, row(source_row: 2, url: 'https://example.com/two')]
|
||||
previewer = instance_double(PostImportPreviewer)
|
||||
allow(PostImportPreviewer).to receive(:new).and_return(previewer)
|
||||
expect(previewer).to receive(:preview_rows)
|
||||
.with(
|
||||
rows: satisfy { _1.map { |row_value| row_value['source_row'] } == [1, 2] },
|
||||
fetch_metadata: false
|
||||
)
|
||||
.and_return([preview, preview(source_row: 2)])
|
||||
allow(PostCreator).to receive(:new).and_return(
|
||||
instance_double(PostCreator, create!: create(:post))
|
||||
)
|
||||
|
||||
result = described_class.new(actor:, rows:).run
|
||||
|
||||
expect(result).to include(created: 2, skipped: 0, failed: 0)
|
||||
end
|
||||
|
||||
it 'treats validation errors as failures before an existing skip' do
|
||||
existing = create(:post)
|
||||
previewer = instance_double(PostImportPreviewer)
|
||||
allow(PostImportPreviewer).to receive(:new).and_return(previewer)
|
||||
allow(previewer).to receive(:preview_rows).and_return([
|
||||
preview(errors: { url: ['URL が重複しています.'] },
|
||||
skip_reason: 'existing', existing_post_id: existing.id)
|
||||
])
|
||||
expect(PostCreator).not_to receive(:new)
|
||||
|
||||
result = described_class.new(actor:, rows: [row]).run.fetch(:rows).first
|
||||
|
||||
expect(result).to include(
|
||||
status: 'failed',
|
||||
errors: { url: ['URL が重複しています.'] },
|
||||
recoverable: true
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the existing post ID for skipped rows' do
|
||||
existing = create(:post)
|
||||
previewer = instance_double(PostImportPreviewer)
|
||||
allow(PostImportPreviewer).to receive(:new).and_return(previewer)
|
||||
allow(previewer).to receive(:preview_rows).and_return([
|
||||
preview(skip_reason: 'existing', existing_post_id: existing.id)
|
||||
])
|
||||
|
||||
result = described_class.new(actor:, rows: [row]).run.fetch(:rows).first
|
||||
|
||||
expect(result).to eq(
|
||||
source_row: 1,
|
||||
status: 'skipped',
|
||||
existing_post_id: existing.id
|
||||
)
|
||||
end
|
||||
|
||||
it 'converts a URL uniqueness validation race into a skip' do
|
||||
existing = create(:post, url: 'https://example.com/race')
|
||||
invalid = Post.new(url: existing.url)
|
||||
invalid.errors.add(:url, :taken)
|
||||
previewer = instance_double(PostImportPreviewer)
|
||||
allow(PostImportPreviewer).to receive(:new).and_return(previewer)
|
||||
allow(previewer).to receive(:preview_rows).and_return([preview])
|
||||
creator = instance_double(PostCreator)
|
||||
allow(PostCreator).to receive(:new).and_return(creator)
|
||||
allow(creator).to receive(:create!).and_raise(ActiveRecord::RecordInvalid.new(invalid))
|
||||
|
||||
result = described_class.new(
|
||||
actor:,
|
||||
rows: [row(url: 'https://EXAMPLE.com/race/')]
|
||||
).run.fetch(:rows).first
|
||||
|
||||
expect(result).to include(status: 'skipped', existing_post_id: existing.id)
|
||||
end
|
||||
|
||||
it 're-raises RecordNotUnique errors unrelated to the posts URL index' do
|
||||
previewer = instance_double(PostImportPreviewer)
|
||||
allow(PostImportPreviewer).to receive(:new).and_return(previewer)
|
||||
allow(previewer).to receive(:preview_rows).and_return([preview])
|
||||
creator = instance_double(PostCreator)
|
||||
allow(PostCreator).to receive(:new).and_return(creator)
|
||||
allow(creator).to receive(:create!)
|
||||
.and_raise(ActiveRecord::RecordNotUnique, 'other_unique_index')
|
||||
|
||||
expect {
|
||||
described_class.new(actor:, rows: [row]).run
|
||||
}.to raise_error(ActiveRecord::RecordNotUnique)
|
||||
end
|
||||
|
||||
it 'converts a posts URL index race into a skip' do
|
||||
existing = create(:post, url: 'https://example.com/index-race')
|
||||
previewer = instance_double(PostImportPreviewer)
|
||||
allow(PostImportPreviewer).to receive(:new).and_return(previewer)
|
||||
allow(previewer).to receive(:preview_rows).and_return([preview])
|
||||
creator = instance_double(PostCreator)
|
||||
allow(PostCreator).to receive(:new).and_return(creator)
|
||||
allow(creator).to receive(:create!).and_raise(
|
||||
ActiveRecord::RecordNotUnique,
|
||||
'duplicate key index_posts_on_url'
|
||||
)
|
||||
|
||||
result = described_class.new(
|
||||
actor:,
|
||||
rows: [row(url: 'https://EXAMPLE.com/index-race/')]
|
||||
).run.fetch(:rows).first
|
||||
|
||||
expect(result).to include(status: 'skipped', existing_post_id: existing.id)
|
||||
end
|
||||
end
|
||||
新しい課題から参照
ユーザをブロックする