416 lines
12 KiB
Ruby
416 lines
12 KiB
Ruby
# typed: false
|
|
require "rails_helper"
|
|
|
|
RSpec.describe Tasks::CreatePostFileFingerprintsTask do
|
|
let(:mode) { raise("mode is required") }
|
|
let(:user_param) { nil }
|
|
let(:start_at) { nil }
|
|
let(:log_sink) { StringIO.new }
|
|
|
|
let(:task) do
|
|
described_class.new(
|
|
mode: mode,
|
|
user_param: user_param,
|
|
start_at: start_at,
|
|
log_sink: log_sink,
|
|
)
|
|
end
|
|
|
|
before do
|
|
# Mock external dependencies
|
|
allow(ProgressBar).to receive(:create).and_return(
|
|
instance_double(
|
|
ProgressBar::Base,
|
|
progress: 0,
|
|
total: 100,
|
|
"progress=": nil,
|
|
),
|
|
)
|
|
allow(Domain::PostFileThumbnailJob).to receive(:new).and_return(
|
|
instance_double(Domain::PostFileThumbnailJob, perform: nil),
|
|
)
|
|
allow(ColorLogger).to receive(:quiet).and_yield
|
|
end
|
|
|
|
after do
|
|
# Clean up any GlobalState entries created during tests
|
|
GlobalState.find_by(key: "task-create-post-file-fingerprints")&.destroy
|
|
end
|
|
|
|
describe "#run" do
|
|
context "with user mode missing user_param" do
|
|
let(:mode) { Tasks::CreatePostFileFingerprintsTask::Mode::User }
|
|
|
|
it "raises an error when user mode is used without user_param" do
|
|
expect { task.run }.to raise_error(
|
|
"need 'user_param' when mode is Mode::User",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "with PostFileDescending mode" do
|
|
let(:mode) do
|
|
Tasks::CreatePostFileFingerprintsTask::Mode::PostFileDescending
|
|
end
|
|
let!(:post_files) do
|
|
# Create test post files with descending IDs
|
|
5
|
|
.times
|
|
.map do |i|
|
|
post = create(:domain_post_fa_post, fa_id: 500 + i)
|
|
create(:domain_post_file, post: post, state: "ok")
|
|
end
|
|
.sort_by { |p| -p.id }
|
|
end
|
|
|
|
it "saves progress every 100 items" do
|
|
# Create 105 post files to trigger progress saving
|
|
105.times do |i|
|
|
post = create(:domain_post_fa_post, fa_id: 600 + i)
|
|
create(:domain_post_file, post: post, state: "ok")
|
|
end
|
|
|
|
task.run
|
|
|
|
# Should have saved progress at least once
|
|
saved_progress = GlobalState.get("task-create-post-file-fingerprints")
|
|
expect(saved_progress).to be_present
|
|
expect(saved_progress.to_i).to be > 0
|
|
end
|
|
|
|
context "with start_at specified" do
|
|
let(:start_at) { post_files[2].id.to_s }
|
|
it "starts from specified start_at ID" do
|
|
task.run
|
|
expect(log_sink.string).to include("resuming from:")
|
|
end
|
|
end
|
|
|
|
context "with start_at='last' and existing progress" do
|
|
let(:start_at) { "last" }
|
|
before do
|
|
# Set up saved progress
|
|
target_post_file = post_files[2]
|
|
GlobalState.set(
|
|
"task-create-post-file-fingerprints",
|
|
target_post_file.id.to_s,
|
|
)
|
|
end
|
|
|
|
it "resumes from saved progress" do
|
|
task.run
|
|
expect(log_sink.string).to include("resuming from:")
|
|
end
|
|
end
|
|
|
|
context "with start_at='last' and no existing progress" do
|
|
let(:start_at) { "last" }
|
|
it "starts from beginning when no saved progress found" do
|
|
task.run
|
|
|
|
expect(log_sink.string).to include("no saved progress")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with PostsDescending mode" do
|
|
let(:mode) do
|
|
Tasks::CreatePostFileFingerprintsTask::Mode::PostsDescending
|
|
end
|
|
let!(:posts) do
|
|
3.times.map { |i| create(:domain_post_fa_post, fa_id: 400 + i) }
|
|
end
|
|
|
|
before do
|
|
# Add files to posts so they actually get processed
|
|
posts.each { |post| create(:domain_post_file, post: post) }
|
|
end
|
|
|
|
it "processes posts in descending order" do
|
|
# Set up mocks before running the task
|
|
expect(Domain::PostFile::Thumbnail).to receive(
|
|
:create_for_post_file!,
|
|
).exactly(3).times
|
|
expect(Domain::PostFile::BitFingerprint).to receive(
|
|
:create_for_post_file!,
|
|
).exactly(3).times
|
|
|
|
task.run
|
|
end
|
|
|
|
it "uses correct progress bar total" do
|
|
progress_bar =
|
|
instance_double(
|
|
ProgressBar::Base,
|
|
progress: 0,
|
|
total: 100,
|
|
"progress=": nil,
|
|
)
|
|
allow(ProgressBar).to receive(:create).and_return(progress_bar)
|
|
|
|
task.run
|
|
|
|
expect(ProgressBar).to have_received(:create).with(
|
|
total: 66_431_808,
|
|
progress_mark: " ",
|
|
remainder_mark: " ",
|
|
format: "%B %c/%C (%r/sec) %J%% %a %E",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "with User mode" do
|
|
let(:mode) { Tasks::CreatePostFileFingerprintsTask::Mode::User }
|
|
let!(:user) { create(:domain_user_fa_user, url_name: "testuser") }
|
|
let!(:posts) do
|
|
3.times.map do |i|
|
|
create(:domain_post_fa_post, creator: user, fa_id: 300 + i)
|
|
end
|
|
end
|
|
|
|
before do
|
|
# Add files to posts so they actually get processed
|
|
posts.each { |post| create(:domain_post_file, post: post) }
|
|
end
|
|
|
|
context "with an existing user " do
|
|
let(:user_param) { user.to_param }
|
|
it "processes posts for specified user" do
|
|
# Set up mocks before running the task
|
|
expect(Domain::PostFile::Thumbnail).to receive(
|
|
:create_for_post_file!,
|
|
).exactly(3).times
|
|
expect(Domain::PostFile::BitFingerprint).to receive(
|
|
:create_for_post_file!,
|
|
).exactly(3).times
|
|
|
|
task.run
|
|
|
|
expect(log_sink.string).to include(
|
|
"migrating posts for #{user.to_param}",
|
|
)
|
|
end
|
|
|
|
it "handles posts without creators" do
|
|
# Create a post without a creator
|
|
create(:domain_post_fa_post, creator: nil)
|
|
|
|
expect(Domain::PostFile::Thumbnail).to receive(
|
|
:create_for_post_file!,
|
|
).exactly(3).times
|
|
expect(Domain::PostFile::BitFingerprint).to receive(
|
|
:create_for_post_file!,
|
|
).exactly(3).times
|
|
|
|
task.run
|
|
end
|
|
end
|
|
|
|
context "with a nonexistent user" do
|
|
let(:user_param) { "nonexistent_user" }
|
|
it "raises error when user is not found" do
|
|
expect { task.run }.to raise_error(
|
|
ActionController::BadRequest,
|
|
"invalid id: \"nonexistent_user\"",
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with UsersDescending mode" do
|
|
let(:mode) do
|
|
Tasks::CreatePostFileFingerprintsTask::Mode::UsersDescending
|
|
end
|
|
let!(:users) do
|
|
3.times.map { |i| create(:domain_user_fa_user, url_name: "user#{i}") }
|
|
end
|
|
|
|
before do
|
|
# Add posts with files to users
|
|
users.each do |user|
|
|
post = create(:domain_post_fa_post, creator: user)
|
|
create(:domain_post_file, post: post)
|
|
end
|
|
end
|
|
|
|
it "processes users in descending order" do
|
|
expect(Domain::PostFile::Thumbnail).to receive(:create_for_post_file!)
|
|
.exactly(3)
|
|
.times
|
|
.and_return([])
|
|
expect(Domain::PostFile::BitFingerprint).to receive(
|
|
:create_for_post_file!,
|
|
).exactly(3).times.and_return([])
|
|
|
|
task.run
|
|
|
|
users.each do |user|
|
|
expect(log_sink.string).to include(
|
|
"migrating posts for #{user.to_param}",
|
|
)
|
|
end
|
|
end
|
|
|
|
it "tracks migrated users in a file" do
|
|
expect(Domain::PostFile::Thumbnail).to receive(
|
|
:create_for_post_file!,
|
|
).exactly(3).times
|
|
|
|
expect(Domain::PostFile::BitFingerprint).to receive(
|
|
:create_for_post_file!,
|
|
).exactly(3).times
|
|
|
|
task.run
|
|
|
|
# Check that the migrated file was created and contains user params
|
|
if File.exist?("migrated_files.txt")
|
|
migrated_content = File.read("migrated_files.txt")
|
|
users.each do |user|
|
|
expect(migrated_content).to include("#{user.to_param}\n")
|
|
end
|
|
end
|
|
end
|
|
|
|
after do
|
|
# Clean up the migrated files
|
|
File.delete("migrated_files.txt") if File.exist?("migrated_files.txt")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "#save_progress" do
|
|
let(:mode) do
|
|
Tasks::CreatePostFileFingerprintsTask::Mode::PostFileDescending
|
|
end
|
|
|
|
it "saves progress to GlobalState" do
|
|
task.send(:save_progress, 12_345.to_s)
|
|
|
|
saved_progress = GlobalState.get("task-create-post-file-fingerprints")
|
|
expect(saved_progress).to eq("12345")
|
|
end
|
|
end
|
|
|
|
describe "error handling" do
|
|
let!(:post_file) { create(:domain_post_file, state: "ok") }
|
|
let(:mode) do
|
|
Tasks::CreatePostFileFingerprintsTask::Mode::PostFileDescending
|
|
end
|
|
|
|
it "handles errors in migrate_post_file gracefully" do
|
|
# Mock the job to raise an error
|
|
allow(Domain::PostFile::Thumbnail).to receive(
|
|
:create_for_post_file!,
|
|
).and_raise(StandardError.new("Test error"))
|
|
|
|
task.run
|
|
|
|
expect(log_sink.string).to include("error: Test error")
|
|
end
|
|
|
|
it "handles errors in migrate_post gracefully" do
|
|
# Create a post with files
|
|
post = create(:domain_post_fa_post, fa_id: 100)
|
|
create(:domain_post_file, post: post)
|
|
|
|
allow(Domain::PostFile::Thumbnail).to receive(
|
|
:create_for_post_file!,
|
|
).and_raise(StandardError.new("Test error"))
|
|
|
|
task.run
|
|
|
|
expect(log_sink.string).to include("error: Test error")
|
|
end
|
|
end
|
|
|
|
describe "utility methods" do
|
|
let(:mode) do
|
|
Tasks::CreatePostFileFingerprintsTask::Mode::PostFileDescending
|
|
end
|
|
|
|
describe "#create_progress_bar" do
|
|
it "creates a progress bar with the correct format" do
|
|
progress_bar = instance_double(ProgressBar::Base)
|
|
allow(ProgressBar).to receive(:create).and_return(progress_bar)
|
|
|
|
result = task.send(:create_progress_bar, 1000)
|
|
|
|
expect(ProgressBar).to have_received(:create).with(
|
|
total: 1000,
|
|
progress_mark: " ",
|
|
remainder_mark: " ",
|
|
format: "%B %c/%C (%r/sec) %J%% %a %E",
|
|
)
|
|
expect(result).to eq(progress_bar)
|
|
end
|
|
end
|
|
|
|
describe "#log" do
|
|
it "writes messages to the log sink" do
|
|
task.send(:log, "Test message")
|
|
|
|
expect(log_sink.string).to include("Test message")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "interrupt functionality" do
|
|
let(:mode) { Tasks::CreatePostFileFingerprintsTask::Mode::PostsDescending }
|
|
let!(:posts) do
|
|
3.times.map { |i| create(:domain_post_fa_post, fa_id: 400 + i) }
|
|
end
|
|
|
|
before do
|
|
# Add files to posts so they actually get processed
|
|
posts.each { |post| create(:domain_post_file, post: post) }
|
|
end
|
|
|
|
it "inherits interrupt functionality from InterruptableTask" do
|
|
expect(task).to respond_to(:interrupted?)
|
|
expect(task).to be_a(Tasks::InterruptableTask)
|
|
end
|
|
|
|
it "stops processing when interrupted in posts_descending mode" do
|
|
# Mock the interrupt to happen after first post
|
|
call_count = 0
|
|
allow(task).to receive(:interrupted?) do
|
|
call_count += 1
|
|
call_count > 1
|
|
end
|
|
|
|
expect(Domain::PostFile::Thumbnail).to receive(
|
|
:create_for_post_file!,
|
|
).at_most(1).times
|
|
expect(Domain::PostFile::BitFingerprint).to receive(
|
|
:create_for_post_file!,
|
|
).at_most(1).times
|
|
|
|
task.run
|
|
end
|
|
|
|
it "stops processing when interrupted in post_file_descending mode" do
|
|
# Create post files for testing
|
|
3.times do |i|
|
|
post = create(:domain_post_fa_post, fa_id: 500 + i)
|
|
create(:domain_post_file, post: post, state: "ok")
|
|
end
|
|
|
|
# Mock the interrupt to happen after first post file
|
|
call_count = 0
|
|
allow(task).to receive(:interrupted?) do
|
|
call_count += 1
|
|
call_count > 1
|
|
end
|
|
|
|
expect(Domain::PostFile::Thumbnail).to receive(
|
|
:create_for_post_file!,
|
|
).at_most(1).times
|
|
expect(Domain::PostFile::BitFingerprint).to receive(
|
|
:create_for_post_file!,
|
|
).at_most(1).times
|
|
|
|
task.run
|
|
end
|
|
end
|
|
end
|