153 行
5.5 KiB
Ruby
153 行
5.5 KiB
Ruby
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
|
|
|
|
it 'returns thumbnail warnings from PostCreator on a created row' do
|
|
previewer = instance_double(PostImportPreviewer)
|
|
allow(PostImportPreviewer).to receive(:new).and_return(previewer)
|
|
allow(previewer).to receive(:preview_rows).and_return([preview])
|
|
created_post = create(:post)
|
|
creator = instance_double(
|
|
PostCreator,
|
|
create!: created_post,
|
|
field_warnings: { thumbnail_base: ['サムネール画像を取得できませんでした.'] })
|
|
allow(PostCreator).to receive(:new).and_return(creator)
|
|
|
|
result = described_class.new(actor:, rows: [row]).run.fetch(:rows).first
|
|
|
|
expect(result).to include(
|
|
status: 'created',
|
|
field_warnings: { thumbnail_base: ['サムネール画像を取得できませんでした.'] }
|
|
)
|
|
end
|
|
end
|