This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Curl do
|
||||
describe ".init" do
|
||||
before { Ethon::Curl.send(:class_variable_set, :@@initialized, false) }
|
||||
|
||||
context "when global_init fails" do
|
||||
it "raises global init error" do
|
||||
expect(Ethon::Curl).to receive(:global_init).and_return(1)
|
||||
expect{ Ethon::Curl.init }.to raise_error(Ethon::Errors::GlobalInit)
|
||||
end
|
||||
end
|
||||
|
||||
context "when global_init works" do
|
||||
before { expect(Ethon::Curl).to receive(:global_init).and_return(0) }
|
||||
|
||||
it "doesn't raises global init error" do
|
||||
expect{ Ethon::Curl.init }.to_not raise_error
|
||||
end
|
||||
|
||||
it "logs" do
|
||||
expect(Ethon.logger).to receive(:debug)
|
||||
Ethon::Curl.init
|
||||
end
|
||||
end
|
||||
|
||||
context "when global_cleanup is called" do
|
||||
before { expect(Ethon::Curl).to receive(:global_cleanup) }
|
||||
|
||||
it "logs" do
|
||||
expect(Ethon.logger).to receive(:debug).twice
|
||||
Ethon::Curl.init
|
||||
Ethon::Curl.cleanup
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,81 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Callbacks do
|
||||
let!(:easy) { Ethon::Easy.new }
|
||||
|
||||
describe "#set_callbacks" do
|
||||
before do
|
||||
expect(Ethon::Curl).to receive(:set_option).exactly(3).times
|
||||
end
|
||||
|
||||
it "sets write-, debug-, and headerfunction" do
|
||||
easy.set_callbacks
|
||||
end
|
||||
|
||||
it "resets @response_body" do
|
||||
easy.set_callbacks
|
||||
expect(easy.instance_variable_get(:@response_body)).to eq("")
|
||||
end
|
||||
|
||||
it "resets @response_headers" do
|
||||
easy.set_callbacks
|
||||
expect(easy.instance_variable_get(:@response_headers)).to eq("")
|
||||
end
|
||||
|
||||
it "resets @debug_info" do
|
||||
easy.set_callbacks
|
||||
expect(easy.instance_variable_get(:@debug_info).to_a).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#progress_callback" do
|
||||
it "returns 0" do
|
||||
expect(easy.progress_callback.call(0,1,1,1,1)).to be(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#body_write_callback" do
|
||||
let(:body_write_callback) { easy.instance_variable_get(:@body_write_callback) }
|
||||
let(:stream) { double(:read_string => "") }
|
||||
context "when body returns not :abort" do
|
||||
it "returns number bigger than 0" do
|
||||
expect(body_write_callback.call(stream, 1, 1, nil) > 0).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when body returns :abort" do
|
||||
before do
|
||||
easy.on_body.clear
|
||||
easy.on_body { :abort }
|
||||
end
|
||||
let(:body_write_callback) { easy.instance_variable_get(:@body_write_callback) }
|
||||
|
||||
it "returns -1 to indicate abort to libcurl" do
|
||||
expect(body_write_callback.call(stream, 1, 1, nil)).to eq(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#header_write_callback" do
|
||||
let(:header_write_callback) { easy.instance_variable_get(:@header_write_callback) }
|
||||
let(:stream) { double(:read_string => "") }
|
||||
context "when header returns not :abort" do
|
||||
it "returns number bigger than 0" do
|
||||
expect(header_write_callback.call(stream, 1, 1, nil) > 0).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "when header returns :abort" do
|
||||
before do
|
||||
easy.on_headers.clear
|
||||
easy.on_headers { :abort }
|
||||
end
|
||||
let(:header_write_callback) { easy.instance_variable_get(:@header_write_callback) }
|
||||
|
||||
it "returns -1 to indicate abort to libcurl" do
|
||||
expect(header_write_callback.call(stream, 1, 1, nil)).to eq(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::DebugInfo do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
|
||||
before do
|
||||
easy.url = "http://localhost:3001/"
|
||||
easy.perform
|
||||
end
|
||||
|
||||
describe "#debug_info" do
|
||||
context "when verbose is not set to true" do
|
||||
it "does not save any debug info after a request" do
|
||||
expect(easy.debug_info.to_a.length).to eq(0)
|
||||
expect(easy.debug_info.to_h.values.flatten.length).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context "when verbose is set to true" do
|
||||
before do
|
||||
easy.verbose = true
|
||||
easy.perform
|
||||
end
|
||||
|
||||
after do
|
||||
easy.verbose = false
|
||||
easy.reset
|
||||
end
|
||||
|
||||
it "saves debug info after a request" do
|
||||
expect(easy.debug_info.to_a.length).to be > 0
|
||||
end
|
||||
|
||||
it "saves request headers" do
|
||||
expect(easy.debug_info.header_out.join).to include('GET / HTTP/1.1')
|
||||
end
|
||||
|
||||
it "saves response headers" do
|
||||
expect(easy.debug_info.header_in.length).to be > 0
|
||||
expect(easy.response_headers).to include(easy.debug_info.header_in.join)
|
||||
end
|
||||
|
||||
it "saves incoming data" do
|
||||
expect(easy.debug_info.data_in.length).to be > 0
|
||||
expect(easy.response_body).to include(easy.debug_info.data_in.join)
|
||||
end
|
||||
|
||||
it "saves debug text" do
|
||||
expect(easy.debug_info.text.length).to be > 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Informations do
|
||||
|
||||
describe "#supports_asynch_dns?" do
|
||||
it "returns boolean" do
|
||||
expect([true, false].include? Ethon::Easy.supports_asynch_dns?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#supports_zlib?" do
|
||||
it "returns boolean" do
|
||||
expect([true, false].include? Ethon::Easy.supports_zlib?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#supports_timeout_ms?" do
|
||||
it "returns boolean" do
|
||||
expect([true, false].include? Ethon::Easy.supports_timeout_ms?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,104 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Form do
|
||||
let(:hash) { {} }
|
||||
let!(:easy) { Ethon::Easy.new }
|
||||
let(:form) { Ethon::Easy::Form.new(easy, hash) }
|
||||
|
||||
describe ".new" do
|
||||
it "assigns attribute to @params" do
|
||||
expect(form.instance_variable_get(:@params)).to eq(hash)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#first" do
|
||||
it "returns a pointer" do
|
||||
expect(form.first).to be_a(FFI::Pointer)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#last" do
|
||||
it "returns a pointer" do
|
||||
expect(form.first).to be_a(FFI::Pointer)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#multipart?" do
|
||||
before { form.instance_variable_set(:@query_pairs, pairs) }
|
||||
|
||||
context "when query_pairs contains string values" do
|
||||
let(:pairs) { [['a', '1'], ['b', '2']] }
|
||||
|
||||
it "returns false" do
|
||||
expect(form.multipart?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context "when query_pairs contains file" do
|
||||
let(:pairs) { [['a', '1'], ['b', ['path', 'encoding', 'abs_path']]] }
|
||||
|
||||
it "returns true" do
|
||||
expect(form.multipart?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context "when options contains multipart=true" do
|
||||
before { form.instance_variable_set(:@multipart, true) }
|
||||
let(:pairs) { [['a', '1'], ['b', '2']] }
|
||||
|
||||
it "returns true" do
|
||||
expect(form.multipart?).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#materialize" do
|
||||
before { form.instance_variable_set(:@query_pairs, pairs) }
|
||||
|
||||
context "when query_pairs contains string values" do
|
||||
let(:pairs) { [['a', '1']] }
|
||||
|
||||
it "adds params to form" do
|
||||
expect(Ethon::Curl).to receive(:formadd)
|
||||
form.materialize
|
||||
end
|
||||
end
|
||||
|
||||
context "when query_pairs contains nil" do
|
||||
let(:pairs) { [['a', nil]] }
|
||||
|
||||
it "adds params to form" do
|
||||
expect(Ethon::Curl).to receive(:formadd)
|
||||
form.materialize
|
||||
end
|
||||
end
|
||||
|
||||
context "when query_pairs contains file" do
|
||||
let(:pairs) { [['a', ["file", "type", "path/file"]]] }
|
||||
|
||||
it "adds file to form" do
|
||||
expect(Ethon::Curl).to receive(:formadd)
|
||||
form.materialize
|
||||
end
|
||||
end
|
||||
|
||||
context "when query_pairs contains file and string values" do
|
||||
let(:pairs) { [['a', ["file", "type", "path/file"]], ['b', '1']] }
|
||||
|
||||
it "adds file to form" do
|
||||
expect(Ethon::Curl).to receive(:formadd).twice
|
||||
form.materialize
|
||||
end
|
||||
end
|
||||
|
||||
context "when query_pairs contains file, string and int values" do
|
||||
let(:pairs) { [['a', ["file", "type", "path/file"]], ['b', '1'], ['c', 1]] }
|
||||
|
||||
it "adds file to form" do
|
||||
expect(Ethon::Curl).to receive(:formadd).exactly(3).times
|
||||
form.materialize
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,79 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Header do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
|
||||
describe "#headers=" do
|
||||
let(:headers) { { 'User-Agent' => 'Ethon' } }
|
||||
|
||||
it "sets header" do
|
||||
expect_any_instance_of(Ethon::Easy).to receive(:set_callbacks)
|
||||
expect(Ethon::Curl).to receive(:set_option)
|
||||
easy.headers = headers
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
easy.headers = headers
|
||||
easy.url = "http://localhost:3001"
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "sends" do
|
||||
expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon"')
|
||||
end
|
||||
|
||||
context "when header value contains null byte" do
|
||||
let(:headers) { { 'User-Agent' => "Ethon\0" } }
|
||||
|
||||
it "escapes" do
|
||||
expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon\\\\0"')
|
||||
end
|
||||
end
|
||||
|
||||
context "when header value has leading whitespace" do
|
||||
let(:headers) { { 'User-Agent' => " Ethon" } }
|
||||
|
||||
it "removes" do
|
||||
expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon"')
|
||||
end
|
||||
end
|
||||
|
||||
context "when header value has traiing whitespace" do
|
||||
let(:headers) { { 'User-Agent' => "Ethon " } }
|
||||
|
||||
it "removes" do
|
||||
expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon"')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#compose_header" do
|
||||
it "has space in between" do
|
||||
expect(easy.compose_header('a', 'b')).to eq('a: b')
|
||||
end
|
||||
|
||||
context "when value is a symbol" do
|
||||
it "works" do
|
||||
expect{ easy.compose_header('a', :b) }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#header_list" do
|
||||
context "when no set_headers" do
|
||||
it "returns nil" do
|
||||
expect(easy.header_list).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context "when set_headers" do
|
||||
it "returns pointer to header list" do
|
||||
easy.headers = {'User-Agent' => 'Custom'}
|
||||
expect(easy.header_list).to be_a(FFI::Pointer)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,177 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Http::Custom do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
let(:url) { "http://localhost:3001/" }
|
||||
let(:params) { nil }
|
||||
let(:form) { nil }
|
||||
let(:custom) { described_class.new("PURGE", url, {:params => params, :body => form}) }
|
||||
|
||||
describe "#setup" do
|
||||
context "when nothing" do
|
||||
it "sets url" do
|
||||
custom.setup(easy)
|
||||
expect(easy.url).to eq(url)
|
||||
end
|
||||
|
||||
it "makes a custom request" do
|
||||
custom.setup(easy)
|
||||
easy.perform
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"')
|
||||
end
|
||||
end
|
||||
|
||||
context "when params" do
|
||||
let(:params) { {:a => "1&"} }
|
||||
|
||||
it "attaches escaped to url" do
|
||||
custom.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a=1%26")
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
custom.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "is a custom verb" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"')
|
||||
end
|
||||
|
||||
it "does not use application/x-www-form-urlencoded content type" do
|
||||
expect(easy.response_body).to_not include('"CONTENT_TYPE":"application/x-www-form-urlencoded"')
|
||||
end
|
||||
|
||||
it "requests parameterized url" do
|
||||
expect(easy.response_body).to include('"REQUEST_URI":"http://localhost:3001/?a=1%26"')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when body" do
|
||||
context "when multipart" do
|
||||
let(:form) { {:a => File.open(__FILE__, 'r')} }
|
||||
|
||||
it "sets httppost" do
|
||||
expect(easy).to receive(:httppost=)
|
||||
custom.setup(easy)
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
custom.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "is a custom verb" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"')
|
||||
end
|
||||
|
||||
it "uses multipart/form-data content type" do
|
||||
expect(easy.response_body).to include('"CONTENT_TYPE":"multipart/form-data')
|
||||
end
|
||||
|
||||
it "submits a body" do
|
||||
expect(easy.response_body).to match('"body":".+"')
|
||||
end
|
||||
|
||||
it "submits the data" do
|
||||
expect(easy.response_body).to include('"filename":"custom_spec.rb"')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when not multipart" do
|
||||
let(:form) { {:a => "1&b=2"} }
|
||||
let(:encoded) { "a=1%26b%3D2" }
|
||||
|
||||
it "sets escaped copypostfields" do
|
||||
expect(easy).to receive(:copypostfields=).with(encoded)
|
||||
custom.setup(easy)
|
||||
end
|
||||
|
||||
it "sets postfieldsize" do
|
||||
expect(easy).to receive(:postfieldsize=).with(encoded.bytesize)
|
||||
custom.setup(easy)
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
custom.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "is a custom verb" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"PURGE"')
|
||||
end
|
||||
|
||||
it "uses multipart/form-data content type" do
|
||||
expect(easy.response_body).to include('"CONTENT_TYPE":"application/x-www-form-urlencoded')
|
||||
end
|
||||
|
||||
it "submits a body" do
|
||||
expect(easy.response_body).to match('"body":"a=1%26b%3D2"')
|
||||
end
|
||||
|
||||
it "submits the data" do
|
||||
expect(easy.response_body).to include('"rack.request.form_hash":{"a":"1&b=2"}')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when string" do
|
||||
let(:form) { "{a: 1}" }
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
custom.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "sends string" do
|
||||
expect(easy.response_body).to include('"body":"{a: 1}"')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when params and body" do
|
||||
let(:form) { {:a => "1"} }
|
||||
let(:params) { {:b => "2"} }
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
custom.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "url contains params" do
|
||||
expect(easy.response_body).to include('"REQUEST_URI":"http://localhost:3001/?b=2"')
|
||||
end
|
||||
|
||||
it "body contains form" do
|
||||
expect(easy.response_body).to include('"body":"a=1"')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Http::Delete do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
let(:url) { "http://localhost:3001/" }
|
||||
let(:params) { nil }
|
||||
let(:form) { nil }
|
||||
let(:delete) { described_class.new(url, {:params => params, :body => form}) }
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
delete.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "makes a delete request" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"DELETE"')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,126 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Http::Get do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
let(:url) { "http://localhost:3001/" }
|
||||
let(:params) { nil }
|
||||
let(:form) { nil }
|
||||
let(:options) { {} }
|
||||
let(:get) { described_class.new(url, {:params => params, :body => form}.merge(options)) }
|
||||
|
||||
describe "#setup" do
|
||||
it "sets url" do
|
||||
get.setup(easy)
|
||||
expect(easy.url).to eq(url)
|
||||
end
|
||||
|
||||
context "when body" do
|
||||
let(:form) { { :a => 1 } }
|
||||
|
||||
it "sets customrequest" do
|
||||
expect(easy).to receive(:customrequest=).with("GET")
|
||||
get.setup(easy)
|
||||
end
|
||||
end
|
||||
|
||||
context "when no body" do
|
||||
it "doesn't set customrequest" do
|
||||
expect(easy).to receive(:customrequest=).never
|
||||
get.setup(easy)
|
||||
end
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
get.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
context "when url already contains params" do
|
||||
let(:url) { "http://localhost:3001/?query=here" }
|
||||
let(:params) { {:a => "1&b=2"} }
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "is a get request" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"GET"')
|
||||
end
|
||||
|
||||
it "requests parameterized url" do
|
||||
expect(easy.effective_url).to eq("http://localhost:3001/?query=here&a=1%26b%3D2")
|
||||
end
|
||||
end
|
||||
|
||||
context "when params and no body" do
|
||||
let(:params) { {:a => "1&b=2"} }
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "is a get request" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"GET"')
|
||||
end
|
||||
|
||||
it "requests parameterized url" do
|
||||
expect(easy.effective_url).to eq("http://localhost:3001/?a=1%26b%3D2")
|
||||
end
|
||||
end
|
||||
|
||||
context "when params and body" do
|
||||
let(:params) { {:a => "1&b=2"} }
|
||||
let(:form) { {:b => "2"} }
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "is a get request" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"GET"')
|
||||
end
|
||||
|
||||
it "requests parameterized url" do
|
||||
expect(easy.effective_url).to eq("http://localhost:3001/?a=1%26b%3D2")
|
||||
end
|
||||
end
|
||||
|
||||
context "with :escape" do
|
||||
let(:params) { {:a => "1&b=2"} }
|
||||
|
||||
context 'missing' do
|
||||
it "escapes values" do
|
||||
expect(easy.url).to eq("#{url}?a=1%26b%3D2")
|
||||
end
|
||||
end
|
||||
|
||||
context 'nil' do
|
||||
let(:options) { {:escape => nil} }
|
||||
|
||||
it "escapes values" do
|
||||
expect(easy.url).to eq("#{url}?a=1%26b%3D2")
|
||||
end
|
||||
end
|
||||
|
||||
context 'true' do
|
||||
let(:options) { {:escape => true} }
|
||||
|
||||
it "escapes values" do
|
||||
expect(easy.url).to eq("#{url}?a=1%26b%3D2")
|
||||
end
|
||||
end
|
||||
|
||||
context 'false' do
|
||||
let(:options) { {:escape => false} }
|
||||
|
||||
it "sends raw values" do
|
||||
expect(easy.url).to eq("#{url}?a=1&b=2")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,80 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Http::Head do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
let(:url) { "http://localhost:3001/" }
|
||||
let(:params) { nil }
|
||||
let(:form) { nil }
|
||||
let(:head) { described_class.new(url, {:params => params, :body => form}) }
|
||||
|
||||
describe "#setup" do
|
||||
context "when nothing" do
|
||||
it "sets nobody" do
|
||||
expect(easy).to receive(:nobody=).with(true)
|
||||
head.setup(easy)
|
||||
end
|
||||
|
||||
it "sets url" do
|
||||
head.setup(easy)
|
||||
expect(easy.url).to eq(url)
|
||||
end
|
||||
end
|
||||
|
||||
context "when params" do
|
||||
let(:params) { {:a => "1&b=2"} }
|
||||
|
||||
it "sets nobody" do
|
||||
expect(easy).to receive(:nobody=).with(true)
|
||||
head.setup(easy)
|
||||
end
|
||||
|
||||
it "attaches escaped to url" do
|
||||
head.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a=1%26b%3D2")
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
head.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "has no body" do
|
||||
expect(easy.response_body).to be_empty
|
||||
end
|
||||
|
||||
it "requests parameterized url" do
|
||||
expect(easy.effective_url).to eq("http://localhost:3001/?a=1%26b%3D2")
|
||||
end
|
||||
|
||||
context "when url already contains params" do
|
||||
let(:url) { "http://localhost:3001/?query=here" }
|
||||
|
||||
it "requests parameterized url" do
|
||||
expect(easy.effective_url).to eq("http://localhost:3001/?query=here&a=1%26b%3D2")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when body" do
|
||||
let(:form) { {:a => 1} }
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
head.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Http::Options do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
let(:url) { "http://localhost:3001/" }
|
||||
let(:params) { nil }
|
||||
let(:form) { nil }
|
||||
let(:options) { described_class.new(url, {:params => params, :body => form}) }
|
||||
|
||||
describe "#setup" do
|
||||
it "sets customrequest" do
|
||||
expect(easy).to receive(:customrequest=).with("OPTIONS")
|
||||
options.setup(easy)
|
||||
end
|
||||
|
||||
it "sets url" do
|
||||
options.setup(easy)
|
||||
expect(easy.url).to eq(url)
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
let(:params) { {:a => "1&b=2"} }
|
||||
|
||||
before do
|
||||
options.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "is a options request" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"OPTIONS"')
|
||||
end
|
||||
|
||||
it "requests parameterized url" do
|
||||
expect(easy.effective_url).to eq("http://localhost:3001/?a=1%26b%3D2")
|
||||
end
|
||||
|
||||
context "when url already contains params" do
|
||||
let(:url) { "http://localhost:3001/?query=here" }
|
||||
|
||||
it "requests parameterized url" do
|
||||
expect(easy.effective_url).to eq("http://localhost:3001/?query=here&a=1%26b%3D2")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Http::Patch do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
let(:url) { "http://localhost:3001/" }
|
||||
let(:params) { nil }
|
||||
let(:form) { nil }
|
||||
let(:patch) { described_class.new(url, {:params => params, :body => form}) }
|
||||
|
||||
describe "#setup" do
|
||||
it "sets customrequest" do
|
||||
expect(easy).to receive(:customrequest=).with("PATCH")
|
||||
patch.setup(easy)
|
||||
end
|
||||
|
||||
it "sets url" do
|
||||
patch.setup(easy)
|
||||
expect(easy.url).to eq(url)
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
let(:params) { {:a => "1&b=2"} }
|
||||
|
||||
before do
|
||||
patch.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "is a patch request" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"PATCH"')
|
||||
end
|
||||
|
||||
it "requests parameterized url" do
|
||||
expect(easy.effective_url).to eq("http://localhost:3001/?a=1%26b%3D2")
|
||||
end
|
||||
|
||||
context "when url already contains params" do
|
||||
let(:url) { "http://localhost:3001/?query=here" }
|
||||
|
||||
it "requests parameterized url" do
|
||||
expect(easy.effective_url).to eq("http://localhost:3001/?query=here&a=1%26b%3D2")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,317 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Http::Post do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
let(:url) { "http://localhost:3001/" }
|
||||
let(:params) { nil }
|
||||
let(:form) { nil }
|
||||
let(:options) { Hash.new }
|
||||
let(:post) { described_class.new(url, options.merge({:params => params, :body => form})) }
|
||||
|
||||
describe "#setup" do
|
||||
context "when nothing" do
|
||||
it "sets url" do
|
||||
post.setup(easy)
|
||||
expect(easy.url).to eq(url)
|
||||
end
|
||||
|
||||
it "sets postfield_size" do
|
||||
expect(easy).to receive(:postfieldsize=).with(0)
|
||||
post.setup(easy)
|
||||
end
|
||||
|
||||
it "sets copy_postfields" do
|
||||
expect(easy).to receive(:copypostfields=).with("")
|
||||
post.setup(easy)
|
||||
end
|
||||
|
||||
it "makes a post request" do
|
||||
post.setup(easy)
|
||||
easy.perform
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"POST"')
|
||||
end
|
||||
end
|
||||
|
||||
context "when params" do
|
||||
let(:params) { {:a => "1&"} }
|
||||
|
||||
it "attaches escaped to url" do
|
||||
post.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a=1%26")
|
||||
end
|
||||
|
||||
context "with arrays" do
|
||||
let(:params) { {:a => %w( foo bar )} }
|
||||
|
||||
context "by default" do
|
||||
it "encodes them with indexes" do
|
||||
post.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a%5B0%5D=foo&a%5B1%5D=bar")
|
||||
end
|
||||
end
|
||||
|
||||
context "when params_encoding is :rack" do
|
||||
let(:options) { {:params_encoding => :rack} }
|
||||
it "encodes them without indexes" do
|
||||
post.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a%5B%5D=foo&a%5B%5D=bar")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with :escape" do
|
||||
context 'missing' do
|
||||
it "escapes values" do
|
||||
post.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a=1%26")
|
||||
end
|
||||
end
|
||||
|
||||
context 'nil' do
|
||||
let(:options) { {:escape => nil} }
|
||||
|
||||
it "escapes values" do
|
||||
post.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a=1%26")
|
||||
end
|
||||
end
|
||||
|
||||
context 'true' do
|
||||
let(:options) { {:escape => true} }
|
||||
|
||||
it "escapes values" do
|
||||
post.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a=1%26")
|
||||
end
|
||||
end
|
||||
|
||||
context 'false' do
|
||||
let(:options) { {:escape => false} }
|
||||
|
||||
it "sends raw values" do
|
||||
post.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a=1&")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "sets postfieldsize" do
|
||||
expect(easy).to receive(:postfieldsize=).with(0)
|
||||
post.setup(easy)
|
||||
end
|
||||
|
||||
it "sets copypostfields" do
|
||||
expect(easy).to receive(:copypostfields=).with("")
|
||||
post.setup(easy)
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
let(:postredir) { nil }
|
||||
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
post.setup(easy)
|
||||
easy.postredir = postredir
|
||||
easy.followlocation = true
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "is a post" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"POST"')
|
||||
end
|
||||
|
||||
it "uses application/x-www-form-urlencoded content type" do
|
||||
expect(easy.response_body).to include('"CONTENT_TYPE":"application/x-www-form-urlencoded"')
|
||||
end
|
||||
|
||||
it "requests parameterized url" do
|
||||
expect(easy.response_body).to include('"REQUEST_URI":"http://localhost:3001/?a=1%26"')
|
||||
end
|
||||
|
||||
context "when redirection" do
|
||||
let(:url) { "localhost:3001/redirect" }
|
||||
|
||||
context "when no postredirs" do
|
||||
it "is a get" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"GET"')
|
||||
end
|
||||
end
|
||||
|
||||
unless ENV['TRAVIS']
|
||||
context "when postredirs" do
|
||||
let(:postredir) { :post_all }
|
||||
|
||||
it "is a post" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"POST"')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when body" do
|
||||
context "when multipart" do
|
||||
let(:form) { {:a => File.open(__FILE__, 'r')} }
|
||||
|
||||
it "sets httppost" do
|
||||
expect(easy).to receive(:httppost=)
|
||||
post.setup(easy)
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
post.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "is a post" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"POST"')
|
||||
end
|
||||
|
||||
it "uses multipart/form-data content type" do
|
||||
expect(easy.response_body).to include('"CONTENT_TYPE":"multipart/form-data')
|
||||
end
|
||||
|
||||
it "submits a body" do
|
||||
expect(easy.response_body).to match('"body":".+"')
|
||||
end
|
||||
|
||||
it "submits the data" do
|
||||
expect(easy.response_body).to include('"filename":"post_spec.rb"')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when not multipart" do
|
||||
let(:form) { {:a => "1&b=2"} }
|
||||
let(:encoded) { "a=1%26b%3D2" }
|
||||
|
||||
it "sets escaped copypostfields" do
|
||||
expect(easy).to receive(:copypostfields=).with(encoded)
|
||||
post.setup(easy)
|
||||
end
|
||||
|
||||
it "sets postfieldsize" do
|
||||
expect(easy).to receive(:postfieldsize=).with(encoded.bytesize)
|
||||
post.setup(easy)
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
post.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "is a post" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"POST"')
|
||||
end
|
||||
|
||||
it "uses multipart/form-data content type" do
|
||||
expect(easy.response_body).to include('"CONTENT_TYPE":"application/x-www-form-urlencoded')
|
||||
end
|
||||
|
||||
it "submits a body" do
|
||||
expect(easy.response_body).to match('"body":"a=1%26b%3D2"')
|
||||
end
|
||||
|
||||
it "submits the data" do
|
||||
expect(easy.response_body).to include('"rack.request.form_hash":{"a":"1&b=2"}')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when string" do
|
||||
let(:form) { "{a: 1}" }
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
post.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "sends string" do
|
||||
expect(easy.response_body).to include('"body":"{a: 1}"')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when binary with null bytes" do
|
||||
let(:form) { [1, 0, 1].pack('c*') }
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
post.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "sends binary data" do
|
||||
expect(easy.response_body).to include('"body":"\\u0001\\u0000\\u0001"')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when arrays" do
|
||||
let(:form) { {:a => %w( foo bar )} }
|
||||
|
||||
context "by default" do
|
||||
it "sets copypostfields with indexed, escaped representation" do
|
||||
expect(easy).to receive(:copypostfields=).with('a%5B0%5D=foo&a%5B1%5D=bar')
|
||||
post.setup(easy)
|
||||
end
|
||||
end
|
||||
|
||||
context "when params_encoding is :rack" do
|
||||
let(:options) { {:params_encoding => :rack} }
|
||||
|
||||
it "sets copypostfields with non-indexed, escaped representation" do
|
||||
expect(easy).to receive(:copypostfields=).with('a%5B%5D=foo&a%5B%5D=bar')
|
||||
post.setup(easy)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when params and body" do
|
||||
let(:form) { {:a => "1"} }
|
||||
let(:params) { {:b => "2"} }
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
post.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "url contains params" do
|
||||
expect(easy.response_body).to include('"REQUEST_URI":"http://localhost:3001/?b=2"')
|
||||
end
|
||||
|
||||
it "body contains form" do
|
||||
expect(easy.response_body).to include('"body":"a=1"')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,168 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Http::Put do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
let(:url) { "http://localhost:3001/" }
|
||||
let(:params) { nil }
|
||||
let(:form) { nil }
|
||||
let(:options) { Hash.new }
|
||||
let(:put) { described_class.new(url, options.merge({:params => params, :body => form})) }
|
||||
|
||||
describe "#setup" do
|
||||
context "when nothing" do
|
||||
it "sets url" do
|
||||
put.setup(easy)
|
||||
expect(easy.url).to eq(url)
|
||||
end
|
||||
|
||||
it "sets upload" do
|
||||
expect(easy).to receive(:upload=).with(true)
|
||||
put.setup(easy)
|
||||
end
|
||||
|
||||
it "sets infilesize" do
|
||||
expect(easy).to receive(:infilesize=).with(0)
|
||||
put.setup(easy)
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
it "makes a put request" do
|
||||
put.setup(easy)
|
||||
easy.perform
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"PUT"')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when params" do
|
||||
let(:params) { {:a => "1&"} }
|
||||
|
||||
it "attaches escaped to url" do
|
||||
put.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a=1%26")
|
||||
end
|
||||
|
||||
context "with arrays" do
|
||||
let(:params) { {:a => %w( foo bar )} }
|
||||
|
||||
context "by default" do
|
||||
it "encodes them with indexes" do
|
||||
put.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a%5B0%5D=foo&a%5B1%5D=bar")
|
||||
end
|
||||
end
|
||||
|
||||
context "when params_encoding is :rack" do
|
||||
let(:options) { {:params_encoding => :rack} }
|
||||
it "encodes them without indexes" do
|
||||
put.setup(easy)
|
||||
expect(easy.url).to eq("#{url}?a%5B%5D=foo&a%5B%5D=bar")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "sets upload" do
|
||||
expect(easy).to receive(:upload=).with(true)
|
||||
put.setup(easy)
|
||||
end
|
||||
|
||||
it "sets infilesize" do
|
||||
expect(easy).to receive(:infilesize=).with(0)
|
||||
put.setup(easy)
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
before do
|
||||
put.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "makes a put request" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"PUT"')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when body" do
|
||||
let(:form) { {:a => "1&b=2"} }
|
||||
|
||||
it "sets infilesize" do
|
||||
expect(easy).to receive(:infilesize=).with(11)
|
||||
put.setup(easy)
|
||||
end
|
||||
|
||||
it "sets readfunction" do
|
||||
expect(easy).to receive(:readfunction)
|
||||
put.setup(easy)
|
||||
end
|
||||
|
||||
it "sets upload" do
|
||||
expect(easy).to receive(:upload=).with(true)
|
||||
put.setup(easy)
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
context "sending string body" do
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
put.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "makes a put request" do
|
||||
expect(easy.response_body).to include('"REQUEST_METHOD":"PUT"')
|
||||
end
|
||||
|
||||
it "submits a body" do
|
||||
expect(easy.response_body).to include('"body":"a=1%26b%3D2"')
|
||||
end
|
||||
end
|
||||
|
||||
context "when injecting a file as body" do
|
||||
let(:file) { File.open(__FILE__) }
|
||||
let(:easy) do
|
||||
e = Ethon::Easy.new(:url => url, :upload => true)
|
||||
e.set_read_callback(file)
|
||||
e.infilesize = File.size(file.path)
|
||||
e
|
||||
end
|
||||
|
||||
before do
|
||||
easy.headers = { 'Expect' => '' }
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "submits file" do
|
||||
expect(easy.response_body).to include("injecting")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when arrays" do
|
||||
let(:form) { {:a => %w( foo bar )} }
|
||||
|
||||
before do
|
||||
put.setup(easy)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
context "by default" do
|
||||
it "submits an indexed, escaped representation" do
|
||||
expect(easy.response_body).to include('"body":"a%5B0%5D=foo&a%5B1%5D=bar"')
|
||||
end
|
||||
end
|
||||
|
||||
context "when params_encoding is :rack" do
|
||||
let(:options) { {:params_encoding => :rack} }
|
||||
|
||||
it "submits an non-indexed, escaped representation" do
|
||||
expect(easy.response_body).to include('"body":"a%5B%5D=foo&a%5B%5D=bar"')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when params and body"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Http do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
|
||||
describe "#http_request" do
|
||||
let(:url) { "http://localhost:3001/" }
|
||||
let(:action_name) { :get }
|
||||
let(:options) { {} }
|
||||
|
||||
let(:get) { double(:setup) }
|
||||
let(:get_class) { Ethon::Easy::Http::Get }
|
||||
|
||||
it "instanciates action" do
|
||||
expect(get).to receive(:setup)
|
||||
expect(get_class).to receive(:new).and_return(get)
|
||||
easy.http_request(url, action_name, options)
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
[ :get, :post, :put, :delete, :head, :patch, :options ].map do |action|
|
||||
it "returns ok" do
|
||||
easy.http_request(url, action, options)
|
||||
easy.perform
|
||||
expect(easy.return_code).to be(:ok)
|
||||
end
|
||||
|
||||
unless action == :head
|
||||
it "makes a #{action.to_s.upcase} request" do
|
||||
easy.http_request(url, action, options)
|
||||
easy.perform
|
||||
expect(easy.response_body).to include("\"REQUEST_METHOD\":\"#{action.to_s.upcase}\"")
|
||||
end
|
||||
|
||||
it "streams the response body from the #{action.to_s.upcase} request" do
|
||||
bytes_read = 0
|
||||
easy.on_body { |chunk, response| bytes_read += chunk.bytesize }
|
||||
easy.http_request(url, action, options)
|
||||
easy.perform
|
||||
content_length = ((easy.response_headers =~ /Content-Length: (\d+)/) && $1.to_i)
|
||||
expect(bytes_read).to eq(content_length)
|
||||
expect(easy.response_body).to eq("")
|
||||
end
|
||||
|
||||
it "notifies when headers are ready" do
|
||||
headers = []
|
||||
easy.on_headers { |r| headers << r.response_headers }
|
||||
easy.http_request(url, action, options)
|
||||
easy.perform
|
||||
expect(headers).to eq([easy.response_headers])
|
||||
expect(headers.first).to match(/Content-Length: (\d+)/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "makes requests with custom HTTP verbs" do
|
||||
easy.http_request(url, :purge, options)
|
||||
easy.perform
|
||||
expect(easy.response_body).to include(%{"REQUEST_METHOD":"PURGE"})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,126 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Informations do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
|
||||
before do
|
||||
easy.url = "http://localhost:3001"
|
||||
easy.perform
|
||||
end
|
||||
|
||||
describe "#httpauth_avail" do
|
||||
it "returns" do
|
||||
expect(easy.httpauth_avail).to be
|
||||
end
|
||||
end
|
||||
|
||||
describe "#total_time" do
|
||||
it "returns float" do
|
||||
expect(easy.total_time).to be_a(Float)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#starttransfer_time" do
|
||||
it "returns float" do
|
||||
expect(easy.starttransfer_time).to be_a(Float)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#appconnect_time" do
|
||||
it "returns float" do
|
||||
expect(easy.appconnect_time).to be_a(Float)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#pretransfer_time" do
|
||||
it "returns float" do
|
||||
expect(easy.pretransfer_time).to be_a(Float)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#connect_time" do
|
||||
it "returns float" do
|
||||
expect(easy.connect_time).to be_a(Float)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#namelookup_time" do
|
||||
it "returns float" do
|
||||
expect(easy.namelookup_time).to be_a(Float)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#redirect_time" do
|
||||
it "returns float" do
|
||||
expect(easy.redirect_time).to be_a(Float)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#effective_url" do
|
||||
it "returns url" do
|
||||
expect(easy.effective_url).to match(/^http:\/\/localhost:3001\/?/)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#primary_ip" do
|
||||
it "returns localhost" do
|
||||
expect(easy.primary_ip).to match(/::1|127\.0\.0\.1/)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#response_code" do
|
||||
it "returns 200" do
|
||||
expect(easy.response_code).to eq(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#redirect_count" do
|
||||
it "returns 0" do
|
||||
expect(easy.redirect_count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#redirect_url" do
|
||||
it "returns nil as there is no redirect" do
|
||||
expect(easy.redirect_url).to be(nil)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#request_size" do
|
||||
it "returns 53" do
|
||||
expect(easy.request_size).to eq(53)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#supports_zlib?" do
|
||||
it "returns true" do
|
||||
expect(Kernel).to receive(:warn)
|
||||
expect(easy.supports_zlib?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe "#size_upload" do
|
||||
it "returns float" do
|
||||
expect(easy.size_upload).to be_a(Float)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#size_download" do
|
||||
it "returns float" do
|
||||
expect(easy.size_download).to be_a(Float)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#speed_upload" do
|
||||
it "returns float" do
|
||||
expect(easy.speed_upload).to be_a(Float)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#speed_download" do
|
||||
it "returns float" do
|
||||
expect(easy.speed_download).to be_a(Float)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Mirror do
|
||||
let(:options) { nil }
|
||||
let(:mirror) { described_class.new(options) }
|
||||
|
||||
describe "::INFORMATIONS_TO_LOG" do
|
||||
[
|
||||
:return_code, :response_code, :response_body, :response_headers,
|
||||
:total_time, :starttransfer_time, :appconnect_time,
|
||||
:pretransfer_time, :connect_time, :namelookup_time, :redirect_time,
|
||||
:size_upload, :size_download, :speed_upload, :speed_upload,
|
||||
:effective_url, :primary_ip, :redirect_count, :redirect_url, :debug_info
|
||||
].each do |name|
|
||||
it "contains #{name}" do
|
||||
expect(described_class::INFORMATIONS_TO_MIRROR).to include(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#to_hash" do
|
||||
let(:options) { {:return_code => 1} }
|
||||
|
||||
it "returns mirror as hash" do
|
||||
expect(mirror.to_hash).to eq(options)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#log_informations" do
|
||||
let(:options) { {:return_code => 1} }
|
||||
|
||||
it "returns hash" do
|
||||
expect(mirror.log_informations).to be_a(Hash)
|
||||
end
|
||||
|
||||
it "only calls methods that exist" do
|
||||
described_class::INFORMATIONS_TO_LOG.each do |method_name|
|
||||
expect(mirror.respond_to? method_name).to eql(true)
|
||||
end
|
||||
end
|
||||
|
||||
it "includes return code" do
|
||||
expect(mirror.log_informations).to include(options)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,271 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Operations do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
|
||||
describe "#handle" do
|
||||
it "returns a pointer" do
|
||||
expect(easy.handle).to be_a(FFI::Pointer)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "#perform" do
|
||||
let(:url) { nil }
|
||||
let(:timeout) { nil }
|
||||
let(:connect_timeout) { nil }
|
||||
let(:follow_location) { nil }
|
||||
let(:max_redirs) { nil }
|
||||
let(:user_pwd) { nil }
|
||||
let(:http_auth) { nil }
|
||||
let(:headers) { nil }
|
||||
let(:protocols) { nil }
|
||||
let(:redir_protocols) { nil }
|
||||
let(:username) { nil }
|
||||
let(:password) { nil }
|
||||
|
||||
before do
|
||||
Ethon.logger.level = Logger::DEBUG
|
||||
easy.url = url
|
||||
easy.timeout = timeout
|
||||
easy.connecttimeout = connect_timeout
|
||||
easy.followlocation = follow_location
|
||||
easy.maxredirs = max_redirs
|
||||
easy.httpauth = http_auth
|
||||
easy.headers = headers
|
||||
easy.protocols = protocols
|
||||
easy.redir_protocols = redir_protocols
|
||||
|
||||
if user_pwd
|
||||
easy.userpwd = user_pwd
|
||||
else
|
||||
easy.username = username
|
||||
easy.password = password
|
||||
end
|
||||
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "calls Curl.easy_perform" do
|
||||
expect(Ethon::Curl).to receive(:easy_perform)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "calls Curl.easy_cleanup" do
|
||||
expect_any_instance_of(FFI::AutoPointer).to receive(:free)
|
||||
easy.cleanup
|
||||
end
|
||||
|
||||
it "logs" do
|
||||
expect(Ethon.logger).to receive(:debug)
|
||||
easy.perform
|
||||
end
|
||||
|
||||
it "doesn't log after completing because completing could reset" do
|
||||
easy.on_complete{ expect(Ethon.logger).to receive(:debug).never }
|
||||
easy.perform
|
||||
end
|
||||
|
||||
context "when url" do
|
||||
let(:url) { "http://localhost:3001/" }
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to eq(:ok)
|
||||
end
|
||||
|
||||
it "sets response body" do
|
||||
expect(easy.response_body).to be
|
||||
end
|
||||
|
||||
it "sets response headers" do
|
||||
expect(easy.response_headers).to be
|
||||
end
|
||||
|
||||
context "when request timed out" do
|
||||
let(:url) { "http://localhost:3001/?delay=1" }
|
||||
let(:timeout) { 1 }
|
||||
|
||||
it "returns operation_timedout" do
|
||||
expect(easy.return_code).to eq(:operation_timedout)
|
||||
end
|
||||
end
|
||||
|
||||
context "when connection timed out" do
|
||||
let(:url) { "http://localhost:3009" }
|
||||
let(:connect_timeout) { 1 }
|
||||
|
||||
it "returns couldnt_connect" do
|
||||
expect(easy.return_code).to eq(:couldnt_connect)
|
||||
end
|
||||
end
|
||||
|
||||
context "when no follow location" do
|
||||
let(:url) { "http://localhost:3001/redirect" }
|
||||
let(:follow_location) { false }
|
||||
|
||||
it "doesn't follow" do
|
||||
expect(easy.response_code).to eq(302)
|
||||
expect(easy.redirect_url).to eq("http://localhost:3001/")
|
||||
end
|
||||
end
|
||||
|
||||
context "when follow location" do
|
||||
let(:url) { "http://localhost:3001/redirect" }
|
||||
let(:follow_location) { true }
|
||||
|
||||
it "follows" do
|
||||
expect(easy.response_code).to eq(200)
|
||||
expect(easy.redirect_url).to eq(nil)
|
||||
end
|
||||
|
||||
context "when infinite redirect loop" do
|
||||
let(:url) { "http://localhost:3001/bad_redirect" }
|
||||
let(:max_redirs) { 5 }
|
||||
|
||||
context "when max redirect set" do
|
||||
it "follows only x times" do
|
||||
expect(easy.response_code).to eq(302)
|
||||
expect(easy.redirect_url).to eq("http://localhost:3001/bad_redirect")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when user agent" do
|
||||
let(:headers) { { 'User-Agent' => 'Ethon' } }
|
||||
|
||||
it "sets" do
|
||||
expect(easy.response_body).to include('"HTTP_USER_AGENT":"Ethon"')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when auth url" do
|
||||
before { easy.url = url }
|
||||
|
||||
context "when basic auth" do
|
||||
let(:url) { "http://localhost:3001/auth_basic/username/password" }
|
||||
|
||||
context "when no user_pwd" do
|
||||
it "returns 401" do
|
||||
expect(easy.response_code).to eq(401)
|
||||
end
|
||||
end
|
||||
|
||||
context "when invalid user_pwd" do
|
||||
let(:user_pwd) { "invalid:invalid" }
|
||||
|
||||
it "returns 401" do
|
||||
expect(easy.response_code).to eq(401)
|
||||
end
|
||||
end
|
||||
|
||||
context "when valid user_pwd" do
|
||||
let(:user_pwd) { "username:password" }
|
||||
|
||||
it "returns 200" do
|
||||
expect(easy.response_code).to eq(200)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user and password" do
|
||||
let(:username) { "username" }
|
||||
let(:password) { "password" }
|
||||
|
||||
it "returns 200" do
|
||||
expect(easy.response_code).to eq(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when ntlm" do
|
||||
let(:url) { "http://localhost:3001/auth_ntlm" }
|
||||
let(:http_auth) { :ntlm }
|
||||
|
||||
context "when no user_pwd" do
|
||||
it "returns 401" do
|
||||
expect(easy.response_code).to eq(401)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user_pwd" do
|
||||
let(:user_pwd) { "username:password" }
|
||||
|
||||
it "returns 200" do
|
||||
expect(easy.response_code).to eq(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when protocols" do
|
||||
context "when asking for a allowed url" do
|
||||
let(:url) { "http://localhost:3001" }
|
||||
let(:protocols) { :http }
|
||||
|
||||
it "returns ok" do
|
||||
expect(easy.return_code).to be(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context "when asking for a not allowed url" do
|
||||
let(:url) { "http://localhost:3001" }
|
||||
let(:protocols) { :https }
|
||||
|
||||
it "returns unsupported_protocol" do
|
||||
expect(easy.return_code).to be(:unsupported_protocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when multiple protocols" do
|
||||
context "when asking for a allowed url" do
|
||||
let(:protocols) { [:http, :https] }
|
||||
|
||||
context "when http" do
|
||||
let(:url) { "http://localhost:3001" }
|
||||
|
||||
it "returns ok for http" do
|
||||
expect(easy.return_code).to be(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context "when https" do
|
||||
let(:url) { "https://localhost:3001" }
|
||||
|
||||
it "returns ssl_connect_error for https" do
|
||||
expect(easy.return_code).to be(:ssl_connect_error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when asking for a not allowed url" do
|
||||
let(:url) { "ssh://localhost" }
|
||||
let(:protocols) { [:https, :http] }
|
||||
|
||||
it "returns unsupported_protocol" do
|
||||
expect(easy.return_code).to be(:unsupported_protocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when redir_protocols" do
|
||||
context "when redirecting to a not allowed url" do
|
||||
let(:url) { "http://localhost:3001/redirect" }
|
||||
let(:follow_location) { true }
|
||||
let(:redir_protocols) { :https }
|
||||
|
||||
it "returns unsupported_protocol" do
|
||||
expect(easy.return_code).to be(:unsupported_protocol)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when no url" do
|
||||
it "returns url_malformat" do
|
||||
expect(easy.perform).to eq(:url_malformat)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,193 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Options do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
|
||||
[
|
||||
:accept_encoding, :cainfo, :capath, :connecttimeout, :connecttimeout_ms, :cookie,
|
||||
:cookiejar, :cookiefile, :copypostfields, :customrequest, :dns_cache_timeout,
|
||||
:followlocation, :forbid_reuse, :http_version, :httpauth, :httpget, :httppost,
|
||||
:infilesize, :interface, :keypasswd, :maxredirs, :nobody, :nosignal,
|
||||
:postfieldsize, :postredir, :protocols, :proxy, :proxyauth, :proxyport, :proxytype,
|
||||
:proxyuserpwd, :readdata, :readfunction, :redir_protocols, :ssl_verifyhost,
|
||||
:ssl_verifypeer, :sslcert, :sslcerttype, :sslkey, :sslkeytype, :sslversion,
|
||||
:timeout, :timeout_ms, :unrestricted_auth, :upload, :url, :useragent,
|
||||
:userpwd, :verbose, :pipewait, :dns_shuffle_addresses, :path_as_is
|
||||
].each do |name|
|
||||
describe "#{name}=" do
|
||||
it "responds_to" do
|
||||
expect(easy).to respond_to("#{name}=")
|
||||
end
|
||||
|
||||
it "sets option" do
|
||||
expect_any_instance_of(Ethon::Easy).to receive(:set_callbacks)
|
||||
expect(Ethon::Curl).to receive(:set_option).with(name, anything, anything)
|
||||
value = case name
|
||||
when :http_version
|
||||
:httpv1_0
|
||||
when :httpauth
|
||||
:basic
|
||||
when :protocols, :redir_protocols
|
||||
:http
|
||||
when :postredir
|
||||
:post_301
|
||||
when :proxytype
|
||||
:http
|
||||
when :sslversion
|
||||
:default
|
||||
when :httppost
|
||||
FFI::Pointer::NULL
|
||||
else
|
||||
1
|
||||
end
|
||||
easy.method("#{name}=").call(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#escape?' do
|
||||
context 'by default' do
|
||||
it 'returns true' do
|
||||
expect(easy.escape?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when #escape=nil' do
|
||||
it 'returns true' do
|
||||
easy.escape = nil
|
||||
expect(easy.escape?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when #escape=true' do
|
||||
it 'returns true' do
|
||||
easy.escape = true
|
||||
expect(easy.escape?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when #escape=false' do
|
||||
it 'returns true' do
|
||||
easy.escape = false
|
||||
expect(easy.escape?).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#multipart?' do
|
||||
context 'by default' do
|
||||
it 'returns false' do
|
||||
expect(easy.multipart?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when #multipart=nil' do
|
||||
it 'returns false' do
|
||||
easy.multipart = nil
|
||||
expect(easy.multipart?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'when #multipart=true' do
|
||||
it 'returns true' do
|
||||
easy.multipart = true
|
||||
expect(easy.multipart?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'when #multipart=false' do
|
||||
it 'returns false' do
|
||||
easy.multipart = false
|
||||
expect(easy.multipart?).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#httppost=" do
|
||||
it "raises unless given a FFI::Pointer" do
|
||||
expect{ easy.httppost = 1 }.to raise_error(Ethon::Errors::InvalidValue)
|
||||
end
|
||||
end
|
||||
|
||||
context "when requesting" do
|
||||
let(:url) { "localhost:3001" }
|
||||
let(:timeout) { nil }
|
||||
let(:timeout_ms) { nil }
|
||||
let(:connecttimeout) { nil }
|
||||
let(:connecttimeout_ms) { nil }
|
||||
let(:userpwd) { nil }
|
||||
|
||||
before do
|
||||
easy.url = url
|
||||
easy.timeout = timeout
|
||||
easy.timeout_ms = timeout_ms
|
||||
easy.connecttimeout = connecttimeout
|
||||
easy.connecttimeout_ms = connecttimeout_ms
|
||||
easy.userpwd = userpwd
|
||||
easy.perform
|
||||
end
|
||||
|
||||
context "when userpwd" do
|
||||
context "when contains /" do
|
||||
let(:url) { "localhost:3001/auth_basic/test/te%2Fst" }
|
||||
let(:userpwd) { "test:te/st" }
|
||||
|
||||
it "works" do
|
||||
expect(easy.response_code).to eq(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when timeout" do
|
||||
let(:timeout) { 1 }
|
||||
|
||||
context "when request takes longer" do
|
||||
let(:url) { "localhost:3001?delay=2" }
|
||||
|
||||
it "times out" do
|
||||
expect(easy.return_code).to eq(:operation_timedout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when connecttimeout" do
|
||||
let(:connecttimeout) { 1 }
|
||||
|
||||
context "when cannot connect" do
|
||||
let(:url) { "localhost:3002" }
|
||||
|
||||
it "times out" do
|
||||
expect(easy.return_code).to eq(:couldnt_connect)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if Ethon::Easy.supports_timeout_ms?
|
||||
context "when timeout_ms" do
|
||||
let(:timeout_ms) { 100 }
|
||||
|
||||
context "when request takes longer" do
|
||||
let(:url) { "localhost:3001?delay=1" }
|
||||
|
||||
it "times out" do
|
||||
expect(easy.return_code).to eq(:operation_timedout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when connecttimeout_ms" do
|
||||
let(:connecttimeout_ms) { 100 }
|
||||
|
||||
context "when cannot connect" do
|
||||
let(:url) { "localhost:3002" }
|
||||
|
||||
it "times out" do
|
||||
# this can either lead to a timeout or couldnt connect depending on which happens first
|
||||
expect([:couldnt_connect, :operation_timedout]).to include(easy.return_code)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,235 @@
|
||||
# encoding: utf-8
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Queryable do
|
||||
let(:hash) { {} }
|
||||
let!(:easy) { Ethon::Easy.new }
|
||||
let(:params) { Ethon::Easy::Params.new(easy, hash) }
|
||||
|
||||
describe "#to_s" do
|
||||
context "when query_pairs empty" do
|
||||
before { params.instance_variable_set(:@query_pairs, []) }
|
||||
|
||||
it "returns empty string" do
|
||||
expect(params.to_s).to eq("")
|
||||
end
|
||||
end
|
||||
|
||||
context "when query_pairs not empty" do
|
||||
context "when escape" do
|
||||
before do
|
||||
params.escape = true
|
||||
end
|
||||
|
||||
{
|
||||
'!' => '%21', '*' => '%2A', "'" => '%27', '(' => '%28',
|
||||
')' => '%29', ';' => '%3B', ':' => '%3A', '@' => '%40',
|
||||
'&' => '%26', '=' => '%3D', '+' => '%2B', '$' => '%24',
|
||||
',' => '%2C', '/' => '%2F', '?' => '%3F', '#' => '%23',
|
||||
'[' => '%5B', ']' => '%5D',
|
||||
|
||||
'<' => '%3C', '>' => '%3E', '"' => '%22', '{' => '%7B',
|
||||
'}' => '%7D', '|' => '%7C', '\\' => '%5C', '`' => '%60',
|
||||
'^' => '%5E', '%' => '%25', ' ' => '%20', "\0" => '%00',
|
||||
|
||||
'まつもと' => '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8',
|
||||
}.each do |value, percent|
|
||||
it "turns #{value.inspect} into #{percent}" do
|
||||
params.instance_variable_set(:@query_pairs, [[:a, value]])
|
||||
expect(params.to_s).to eq("a=#{percent}")
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
'.' => '%2E', '-' => '%2D', '_' => '%5F', '~' => '%7E',
|
||||
}.each do |value, percent|
|
||||
it "leaves #{value.inspect} instead of turning into #{percent}" do
|
||||
params.instance_variable_set(:@query_pairs, [[:a, value]])
|
||||
expect(params.to_s).to eq("a=#{value}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when no escape" do
|
||||
before { params.instance_variable_set(:@query_pairs, [[:a, 1], [:b, 2]]) }
|
||||
|
||||
it "returns concatenated query string" do
|
||||
expect(params.to_s).to eq("a=1&b=2")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when query_pairs contains a string" do
|
||||
before { params.instance_variable_set(:@query_pairs, ["{a: 1}"]) }
|
||||
|
||||
it "returns correct string" do
|
||||
expect(params.to_s).to eq("{a: 1}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#build_query_pairs" do
|
||||
let(:pairs) { params.method(:build_query_pairs).call(hash) }
|
||||
|
||||
context "when params is empty" do
|
||||
it "returns empty array" do
|
||||
expect(pairs).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context "when params is string" do
|
||||
let(:hash) { "{a: 1}" }
|
||||
|
||||
it "wraps it in an array" do
|
||||
expect(pairs).to eq([hash])
|
||||
end
|
||||
end
|
||||
|
||||
context "when params is simple hash" do
|
||||
let(:hash) { {:a => 1, :b => 2} }
|
||||
|
||||
it "transforms" do
|
||||
expect(pairs).to include([:a, 1])
|
||||
expect(pairs).to include([:b, 2])
|
||||
end
|
||||
end
|
||||
|
||||
context "when params is a nested hash" do
|
||||
let(:hash) { {:a => 1, :b => {:c => 2}} }
|
||||
|
||||
it "transforms" do
|
||||
expect(pairs).to include([:a, 1])
|
||||
expect(pairs).to include(["b[c]", 2])
|
||||
end
|
||||
end
|
||||
|
||||
context "when params contains an array" do
|
||||
let(:hash) { {:a => 1, :b => [2, 3]} }
|
||||
|
||||
context "by default" do
|
||||
it "transforms" do
|
||||
expect(pairs).to include([:a, 1])
|
||||
expect(pairs).to include(["b[0]", 2])
|
||||
expect(pairs).to include(["b[1]", 3])
|
||||
end
|
||||
end
|
||||
|
||||
context "when params_encoding is :rack" do
|
||||
before { params.params_encoding = :rack }
|
||||
it "transforms without indexes" do
|
||||
expect(pairs).to include([:a, 1])
|
||||
expect(pairs).to include(["b[]", 2])
|
||||
expect(pairs).to include(["b[]", 3])
|
||||
end
|
||||
end
|
||||
|
||||
context "when params_encoding is :none" do
|
||||
before { params.params_encoding = :none }
|
||||
it "does no transformation" do
|
||||
expect(pairs).to include([:a, 1])
|
||||
expect(pairs).to include([:b, [2, 3]])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when params contains something nested in an array" do
|
||||
context "when string" do
|
||||
let(:hash) { {:a => {:b => ["hello", "world"]}} }
|
||||
|
||||
it "transforms" do
|
||||
expect(pairs).to eq([["a[b][0]", "hello"], ["a[b][1]", "world"]])
|
||||
end
|
||||
end
|
||||
|
||||
context "when hash" do
|
||||
let(:hash) { {:a => {:b => [{:c =>1}, {:d => 2}]}} }
|
||||
|
||||
it "transforms" do
|
||||
expect(pairs).to eq([["a[b][0][c]", 1], ["a[b][1][d]", 2]])
|
||||
end
|
||||
end
|
||||
|
||||
context "when file" do
|
||||
let(:file) { File.open("spec/spec_helper.rb") }
|
||||
let(:file_info) { params.method(:file_info).call(file) }
|
||||
let(:hash) { {:a => {:b => [file]}} }
|
||||
let(:mime_type) { file_info[1] }
|
||||
|
||||
it "transforms" do
|
||||
expect(pairs).to eq([["a[b][0]", file_info]])
|
||||
end
|
||||
|
||||
context "when MIME" do
|
||||
if defined?(MIME)
|
||||
context "when mime type" do
|
||||
it "sets mime type to text" do
|
||||
expect(mime_type).to eq("application/x-ruby")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when no mime type" do
|
||||
let(:file) { Tempfile.new("fubar") }
|
||||
|
||||
it "sets mime type to default application/octet-stream" do
|
||||
expect(mime_type).to eq("application/octet-stream")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when no MIME" do
|
||||
before { hide_const("MIME") }
|
||||
|
||||
it "sets mime type to default application/octet-stream" do
|
||||
expect(mime_type).to eq("application/octet-stream")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context "when params contains file" do
|
||||
let(:file) { Tempfile.new("fubar") }
|
||||
let(:file_info) { params.method(:file_info).call(file) }
|
||||
let(:hash) { {:a => 1, :b => file} }
|
||||
|
||||
it "transforms" do
|
||||
expect(pairs).to include([:a, 1])
|
||||
expect(pairs).to include([:b, file_info])
|
||||
end
|
||||
end
|
||||
|
||||
context "when params key contains a null byte" do
|
||||
let(:hash) { {:a => "1\0" } }
|
||||
|
||||
it "preserves" do
|
||||
expect(pairs).to eq([[:a, "1\0"]])
|
||||
end
|
||||
end
|
||||
|
||||
context "when params value contains a null byte" do
|
||||
let(:hash) { {"a\0" => 1 } }
|
||||
|
||||
it "preserves" do
|
||||
expect(pairs).to eq([["a\0", 1]])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#empty?" do
|
||||
context "when params empty" do
|
||||
it "returns true" do
|
||||
expect(params.empty?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context "when params not empty" do
|
||||
let(:hash) { {:a => 1} }
|
||||
|
||||
it "returns false" do
|
||||
expect(params.empty?).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,152 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::ResponseCallbacks do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
|
||||
[:on_complete, :on_headers, :on_body, :on_progress].each do |callback_type|
|
||||
describe "##{callback_type}" do
|
||||
it "responds" do
|
||||
expect(easy).to respond_to("#{callback_type}")
|
||||
end
|
||||
|
||||
context "when no block given" do
|
||||
it "returns @#{callback_type}" do
|
||||
expect(easy.send("#{callback_type}")).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context "when block given" do
|
||||
it "stores" do
|
||||
easy.send(callback_type) { p 1 }
|
||||
expect(easy.instance_variable_get("@#{callback_type}").size).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "when multiple blocks given" do
|
||||
it "stores" do
|
||||
easy.send(callback_type) { p 1 }
|
||||
easy.send(callback_type) { p 2 }
|
||||
expect(easy.instance_variable_get("@#{callback_type}").size).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#complete" do
|
||||
before do
|
||||
easy.on_complete {|r| String.new(r.url) }
|
||||
end
|
||||
|
||||
it "executes blocks and passes self" do
|
||||
expect(String).to receive(:new).with(easy.url)
|
||||
easy.complete
|
||||
end
|
||||
|
||||
context "when @on_complete nil" do
|
||||
it "doesn't raise" do
|
||||
easy.instance_variable_set(:@on_complete, nil)
|
||||
expect{ easy.complete }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#headers" do
|
||||
before do
|
||||
easy.on_headers {|r| String.new(r.url) }
|
||||
end
|
||||
|
||||
it "executes blocks and passes self" do
|
||||
expect(String).to receive(:new).with(easy.url)
|
||||
easy.headers
|
||||
end
|
||||
|
||||
context "when @on_headers nil" do
|
||||
it "doesn't raise" do
|
||||
easy.instance_variable_set(:@on_headers, nil)
|
||||
expect{ easy.headers }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#progress" do
|
||||
context "when requesting for realz" do
|
||||
it "executes callback" do
|
||||
post = Ethon::Easy::Http::Post.new("http://localhost:3001", {:body => "bar=fu"})
|
||||
post.setup(easy)
|
||||
@called = false
|
||||
@has_dltotal = false
|
||||
@has_ultotal = false
|
||||
easy.on_progress { @called = true }
|
||||
easy.on_progress { |dltotal, _, _, _| @has_dltotal ||= true }
|
||||
easy.on_progress { |_, _, ultotal, _| @has_ultotal ||= true }
|
||||
easy.perform
|
||||
expect(@called).to be true
|
||||
expect(@has_dltotal).to be true
|
||||
expect(@has_ultotal).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context "when pretending" do
|
||||
before do
|
||||
@dltotal = nil
|
||||
@dlnow = nil
|
||||
@ultotal = nil
|
||||
@ulnow = nil
|
||||
easy.on_progress { |dltotal, dlnow, ultotal, ulnow| @dltotal = dltotal ; @dlnow = dlnow; @ultotal = ultotal; @ulnow = ulnow }
|
||||
end
|
||||
|
||||
it "executes blocks and passes dltotal" do
|
||||
easy.progress(1, 2, 3, 4)
|
||||
expect(@dltotal).to eq(1)
|
||||
end
|
||||
|
||||
it "executes blocks and passes dlnow" do
|
||||
easy.progress(1, 2, 3, 4)
|
||||
expect(@dlnow).to eq(2)
|
||||
end
|
||||
|
||||
it "executes blocks and passes ultotal" do
|
||||
easy.progress(1, 2, 3, 4)
|
||||
expect(@ultotal).to eq(3)
|
||||
end
|
||||
|
||||
it "executes blocks and passes ulnow" do
|
||||
easy.progress(1, 2, 3, 4)
|
||||
expect(@ulnow).to eq(4)
|
||||
end
|
||||
|
||||
context "when @on_progress nil" do
|
||||
it "doesn't raise" do
|
||||
easy.instance_variable_set(:@on_progress, nil)
|
||||
expect{ easy.progress(1, 2, 3, 4) }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#body" do
|
||||
before do
|
||||
@chunk = nil
|
||||
@r = nil
|
||||
easy.on_body { |chunk, r| @chunk = chunk ; @r = r }
|
||||
end
|
||||
|
||||
it "executes blocks and passes self" do
|
||||
easy.body("the chunk")
|
||||
expect(@r).to be(easy)
|
||||
end
|
||||
|
||||
it "executes blocks and passes chunk" do
|
||||
easy.body("the chunk")
|
||||
expect(@chunk).to eq("the chunk")
|
||||
end
|
||||
|
||||
context "when @on_body nil" do
|
||||
it "doesn't raise" do
|
||||
easy.instance_variable_set(:@on_body, nil)
|
||||
expect{ easy.body("the chunk") }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy::Util do
|
||||
class Dummy
|
||||
include Ethon::Easy::Util
|
||||
end
|
||||
|
||||
let(:klass) { Dummy.new }
|
||||
|
||||
describe "escape_zero_byte" do
|
||||
context "when value has no zero byte" do
|
||||
let(:value) { "hello world" }
|
||||
|
||||
it "returns same value" do
|
||||
expect(klass.escape_zero_byte(value)).to be(value)
|
||||
end
|
||||
end
|
||||
|
||||
context "when value has zero byte" do
|
||||
let(:value) { "hello \0world" }
|
||||
|
||||
it "returns escaped" do
|
||||
expect(klass.escape_zero_byte(value)).to eq("hello \\0world")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,203 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Easy do
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
|
||||
describe ".new" do
|
||||
it "inits curl" do
|
||||
expect(Ethon::Curl).to receive(:init)
|
||||
easy
|
||||
end
|
||||
|
||||
context "when options are empty" do
|
||||
it "sets only callbacks" do
|
||||
expect_any_instance_of(Ethon::Easy).to receive(:set_callbacks)
|
||||
expect(Ethon::Easy).to receive(:set_option).never
|
||||
easy
|
||||
end
|
||||
end
|
||||
|
||||
context "when options not empty" do
|
||||
context "when followlocation is set" do
|
||||
let(:options) { { :followlocation => true } }
|
||||
let(:easy) { Ethon::Easy.new(options) }
|
||||
|
||||
it "sets followlocation" do
|
||||
expect_any_instance_of(Ethon::Easy).to receive(:set_callbacks)
|
||||
expect(Ethon::Curl).to receive(:set_option).with(:followlocation, true, anything)
|
||||
easy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#set_attributes" do
|
||||
context "when options are empty" do
|
||||
it "sets only callbacks" do
|
||||
expect_any_instance_of(Ethon::Easy).to receive(:set_callbacks)
|
||||
expect(Ethon::Easy).to receive(:set_option).never
|
||||
easy
|
||||
end
|
||||
end
|
||||
|
||||
context "when options aren't empty" do
|
||||
context "when valid key" do
|
||||
it "sets" do
|
||||
expect(easy).to receive(:verbose=).with(true)
|
||||
easy.set_attributes({:verbose => true})
|
||||
end
|
||||
end
|
||||
|
||||
context "when invalid key" do
|
||||
it "raises invalid option error" do
|
||||
expect{ easy.set_attributes({:fubar => 1}) }.to raise_error(Ethon::Errors::InvalidOption)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#reset" do
|
||||
before { easy.url = "www.example.com" }
|
||||
|
||||
it "resets url" do
|
||||
easy.reset
|
||||
expect(easy.url).to be_nil
|
||||
end
|
||||
|
||||
it "resets escape?" do
|
||||
easy.escape = false
|
||||
easy.reset
|
||||
expect(easy.escape?).to be_truthy
|
||||
end
|
||||
|
||||
it "resets hash" do
|
||||
easy.reset
|
||||
expect(easy.instance_variable_get(:@hash)).to be_nil
|
||||
end
|
||||
|
||||
it "resets easy handle" do
|
||||
expect(Ethon::Curl).to receive(:easy_reset)
|
||||
easy.reset
|
||||
end
|
||||
|
||||
it "resets on_complete" do
|
||||
easy.on_complete { p 1 }
|
||||
easy.reset
|
||||
expect(easy.on_complete).to be_empty
|
||||
end
|
||||
|
||||
it "resets on_headers" do
|
||||
easy.on_headers { p 1 }
|
||||
easy.reset
|
||||
expect(easy.on_headers).to be_empty
|
||||
end
|
||||
|
||||
it "resets on_body" do
|
||||
easy.on_body { p 1 }
|
||||
easy.reset
|
||||
expect(easy.on_body).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "#dup" do
|
||||
let!(:easy) do
|
||||
easy = Ethon::Easy.new
|
||||
easy.url = "http://localhost:3001/"
|
||||
easy.on_complete { 'on_complete' }
|
||||
easy.on_headers { 'on_headers' }
|
||||
easy.on_progress { 'on_progress' }
|
||||
easy.response_body = String.new('test_body')
|
||||
easy.response_headers = String.new('test_headers')
|
||||
easy
|
||||
end
|
||||
let!(:e) { easy.dup }
|
||||
|
||||
it "sets a new handle" do
|
||||
expect(e.handle).not_to eq(easy.handle)
|
||||
end
|
||||
|
||||
it "preserves url" do
|
||||
expect(e.url).to eq(easy.url)
|
||||
end
|
||||
|
||||
it "preserves on_complete callback" do
|
||||
expect(e.on_complete).to be(easy.on_complete)
|
||||
end
|
||||
|
||||
it "preserves on_headers callback" do
|
||||
expect(e.on_headers).to be(easy.on_headers)
|
||||
end
|
||||
|
||||
it 'preserves body_write_callback of original handle' do
|
||||
expect { easy.perform }.to change { easy.response_body }
|
||||
expect { easy.perform }.not_to change { e.response_body }
|
||||
end
|
||||
|
||||
it "preserves on_progress callback" do
|
||||
expect(e.on_progress).to be(easy.on_progress)
|
||||
end
|
||||
|
||||
it 'sets new body_write_callback of duplicated handle' do
|
||||
expect { e.perform }.to change { e.response_body }
|
||||
expect { e.perform }.not_to change { easy.response_body }
|
||||
end
|
||||
|
||||
it 'preserves headers_write_callback of original handle' do
|
||||
expect { easy.perform }.to change { easy.response_headers }
|
||||
expect { easy.perform }.not_to change { e.response_headers }
|
||||
end
|
||||
|
||||
it 'sets new headers_write_callback of duplicated handle' do
|
||||
expect { e.perform }.to change { e.response_headers }
|
||||
expect { e.perform }.not_to change { easy.response_headers }
|
||||
end
|
||||
|
||||
it "resets response_body" do
|
||||
expect(e.response_body).to be_empty
|
||||
end
|
||||
|
||||
it "resets response_headers" do
|
||||
expect(e.response_headers).to be_empty
|
||||
end
|
||||
|
||||
it "sets response_body for duplicated Easy" do
|
||||
e.perform
|
||||
expect(e.response_body).not_to be_empty
|
||||
end
|
||||
|
||||
it "sets response_headers for duplicated Easy" do
|
||||
e.perform
|
||||
expect(e.response_headers).not_to be_empty
|
||||
end
|
||||
|
||||
it "preserves response_body for original Easy" do
|
||||
e.perform
|
||||
expect(easy.response_body).to eq('test_body')
|
||||
end
|
||||
|
||||
it "preserves response_headers for original Easy" do
|
||||
e.perform
|
||||
expect(easy.response_headers).to eq('test_headers')
|
||||
end
|
||||
end
|
||||
|
||||
describe "#mirror" do
|
||||
it "returns a Mirror" do
|
||||
expect(easy.mirror).to be_a(Ethon::Easy::Mirror)
|
||||
end
|
||||
|
||||
it "builds from easy" do
|
||||
expect(Ethon::Easy::Mirror).to receive(:from_easy).with(easy)
|
||||
easy.mirror
|
||||
end
|
||||
end
|
||||
|
||||
describe "#log_inspect" do
|
||||
[ :url, :response_code, :return_code, :total_time ].each do |name|
|
||||
it "contains #{name}" do
|
||||
expect(easy.log_inspect).to match name.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Libc do
|
||||
describe "#getdtablesize", :if => !Ethon::Curl.windows? do
|
||||
it "returns an integer" do
|
||||
expect(Ethon::Libc.getdtablesize).to be_a(Integer)
|
||||
end
|
||||
|
||||
it "returns bigger zero", :if => !Ethon::Curl.windows? do
|
||||
expect(Ethon::Libc.getdtablesize).to_not be_zero
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
require "spec_helper"
|
||||
|
||||
describe Ethon::Loggable do
|
||||
|
||||
describe "#logger=" do
|
||||
|
||||
let(:logger) do
|
||||
Logger.new($stdout).tap do |log|
|
||||
log.level = Logger::INFO
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
Ethon.logger = logger
|
||||
end
|
||||
|
||||
it "sets the logger" do
|
||||
expect(Ethon.logger).to eq(logger)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,298 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Multi::Operations do
|
||||
let(:multi) { Ethon::Multi.new }
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
let(:pointer) { FFI::MemoryPointer.new(:int) }
|
||||
|
||||
describe "#handle" do
|
||||
it "returns a pointer" do
|
||||
expect(multi.handle).to be_a(FFI::Pointer)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#running_count" do
|
||||
context "when hydra has no easy" do
|
||||
it "returns nil" do
|
||||
expect(multi.send(:running_count)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when hydra has easy" do
|
||||
before do
|
||||
easy.url = "http://localhost:3001/"
|
||||
multi.add(easy)
|
||||
multi.send(:trigger, pointer)
|
||||
end
|
||||
|
||||
it "returns 1" do
|
||||
expect(multi.send(:running_count)).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "when hydra has more easys" do
|
||||
let(:another_easy) { Ethon::Easy.new }
|
||||
|
||||
before do
|
||||
easy.url = "http://localhost:3001/"
|
||||
another_easy.url = "http://localhost:3001/"
|
||||
multi.add(easy)
|
||||
multi.add(another_easy)
|
||||
multi.send(:trigger, pointer)
|
||||
end
|
||||
|
||||
it "returns 2" do
|
||||
expect(multi.send(:running_count)).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#get_timeout" do
|
||||
context "when code ok" do
|
||||
let(:timeout) { 1 }
|
||||
|
||||
before do
|
||||
expect(Ethon::Curl).to receive(:multi_timeout).and_return(:ok)
|
||||
multi.instance_variable_set(:@timeout, double(:read_long => timeout))
|
||||
end
|
||||
|
||||
it "doesn't raise" do
|
||||
expect{ multi.send(:get_timeout) }.to_not raise_error
|
||||
end
|
||||
|
||||
context "when timeout smaller zero" do
|
||||
let(:timeout) { -1 }
|
||||
|
||||
it "returns 1" do
|
||||
expect(multi.send(:get_timeout)).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "when timeout bigger or equal zero" do
|
||||
let(:timeout) { 2 }
|
||||
|
||||
it "returns timeout" do
|
||||
expect(multi.send(:get_timeout)).to eq(timeout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when code not ok" do
|
||||
before { expect(Ethon::Curl).to receive(:multi_timeout).and_return(:not_ok) }
|
||||
|
||||
it "raises MultiTimeout error" do
|
||||
expect{ multi.send(:get_timeout) }.to raise_error(Ethon::Errors::MultiTimeout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#set_fds" do
|
||||
let(:timeout) { 1 }
|
||||
let(:max_fd) { 1 }
|
||||
|
||||
context "when code ok" do
|
||||
before { expect(Ethon::Curl).to receive(:multi_fdset).and_return(:ok) }
|
||||
|
||||
it "doesn't raise" do
|
||||
expect{ multi.method(:set_fds).call(timeout) }.to_not raise_error
|
||||
end
|
||||
|
||||
context "when max_fd -1" do
|
||||
let(:max_fd) { -1 }
|
||||
|
||||
before do
|
||||
multi.instance_variable_set(:@max_fd, double(:read_int => max_fd))
|
||||
expect(multi).to receive(:sleep).with(0.001)
|
||||
end
|
||||
|
||||
it "waits 100ms" do
|
||||
multi.method(:set_fds).call(timeout)
|
||||
end
|
||||
end
|
||||
|
||||
context "when max_fd not -1" do
|
||||
context "when code smaller zero" do
|
||||
before { expect(Ethon::Curl).to receive(:select).and_return(-1) }
|
||||
|
||||
it "raises Select error" do
|
||||
expect{ multi.method(:set_fds).call(timeout) }.to raise_error(Ethon::Errors::Select)
|
||||
end
|
||||
end
|
||||
|
||||
context "when code bigger or equal zero" do
|
||||
before { expect(Ethon::Curl).to receive(:select).and_return(0) }
|
||||
|
||||
it "doesn't raise" do
|
||||
expect{ multi.method(:set_fds).call(timeout) }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when code not ok" do
|
||||
before { expect(Ethon::Curl).to receive(:multi_fdset).and_return(:not_ok) }
|
||||
|
||||
it "raises MultiFdset error" do
|
||||
expect{ multi.method(:set_fds).call(timeout) }.to raise_error(Ethon::Errors::MultiFdset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#perform" do
|
||||
context "when no easy handles" do
|
||||
it "returns nil" do
|
||||
expect(multi.perform).to be_nil
|
||||
end
|
||||
|
||||
it "logs" do
|
||||
expect(Ethon.logger).to receive(:debug).twice
|
||||
multi.perform
|
||||
end
|
||||
end
|
||||
|
||||
context "when easy handle" do
|
||||
before do
|
||||
easy.url = "http://localhost:3001/"
|
||||
multi.add(easy)
|
||||
end
|
||||
|
||||
it "requests" do
|
||||
multi.perform
|
||||
end
|
||||
|
||||
it "sets easy" do
|
||||
multi.perform
|
||||
expect(easy.response_code).to eq(200)
|
||||
end
|
||||
end
|
||||
|
||||
context "when four easy handles" do
|
||||
let(:easies) do
|
||||
ary = []
|
||||
4.times do
|
||||
ary << another_easy = Ethon::Easy.new
|
||||
another_easy.url = "http://localhost:3001/"
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
before do
|
||||
easies.each { |e| multi.add(e) }
|
||||
multi.perform
|
||||
end
|
||||
|
||||
it "sets response codes" do
|
||||
expect(easies.all?{ |e| e.response_code == 200 }).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#ongoing?" do
|
||||
context "when easy_handles" do
|
||||
before { multi.easy_handles << 1 }
|
||||
|
||||
context "when running_count not greater 0" do
|
||||
before { multi.instance_variable_set(:@running_count, 0) }
|
||||
|
||||
it "returns true" do
|
||||
expect(multi.method(:ongoing?).call).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context "when running_count greater 0" do
|
||||
before { multi.instance_variable_set(:@running_count, 1) }
|
||||
|
||||
it "returns true" do
|
||||
expect(multi.method(:ongoing?).call).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when no easy_handles" do
|
||||
context "when running_count not greater 0" do
|
||||
before { multi.instance_variable_set(:@running_count, 0) }
|
||||
|
||||
it "returns false" do
|
||||
expect(multi.method(:ongoing?).call).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context "when running_count greater 0" do
|
||||
before { multi.instance_variable_set(:@running_count, 1) }
|
||||
|
||||
it "returns true" do
|
||||
expect(multi.method(:ongoing?).call).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#init_vars" do
|
||||
it "sets @timeout" do
|
||||
expect(multi.instance_variable_get(:@timeout)).to be_a(FFI::MemoryPointer)
|
||||
end
|
||||
|
||||
it "sets @timeval" do
|
||||
expect(multi.instance_variable_get(:@timeval)).to be_a(Ethon::Curl::Timeval)
|
||||
end
|
||||
|
||||
it "sets @fd_read" do
|
||||
expect(multi.instance_variable_get(:@fd_read)).to be_a(Ethon::Curl::FDSet)
|
||||
end
|
||||
|
||||
it "sets @fd_write" do
|
||||
expect(multi.instance_variable_get(:@fd_write)).to be_a(Ethon::Curl::FDSet)
|
||||
end
|
||||
|
||||
it "sets @fd_excep" do
|
||||
expect(multi.instance_variable_get(:@fd_excep)).to be_a(Ethon::Curl::FDSet)
|
||||
end
|
||||
|
||||
it "sets @max_fd" do
|
||||
expect(multi.instance_variable_get(:@max_fd)).to be_a(FFI::MemoryPointer)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#reset_fds" do
|
||||
after { multi.method(:reset_fds).call }
|
||||
|
||||
it "resets @fd_read" do
|
||||
expect(multi.instance_variable_get(:@fd_read)).to receive(:clear)
|
||||
end
|
||||
|
||||
it "resets @fd_write" do
|
||||
expect(multi.instance_variable_get(:@fd_write)).to receive(:clear)
|
||||
end
|
||||
|
||||
it "resets @fd_excep" do
|
||||
expect(multi.instance_variable_get(:@fd_excep)).to receive(:clear)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#check" do
|
||||
it { skip("untested") }
|
||||
end
|
||||
|
||||
describe "#run" do
|
||||
it { skip("untested") }
|
||||
end
|
||||
|
||||
describe "#trigger" do
|
||||
it "calls multi perform" do
|
||||
expect(Ethon::Curl).to receive(:multi_perform)
|
||||
multi.send(:trigger, pointer)
|
||||
end
|
||||
|
||||
it "sets running count" do
|
||||
multi.instance_variable_set(:@running_count, nil)
|
||||
multi.send(:trigger, pointer)
|
||||
expect(multi.instance_variable_get(:@running_count)).to_not be_nil
|
||||
end
|
||||
|
||||
it "returns multi perform code" do
|
||||
expect(Ethon::Curl).to receive(:multi_perform).and_return(:ok)
|
||||
expect(multi.send(:trigger, pointer)).to eq(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,182 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Multi::Options do
|
||||
let(:multi) { Ethon::Multi.new }
|
||||
|
||||
[
|
||||
:maxconnects, :pipelining, :socketdata, :socketfunction,
|
||||
:timerdata, :timerfunction, :max_total_connections
|
||||
].each do |name|
|
||||
describe "#{name}=" do
|
||||
it "responds_to" do
|
||||
expect(multi).to respond_to("#{name}=")
|
||||
end
|
||||
|
||||
it "sets option" do
|
||||
expect(Ethon::Curl).to receive(:set_option).with(name, anything, anything, anything)
|
||||
multi.method("#{name}=").call(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "socket_action mode" do
|
||||
let(:multi) { Ethon::Multi.new(execution_mode: :socket_action) }
|
||||
|
||||
describe "#socketfunction callbacks" do
|
||||
it "allows multi_code return values" do
|
||||
calls = []
|
||||
multi.socketfunction = proc do |handle, sock, what, userp, socketp|
|
||||
calls << what
|
||||
:ok
|
||||
end
|
||||
|
||||
easy = Ethon::Easy.new
|
||||
easy.url = "http://localhost:3001/?delay=1"
|
||||
multi.add(easy)
|
||||
expect(calls).to eq([])
|
||||
5.times do
|
||||
multi.socket_action
|
||||
break unless calls.empty?
|
||||
sleep 0.1
|
||||
end
|
||||
expect(calls.last).to eq(:in).or(eq(:out))
|
||||
multi.delete(easy)
|
||||
expect(calls.last).to eq(:remove)
|
||||
end
|
||||
|
||||
it "allows integer return values (compatibility)" do
|
||||
called = false
|
||||
multi.socketfunction = proc do |handle, sock, what, userp, socketp|
|
||||
called = true
|
||||
0
|
||||
end
|
||||
|
||||
easy = Ethon::Easy.new
|
||||
easy.url = "http://localhost:3001/?delay=1"
|
||||
multi.add(easy)
|
||||
5.times do
|
||||
multi.socket_action
|
||||
break if called
|
||||
sleep 0.1
|
||||
end
|
||||
multi.delete(easy)
|
||||
|
||||
expect(called).to be_truthy
|
||||
end
|
||||
|
||||
it "errors on invalid return codes" do
|
||||
called = false
|
||||
multi.socketfunction = proc do |handle, sock, what, userp, socketp|
|
||||
called = true
|
||||
"hi"
|
||||
end
|
||||
|
||||
easy = Ethon::Easy.new
|
||||
easy.url = "http://localhost:3001/?delay=1"
|
||||
multi.add(easy)
|
||||
expect {
|
||||
5.times do
|
||||
multi.socket_action
|
||||
break if called
|
||||
sleep 0.1
|
||||
end
|
||||
}.to raise_error(ArgumentError)
|
||||
expect { multi.delete(easy) }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#timerfunction callbacks" do
|
||||
it "allows multi_code return values" do
|
||||
calls = []
|
||||
multi.timerfunction = proc do |handle, timeout_ms, userp|
|
||||
calls << timeout_ms
|
||||
:ok
|
||||
end
|
||||
|
||||
easy = Ethon::Easy.new
|
||||
easy.url = "http://localhost:3001/?delay=1"
|
||||
multi.add(easy)
|
||||
expect(calls.last).to be >= 0 # adds an immediate timeout
|
||||
|
||||
multi.delete(easy)
|
||||
expect(calls.last).to eq(-1) # cancels the timer
|
||||
end
|
||||
|
||||
it "allows integer return values (compatibility)" do
|
||||
called = false
|
||||
multi.timerfunction = proc do |handle, timeout_ms, userp|
|
||||
called = true
|
||||
0
|
||||
end
|
||||
|
||||
easy = Ethon::Easy.new
|
||||
easy.url = "http://localhost:3001/?delay=1"
|
||||
multi.add(easy)
|
||||
multi.socket_action
|
||||
multi.delete(easy)
|
||||
|
||||
expect(called).to be_truthy
|
||||
end
|
||||
|
||||
it "errors on invalid return codes" do
|
||||
called = false
|
||||
multi.timerfunction = proc do |handle, timeout_ms, userp|
|
||||
called = true
|
||||
"hi"
|
||||
end
|
||||
|
||||
easy = Ethon::Easy.new
|
||||
easy.url = "http://localhost:3001/?delay=1"
|
||||
expect { multi.add(easy) }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#value_for" do
|
||||
context "when option in bool" do
|
||||
context "when value true" do
|
||||
let(:value) { true }
|
||||
|
||||
it "returns 1" do
|
||||
expect(multi.method(:value_for).call(value, :bool)).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "when value false" do
|
||||
let(:value) { false }
|
||||
|
||||
it "returns 0" do
|
||||
expect(multi.method(:value_for).call(value, :bool)).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
context "when value in int" do
|
||||
let(:value) { "2" }
|
||||
|
||||
it "returns value casted to int" do
|
||||
expect(multi.method(:value_for).call(value, :int)).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context "when value in unspecific_options" do
|
||||
context "when value a string" do
|
||||
let(:value) { "www.example.\0com" }
|
||||
|
||||
it "returns zero byte escaped string" do
|
||||
expect(multi.method(:value_for).call(value, nil)).to eq("www.example.\\0com")
|
||||
end
|
||||
end
|
||||
|
||||
context "when value not a string" do
|
||||
let(:value) { 1 }
|
||||
|
||||
it "returns value" do
|
||||
expect(multi.method(:value_for).call(value, nil)).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,80 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Multi::Stack do
|
||||
let(:multi) { Ethon::Multi.new }
|
||||
let(:easy) { Ethon::Easy.new }
|
||||
|
||||
describe "#add" do
|
||||
context "when easy already added" do
|
||||
before { multi.add(easy) }
|
||||
|
||||
it "returns nil" do
|
||||
expect(multi.add(easy)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when easy new" do
|
||||
it "adds easy to multi" do
|
||||
expect(Ethon::Curl).to receive(:multi_add_handle).and_return(:ok)
|
||||
multi.add(easy)
|
||||
end
|
||||
|
||||
it "adds easy to easy_handles" do
|
||||
multi.add(easy)
|
||||
expect(multi.easy_handles).to include(easy)
|
||||
end
|
||||
end
|
||||
|
||||
context "when multi_add_handle fails" do
|
||||
it "raises multi add error" do
|
||||
expect(Ethon::Curl).to receive(:multi_add_handle).and_return(:bad_easy_handle)
|
||||
expect{ multi.add(easy) }.to raise_error(Ethon::Errors::MultiAdd)
|
||||
end
|
||||
end
|
||||
|
||||
context "when multi cleaned up before" do
|
||||
it "raises multi add error" do
|
||||
Ethon::Curl.multi_cleanup(multi.handle)
|
||||
expect{ multi.add(easy) }.to raise_error(Ethon::Errors::MultiAdd)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#delete" do
|
||||
context "when easy in easy_handles" do
|
||||
before { multi.add(easy) }
|
||||
|
||||
it "deletes easy from multi" do
|
||||
expect(Ethon::Curl).to receive(:multi_remove_handle).and_return(:ok)
|
||||
multi.delete(easy)
|
||||
end
|
||||
|
||||
it "deletes easy from easy_handles" do
|
||||
multi.delete(easy)
|
||||
expect(multi.easy_handles).to_not include(easy)
|
||||
end
|
||||
end
|
||||
|
||||
context "when easy is not in easy_handles" do
|
||||
it "does nothing" do
|
||||
expect(Ethon::Curl).to receive(:multi_add_handle).and_return(:ok)
|
||||
multi.add(easy)
|
||||
end
|
||||
|
||||
it "adds easy to easy_handles" do
|
||||
multi.add(easy)
|
||||
expect(multi.easy_handles).to include(easy)
|
||||
end
|
||||
end
|
||||
|
||||
context "when multi_remove_handle fails" do
|
||||
before { multi.add(easy) }
|
||||
|
||||
it "raises multi remove error" do
|
||||
expect(Ethon::Curl).to receive(:multi_remove_handle).and_return(:bad_easy_handle)
|
||||
expect{ multi.delete(easy) }.to raise_error(Ethon::Errors::MultiRemove)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,152 @@
|
||||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ethon::Multi do
|
||||
describe ".new" do
|
||||
it "inits curl" do
|
||||
expect(Ethon::Curl).to receive(:init)
|
||||
Ethon::Multi.new
|
||||
end
|
||||
|
||||
context "with default options" do
|
||||
it "allows running #perform with the default execution_mode" do
|
||||
Ethon::Multi.new.perform
|
||||
end
|
||||
|
||||
it "refuses to run #socket_action" do
|
||||
expect { Ethon::Multi.new.socket_action }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context "when options not empty" do
|
||||
context "when pipelining is set" do
|
||||
let(:options) { { :pipelining => true } }
|
||||
|
||||
it "sets pipelining" do
|
||||
expect_any_instance_of(Ethon::Multi).to receive(:pipelining=).with(true)
|
||||
Ethon::Multi.new(options)
|
||||
end
|
||||
end
|
||||
|
||||
context "when execution_mode option is :socket_action" do
|
||||
let(:options) { { :execution_mode => :socket_action } }
|
||||
let(:multi) { Ethon::Multi.new(options) }
|
||||
|
||||
it "refuses to run #perform" do
|
||||
expect { multi.perform }.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "allows running #socket_action" do
|
||||
multi.socket_action
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#socket_action" do
|
||||
let(:options) { { :execution_mode => :socket_action } }
|
||||
let(:select_state) { { :readers => [], :writers => [], :timeout => 0 } }
|
||||
let(:multi) {
|
||||
multi = Ethon::Multi.new(options)
|
||||
multi.timerfunction = proc do |handle, timeout_ms, userp|
|
||||
timeout_ms = nil if timeout_ms == -1
|
||||
select_state[:timeout] = timeout_ms
|
||||
:ok
|
||||
end
|
||||
multi.socketfunction = proc do |handle, sock, what, userp, socketp|
|
||||
case what
|
||||
when :remove
|
||||
select_state[:readers].delete(sock)
|
||||
select_state[:writers].delete(sock)
|
||||
when :in
|
||||
select_state[:readers].push(sock) unless select_state[:readers].include? sock
|
||||
select_state[:writers].delete(sock)
|
||||
when :out
|
||||
select_state[:readers].delete(sock)
|
||||
select_state[:writers].push(sock) unless select_state[:writers].include? sock
|
||||
when :inout
|
||||
select_state[:readers].push(sock) unless select_state[:readers].include? sock
|
||||
select_state[:writers].push(sock) unless select_state[:writers].include? sock
|
||||
else
|
||||
raise ArgumentError, "invalid value for 'what' in socketfunction callback"
|
||||
end
|
||||
:ok
|
||||
end
|
||||
multi
|
||||
}
|
||||
|
||||
def fds_to_ios(fds)
|
||||
fds.map do |fd|
|
||||
IO.for_fd(fd).tap { |io| io.autoclose = false }
|
||||
end
|
||||
end
|
||||
|
||||
def perform_socket_action_until_complete
|
||||
multi.socket_action # start things off
|
||||
|
||||
while multi.ongoing?
|
||||
readers, writers, _ = IO.select(
|
||||
fds_to_ios(select_state[:readers]),
|
||||
fds_to_ios(select_state[:writers]),
|
||||
[],
|
||||
select_state[:timeout]
|
||||
)
|
||||
|
||||
to_notify = Hash.new { |hash, key| hash[key] = [] }
|
||||
unless readers.nil?
|
||||
readers.each do |reader|
|
||||
to_notify[reader] << :in
|
||||
end
|
||||
end
|
||||
unless writers.nil?
|
||||
writers.each do |writer|
|
||||
to_notify[writer] << :out
|
||||
end
|
||||
end
|
||||
|
||||
to_notify.each do |io, readiness|
|
||||
multi.socket_action(io, readiness)
|
||||
end
|
||||
|
||||
# if we didn't have anything to notify, then we timed out
|
||||
multi.socket_action if to_notify.empty?
|
||||
end
|
||||
ensure
|
||||
multi.easy_handles.dup.each do |h|
|
||||
multi.delete(h)
|
||||
end
|
||||
end
|
||||
|
||||
it "supports an end-to-end request" do
|
||||
easy = Ethon::Easy.new
|
||||
easy.url = "http://localhost:3001/"
|
||||
multi.add(easy)
|
||||
|
||||
perform_socket_action_until_complete
|
||||
|
||||
expect(multi.ongoing?).to eq(false)
|
||||
end
|
||||
|
||||
it "supports multiple concurrent requests" do
|
||||
handles = []
|
||||
10.times do
|
||||
easy = Ethon::Easy.new
|
||||
easy.url = "http://localhost:3001/?delay=1"
|
||||
multi.add(easy)
|
||||
handles << easy
|
||||
end
|
||||
|
||||
start = Time.now
|
||||
perform_socket_action_until_complete
|
||||
duration = Time.now - start
|
||||
|
||||
# these should have happened concurrently
|
||||
expect(duration).to be < 2
|
||||
expect(multi.ongoing?).to eq(false)
|
||||
|
||||
handles.each do |handle|
|
||||
expect(handle.response_code).to eq(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user