ProxyTester - testing with rspec for http and socks proxies
The proxy_tester helps you maintaining your proxy infrastructure by writing
tests which can be run automatically. It uses rspec in the background to make
that thing possible and adds some helper methods to make testing proxies
easier.
Today proxy_tester supports "HTTP"- and "SOCKS5"-Proxies.
Installation
Add this line to your application's Gemfile:
gem 'proxy_tester'
And then execute:
$ bundle
Or install it yourself as:
$ gem install proxy_tester
Usage
Quick start
Initialize proxy tester
This will create all files and directories needed to run proxy tester. Thanks
to --pre-seed it will also add some example files to your test
infrastructure, e.g. spec_helper, example_spec.rb.
% proxy_tester init --pre-seed
Modify example spec
- Add your proxy, e.g.
localhost:3128to the spec - Add a user + password to
users.csvand to theexample_spec.rbif your proxy needs authentication
Run tests
% proxy_tester test rspec
Getting in touch with the screenshots taken during test
# Please choose your preferred viewer, e.g. feh and the correct date/time for your test
% feh ~/.local/share/proxy_tester/reports.d/2014-03-19_17:22:00/*.png
Getting started (WIP)
Getting help
% proxy_tester help
% proxy_tester help <cmd>
Prepare a configuration file for proxy_tester
For more details please see the chapter Writing Configuration File for that.
Initialize proxy tester
This will create all files and directories needed to run proxy tester.
% proxy_tester init
By default it creates the following directories/files:
$HOME/.local/share/proxy_tester$HOME/.local/share/proxy_tester/reports.d$HOME/.config/proxy_tester$HOME/.config/proxy_tester/testcases.d$HOME/.config/proxy_tester/config.yaml$HOME/.config/proxy_tester/users.csv
proxy_tester supports different options for init
% proxy_tester help init
# Usage:
# proxy_tester init
#
# Options:
# [--force] # Overwrite existing files?
# [--pre-seed] # Add examples to test cases directory
# [--test-cases-directory=TEST_CASES_DIRECTORY] # Directory with test cases
# [--user-file=USER_FILE] # Path to user file
# [--create-config-file] # Create config file
# # Default: true
# [--create-user-file] # Create user file
# # Default: true
# [--create-test-cases-directory] # Create directory to store test cases
# # Default: true
# [--config-file=CONFIG_FILE] # Config file
# [--log-level=LOG_LEVEL] # Log level for ui logging
# [--debug-mode] # Run application in debug mode
Adding users to users.csv
If your proxy needs user authentication you can use the use_user-method
together with the users.csv. Make sure you keep the users.csv safe (and out
of a central vcs) because the passwords need to be stored in plain text.
"name","password","description"
"user1","password1","test user abc"
"user2","password2","test user efg"
In your spec you can refer to the users by using the following code snippet.
There is no need to write the password into the spec file. proxy_helper will
look it up.
it 'blocks www.example.org' do
use_proxy 'localhost:3128'
use_user 'user1'
use_timeout(100) do
visit 'http://www.example.org'
end
expect(page).to have_content('Access forbidden')
expect(page.status_code).to eq 403
end
Writing specs
For more details please see the chapter Writing Specs for that.
Run the tests
Run the tests on commandline
% proxy_tester test
It supports several different options:
% proxy_tester help test
# Usage:
# proxy_tester test
#
# Options:
# [--test-cases-directory=TEST_CASES_DIRECTORY] # Directory with test cases
# [--tags=one two three] # Filter tests based on tags
# [--config-file=CONFIG_FILE] # Config file
# [--log-level=LOG_LEVEL] # Log level for ui logging
# [--debug-mode] # Run application in debug mode
#
If you want to limit the tests which should be run by proxy_tester you can
use the --tags-switch. Make sure you've flagged your examples correctly - see
rspec documentation
for more about this topic.
Writing Specs
Introduction
Recommended structure for testcases
For my specs I use the following structure. Additionally I use git for
version control.
testcase.d
infrastructure1
infrastructure1_abc_spec.rb
infrastructure1_def_spec.rb
spec_helper.rb
support
rspec.rb
shared_examples-base.rb
[...]
The spec file(s)
I took screenshots for all my tests in an after-hook to be able to provide reports after
testing. For each environment, e.g. production, staging, I create a diffent
context adding a flag, e.g :production, for easier filterung. For each proxy
I add a context and set the subject to the proxy hostname/ip address + port.
The it_behaves_like-method is special because it references a shared example.
More on this can be found later.
Name: infrastructure1_abc_spec.rb
# encoding: utf-8
describe 'Infrastructure XY' do
after :each do
take_screenshot
end
before :each do
use_user 'user1'
end
context 'Production', :production do
context 'proxy1' do
subject { 'proxy1.localdomain:8080' }
it_behaves_like 'a base proxy'
end
context 'proxy2' do
subject { 'proxy2.localdomain:8080' }
it_behaves_like 'a base proxy'
end
end
context 'Staging', :staging do
context 'proxy1-int' do
subject { 'proxy1-int.localdomain:8080' }
it_behaves_like 'a base proxy'
end
context 'proxy2-int' do
subject { 'proxy2-int.localdomain:8080' }
it_behaves_like 'a base proxy'
end
end
end
The spec helper file
In my spec helper file I load all files found in support to make shared examples and other files available.
Name: spec_helper.rb
# encoding: utf-8
# Loading support files
Dir.glob(::File.('../support/*.rb', __FILE__)).each { |f| require_relative f }
begin
require 'bundler'
Bundler.require :default
rescue
$stderr.puts 'Please install ruby gem "bundler" before using this spec_helper.rb in your proxy_tester-spec files.'
exit(-1)
end
# *open-uri*
# Easier use of urls in scripts
# Please see "http://www.ruby-doc.org/stdlib-2.1.1/libdoc/open-uri/rdoc/OpenURI.html" for further information
require 'open-uri'
The spec configuration
I want rspec to run only those examples which are flagged with :focus. If
there is no example with a flag :focus. It should run all examples. I use the
:focus-flag while developing a test or debugging existing tests.
Name: rspec.rb
RSpec.configure do |c|
c.filter_run_including :focus => true
c.run_all_when_everything_filtered = true
c. = true
end
Shared examples: base
To DRY my examples up I use shared examples. Please see rspec
documentation
for more detailed information.
Name: shared_examples-base.rb
# encoding: utf-8
require_relative 'shared_examples'
shared_examples 'a base proxy' do
before :each do
use_proxy subject
end
it 'works with http://www.example.org' do
visit 'http://www.example.org'
expect(page).to have_content('Example Domain')
expect(page.status_code).to eq 200
end
it 'blocks eicar virus' do
visit 'http://www.eicar.org/download/eicar.com'
find('div#lk_info').trigger('click')
expect(page).to have_content('Access blocked')
expect(page.status_code).to eq 403
end
end
Examples
Testing proxy pac
# encoding: utf-8
describe 'a proxy pac' do
before :each do
# no online activities are needed to
# parse proxy pac => set_offline true
set_offline true
subject { 'http://url/for/proxy.pac' }
end
it 'works with http://www.example.org' do
use_proxy :pac, subject
visit 'http://www.example.org'
expect(proxy_pac).to return_proxy('PROXY 127.0.0.1:3128')
end
end
Helper methods
This gem includes all helper methods provided by
capybara and
poltergeist plus those listed
below. To see the most current list of available methods please look for
lib/proxy_tester/rspec/helper.rb.
runtime
This method returns the time of proxy_tester-run.
# time of run, YYYY-MM-DD_HH:mm:ss
runtime
- proxy
This method returns the proxy set earlier. It holds the proxy-object.
# proxy host
proxy.host
proxy.host = 'host'
# proxy port
proxy.port
proxy.port = 3128
# proxy type :none, :http, :socks5
proxy.type
proxy.type = :none
# is blank?
proxy.blank?
- proxy_pac
This method returns the proxy pac set earlier. It holds the proxy_pac-object.
# proxy host
proxy_pac.host
# proxy port
proxy_pac.port
# client ip
proxy_pac.client_ip
proxy_pac.client_ip = '127.0.0.1'
# url to proxy pac
proxy_pac.url
proxy_pac.url = 'http://www.example.org'
proxy_pac.url = 'www.example.org' # heuristic parsing via addressable
# path/url to proxy pac-file
proxy_pac.pac_file
proxy_pac.pac_file = 'http://pac.in.domain.org/proxy.pac'
proxy_pac.pac_file = '/path/to/file.pac'
# is blank? => port + host = blank
proxy_pac.blank?
# direct? => no proxy
proxy_pac.direct?
- cleanup_reports
The gem supports a method to create a screenshot for a visited website. To
keep the reports.d-directory clean, the last 5 report-directories are kept only.
before :all do
cleanup_reports
end
If you want to change this, set the @__keep_report_directories to the needed
value in an before(:all)-hook.
before :all do
# keep the last 10 report directories
@__keep_report_directories = 10
cleanup_reports
end
- take_screenshot
If you want to take a screenshot of a visited website, you can use
take_screenshot. By default all screenshots are stored in reports.d.
after :each do
take_screenshot
end
- view_screenshot (alias: show_screenshot)
While debugging an example it might be handy to view the fetched website. This
can be done using the view_screenshot-helper. It opens the screenshot in your
preferred image viewer.
context 'proxy1' do
subject { 'proxy1.localdomain:8080' }
it 'works with http://www.example.org' do
use_proxy subject
visit 'http://www.example.org'
view_screenshot
expect(page).to have_content('Example Domain')
expect(page.status_code).to eq 200
end
end
- use_user_agent
Set the user agent used by the driver.
context 'proxy1' do
subject { 'proxy1.localdomain:8080' }
it 'works with http://www.example.org' do
use_proxy subject
use_user_agent 'Mozilla/5.0 (X11; Linux x86_64; rv:27.0) Gecko/20100101 Firefox/27.0'
visit 'http://www.example.org'
expect(page).to have_content('Example Domain')
expect(page.status_code).to eq 200
end
end
- use_user
Set the user used to connect to the proxy.
Lookup user password using users.csv
it 'blocks www.example.org for user "user1"' do
use_proxy subject
use_user 'user1'
visit 'http://www.example.org'
expect(page).to have_content('Access forbidden')
expect(page.status_code).to eq 403
end
Ask user for user name and user password
it 'blocks www.example.org for user' do
use_proxy subject
use_user :ask
visit 'http://www.example.org'
expect(page).to have_content('Access forbidden')
expect(page.status_code).to eq 403
end
Ask user for user password
it 'blocks www.example.org for user' do
use_proxy subject
use_user 'user', :ask_password
visit 'http://www.example.org'
expect(page).to have_content('Access forbidden')
expect(page.status_code).to eq 403
end
Build a special user name based on ENV
it 'blocks www.example.org for user' do
use_proxy subject
use_user 'user', :credential_merging
# => ENV['USER']-user
visit 'http://www.example.org'
expect(page).to have_content('Access forbidden')
expect(page.status_code).to eq 403
end
- use_client_ip
Set the client ip address used during proxy pac evaluation.
it 'blocks www.example.org' do
use_proxy :pac, 'http://localhost/proxy.pac'
use_client_ip '127.0.0.1'
visit 'http://www.example.org'
expect(page).to have_content('Access forbidden')
expect(page.status_code).to eq 403
end
- use_time
Set the time used during proxy pac evaluation.
it 'blocks www.example.org' do
use_proxy :pac, 'http://localhost/proxy.pac'
use_time '2014-03-07 05:12:20'
visit 'http://www.example.org'
expect(page).to have_content('Access forbidden')
expect(page.status_code).to eq 403
end
- use_proxy
Set the proxy host or proxy pac used to forward the traffic.
use_proxy :host, 'localhost:3128'
use_proxy 'localhost:3128'
use_proxy :pac, 'http://localhost/proxy.pac'
- use_timeout
Set the timeout in seconds for visiting a website.
it 'blocks www.example.org' do
use_proxy 'localhost:3128'
use_time '2014-03-07 05:12:20'
use_timeout(100) do
visit 'http://www.example.org'
end
expect(page).to have_content('Access forbidden')
expect(page.status_code).to eq 403
end
- set_offline
Disable online activities issued by visit.
it 'does not send out requests if is offline' do
set_offline true
visit 'http://www.example.org'
expect(page).to eq "<html><head></head><body></body></html>"
end
* offline?
### Custom matchers
* have_requests_with_status_code
Checks if a page requested other objects with given status code(s). Because
some sites support `chunked` data, the status code needs to be an array.
```ruby
it 'requested given resources' do
use_proxy 'proxy1.localdomain:8080'
use_user 'user1', :ask_password
visit 'http://www.example.org'
domains_with_status_code = {
'http://www.example.org' => [200],
}
expect(page).to have_requests_with_status_code domains_with_status_code
end
- reference_domains
Checks if a page requested objects from given domains.
it 'requested given resources' do
use_proxy 'proxy1.localdomain:8080'
use_user 'user1', :ask_password
visit 'http://www.example.org'
domains = %w{ www.example.org }
expect(page).to reference_domains domains
end
Writing Configuration File
The configuration file of proxy_tester is a simple
yaml-file. Those configuration variables are overwritten
if you choose a corresponding commandline-switch, e.g. --config-file-switch
for config_file-option. If a configuration option is not defined in config
file and not given on commandline proxy_tester uses default hardcoded within
the application. It supports the following variables.
# configuration options with defaults
# $HOME = '/home/user'
:config_file: /home/user/.config/proxy_tester/config.yaml
:user_file: /home/user/.config/proxy_tester/user.csv
:test_cases_directory: /home/user/.config/proxy_tester/test_cases.d
:examples_directory: /home/user/.config/proxy_tester/test_cases.d/examples
:reports_directory: /home/user/.share/proxy_tester/reports.d
Testing Proxies Directly
Introduction
proxy_tester does also provides a mode where you can test a proxy directly by opening a url.
% proxy_tester test url --proxy localhost:3128 http://www.example.net
Options
Usage:
proxy_tester url --proxy=one two three
Options:
--proxy=one two three # System(s) under test (required)
[--count=N] # Number of requests
# Default: 10
[--wait=N] # Wait n seconds before kill children
# Default: 60
[--user=USER] # User for authentication
# Default: xgvndeg
[--log-level=LOG_LEVEL] # Log level
[--concurrent] # Simulate concurrent requests
[--output=OUTPUT] # Write output to file
Usage
Simple
proxy_tester requires --proxy-argument with host:port and at least one
url given.
% proxy_tester test url --proxy localhost:3128 http://www.example.net
Fetching multiple urls
% proxy_tester test url --proxy localhost:3128 http://example.net http://www.example.org
Fetching multiple urls using shell expansion
% proxy_tester test url --proxy localhost:3128 http://example{1,2,3,4}.net
Fetching url via multiple proxies using shell expansion
% proxy_tester test url --proxy proxy{1,2,3}.localdomain:3128 http://example.net
Fetching url n-times
Using the --count-option proxy_tester tries to fetch the given url
--count-times within --timeout (default: 60s).
% proxy_tester test url --proxy proxy1.localdomain:3128 http://example.net --count 5
Fetching url concurrently via proxy using shell expansion
Using the --concurrent-option proxy_tester creates 10 Threads which try to
fetch the url as often as possible during 60 s. To change the number of threads
you can use the --count-option. To change the timeout value please use
--timeout.
% proxy_tester test url --proxy proxy1.localdomain:3128 http://example.net --concurrent
Write output to file instead of stdout
% proxy_tester test url --proxy proxy1.localdomain:3128 http://example.net --output /tmp/file.log
Contributing
- Fork it ( http://github.com/dg-vrnetze/proxy_tester/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request