174 行
6.6 KiB
Ruby
174 行
6.6 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe PostBulkCreator do
|
|
include_context 'Ainu locale'
|
|
|
|
it 'limits workers to two and keeps failures in their request slots' do
|
|
locale = Locale.nipponese
|
|
actor = instance_double(User, id: 123)
|
|
allow(User).to receive(:find).with(123) {
|
|
instance_double(User, id: 123)
|
|
}
|
|
mutex = Mutex.new
|
|
active = 0
|
|
maximum_active = 0
|
|
creators = []
|
|
|
|
allow(PostCreatePreflight).to receive(:new) do |attributes:, **|
|
|
preflight = instance_double(PostCreatePreflight)
|
|
allow(preflight).to receive(:run) do
|
|
mutex.synchronize do
|
|
active += 1
|
|
maximum_active = [maximum_active, active].max
|
|
end
|
|
sleep 0.02
|
|
mutex.synchronize { active -= 1 }
|
|
attributes.symbolize_keys.merge(
|
|
existing_post_id: nil,
|
|
field_warnings: { },
|
|
base_warnings: [])
|
|
end
|
|
preflight
|
|
end
|
|
allow(PostCreator).to receive(:new) do |attributes:, **|
|
|
creator = instance_double(PostCreator)
|
|
mutex.synchronize { creators << creator }
|
|
if attributes[:title] == 'broken'
|
|
allow(creator).to receive(:create!).and_raise(StandardError, 'broken')
|
|
else
|
|
post = instance_double(Post, id: attributes[:title].delete_prefix('post ').to_i)
|
|
allow(creator).to receive(:create!).and_return(post)
|
|
end
|
|
creator
|
|
end
|
|
posts = [
|
|
{ 'title' => 'post 1', 'url' => 'https://example.com/1' },
|
|
{ 'title' => 'broken', 'url' => 'https://example.com/2' },
|
|
{ 'title' => 'post 3', 'url' => 'https://example.com/3' },
|
|
{ 'title' => 'post 4', 'url' => 'https://example.com/4' }]
|
|
|
|
results = described_class.new(
|
|
actor:,
|
|
posts:,
|
|
thumbnails: { }).run(locale).fetch(:results)
|
|
|
|
expect(creators.length).to eq(posts.length)
|
|
creators.each do |creator|
|
|
expect(creator).to have_received(:create!).with(locale).once
|
|
end
|
|
|
|
expect(maximum_active).to eq(2)
|
|
expect(results.length).to eq(posts.length)
|
|
expect(results.map { _1[:status] }).to eq(
|
|
['created', 'failed', 'created', 'created'])
|
|
expect(results[0].dig(:post, :id)).to eq(1)
|
|
expect(results[1]).to include(status: 'failed', recoverable: false)
|
|
expect(results[2].dig(:post, :id)).to eq(3)
|
|
expect(results[3].dig(:post, :id)).to eq(4)
|
|
end
|
|
|
|
it 'passes a future Ainu locale to preflight and creation in every worker' do
|
|
locale
|
|
actor = instance_double(User, id: 123)
|
|
allow(User).to receive(:find).with(123).and_return(actor)
|
|
preflight = instance_double(PostCreatePreflight)
|
|
creator = instance_double(PostCreator)
|
|
post = instance_double(Post, id: 1)
|
|
allow(PostCreatePreflight).to receive(:new).and_return(preflight)
|
|
allow(preflight).to receive(:run).with(locale).and_return(existing_post_id: nil)
|
|
allow(PostCreator).to receive(:new).and_return(creator)
|
|
allow(creator).to receive(:create!).with(locale).and_return(post)
|
|
|
|
results = described_class.new(
|
|
actor:, posts: [{ title: 'future post' }], thumbnails: { }).run(locale)
|
|
|
|
expect(results.fetch(:results)).to contain_exactly(
|
|
hash_including(status: 'created', post: { id: 1 }))
|
|
expect(preflight).to have_received(:run).with(locale).once
|
|
expect(creator).to have_received(:create!).with(locale).once
|
|
end
|
|
end
|
|
|
|
RSpec.describe 'PostBulkCreator event grouping', type: :model do
|
|
self.use_transactional_tests = false
|
|
|
|
before do
|
|
expect(Rails.env.test?).to be(true)
|
|
expect(ApplicationRecord.connection.open_transactions).to eq(0)
|
|
@prefix = "bulk_event_#{ SecureRandom.hex(8) }"
|
|
@actor = create(:user, :member)
|
|
allow(PostCreatePreflight).to receive(:new) do |attributes:, **|
|
|
specs = [{ name: attributes.fetch(:title), category: :general }]
|
|
instance_double(PostCreatePreflight, run: attributes.merge(
|
|
existing_post_id: nil, snapshot_tag_specs: specs, post_tag_specs: specs,
|
|
normalised_parent_post_ids: [], tag_sections: { }, video_ms: nil))
|
|
end
|
|
end
|
|
|
|
after do
|
|
if @actor
|
|
posts = Post.where(uploaded_user: @actor)
|
|
PostTagSection.where(post_id: posts.select(:id)).delete_all
|
|
PostTag.where(post_id: posts.select(:id)).delete_all
|
|
PostVersion.where(post_id: posts.select(:id)).delete_all
|
|
posts.delete_all
|
|
tag_ids = TagName.where(name: ["#{ @prefix }_1", "#{ @prefix }_2"]).pluck(:tag_id)
|
|
TagVersion.where(tag_id: tag_ids).delete_all
|
|
TagName.where(tag_id: tag_ids).delete_all
|
|
Tag.where(id: tag_ids).delete_all
|
|
EventRecords::EventRow.where(created_by_user_id: @actor.id).delete_all
|
|
@actor.delete
|
|
end
|
|
end
|
|
|
|
it 'shares one creation event between real workers and their tag histories' do
|
|
posts = [1, 2].map do |number|
|
|
{ title: "#{ @prefix }_#{ number }",
|
|
url: "https://example.com/#{ @prefix }/#{ number }" }
|
|
end
|
|
|
|
result = nil
|
|
expect {
|
|
result = PostBulkCreator.new(actor: @actor, posts:, thumbnails: { })
|
|
.run(Locale.nipponese)
|
|
}.to change { event_rows.length }.by(1)
|
|
|
|
expect(result.fetch(:results).map { _1.fetch(:status) }).to eq(['created', 'created'])
|
|
event = event_rows.last
|
|
expect(event).to include(
|
|
'event_type' => 'posts.create', 'created_by_user_id' => @actor.id)
|
|
versions = PostVersion.where(created_by_user_id: @actor.id)
|
|
expect(versions.count).to eq(2)
|
|
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
|
|
tag_versions = TagVersion.where(created_by_user_id: @actor.id)
|
|
expect(tag_versions.count).to eq(2)
|
|
expect(tag_versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
|
|
end
|
|
|
|
it 'does not leave an event when every row fails validation' do
|
|
preflight = instance_double(PostCreatePreflight)
|
|
allow(PostCreatePreflight).to receive(:new).and_return(preflight)
|
|
allow(preflight).to receive(:run).and_raise(
|
|
PostCreatePreflight::ValidationFailed.new(fields: { url: ['invalid'] }))
|
|
events = event_rows
|
|
|
|
result = PostBulkCreator.new(
|
|
actor: @actor, posts: [{ title: 'invalid' }], thumbnails: { }).run(Locale.nipponese)
|
|
|
|
expect(result.fetch(:results).map { _1.fetch(:status) }).to eq(['failed'])
|
|
expect(event_rows).to eq(events)
|
|
end
|
|
|
|
it 'does not leave an event when every row is skipped' do
|
|
preflight = instance_double(PostCreatePreflight, run: { existing_post_id: 123 })
|
|
allow(PostCreatePreflight).to receive(:new).and_return(preflight)
|
|
events = event_rows
|
|
|
|
result = PostBulkCreator.new(
|
|
actor: @actor, posts: [{ title: 'existing' }], thumbnails: { }).run(Locale.nipponese)
|
|
|
|
expect(result.fetch(:results).map { _1.fetch(:status) }).to eq(['skipped'])
|
|
expect(event_rows).to eq(events)
|
|
end
|
|
end
|