require 'spec_helper'
require 'multi_xml'

RSpec.describe HTTParty::Parser do
  describe ".SupportedFormats" do
    it "returns a hash" do
      expect(HTTParty::Parser::SupportedFormats).to be_instance_of(Hash)
    end
  end

  describe ".call" do
    it "generates an HTTParty::Parser instance with the given body and format" do
      expect(HTTParty::Parser).to receive(:new).with('body', :plain).and_return(double(parse: nil))
      HTTParty::Parser.call('body', :plain)
    end

    it "calls #parse on the parser" do
      parser = double('Parser')
      expect(parser).to receive(:parse)
      allow(HTTParty::Parser).to receive_messages(new: parser)
      parser = HTTParty::Parser.call('body', :plain)
    end
  end

  describe ".formats" do
    it "returns the SupportedFormats constant" do
      expect(HTTParty::Parser.formats).to eq(HTTParty::Parser::SupportedFormats)
    end

    it "returns the SupportedFormats constant for subclasses" do
      klass = Class.new(HTTParty::Parser)
      klass::SupportedFormats = { "application/atom+xml" => :atom }

      expect(klass.formats).to eq({"application/atom+xml" => :atom})
    end
  end

  describe ".format_from_mimetype" do
    it "returns a symbol representing the format mimetype" do
      expect(HTTParty::Parser.format_from_mimetype("text/plain")).to eq(:plain)
    end

    it "returns nil when the mimetype is not supported" do
      expect(HTTParty::Parser.format_from_mimetype("application/atom+xml")).to be_nil
    end
  end

  describe ".supported_formats" do
    it "returns a unique set of supported formats represented by symbols" do
      expect(HTTParty::Parser.supported_formats).to eq(HTTParty::Parser::SupportedFormats.values.uniq)
    end
  end

  describe ".supports_format?" do
    it "returns true for a supported format" do
      allow(HTTParty::Parser).to receive_messages(supported_formats: [:json])
      expect(HTTParty::Parser.supports_format?(:json)).to be_truthy
    end

    it "returns false for an unsupported format" do
      allow(HTTParty::Parser).to receive_messages(supported_formats: [])
      expect(HTTParty::Parser.supports_format?(:json)).to be_falsey
    end
  end

  describe "#parse" do
    it "attempts to parse supported formats" do
      parser = HTTParty::Parser.new('body', :json)
      allow(parser).to receive_messages(supports_format?: true)

      expect(parser).to receive(:parse_supported_format)
      parser.parse
    end

    it "returns the unparsed body when the format is unsupported" do
      parser = HTTParty::Parser.new('body', :json)
      allow(parser).to receive_messages(supports_format?: false)

      expect(parser.parse).to eq(parser.body)
    end

    it "returns nil for an empty body" do
      parser = HTTParty::Parser.new('', :json)
      expect(parser.parse).to be_nil
    end

    it "returns nil for a nil body" do
      parser = HTTParty::Parser.new(nil, :json)
      expect(parser.parse).to be_nil
    end

    it "returns nil for a 'null' body" do
      parser = HTTParty::Parser.new("null", :json)
      expect(parser.parse).to be_nil
    end

    it "returns nil for a body with spaces only" do
      parser = HTTParty::Parser.new("   ", :json)
      expect(parser.parse).to be_nil
    end

    it "does not raise exceptions for bodies with invalid encodings" do
      parser = HTTParty::Parser.new("\x80", :invalid_format)
      expect(parser.parse).to_not be_nil
    end

    it "ignores utf-8 bom" do
      parser = HTTParty::Parser.new("\xEF\xBB\xBF\{\"hi\":\"yo\"\}", :json)
      expect(parser.parse).to eq({"hi"=>"yo"})
    end

    it "parses ascii 8bit encoding" do
      parser = HTTParty::Parser.new(
        "{\"currency\":\"\xE2\x82\xAC\"}".force_encoding('ASCII-8BIT'),
        :json
      )
      expect(parser.parse).to eq({"currency" => "€"})
    end

    it "parses frozen strings" do
      parser = HTTParty::Parser.new('{"a":1}'.freeze, :json)
      expect(parser.parse).to eq("a" => 1)
    end
  end

  describe "#supports_format?" do
    it "utilizes the class method to determine if the format is supported" do
      expect(HTTParty::Parser).to receive(:supports_format?).with(:json)
      parser = HTTParty::Parser.new('body', :json)
      parser.send(:supports_format?)
    end
  end

  describe "#parse_supported_format" do
    it "calls the parser for the given format" do
      parser = HTTParty::Parser.new('body', :json)
      expect(parser).to receive(:json)
      parser.send(:parse_supported_format)
    end

    context "when a parsing method does not exist for the given format" do
      it "raises an exception" do
        parser = HTTParty::Parser.new('body', :atom)
        expect do
          parser.send(:parse_supported_format)
        end.to raise_error(NotImplementedError, "HTTParty::Parser has not implemented a parsing method for the :atom format.")
      end

      it "raises a useful exception message for subclasses" do
        atom_parser = Class.new(HTTParty::Parser) do
          def self.name
            'AtomParser'
          end
        end
        parser = atom_parser.new 'body', :atom
        expect do
          parser.send(:parse_supported_format)
        end.to raise_error(NotImplementedError, "AtomParser has not implemented a parsing method for the :atom format.")
      end
    end
  end

  context "parsers" do
    subject do
      HTTParty::Parser.new('body', nil)
    end

    it "parses xml with MultiXml" do
      expect(MultiXml).to receive(:parse).with('body')
      subject.send(:xml)
    end

    it "parses json with JSON" do
      expect(JSON).to receive(:parse).with('body', :quirks_mode => true, :allow_nan => true)
      subject.send(:json)
    end

    it "parses html by simply returning the body" do
      expect(subject.send(:html)).to eq('body')
    end

    it "parses plain text by simply returning the body" do
      expect(subject.send(:plain)).to eq('body')
    end

    it "parses csv with CSV" do
      require 'csv'
      expect(CSV).to receive(:parse).with('body')
      subject.send(:csv)
    end
  end
end
