diff --git a/app/aspects/zipper.rb b/app/aspects/zipper.rb new file mode 100644 index 00000000..86bea457 --- /dev/null +++ b/app/aspects/zipper.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "dry/monads" +require "initable" +require "refinements/string" +require "zip" + +module Terminus + module Aspects + # A monadic compressor of zip file content. + class Zipper + include Initable[io: Zip::OutputStream] + include Dry::Monads[:result] + + using Refinements::String + + def self.compress manifest, buffer + manifest.each do |name, content| + buffer.put_next_entry name + buffer.write content + end + end + + def call manifest + io.write_buffer { self.class.compress manifest, it } + .tap(&:rewind) + .then { Success it } + rescue TypeError, Zip::Error => error + Failure error.message.up + end + end + end +end diff --git a/spec/app/aspects/zipper_spec.rb b/spec/app/aspects/zipper_spec.rb new file mode 100644 index 00000000..e38d3d5f --- /dev/null +++ b/spec/app/aspects/zipper_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "hanami_helper" + +RSpec.describe Terminus::Aspects::Zipper do + subject(:zipper) { described_class.new } + + describe "#call" do + let(:manifest) { {"one.txt" => "One", "two.txt" => "Two"} } + + it "create zip file in memory" do + io = zipper.call(manifest).value! + + content = Zip::File.open_buffer(io).each.with_object({}) do |entry, attributes| + attributes[entry.name] = entry.get_input_stream.read + end + + expect(content).to eq(manifest) + end + + it "answers StringIO instance" do + expect(zipper.call(manifest)).to match(Success(kind_of(StringIO))) + end + + it "answers failure with invalid type" do + expect(zipper.call({bogus: Object.new})).to be_failure( + "No implicit conversion of Object into String" + ) + end + + it "answers failure with zip error" do + io = class_double Zip::OutputStream + zipper = described_class.new(io:) + + allow(io).to receive(:write_buffer).and_raise(Zip::Error, "Danger!") + + expect(zipper.call({test: "test"})).to be_failure("Danger!") + end + end +end