# frozen_string_literal: true

RSpec.describe HTTP::Response do
  subject(:response) do
    HTTP::Response.new(
      status:  200,
      version: "1.1",
      headers: headers,
      body:    body,
      request: request
    )
  end

  let(:body)          { "Hello world!" }
  let(:uri)           { "http://example.com/" }
  let(:headers)       { {} }
  let(:request)       { HTTP::Request.new(verb: :get, uri: uri) }

  it "includes HTTP::Headers::Mixin" do
    expect(described_class).to include HTTP::Headers::Mixin
  end

  describe "to_a" do
    let(:body)         { "Hello world" }
    let(:content_type) { "text/plain" }
    let(:headers)      { {"Content-Type" => content_type} }

    it "returns a Rack-like array" do
      expect(subject.to_a).to eq([200, headers, body])
    end
  end

  describe "#content_length" do
    subject { response.content_length }

    context "without Content-Length header" do
      it { is_expected.to be_nil }
    end

    context "with Content-Length: 5" do
      let(:headers) { {"Content-Length" => "5"} }

      it { is_expected.to eq 5 }
    end

    context "with invalid Content-Length" do
      let(:headers) { {"Content-Length" => "foo"} }

      it { is_expected.to be_nil }
    end
  end

  describe "mime_type" do
    subject { response.mime_type }

    context "without Content-Type header" do
      let(:headers) { {} }

      it { is_expected.to be_nil }
    end

    context "with Content-Type: text/html" do
      let(:headers) { {"Content-Type" => "text/html"} }

      it { is_expected.to eq "text/html" }
    end

    context "with Content-Type: text/html; charset=utf-8" do
      let(:headers) { {"Content-Type" => "text/html; charset=utf-8"} }

      it { is_expected.to eq "text/html" }
    end
  end

  describe "charset" do
    subject { response.charset }

    context "without Content-Type header" do
      let(:headers) { {} }

      it { is_expected.to be_nil }
    end

    context "with Content-Type: text/html" do
      let(:headers) { {"Content-Type" => "text/html"} }

      it { is_expected.to be_nil }
    end

    context "with Content-Type: text/html; charset=utf-8" do
      let(:headers) { {"Content-Type" => "text/html; charset=utf-8"} }

      it { is_expected.to eq "utf-8" }
    end
  end

  describe "#parse" do
    let(:headers)   { {"Content-Type" => content_type} }
    let(:body)      { '{"foo":"bar"}' }

    context "with known content type" do
      let(:content_type) { "application/json" }

      it "returns parsed body" do
        expect(response.parse).to eq "foo" => "bar"
      end
    end

    context "with unknown content type" do
      let(:content_type) { "application/deadbeef" }

      it "raises HTTP::ParseError" do
        expect { response.parse }.to raise_error HTTP::ParseError
      end
    end

    context "with explicitly given mime type" do
      let(:content_type) { "application/deadbeef" }

      it "ignores mime_type of response" do
        expect(response.parse("application/json")).to eq "foo" => "bar"
      end

      it "supports mime type aliases" do
        expect(response.parse(:json)).to eq "foo" => "bar"
      end
    end

    context "when underlying parser fails" do
      let(:content_type) { "application/deadbeef" }
      let(:body)         { "" }

      it "raises HTTP::ParseError" do
        expect { response.parse }.to raise_error HTTP::ParseError
      end
    end
  end

  describe "#flush" do
    let(:body) { double to_s: "" }

    it "returns response self-reference" do
      expect(response.flush).to be response
    end

    it "flushes body" do
      expect(body).to receive :to_s
      response.flush
    end
  end

  describe "#inspect" do
    subject { response.inspect }

    let(:headers) { {content_type: "text/plain"} }
    let(:body)    { double to_s: "foobar" }

    it { is_expected.to eq "#<HTTP::Response/1.1 200 OK text/plain>" }
  end

  describe "#cookies" do
    subject(:jar) { response.cookies }

    let(:cookies) { ["a=1", "b=2; domain=example.com", "c=3; domain=bad.org"] }
    let(:headers) { {"Set-Cookie" => cookies} }

    it { is_expected.to be_an HTTP::CookieJar }

    it "contains cookies without domain restriction" do
      expect(jar.count { |c| "a" == c.name }).to eq 1
    end

    it "contains cookies limited to domain of request uri" do
      expect(jar.count { |c| "b" == c.name }).to eq 1
    end

    it "does not contains cookies limited to non-requeted uri" do
      expect(jar.count { |c| "c" == c.name }).to eq 0
    end
  end

  describe "#connection" do
    subject(:response) do
      HTTP::Response.new(
        version:    "1.1",
        status:     200,
        connection: connection,
        request:    request
      )
    end

    let(:connection) { double }

    it "returns the connection object used to instantiate the response" do
      expect(response.connection).to eq connection
    end
  end

  describe "#chunked?" do
    subject { response }

    context "when encoding is set to chunked" do
      let(:headers) { {"Transfer-Encoding" => "chunked"} }

      it { is_expected.to be_chunked }
    end

    it { is_expected.not_to be_chunked }
  end

  describe "backwards compatibilty with :uri" do
    context "with no :verb" do
      subject(:response) do
        HTTP::Response.new(
          status:  200,
          version: "1.1",
          headers: headers,
          body:    body,
          uri:     uri
        )
      end

      it "defaults the uri to :uri" do
        expect(response.request.uri.to_s).to eq uri
      end

      it "defaults to the verb to :get" do
        expect(response.request.verb).to eq :get
      end
    end

    context "with both a :request and :uri" do
      subject(:response) do
        HTTP::Response.new(
          status:  200,
          version: "1.1",
          headers: headers,
          body:    body,
          uri:     uri,
          request: request
        )
      end

      it "raises ArgumentError" do
        expect { response }.to raise_error(ArgumentError)
      end
    end
  end

  describe "#body" do
    subject(:response) do
      HTTP::Response.new(
        status:     200,
        version:    "1.1",
        headers:    headers,
        request:    request,
        connection: connection
      )
    end

    let(:connection) { double(sequence_id: 0) }
    let(:chunks)     { ["Hello, ", "World!"] }

    before do
      allow(connection).to receive(:readpartial) { chunks.shift }
      allow(connection).to receive(:body_completed?) { chunks.empty? }
    end

    context "with no Content-Type" do
      let(:headers) { {} }

      it "returns a body with default binary encoding" do
        expect(response.body.to_s.encoding).to eq Encoding::BINARY
      end
    end

    context "with Content-Type: application/json" do
      let(:headers) { {"Content-Type" => "application/json"} }

      it "returns a body with a default UTF_8 encoding" do
        expect(response.body.to_s.encoding).to eq Encoding::UTF_8
      end
    end
  end
end
