0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-08 17:53:55 +08:00
discourse/spec/lib/compression/safe_zip_reader_spec.rb
Steven Chang 07a13cfd47
FEATURE: Add emoji_picker_pinned_groups for pinning groups in the picker (#41881)
This adds the `emoji_picker_pinned_groups` site setting for pinning
emoji groups at the top of the emoji picker list. It accepts both custom
& non custom emoji group names, and can be ordered to preference.

If a custom emoji group is added, and the group is deleted, it will
gracefully ignore the missing group. A validator is included to ensure
the site setting is correct at time of save.

<img width="912" height="228" alt="image"
src="https://github.com/user-attachments/assets/0a832cd9-146d-4d46-b9d1-4e8d4ea3b30d"
/>

<img width="464" height="354" alt="image"
src="https://github.com/user-attachments/assets/2512df0e-035c-457a-bed3-b5d0ac3edc2c"
/>

---------

Co-authored-by: Martin Brennan <martin@discourse.org>
2026-07-22 09:51:15 -07:00

134 lines
4 KiB
Ruby
Vendored

# frozen_string_literal: true
require "compression/safe_zip_reader"
RSpec.describe Compression::SafeZipReader do
let(:temp_folder) do
path = "#{Pathname.new(Dir.tmpdir).realpath}/#{SecureRandom.hex}"
FileUtils.mkdir(path)
path
end
let(:zip_path) { File.join(temp_folder, "archive.zip") }
after { FileUtils.rm_rf(temp_folder) }
def create_zip(entries)
Zip::File.open(zip_path, create: true) do |zip_file|
entries.each do |name, content|
zip_file.get_output_stream(name) { |stream| stream.write(content) }
end
end
end
it "reads an entry within the configured limits" do
create_zip("document.xml" => "hello")
described_class.open(zip_path, max_entries: 10, max_total_bytes: 100) do |zip|
expect(zip.read_entry("document.xml", max_bytes: 10)).to eq("hello")
expect(zip.remaining_total_bytes).to eq(95)
end
end
it "returns nil for missing entries" do
create_zip("document.xml" => "hello")
described_class.open(zip_path) do |zip|
expect(zip.read_entry("missing.xml", max_bytes: 10)).to be_nil
end
end
it "raises for missing required entries" do
create_zip("document.xml" => "hello")
described_class.open(zip_path) do |zip|
expect { zip.read_entry("missing.xml", max_bytes: 10, required: true) }.to raise_error(
described_class::MissingEntryError,
)
end
end
it "raises when an entry exceeds its per-entry limit" do
create_zip("document.xml" => "hello")
described_class.open(zip_path) do |zip|
expect { zip.read_entry("document.xml", max_bytes: 4) }.to raise_error(
described_class::EntryTooLargeError,
)
end
end
it "raises when reads exceed the total inflated byte budget" do
create_zip("a.xml" => "hello", "b.xml" => "world")
described_class.open(zip_path, max_total_bytes: 6) do |zip|
expect(zip.read_entry("a.xml", max_bytes: 10)).to eq("hello")
expect { zip.read_entry("b.xml", max_bytes: 10) }.to raise_error(
described_class::EntryTooLargeError,
)
end
end
it "raises when the archive has too many entries" do
create_zip("a.xml" => "a", "b.xml" => "b")
expect { described_class.open(zip_path, max_entries: 1) { nil } }.to raise_error(
described_class::TooManyEntriesError,
)
end
describe "root prefix handling" do
it "resolves entries nested under a single root directory, ignoring macOS metadata entries" do
create_zip(
"cats/emojis.csv" => "csv",
"cats/smile.png" => "png",
"__MACOSX/._cats" => "junk",
"__MACOSX/cats/._emojis.csv" => "junk",
".DS_Store" => "junk",
)
described_class.open(zip_path) do |zip|
expect(zip.read_entry("emojis.csv", max_bytes: 10)).to eq("csv")
expect(zip.read_entry("smile.png", max_bytes: 10)).to eq("png")
end
end
it "still resolves entries by their exact nested name" do
create_zip("cats/emojis.csv" => "csv")
described_class.open(zip_path) do |zip|
expect(zip.read_entry("cats/emojis.csv", max_bytes: 10)).to eq("csv")
end
end
it "does not apply a prefix when entries span multiple top-level names" do
create_zip("cats/emojis.csv" => "csv", "dogs/emojis.csv" => "csv")
described_class.open(zip_path) do |zip|
expect(zip.read_entry("emojis.csv", max_bytes: 10)).to be_nil
end
end
it "does not treat a lone root-level file as a prefix" do
create_zip("emojis.csv" => "csv")
described_class.open(zip_path) do |zip|
expect(zip.read_entry("emojis.csv", max_bytes: 10)).to eq("csv")
expect(zip.read_entry("missing.csv", max_bytes: 10)).to be_nil
end
end
end
it "streams entries to files within the configured limits" do
create_zip("document.xml" => "hello")
Tempfile.create("safe-zip-reader") do |tempfile|
described_class.open(zip_path) do |zip|
zip.stream_entry_to_file("document.xml", tempfile, max_bytes: 10)
end
tempfile.rewind
expect(tempfile.read).to eq("hello")
end
end
end