From b569af2483b87a03c3e732ef9175ed613ee38d3b Mon Sep 17 00:00:00 2001 From: Laura Ghiorghisor Date: Wed, 4 Dec 2024 15:40:34 +0000 Subject: [PATCH] Add rake task to update missing replacement links We've identified a bug where if the user replaces an attachments on a new draft, without having saved the edition draft first, the supposedly replaced original asset remains live, because it fails to redirect to its replacement. This rake task figures out the last AttachmentData in the chain, and reruns the updater on the original AttachmentData with the replacement_id set to the last AttachmentData's asset_manager_id. It fetches the ID based on variant value, sending the correct `replacement_id` field to `asset-manager`. This is only a data patch solution. A fix for the issue will be implemented separately. --- .../fix_asset_variant_replacement.rake | 33 +++++ .../find_asset_variant_replacement_test.rb | 135 ++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 lib/tasks/attachments/fix_asset_variant_replacement.rake create mode 100644 test/unit/lib/tasks/find_asset_variant_replacement_test.rb diff --git a/lib/tasks/attachments/fix_asset_variant_replacement.rake b/lib/tasks/attachments/fix_asset_variant_replacement.rake new file mode 100644 index 00000000000..bd2c46100c4 --- /dev/null +++ b/lib/tasks/attachments/fix_asset_variant_replacement.rake @@ -0,0 +1,33 @@ +desc "Identify and update corresponding replacement asset variant" +task :fix_asset_variant_replacement, %i[csv_path] => :environment do |_, args| + csv_path = args[:csv_path] + + CSV.foreach(csv_path, headers: false) do |row| + attachment_data_id = row[0] + asset_manager_id = row[1] + variant = row[2] + + attachment_data = AttachmentData.find(attachment_data_id) + + unless attachment_data.replaced? + puts "Attachment Data ID: #{attachment_data.id}, Asset ID: #{asset_manager_id} - SKIPPED. No replacement found." + next + end + + replacement_id = attachment_data.replaced_by_id + + until replacement_id.nil? + replacement_data = AttachmentData.find(replacement_id) + replacement_id = replacement_data.replaced_by_id + end + + begin + replacement_id = replacement_data.assets.where(variant:).first&.asset_manager_id || replacement_data.assets.where(variant: "original").first&.asset_manager_id + AssetManager::AssetUpdater.call(asset_manager_id, { "replacement_id" => replacement_id }) + + puts "Attachment Data ID: #{attachment_data_id}, Asset ID: #{asset_manager_id}, Variant: #{variant}, Replacement ID: #{replacement_id} - OK" + rescue StandardError => e + puts "Attachment Data ID: #{attachment_data_id}, Asset ID #{asset_manager_id} - ERROR, message: #{e.message}" + end + end +end diff --git a/test/unit/lib/tasks/find_asset_variant_replacement_test.rb b/test/unit/lib/tasks/find_asset_variant_replacement_test.rb new file mode 100644 index 00000000000..1fc0c1afe3d --- /dev/null +++ b/test/unit/lib/tasks/find_asset_variant_replacement_test.rb @@ -0,0 +1,135 @@ +require "test_helper" +require "rake" + +class FixAssetVariantReplacementTest < ActiveSupport::TestCase + extend Minitest::Spec::DSL + + describe "#fix_asset_variant_replacement" do + let(:task) { Rake::Task["fix_asset_variant_replacement"] } + let(:file) { Tempfile.new("read_assets_file") } + let(:filepath) { file.path } + let(:attachable) { create(:news_article) } + let(:attachment_data) { create(:attachment_data, id: 123_456, attachable:) } + let(:replacement_data) { create(:attachment_data, attachable:) } + let(:replacement_data2) { create(:attachment_data, attachable:) } + let(:original_asset) { attachment_data.assets.original.first } + let(:thumbnail_asset) { attachment_data.assets.thumbnail.first } + let(:replacement_original_asset) { replacement_data2.assets.original.first } + let(:replacement_thumbnail_asset) { replacement_data2.assets.thumbnail.first } + + teardown { task.reenable } + + before do + csv_file = <<~CSV + 123456,1592008029c8c3e4dc76256c,original + 123456,1592008029c8c3e4dc76256d,thumbnail + CSV + file.write(csv_file) + file.close + + attachment_data.replaced_by = replacement_data + replacement_data.replaced_by = replacement_data2 + + original_asset.asset_manager_id = "1592008029c8c3e4dc76256c" + original_asset.save! + thumbnail_asset.asset_manager_id = "1592008029c8c3e4dc76256d" + thumbnail_asset.save! + replacement_original_asset.asset_manager_id = "2592008029c8c3e4dc76256c" + replacement_original_asset.save! + replacement_thumbnail_asset.asset_manager_id = "2592008029c8c3e4dc76256d" + replacement_thumbnail_asset.save! + + attachment_data.replaced_by = replacement_data + replacement_data.replaced_by = replacement_data2 + attachment_data.save! + replacement_data.save! + replacement_data2.save! + end + + test "it sends the last replacement in the chain to asset-manager" do + Services.asset_manager.stubs(:asset).with(original_asset.asset_manager_id).returns("id" => "http://asset-manager/assets/#{original_asset.asset_manager_id}", "name" => "original.pdf") + Services.asset_manager.stubs(:asset).with(thumbnail_asset.asset_manager_id).returns("id" => "http://asset-manager/assets/#{thumbnail_asset.asset_manager_id}", "name" => "thumbnail.pdf.png") + + expected_output = <<~OUTPUT + Attachment Data ID: #{attachment_data.id}, Asset ID: #{original_asset.asset_manager_id}, Variant: #{original_asset.variant}, Replacement ID: #{replacement_original_asset.asset_manager_id} - OK + Attachment Data ID: #{attachment_data.id}, Asset ID: #{thumbnail_asset.asset_manager_id}, Variant: #{thumbnail_asset.variant}, Replacement ID: #{replacement_thumbnail_asset.asset_manager_id} - OK + OUTPUT + Services.asset_manager.expects(:update_asset) + .at_least_once + .with(original_asset.asset_manager_id, { "replacement_id" => replacement_original_asset.asset_manager_id }) + + Services.asset_manager.expects(:update_asset) + .at_least_once + .with(thumbnail_asset.asset_manager_id, { "replacement_id" => replacement_thumbnail_asset.asset_manager_id }) + + output, _err = capture_io { task.invoke(filepath) } + assert_equal expected_output, output + end + + test "it sends original replacement if thumbnail is missing" do + replacement_thumbnail_asset.destroy! + + Services.asset_manager.stubs(:asset).with(original_asset.asset_manager_id).returns("id" => "http://asset-manager/assets/#{original_asset.asset_manager_id}", "name" => "original.pdf") + Services.asset_manager.stubs(:asset).with(thumbnail_asset.asset_manager_id).returns("id" => "http://asset-manager/assets/#{thumbnail_asset.asset_manager_id}", "name" => "thumbnail.pdf.png") + + expected_output = <<~OUTPUT + Attachment Data ID: #{attachment_data.id}, Asset ID: #{original_asset.asset_manager_id}, Variant: #{original_asset.variant}, Replacement ID: #{replacement_original_asset.asset_manager_id} - OK + Attachment Data ID: #{attachment_data.id}, Asset ID: #{thumbnail_asset.asset_manager_id}, Variant: #{thumbnail_asset.variant}, Replacement ID: #{replacement_original_asset.asset_manager_id} - OK + OUTPUT + Services.asset_manager.expects(:update_asset) + .at_least_once + .with(original_asset.asset_manager_id, { "replacement_id" => replacement_original_asset.asset_manager_id }) + + Services.asset_manager.expects(:update_asset) + .at_least_once + .with(thumbnail_asset.asset_manager_id, { "replacement_id" => replacement_original_asset.asset_manager_id }) + + output, _err = capture_io { task.invoke(filepath) } + assert_equal expected_output, output + end + + test "it skips and logs if no replacement is found" do + attachment_data.replaced_by_id = nil + attachment_data.save! + + expected_output = <<~OUTPUT + Attachment Data ID: #{attachment_data.id}, Asset ID: #{original_asset.asset_manager_id} - SKIPPED. No replacement found. + Attachment Data ID: #{attachment_data.id}, Asset ID: #{thumbnail_asset.asset_manager_id} - SKIPPED. No replacement found. + OUTPUT + + output, _err = capture_io { task.invoke(filepath) } + assert_equal expected_output, output + end + + test "it logs error if asset not found" do + error = GdsApi::HTTPClientError.new(404, "Not found") + Services.asset_manager.expects(:asset).with(original_asset.asset_manager_id).raises(error) + Services.asset_manager.stubs(:asset).with(thumbnail_asset.asset_manager_id).returns("id" => "http://asset-manager/assets/#{thumbnail_asset.asset_manager_id}", "name" => "thumbnail.pdf.png") + Services.asset_manager.stubs(:update_asset).with(thumbnail_asset.asset_manager_id, { "replacement_id" => replacement_original_asset.asset_manager_id }).raises(error) + + expected_output = <<~OUTPUT + Attachment Data ID: #{attachment_data.id}, Asset ID #{original_asset.asset_manager_id} - ERROR, message: #{error.message} + Attachment Data ID: #{attachment_data.id}, Asset ID: #{thumbnail_asset.asset_manager_id}, Variant: #{thumbnail_asset.variant}, Replacement ID: #{replacement_thumbnail_asset.asset_manager_id} - OK + OUTPUT + + output, _err = capture_io { task.invoke(filepath) } + assert_equal expected_output, output + end + + test "it logs error if update fails" do + error = GdsApi::HTTPClientError.new(500, "Server error") + Services.asset_manager.stubs(:asset).with(original_asset.asset_manager_id).returns("id" => "http://asset-manager/assets/#{original_asset.asset_manager_id}", "name" => "original.pdf", "replacement_id" => nil) + Services.asset_manager.stubs(:asset).with(thumbnail_asset.asset_manager_id).returns("id" => "http://asset-manager/assets/#{thumbnail_asset.asset_manager_id}", "name" => "thumbnail.pdf.png") + Services.asset_manager.stubs(:update_asset).with(original_asset.asset_manager_id, { "replacement_id" => replacement_original_asset.asset_manager_id }).raises(error) + Services.asset_manager.stubs(:update_asset).with(thumbnail_asset.asset_manager_id, { "replacement_id" => replacement_thumbnail_asset.asset_manager_id }) + + expected_output = <<~OUTPUT + Attachment Data ID: #{attachment_data.id}, Asset ID #{original_asset.asset_manager_id} - ERROR, message: Server error + Attachment Data ID: #{attachment_data.id}, Asset ID: #{thumbnail_asset.asset_manager_id}, Variant: #{thumbnail_asset.variant}, Replacement ID: #{replacement_thumbnail_asset.asset_manager_id} - OK + OUTPUT + + output, _err = capture_io { task.invoke(filepath) } + assert_equal expected_output, output + end + end +end