require File.expand_path("./spec") + '/spec_helper.rb'
require 'webrick'
require 'yaml'
require 'base64'
require 'fileutils'

describe Patron::Session do

  def yaml_load(str)
    if RUBY_VERSION >= '3.1.0'
      YAML::safe_load(str, permitted_classes: [OpenStruct])
    else
      YAML::load(str)
    end
  end

  before(:each) do
    @session = Patron::Session.new
    @session.base_url = "https://localhost:9043"
    @session.insecure = true
  end

  it "should retrieve a url with :get" do
    response = @session.get("/test")
    body = yaml_load(response.body)
    expect(body.request_method).to be == "GET"
  end

  it "should download content with :get and a file path" do
    tmpfile = "/tmp/patron_test.yaml"
    response = @session.get_file "/test", tmpfile
    expect(response.body).to be_nil
    body = yaml_load(File.open(tmpfile).read)
    expect(body.request_method).to be == "GET"
    FileUtils.rm tmpfile
  end

  # See https://github.com/curl/curl/issues/788
  # Basically any HTTPS operation crashes on OSX with securetransport-enabled libCURL
  it "should download content in a forked subprocess" do
    # To trigger the bug, we need to perform a request in the master process first
    tmpfile = "/tmp/patron_test.yaml"
    @session.get_file "/test", tmpfile
    FileUtils.rm tmpfile
    
    # and this one segfaults
    pid = fork do
      response = @session.get_file "/test", tmpfile
      expect(response.body).to be_nil
      body = yaml_load(File.open(tmpfile).read)
      expect(body.request_method).to be == "GET"
      FileUtils.rm tmpfile
    end
    
    exit_pid, status = Process.wait2(pid)
    expect(status.exitstatus).to be_zero
  end
  
  it "should download correctly(md5 ok) with get_file" do
    tmpfile = "/tmp/picture"
    response = @session.get_file "/picture", tmpfile
    expect(response.body).to be_nil
    expect(File.size(File.join(File.dirname(__FILE__),"../pic.png"))).to be == File.size(tmpfile)
    FileUtils.rm tmpfile
  end

  it "should include custom headers in a request" do
    response = @session.get("/test", {"User-Agent" => "PatronTest"})
    body = yaml_load(response.body)
    expect(body.header["user-agent"]).to be == ["PatronTest"]
  end

  it "should merge custom headers with session headers" do
    @session.headers["X-Test"] = "Testing"
    response = @session.get("/test", {"User-Agent" => "PatronTest"})
    body = yaml_load(response.body)
    expect(body.header["user-agent"]).to be == ["PatronTest"]
    expect(body.header["x-test"]).to be == ["Testing"]
  end

  it "should raise an exception on timeout" do
    @session.timeout = 1
    expect {
      @session.get("/timeout?millis=1100")
    }.to raise_error(Patron::TimeoutError)
  end

  it "should follow redirects by default" do
    @session.max_redirects = 1
    response = @session.get("/redirect")
    body = yaml_load(response.body)
    expect(response.status).to be == 200
    expect(body.path).to be == "/test"
  end

  it "should include redirect count in response" do
    @session.max_redirects = 1
    response = @session.get("/redirect")
    expect(response.redirect_count).to be == 1
  end

  it "should not follow redirects when configured to do so" do
    @session.max_redirects = 0
    response = @session.get("/redirect")
    expect(response.status).to be == 301
    expect(response.body).to be_empty
  end

  it "should retrieve URL metadata with :head" do
    response = @session.head("/test")
    expect(response.status).to be == 200
    expect(response.body).to be_empty
    expect(response.headers).to_not be_empty
  end

  it "should send a delete request with :delete" do
    response = @session.delete("/test")
    body = yaml_load(response.body)
    expect(body.request_method).to be == "DELETE"
  end

  it "should send a COPY request with :copy" do
    response = @session.copy("/test", "/test2")
    body = yaml_load(response.body)
    expect(body.request_method).to be == "COPY"
  end

  it "should include a Destination header in COPY requests" do
    response = @session.copy("/test", "/test2")
    body = yaml_load(response.body)
    expect(body.header['destination'].first).to be == "/test2"
  end

  it "should upload data with :get" do
    data = "upload data"
    response = @session.request(:get, "/test", {}, :data => data)
    body = yaml_load(response.body)
    expect(body.request_method).to be == "GET"
    expect(body.header['content-length']).to be == [data.size.to_s]
  end

  it "should upload data with :put" do
    data = "upload data"
    response = @session.put("/test", data)
    body = yaml_load(response.body)
    expect(body.request_method).to be == "PUT"
    expect(body.header['content-length']).to be == [data.size.to_s]
  end

  it "should raise when no data is provided to :put" do
    expect { @session.put("/test", nil) }.to raise_error(ArgumentError)
  end

  it "should upload a file with :put" do
    response = @session.put_file("/test", "LICENSE")
    body = yaml_load(response.body)
    expect(body.request_method).to be == "PUT"
  end

  it "should raise when no file is provided to :put" do
    expect { @session.put_file("/test", nil) }.to raise_error(ArgumentError)
  end

  it "should use chunked encoding when uploading a file with :put" do
    response = @session.put_file("/test", "LICENSE")
    body = yaml_load(response.body)
    expect(body.header['transfer-encoding'].first).to be == "chunked"
  end

  it "should upload data with :post" do
    data = "upload data"
    response = @session.post("/test", data)
    body = yaml_load(response.body)
    expect(body.request_method).to be == "POST"
    expect(body.header['content-length']).to be == [data.size.to_s]
  end

  it "should post a hash of arguments as a urlencoded form" do
    data = {:foo => 123, 'baz' => '++hello world++'}
    response = @session.post("/testpost", data)
    body = yaml_load(response.body)
    expect(body['content_type']).to be == "application/x-www-form-urlencoded"
    expect(body['body']).to match(/baz=%2B%2Bhello%20world%2B%2B/)
    expect(body['body']).to match(/foo=123/)
  end

  it "should raise when no data is provided to :post" do
    expect { @session.post("/test", nil) }.to raise_error(ArgumentError)
  end

  it "should upload a file with :post" do
    response = @session.post_file("/test", "LICENSE")
    body = yaml_load(response.body)
    expect(body.request_method).to be == "POST"
  end

  it "should upload a multipart with :post" do
    response = @session.post_multipart("/test", { :test_data => "123" }, { :test_file => "LICENSE" } )
    body = yaml_load(response.body)
    expect(body.request_method).to be == "POST"
  end

  it "should raise when no file is provided to :post" do
    expect { @session.post_file("/test", nil) }.to raise_error(ArgumentError)
  end

  it "should use chunked encoding when uploading a file with :post" do
    response = @session.post_file("/test", "LICENSE")
    body = yaml_load(response.body)
    expect(body.header['transfer-encoding'].first).to be == "chunked"
  end

  it "should handle cookies if set" do
    @session.handle_cookies
    response = @session.get("/setcookie").body
    expect(yaml_load(response).header['cookie'].first).to be == "session_id=foo123"
  end

  it "should not handle cookies by default" do
    response = @session.get("/setcookie").body
    expect(yaml_load(response).header).to_not include('cookie')
  end

  it "should raise exception if cookie store is not writable or readable" do
    expect { @session.handle_cookies("/trash/clash/foo") }.to raise_error(ArgumentError)
  end

  it "should work with multiple threads" do
    threads = []
    3.times do
      threads << Thread.new do
        session = Patron::Session.new
        session.base_url = "https://localhost:9043"
        session.insecure = true
        session.post_file("/test", "LICENSE")
      end
    end
    threads.each {|t| t.join }
  end

  it "should fail when insecure mode is off" do
    expect {
      @session.insecure = nil
      response = @session.get("/test")
    }.to raise_error(Patron::Error)
  end

  it "should work when insecure mode is off but certificate is supplied" do
    @session.insecure = nil
    @session.cacert = File.join(__dir__, 'support', 'certs', 'cacert.pem')
    response = @session.get("/test")
    body = yaml_load(response.body)
    expect(body.request_method).to be == "GET"
  end

  it "should allow to specify insecure mode per-request" do
    @session.insecure = false
    expect {
      @session.request(:get, "/test", {}, insecure: true)
    }.not_to raise_error
    expect {
      @session.request(:get, "/test", {})
    }.to raise_error(Patron::Error)
  end

  it "should work with different SSL versions" do
    ['TLSv1_0','TLSv1_1'].each do |version|
      @session.ssl_version = version
      response = @session.get("/test")
      expect(response.status).to be == 200
    end
  end

  it "should raise when an unsupported or unknown SSL version is requested" do
    ['something', 1].each do |version|
      @session.ssl_version = version
      expect {
        @session.get("/test")
      }.to raise_error(Patron::UnsupportedSSLVersion)
    end
  end

  it "should work with different HTTP versions" do
    ['HTTPv1_0','HTTPv1_1'].each do |version|
      @session.http_version = version
      response = @session.get("/test")
      expect(response.status).to be == 200
    end
  end

  it "should raise when an unsupported or unknown HTTP version is requested" do
    ['something', 1].each do |version|
      @session.http_version = version
      expect {
        @session.get("/test")
      }.to raise_error(Patron::UnsupportedHTTPVersion)
    end
  end

  # ------------------------------------------------------------------------
  describe 'when debug is enabled' do
    it 'it should not clobber stderr' do
      rdev = STDERR.stat.rdev

      @session.enable_debug
      expect(STDERR.stat.rdev).to be == rdev

      @session.enable_debug
      expect(STDERR.stat.rdev).to be == rdev
    end
  end

end
