0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-07 13:19:19 +08:00
discourse/spec/services/custom_emoji/preview_import_spec.rb
Martin Brennan b736e72b1c
DEV: Retroactive review and refactor of custom emoji bulk import/export (#41860)
Followup bd3906ce64

Converts the new endpoints made in the original PR into
service classes, using AI to drive implementation based
on the service authoring skill:

* CustomEmoji::Export
* CustomEmoji::PreviewImport
* CustomEmoji::ConfirmImport

In addition, some supporting action, lib, and serializer
classes have been added. This fixed a couple of
bugs as well:

* Import names now go through `Emoji.sanitize_emoji_name` like
single-emoji create
* No-group imports no longer persist a literal "default" string
* Two 500s (suspicious zip entries, malformed CSV) became proper 422s
* Duplicate CSV names are flagged at preview instead of exploding the
confirm transaction
*` Emoji.clear_cache `moved outside the transaction
2026-07-22 10:06:54 +10:00

82 lines
2.6 KiB
Ruby
Vendored

# frozen_string_literal: true
require "zip"
RSpec.describe CustomEmoji::PreviewImport do
describe described_class::Contract, type: :model do
it { is_expected.to validate_presence_of(:file) }
end
describe ".call" do
subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user, :admin)
let(:params) { { file: } }
let(:dependencies) { { guardian: current_user.guardian } }
let(:image_path) { Rails.root.join("spec/fixtures/images/logo.png") }
let(:csv_content) { "name,group,filename\npreview-emoji,,preview-emoji.png\n" }
let(:images) { { "preview-emoji.png" => image_path } }
let(:file) { build_emoji_zip(csv_content, images) }
def build_emoji_zip(csv_content, images = {})
tmp = Tempfile.new(%w[emoji_import_ .zip])
tmp.close
Zip::File.open(tmp.path, create: true) do |zip|
zip.get_output_stream("emojis.csv") { |entry| entry.write(csv_content) } if csv_content
images.each { |filename, path| zip.add(filename, path) }
end
Rack::Test::UploadedFile.new(tmp.path, "application/zip")
end
context "when contract is invalid" do
let(:file) { nil }
it { is_expected.to fail_a_contract }
end
context "when the ZIP has no emojis.csv entry" do
let(:file) do
tmp = Tempfile.new(%w[no_csv_ .zip])
tmp.close
Zip::File.open(tmp.path, create: true) do |zip|
zip.get_output_stream("readme.txt") { |entry| entry.write("hi") }
end
Rack::Test::UploadedFile.new(tmp.path, "application/zip")
end
it { is_expected.to fail_with_exception(Compression::SafeZipReader::MissingEntryError) }
end
context "when the manifest only contains headers" do
let(:csv_content) { "name,group,filename\n" }
let(:images) { {} }
it { is_expected.to fail_a_policy(:manifest_not_empty) }
end
context "when everything's ok" do
it { is_expected.to run_successfully }
it "returns a token and the staged rows with their categories" do
expect(result[:token]).to be_present
expect(result[:rows].map { |row| [row.name, row.category] }).to eq(
[["preview-emoji", CustomEmoji::ImportRow::CATEGORY_NEW]],
)
end
it "stores the staged rows in Redis under the user and token" do
expect(
Discourse.redis.exists?("emoji_import_preview:#{current_user.id}:#{result[:token]}"),
).to eq(true)
end
it "creates a staged upload retained for three hours" do
expect { result }.to change { Upload.count }.by(1)
expect(Upload.last.retain_hours).to eq(3)
end
end
end
end