# frozen_string_literal: true

RSpec.describe HTTP::Request::Body do
  subject    { HTTP::Request::Body.new(body) }

  let(:body) { "" }

  describe "#initialize" do
    context "when body is nil" do
      let(:body) { nil }

      it "does not raise an error" do
        expect { subject }.not_to raise_error
      end
    end

    context "when body is a string" do
      let(:body) { "string body" }

      it "does not raise an error" do
        expect { subject }.not_to raise_error
      end
    end

    context "when body is an IO" do
      let(:body) { FakeIO.new("IO body") }

      it "does not raise an error" do
        expect { subject }.not_to raise_error
      end
    end

    context "when body is an Enumerable" do
      let(:body) { %w[bees cows] }

      it "does not raise an error" do
        expect { subject }.not_to raise_error
      end
    end

    context "when body is of unrecognized type" do
      let(:body) { 123 }

      it "raises an error" do
        expect { subject }.to raise_error(HTTP::RequestError)
      end
    end
  end

  describe "#source" do
    it "returns the original object" do
      expect(subject.source).to eq ""
    end
  end

  describe "#size" do
    context "when body is nil" do
      let(:body) { nil }

      it "returns zero" do
        expect(subject.size).to eq 0
      end
    end

    context "when body is a string" do
      let(:body) { "Привет, мир!" }

      it "returns string bytesize" do
        expect(subject.size).to eq 21
      end
    end

    context "when body is an IO with size" do
      let(:body) { FakeIO.new("content") }

      it "returns IO size" do
        expect(subject.size).to eq 7
      end
    end

    context "when body is an IO without size" do
      let(:body) { IO.pipe[0] }

      it "raises a RequestError" do
        expect { subject.size }.to raise_error(HTTP::RequestError)
      end
    end

    context "when body is an Enumerable" do
      let(:body) { %w[bees cows] }

      it "raises a RequestError" do
        expect { subject.size }.to raise_error(HTTP::RequestError)
      end
    end
  end

  describe "#each" do
    let(:chunks) do
      chunks = []
      subject.each { |chunk| chunks << chunk.dup } # rubocop:disable Style/MapIntoArray
      chunks
    end

    context "when body is nil" do
      let(:body) { nil }

      it "yields nothing" do
        expect(chunks).to eq []
      end
    end

    context "when body is a string" do
      let(:body) { "content" }

      it "yields the string" do
        expect(chunks).to eq %w[content]
      end
    end

    context "when body is a non-Enumerable IO" do
      let(:body) { FakeIO.new(("a" * 16 * 1024) + ("b" * 10 * 1024)) }

      it "yields chunks of content" do
        expect(chunks.inject("", :+)).to eq ("a" * 16 * 1024) + ("b" * 10 * 1024)
      end
    end

    context "when body is a pipe" do
      let(:ios)  { IO.pipe }
      let(:body) { ios[0] }

      around do |example|
        writer = Thread.new(ios[1]) do |io|
          io << "abcdef"
          io.close
        end

        begin
          example.run
        ensure
          writer.join
        end
      end

      it "yields chunks of content" do
        expect(chunks.inject("", :+)).to eq("abcdef")
      end
    end

    context "when body is an Enumerable IO" do
      let(:data) { ("a" * 16 * 1024) + ("b" * 10 * 1024) }
      let(:body) { StringIO.new data }

      it "yields chunks of content" do
        expect(chunks.inject("", :+)).to eq data
      end

      it "allows to enumerate multiple times" do
        results = []

        2.times do
          result = ""
          subject.each { |chunk| result += chunk }
          results << result
        end

        aggregate_failures do
          expect(results.count).to eq 2
          expect(results).to all eq data
        end
      end
    end

    context "when body is an Enumerable" do
      let(:body) { %w[bees cows] }

      it "yields elements" do
        expect(chunks).to eq %w[bees cows]
      end
    end
  end

  describe "#==" do
    context "when sources are equivalent" do
      let(:body1) { HTTP::Request::Body.new("content") }
      let(:body2) { HTTP::Request::Body.new("content") }

      it "returns true" do
        expect(body1).to eq body2
      end
    end

    context "when sources are not equivalent" do
      let(:body1) { HTTP::Request::Body.new("content") }
      let(:body2) { HTTP::Request::Body.new(nil) }

      it "returns false" do
        expect(body1).not_to eq body2
      end
    end

    context "when objects are not of the same class" do
      let(:body1) { HTTP::Request::Body.new("content") }
      let(:body2) { "content" }

      it "returns false" do
        expect(body1).not_to eq body2
      end
    end
  end
end
