require 'integration_spec_helper' # require 'spec_helper' require 'agile_proxy' require 'resolv' shared_examples_for 'a proxy server' do |options = {}| if options.key?(:recording) && options[:recording] after :each do expect(JSON.parse(recordings_resource.get)['total']).to eql(1) end end it 'should proxy GET requests' do expect(http.get('/echo').body).to eql 'GET /echo' end it 'should proxy POST requests' do expect(http.post('/echo', foo: 'bar').body).to eql "POST /echo\nfoo=bar" end it 'should proxy PUT requests' do expect(http.post('/echo', foo: 'bar').body).to eql "POST /echo\nfoo=bar" end it 'should proxy HEAD requests' do expect(http.head('/echo').headers['HTTP-X-EchoServer']).to eql 'HEAD /echo' end it 'should proxy DELETE requests' do expect(http.delete('/echo').body).to eql 'DELETE /echo' end end shared_examples_for 'a request stub' do |options = {}| if options.key?(:recording) && options[:recording] after :each do data = recordings_resource.get count = JSON.parse(data)['total'] expect(count).to eql(1) end end it 'should stub GET requests' do expect(http.get('/index.html').body).to eql 'Mocked Content' end it 'should stub GET response statuses' do expect(http.get('/index.html').status).to eql 200 end it 'Should stub a different get request with json response' do resp = http.get('/api/forums') expect(ActiveSupport::JSON.decode(resp.body).symbolize_keys).to eql forums: [], total: 0 expect(resp.status).to eql 200 expect(resp.headers['Content-Type']).to eql 'application/json' end it 'Should get the mocked content with parameter substitution for the /api/forums/:forum_id/posts url' do resp = http.get '/api/forums/my_forum/posts' expect(ActiveSupport::JSON.decode(resp.body)).to eql 'posts' => [{ 'forum_id' => 'my_forum', 'subject' => 'My first post' }, { 'forum_id' => 'my_forum', 'subject' => 'My second post' }, { 'forum_id' => 'my_forum', 'subject' => 'My third post' }], 'total' => 3 expect(resp.status).to eql 200 expect(resp.headers['Content-Type']).to eql 'application/json' end it 'Should get the mocked content for api/forums/:forum_id/:post_id with parameter substitution including query parameters' do resp = http.get '/api/forums/my_forum/my_post?sort=id' expect(resp.body).to eql '

Sorted By: id

my_forum

my_post

' expect(resp.status).to eql 200 expect(resp.headers['Content-Type']).to eql 'text/html' end it 'Should get the mocked content for api/forums/:forum_id/:post_id.html with parameter substitution including query parameters' do resp = http.get '/api/forums/my_forum/my_post.html?sort=id' expect(resp.body).to eql '

Sorted By: id

my_forum

my_post

' expect(resp.status).to eql 200 expect(resp.headers['Content-Type']).to eql 'text/html' end it 'Should respond with an error for api/forums/:forum_id/:post_id.html with parameter substitution with a missing query parameter' do resp = http.get '/api/forums/my_forum/my_post.html' expect(resp.body).to eql "Missing var or method 'sort' in data." expect(resp.status).to eql 500 end it 'Should match the route by posted json data and the posted data can be output via the template' do resp = http.post '/api/forums/my_forum', '{"posted_var": "special_value"}', 'Content-Type' => 'application/json' expect(resp.body).to eql '

special_value

my_forum

This should get data from the POSTed data

' expect(resp.status).to eql 200 expect(resp.headers['Content-Type']).to eql 'text/html' end it 'Should match the route by posted plain text data and the posted data can be output via the template' do resp = http.post '/api/forums/my_forum', "posted_var=special_value\n", 'Content-Type' => 'text/plain' expect(resp.body).to eql '

special_value

my_forum

This should get data from the POSTed data

' expect(resp.status).to eql 200 expect(resp.headers['Content-Type']).to eql 'text/html' end it 'Should match the route by posted plain text data and the posted data can be output via the template' do resp = http.post '/api/forums/my_forum', "dummy=\nposted_var=special_value\n", 'Content-Type' => 'text/plain' expect(resp.body).to eql '

special_value

my_forum

This should get data from the POSTed data

' expect(resp.status).to eql 200 expect(resp.headers['Content-Type']).to eql 'text/html' end # it 'Should match the route by posted xml data and the posted data can be output via the template' do # resp = http.post "/api/forums/my_forum", 'special_value', {'Content-Type' => 'application/xml'} # expect(resp.body).to eql '

special_value

my_forum

This should get data from the POSTed data

' # expect(resp.status).to eql 200 # end it 'Should match the route by posted url encoded data and the posted data can be output via the template' do resp = http.post '/api/forums/my_forum', 'posted_var=special_value', 'Content-Type' => 'application/x-www-form-urlencoded' expect(resp.body).to eql '

special_value

my_forum

This should get data from the POSTed data

' expect(resp.status).to eql 200 end it 'Should match the route by posted multipart encoded data and the posted data can be output via the template' do resp = http.post '/api/forums/my_forum', 'posted_var=special_value', 'Content-Type' => 'multipart/form-data' expect(resp.body).to eql '

special_value

my_forum

This should get data from the POSTed data

' expect(resp.status).to eql 200 end it 'should stub POST requests' do resp = http.post('/api/forums', foo: :bar) expect(resp.body).to eql '{"created": true}' expect(resp.headers['Content-Type']).to eql 'application/json' end it 'should stub PUT requests' do resp = http.put('/api/forums/forum_1/my_post', foo: :bar) expect(resp.body).to eql '{"updated": true}' expect(resp.headers['Content-Type']).to eql 'application/json' end it 'should stub DELETE requests' do resp = http.delete('/api/forums/forum_1/my_post') expect(resp.body).to eql '{"deleted": true}' expect(resp.headers['Content-Type']).to eql 'application/json' end end shared_examples_for 'a cache' do context 'whitelisted GET requests' do it 'should not be cached' do assert_noncached_url end context 'with ports' do before do rack_app_url = URI(http.url_prefix) AgileProxy.config.whitelist = ["#{rack_app_url.host}:#{rack_app_url.port}"] end it 'should not be cached ' do assert_noncached_url end end end context 'non-whitelisted GET requests' do before do AgileProxy.config.whitelist = [] end it 'should be cached' do assert_cached_url end context 'with ports' do before do rack_app_url = URI(http.url_prefix) AgileProxy.config.whitelist = ["#{rack_app_url.host}:#{rack_app_url.port + 1}"] end it 'should be cached' do assert_cached_url end end end context 'ignore_params GET requests' do before do AgileProxy.config.ignore_params = ['/analytics'] end it 'should be cached' do r = http.get('/analytics?some_param=5') expect(r.body).to eql 'GET /analytics' expect do expect do r = http.get('/analytics?some_param=20') end.to change { r.headers['HTTP-X-EchoCount'].to_i }.by(1) end.to_not change { r.body } end end context 'path_blacklist GET requests' do before do AgileProxy.config.path_blacklist = ['/api'] end it 'should be cached' do assert_cached_url('/api') end end context 'cache persistence' do let(:cached_key) { proxy.cache.key('get', "#{url}/foo", '') } let(:cached_file) do f = cached_key + '.yml' File.join(AgileProxy.config.cache_path, f) end before { AgileProxy.config.whitelist = [] } after do File.delete(cached_file) if File.exist?(cached_file) end context 'enabled' do before { AgileProxy.config.persist_cache = true } it 'should persist' do http.get('/foo') expect(File.exist?(cached_file)).to be_true end it 'should be read initially from persistent cache' do File.open(cached_file, 'w') do |f| cached = { headers: {}, content: 'GET /foo cached' } f.write(cached.to_yaml(Encoding: :Utf8)) end r = http.get('/foo') expect(r.body).to eql 'GET /foo cached' end context 'cache_request_headers requests' do it 'should not be cached by default' do http.get('/foo') saved_cache = AgileProxy.proxy.cache.fetch_from_persistence(cached_key) expect(saved_cache.keys).not_to include :request_headers end context 'when enabled' do before do AgileProxy.config.cache_request_headers = true end it 'should be cached' do http.get('/foo') saved_cache = AgileProxy.proxy.cache.fetch_from_persistence(cached_key) expect(saved_cache.keys).to include :request_headers end end end context 'ignore_cache_port requests' do it 'should be cached without port' do r = http.get('/foo') url = URI(r.env[:url]) saved_cache = AgileProxy.proxy.cache.fetch_from_persistence(cached_key) expect(saved_cache[:url]).to_not eql(url.to_s) expect(saved_cache[:url]).to eql(url.to_s.gsub(":#{url.port}", '')) end end context 'non_whitelisted_requests_disabled requests' do before { AgileProxy.config.non_whitelisted_requests_disabled = true } it 'should raise error when disabled' do # TODO: Suppress stderr output: https://gist.github.com/adamstegman/926858 expect { http.get('/foo') }.to raise_error(Faraday::Error::ConnectionFailed, 'end of file reached') end end context 'non_successful_cache_disabled requests' do before do rack_app_url = URI(http_error.url_prefix) AgileProxy.config.whitelist = ["#{rack_app_url.host}:#{rack_app_url.port}"] AgileProxy.config.non_successful_cache_disabled = true end it 'should not cache non-successful response when enabled' do http_error.get('/foo') expect(File.exist?(cached_file)).to be_false end it 'should cache successful response when enabled' do assert_cached_url end end context 'non_successful_error_level requests' do before do rack_app_url = URI(http_error.url_prefix) AgileProxy.config.whitelist = ["#{rack_app_url.host}:#{rack_app_url.port}"] AgileProxy.config.non_successful_error_level = :error end it 'should raise error for non-successful responses when :error' do # When this config setting is set, the EventMachine running the test servers is killed upon error raising # The `raise` is required to bubble up the error to the test running it # The Faraday error is raised upon `close_connection` so this can be non-pending if we can do one of the following: # 1) Remove the `raise error_message` conditionally for this test # 2) Restart the test servers if they aren't running # 3) Change the test servers to start/stop for each test instead of before all # 4) Remove the test server completely and rely on the server instantiated by the app pending 'Unable to test this without affecting the running test servers' expect { http_error.get('/foo') }.to raise_error(Faraday::Error::ConnectionFailed) end end end context 'disabled' do before { AgileProxy.config.persist_cache = false } it 'shouldnt persist' do http.get('/foo') expect(File.exist?(cached_file)).to be_false end end end def assert_noncached_url(url = '/foo') r = http.get(url) expect(r.body).to eql "GET #{url}" expect do expect do r = http.get(url) end.to change { r.headers['HTTP-X-EchoCount'].to_i }.by(1) end.to_not change { r.body } end def assert_cached_url(url = '/foo') r = http.get(url) expect(r.body).to eql "GET #{url}" expect do expect do r = http.get(url) end.to_not change { r.headers['HTTP-X-EchoCount'] } end.to_not change { r.body } end end describe AgileProxy::Server, :type => :integration do extend AgileProxy::Test::Integration::RequestSpecHelper describe 'Without recording' do load_small_set_of_request_specs before do # Adding non-valid Faraday options throw an error: https://github.com/arsduo/koala/pull/311 # Valid options: :request, :proxy, :ssl, :builder, :url, :parallel_manager, :params, :headers, :builder_class faraday_options = { request: {timeout: 10.0} } faraday_options_with_proxy = faraday_options.merge({ proxy: { uri: "http://anonymous:password@localhost:#{proxy_port}" } }) @http = Faraday.new @http_url, faraday_options_with_proxy @https = Faraday.new @https_url, faraday_options_with_proxy.merge(ssl: { verify: false }) @http_no_proxy = Faraday.new @http_url_no_proxy, faraday_options @https_no_proxy = Faraday.new @https_url_no_proxy, faraday_options.merge(ssl: { verify: false }) @http_error = Faraday.new @error_url, faraday_options_with_proxy @http_error_no_proxy = Faraday.new @error_url, faraday_options_with_proxy end context 'proxying' do context 'HTTP' do let!(:http) { @http } it_should_behave_like 'a proxy server' end context 'HTTPS' do let!(:http) { @https } it_should_behave_like 'a proxy server' end end context 'stubbing' do context 'In Proxy Mode' do context 'HTTP' do let!(:url) { @http_url } let!(:http) { @http } it_should_behave_like 'a request stub' end context 'HTTPS' do let!(:url) { @https_url } let!(:http) { @https } it_should_behave_like 'a request stub' end end #Server mode only supports http - no real point for https at the moment context 'In Server Mode' do context 'HTTP' do let!(:url) { @http_url_no_proxy } let!(:http) { @http_no_proxy } it_should_behave_like 'a request stub' end end end end describe 'With recording' do load_small_set_of_request_specs recording: true before do # Adding non-valid Faraday options throw an error: https://github.com/arsduo/koala/pull/311 # Valid options: :request, :proxy, :ssl, :builder, :url, :parallel_manager, :params, :headers, :builder_class faraday_options = { proxy: { uri: 'http://recording:password@localhost:3101' }, request: { timeout: 10.0 } } @http = Faraday.new @http_url, faraday_options @https = Faraday.new @https_url, faraday_options.merge(ssl: { verify: false }) @http_error = Faraday.new @error_url, faraday_options end context 'proxying' do context 'HTTP' do let!(:http) { @http } it_should_behave_like 'a proxy server', recording: true end context 'HTTPS' do let!(:http) { @https } it_should_behave_like 'a proxy server', recording: true end end context 'stubbing' do context 'HTTP' do let!(:url) { @http_url } let!(:http) { @http } it_should_behave_like 'a request stub', recording: true end context 'HTTPS' do let!(:url) { @https_url } let!(:http) { @https } it_should_behave_like 'a request stub', recording: true end end end end