Skip to content
Browse files

Merge branch 'verify-double'

  • Loading branch information...
2 parents 379254d + 1777ef3 commit 61e1b7749725ff98db8ad007cbabaca4832c2b12 @artemave artemave committed Nov 14, 2011
Showing with 1,821 additions and 513 deletions.
  1. +0 −1 .gitignore
  2. +3 −0 Gemfile
  3. +142 −0 Gemfile.lock
  4. +1 −1 Guardfile
  5. +124 −37 README.markdown
  6. +1 −2 bin/console
  7. +50 −7 bin/rest-assured
  8. +15 −0 db/migrate/20111013122857_create_requests.rb
  9. +9 −0 db/migrate/20111016174101_rename_method_to_verb.rb
  10. +9 −0 db/migrate/20111021113953_add_status_to_doubles.rb
  11. +74 −0 features/command_line_options.feature
  12. +0 −24 features/persistence.feature
  13. +18 −16 features/{doubles_via_api.feature → rest_api/doubles.feature}
  14. +11 −8 features/{redirect_rules_via_api.feature → rest_api/redirects.feature}
  15. +15 −0 features/ruby_api/verify_requests.feature
  16. +39 −0 features/ruby_api/wait_for_requests.feature
  17. +47 −0 features/step_definitions/command_line_options_steps.rb
  18. +24 −17 features/step_definitions/doubles_steps.rb
  19. +0 −13 features/step_definitions/persistence_steps.rb
  20. +24 −5 features/step_definitions/redirect_rules_steps.rb
  21. +69 −0 features/step_definitions/ruby_api_steps.rb
  22. +25 −2 features/support/env.rb
  23. +46 −0 features/support/test-server.rb
  24. +31 −0 features/support/world_helpers.rb
  25. 0 features/{doubles_via_ui.feature → web_ui/doubles.feature}
  26. 0 features/{redirect_rules_via_ui.feature → web_ui/redirects.feature}
  27. 0 lib/active_record/{leak_connection_patch.rb → leaky_connections_patch.rb}
  28. +9 −31 lib/rest-assured.rb
  29. +17 −0 lib/rest-assured/client.rb
  30. +18 −0 lib/rest-assured/client/resources.rb
  31. +162 −16 lib/rest-assured/config.rb
  32. +0 −25 lib/rest-assured/init.rb
  33. +36 −21 lib/rest-assured/models/double.rb
  34. +33 −31 lib/rest-assured/models/redirect.rb
  35. +16 −0 lib/rest-assured/models/request.rb
  36. +25 −11 lib/rest-assured/routes/double.rb
  37. +10 −7 lib/rest-assured/routes/redirect.rb
  38. +22 −0 lib/rest-assured/routes/response.rb
  39. +1 −1 lib/rest-assured/version.rb
  40. +25 −0 lib/sinatra/handler_options_patch.rb
  41. 0 views/base.scss → public/css/base.css
  42. +1 −3 rest-assured.gemspec
  43. +83 −0 spec/client/resource_double_spec.rb
  44. +133 −0 spec/config_spec.rb
  45. +7 −0 spec/custom_matchers.rb
  46. +117 −83 spec/functional/double_routes_spec.rb
  47. +86 −75 spec/functional/redirect_routes_spec.rb
  48. +80 −0 spec/functional/response_spec.rb
  49. +67 −49 spec/models/double_spec.rb
  50. +28 −25 spec/models/redirect_spec.rb
  51. +17 −0 spec/models/request_spec.rb
  52. +22 −2 spec/spec_helper.rb
  53. +14 −0 ssl/localhost.crt
  54. +15 −0 ssl/localhost.key
View
1 .gitignore
@@ -1,6 +1,5 @@
*.gem
.bundle
-Gemfile.lock
pkg/*
bundle_bin
*.db
View
3 Gemfile
@@ -17,4 +17,7 @@ gem 'launchy'
gem 'rake'
gem "spork", "> 0.9.0.rc"
gem "guard-spork"
+gem 'rb-fsevent' if RUBY_PLATFORM =~ /darwin/
gem 'sinatra-activerecord'
+gem 'mysql'
+gem 'sqlite3', '~> 1.3.4'
View
142 Gemfile.lock
@@ -0,0 +1,142 @@
+PATH
+ remote: .
+ specs:
+ rest-assured (0.2.0)
+ activerecord (~> 3.1.0)
+ activeresource (~> 3.1.0)
+ haml (>= 3.1.3)
+ rack-flash (>= 0.1.2)
+ sinatra (>= 1.3.1)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ activemodel (3.1.1)
+ activesupport (= 3.1.1)
+ builder (~> 3.0.0)
+ i18n (~> 0.6)
+ activerecord (3.1.1)
+ activemodel (= 3.1.1)
+ activesupport (= 3.1.1)
+ arel (~> 2.2.1)
+ tzinfo (~> 0.3.29)
+ activeresource (3.1.1)
+ activemodel (= 3.1.1)
+ activesupport (= 3.1.1)
+ activesupport (3.1.1)
+ multi_json (~> 1.0)
+ addressable (2.2.6)
+ arel (2.2.1)
+ awesome_print (0.4.0)
+ builder (3.0.0)
+ capybara (1.1.1)
+ mime-types (>= 1.16)
+ nokogiri (>= 1.3.3)
+ rack (>= 1.0.0)
+ rack-test (>= 0.5.4)
+ selenium-webdriver (~> 2.0)
+ xpath (~> 0.1.4)
+ capybara-firebug (0.0.10)
+ capybara (~> 1.0)
+ childprocess (0.2.2)
+ ffi (~> 1.0.6)
+ columnize (0.3.4)
+ cucumber (1.1.0)
+ builder (>= 2.1.2)
+ diff-lcs (>= 1.1.2)
+ gherkin (~> 2.5.0)
+ json (>= 1.4.6)
+ term-ansicolor (>= 1.0.6)
+ database_cleaner (0.6.7)
+ diff-lcs (1.1.3)
+ ffi (1.0.9)
+ gherkin (2.5.4)
+ json (>= 1.4.6)
+ guard (0.8.8)
+ thor (~> 0.14.6)
+ guard-spork (0.3.1)
+ guard (>= 0.8.4)
+ spork (>= 0.8.4)
+ haml (3.1.3)
+ i18n (0.6.0)
+ interactive_editor (0.0.10)
+ spoon (>= 0.0.1)
+ json (1.6.1)
+ json_pure (1.6.1)
+ launchy (2.0.5)
+ addressable (~> 2.2.6)
+ linecache (0.46)
+ rbx-require-relative (> 0.0.4)
+ mime-types (1.16)
+ multi_json (1.0.3)
+ mysql (2.8.1)
+ nokogiri (1.5.0)
+ rack (1.3.5)
+ rack-flash (0.1.2)
+ rack
+ rack-protection (1.1.4)
+ rack
+ rack-test (0.6.1)
+ rack (>= 1.0)
+ rake (0.9.2.2)
+ rb-fsevent (0.4.3.1)
+ rbx-require-relative (0.0.5)
+ rspec (2.7.0)
+ rspec-core (~> 2.7.0)
+ rspec-expectations (~> 2.7.0)
+ rspec-mocks (~> 2.7.0)
+ rspec-core (2.7.1)
+ rspec-expectations (2.7.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.7.0)
+ ruby-debug (0.10.4)
+ columnize (>= 0.1)
+ ruby-debug-base (~> 0.10.4.0)
+ ruby-debug-base (0.10.4)
+ linecache (>= 0.3)
+ rubyzip (0.9.4)
+ selenium-webdriver (2.9.1)
+ childprocess (>= 0.2.1)
+ ffi (= 1.0.9)
+ json_pure
+ rubyzip
+ shoulda-matchers (1.0.0.beta3)
+ sinatra (1.3.1)
+ rack (~> 1.3, >= 1.3.4)
+ rack-protection (~> 1.1, >= 1.1.2)
+ tilt (~> 1.3, >= 1.3.3)
+ sinatra-activerecord (0.1.3)
+ sinatra (>= 0.9.4)
+ spoon (0.0.1)
+ spork (0.9.0.rc9)
+ sqlite3 (1.3.4)
+ term-ansicolor (1.0.7)
+ thor (0.14.6)
+ tilt (1.3.3)
+ tzinfo (0.3.31)
+ xpath (0.1.4)
+ nokogiri (~> 1.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ awesome_print
+ capybara
+ capybara-firebug
+ cucumber
+ database_cleaner
+ guard-spork
+ interactive_editor
+ launchy
+ mysql
+ rack-test
+ rake
+ rb-fsevent
+ rest-assured!
+ rspec
+ ruby-debug
+ shoulda-matchers
+ sinatra-activerecord
+ spork (> 0.9.0.rc)
+ sqlite3 (~> 1.3.4)
View
2 Guardfile
@@ -4,7 +4,7 @@
guard 'spork', :cucumber_env => { 'RACK_ENV' => 'test' }, :rspec_env => { 'RACK_ENV' => 'test' } do
watch('Gemfile')
watch('Gemfile.lock')
- watch('spec/spec_helper.rb')
+ watch(%r{spec/.+\.rb})
watch(%r{features/support/.+\.rb$})
watch(%r{^lib/.+\.rb$})
end
View
161 README.markdown
@@ -2,82 +2,169 @@
## Overview
-A tool for stubbing/mocking external http based services that app under test is talking to. This is useful for blackbox testing or in cases where it is not possible to access application objects directly from test code.
+A tool for stubbing/mocking external http based services that your app under test interacts with. This is useful for blackbox/integration testing.
There are three main use cases:
-* stubbing out external data sources with predefined data, so that test code has known data to assert against
-* setting expectations on messages to external services (currently not yet implemented)
-* mimic different responses from external services during development. For that purpose there is web UI
+* stubbing out external data sources with predefined data
+* verify requests to external services
+* quickly emulate different behavior of external services during development (using web UI)
## Usage
-You are going to need ruby >= 1.8.7. Install gem and run:
+You are going to need ruby >= 1.8.7.
- bash$ sudo gem install rest-assured # omit sudo if using rvm
- bash$ rest-assured &
+First make sure there is database adapter:
+
+ bash$ gem install mysql # or sqlite
+
+If using mysql, rest-assured expects database 'rest\_assured' to be accessible by user 'root' with no password. Those are defaults and are changeable with command line options.
+
+Then install gem and run:
+
+ bash$ gem install rest-assured
+ bash$ rest-assured -a mysql &
Or clone from github and run:
bash$ git clone git@github.com:BBC/rest-assured.git
- bash$ cd rest-assured && bundle install # `gem install bundler` if command not found
- bash$ ./bin/rest-assured &
+ bash$ cd rest-assured && bundle install
+ bash$ ./bin/rest-assured -a mysql &
+
+This starts an instance of rest-assured on port 4578 (changable with --port option). You can now access it via REST or web interfaces on 'http://localhost:4578'
-This starts an instance of rest-assured on port 4578 (changable with --port option) and creates rest-assured.db (changable with --database option) in the current directory. You can now access it via REST or web interfaces on http://localhost:4578
+Various options (such as ssl, port, db credentials, etc.) are available through command line options. Check out `rest-assured -h` to see what they are.
+
+NOTE that although sqlite is an option, I found it locking under any non-trivial load. Mysql feels much more reliable. But may be that is me sqliting it wrong.
+
+## REST API
### Doubles
-Double is a stub/mock of a particular external call. There is the following rest API for setting up doubles:
+Double is a stub/mock of a particular external call.
+
+#### Ruby Client API
+
+Rest-assured provides client library which partially implements ActiveResource (create and get). To make it available put the following in your test setup code (e.g. env.rb)
+
+```ruby
+require 'rest-assured/client'
+
+RestAssured::Client.config.server_address = 'http://localhost:4578' # or wherever your rest-assured is
+```
+
+You can then create doubles in your tests
+
+```ruby
+RestAssured::Double.create(fullpath: '/products', content: 'this is content')
+```
+
+Or, in case you need verifications, create double in a Given part
+
+```ruby
+@double = RestAssured::Double.create(fullpath: '/products', verb: 'POST')
+```
+
+And verify requests happened on that double in a Then part
+
+```ruby
+@double.wait_for_requests(1, :timeout => 10) # default timeout 5 seconds
+
+req = @double.requests.first
-* `POST '/doubles', { fullpath: path, content: content, method: method }`
- Creates double with the following parameters:
+req.body.should == expected_payload
+JSON.parse(req.params).should == expected_params_hash
+JSON.parse(req.rack_env)['ACCEPT'].should == 'Application/json'
+```
- - __fullpath__ - e.g., `/some/api/object`, or with parameters in query string (useful for doubling GETs) - `/some/other/api/object?a=2&b=c`. Mandatory.
- - __content__ - whatever you want this double to respond with. Mandatory.
- - __method__ - one of http the following http verbs: GET, POST, PUT, DELETE. Optional. GET is default.
+#### Plain REST API
+
+##### Create double
+ HTTP POST to '/doubles.json' creates double and returns its json representation.
+ The following options can be passed as request parameters:
+
+ - __fullpath__ - e.g., '/some/api/object', or with parameters in query string (useful for doubling GETs) - '/some/other/api/object?a=2&b=c'. Mandatory.
+ - __content__ - whatever you want this double to respond with. Optional.
+ - __verb__ - one of http the following http verbs: GET, POST, PUT, DELETE. Optional. GET is default.
+ - __status__ - status returned when double is requested. Optional. 200 is default.
Example (using ruby RestClient):
- RestClient.post 'http://localhost:4578/doubles', { fullpath: '/api/v2/products?type=fresh', method: 'GET', content: 'this is list of products' }
+```ruby
+response = RestClient.post 'http://localhost:4578/doubles', { fullpath: '/api/v2/products?type=fresh', verb: 'GET', content: 'this is list of products', status: 200 }
+puts response.body
+```
+ Produces:
+
+ "{\"double\":{\"fullpath\":\"/api/v2/products?type=fresh\",\"verb\":\"GET\",\"id\":123,\"content\":\"this is list of products\",\"description\":null,\"status\":null,\"active\":true}}"
- Now GETting http://localhost:4578/api/v2/products?type=fresh (in browser for instance) should return "this is list of products".
+ And then GETting 'http://localhost:4578/api/v2/products?type=fresh' (in browser for instance) should return "this is list of products".
- If there is more than one double for the same request\_fullpath and method, the last created one gets served. In UI you can manually control which double is 'active' (gets served).
+ If there is more than one double for the same fullpath and verb, the last created one gets served. In UI you can manually control which double is 'active' (gets served).
-* `DELETE '/doubles/all'`
- Deletes all doubles.
+##### Get double state
+ HTTP GET to '/double/:id.json' returns json with double current state. Use id from create json as :id.
+
+ Example (using ruby RestClient):
+
+```ruby
+response = RestClient.get 'http://localhost:4578/doubles/123.json'
+puts response.body
+```
+
+ Assuming the above double has been requested once, this call would produce
+
+ "{\"double\":{\"fullpath\":\"/api/v2/products?type=fresh\",\"verb\":\"GET\",\"id\":123,\"requests\":[{\"rack_env\":\"LOOK FOR YOUR HEADERS HERE\",\"created_at\":\"2011-11-07T18:34:21+00:00\",\"body\":\"\",\"params\":\"{}\"}],\"content\":\"this is list of products\",\"description\":null,\"status\":null,\"active\":true}}"
+
+ The important bit here is 'requests' array. This is history of requests for that double (in chronological order). Each element contains the following data (keys):
+
+ - __body__ - request payload
+ - __params__ - request parameters
+ - __created_at__ - request timestamp
+ - __rack_env__ - raw request dump (key value pairs). Including request headers
+
+##### Delete all doubles
+ HTTP DELETE to '/doubles/all' deletes all doubles. Useful for cleaning up between tests.
### Redirects
It is sometimes desirable to only double certain calls while letting others through to the 'real' services. Meet Redirects. Kind of "rewrite rules" for requests that didn't match any double. Here is the rest API for managing redirects:
-* `POST '/redirects', { pattern: pattern, to: uri }` Creates redirect with the following parameters:
+#### Create redirect
+ HTTP POST to '/redirects' creates redirect.
+ The following options can be passed as request parameters:
- __pattern__ - regex (perl5 style) tested against request fullpath. Mandatory
- - __to__ - url base e.g., `https://myserver:8787/api`. Mandatory
+ - __to__ - url base e.g., 'https://myserver:8787/api'. Mandatory
Example (using ruby RestClient):
- RestClient.post 'http://localhost:4578/redirects', { pattern: '^/auth', to: 'https://myserver.com/api' }
+```ruby
+RestClient.post 'http://localhost:4578/redirects', { pattern: '^/auth', to: 'https://myserver.com/api' }
+```
- Now request (any method) to http://localhost:4578/auth/services/1 will get redirected to https://myserver.com/api/auth/services/1. Provided of course there is no double matched for that fullpath and method.
+ Now request (any verb) to 'http://localhost:4578/auth/services/1' will get redirected to 'https://myserver.com/api/auth/services/1.' Provided of course there is no double matched for that fullpath and verb.
Much like rewrite rules, redirects are evaluated in order (of creation). In UI you can manually rearrange the order.
-### Storage
-
-By default when you start rest-assured it creates (unless already exists) sqlite database and stores it into file in the current directory. This is good for using it for development - when you want doubles/redirects to persist across restarts - but may not be so desirable for using with tests, where you want each test run to start from blank slate. For that reason, you can specify `--database :memory:` so that database is kept in memory.
-
-### Logging
-
-It is sometimes useful to see what requests rest-assured is being hit. Either to explore what requests your app is making or to check that test setup is right and doubles indeed get returned. By default, when started, rest-assured creates log file in the current directory. This is configurable with `--logfile` option.
+#### Delete all redirects
+ HTTP DELETE to '/redirects/all' deletes all redirects. Useful for cleaning up between tests.
## TODO
-* Implement expectations
-* Support headers (extends previous point)
-* Ruby client library
-* Support methods in UI (at the moment it is always GET)
-* Don't allow to double internal routes. Just in case
+* Hide wiring rest-assured into ruby project behind client api
+* Bring UI upto date with rest-api (add verbs, statuses, request history)
+* Add custom response headers
## Author
[Artem Avetisyan](https://github.com/artemave)
+
+## Changelog
+
+0.2
+ - adds verifications
+ - adds ruby client
+ - adds custom return statuses
+ - adds ssl
+ - adds mysql support
+0.1 initial public release
+
View
3 bin/console
@@ -1,11 +1,10 @@
#!/usr/bin/env ruby
require "irb"
-require 'bundler/setup'
$:.push File.expand_path('../../lib', __FILE__)
require 'rest-assured/config'
-AppConfig[:database] = ENV['FRS_DB'] || File.expand_path('../../db/development.db', __FILE__)
+RestAssured::Config.build :database => ( ENV['FRS_DB'] || File.expand_path('../../db/development.db', __FILE__) )
require 'rest-assured'
View
57 bin/rest-assured
@@ -6,27 +6,70 @@ require 'rubygems'
require 'optparse'
require 'rest-assured/config'
+user_opts = {}
+
OptionParser.new do |opts|
opts.banner = "Usage: rest-assured [options]"
- opts.on('-d', '--database FILENAME', "Path to database file. Defaults to ./rest-assured.db. There is a special value ':memory:' for in memory database.") do |fname|
- AppConfig[:database] = fname
+ opts.on('-a', '--adapter mysql|sqlite') do |adapter|
+ user_opts[:adapter] = adapter
+ end
+
+ opts.on('-d', '--database FILENAME', "Either path to database file (sqlite, defaults to ./rest-assured.db) or db name (defaults to rest_assured)") do |fname|
+ user_opts[:database] = fname
+ end
+
+ opts.on('-u', '--dbuser DBUSER', "Db username (mysql only)") do |user|
+ user_opts[:user] = user
+ end
+
+ opts.on('--dbpass DBPASSWORD', 'Db password (mysql only). Defaults to empty') do |password|
+ user_opts[:dbpass] = password
+ end
+
+ opts.on('--dbhost DBHOST', 'Db host (mysql only). Defaults to mysql default host (localhost)') do |dbhost|
+ user_opts[:dbhost] = dbhost
+ end
+
+ opts.on('--dbport DBPORT', Integer, 'Db port (mysql only). Defaults to mysql default port (3306)') do |dbport|
+ user_opts[:dbport] = dbport
+ end
+
+ opts.on('--dbencoding DBENCODING', 'Db encoding (mysql only). Defaults to mysql default encoding') do |dbencoding|
+ user_opts[:dbencoding] = dbencoding
+ end
+
+ opts.on('--dbsocket DBSOCKET', 'Db socket (mysql only). Defaults to mysql default socket') do |dbsocket|
+ user_opts[:dbsocket] = dbsocket
end
opts.on('-p', '--port PORT', Integer, "Server port. Defaults to 4578") do |port|
- AppConfig[:port] = port
+ user_opts[:port] = port
end
- opts.on('-l', '--logfile FILENAME', "Path to logfile. Defaults to ./rest-assured.log") do |log_file|
- AppConfig[:log_file] = log_file
+ opts.on('-l', '--logfile FILENAME', "Path to logfile. Defaults to ./rest-assured.log") do |logfile|
+ user_opts[:logfile] = logfile
end
- opts.on_tail("-h", "--help", "Show this message") do
+ opts.on('--ssl', "Whether or not to run on https. Defaults to false (http)") do |use_ssl|
+ user_opts[:use_ssl] = true
+ end
+
+ opts.on('-c', '--ssl_cert FILENAME', "Path to ssl_cert. Defaults to self signed certificate shipped with rest-assured") do |ssl_cert|
+ user_opts[:ssl_cert] = ssl_cert
+ end
+
+ opts.on('-k', '--ssl_key FILENAME', "Path to ssl_key. Defaults to key shipped with rest-assured") do |ssl_key|
+ user_opts[:ssl_key] = ssl_key
+ end
+
+ opts.on_tail("--help", "Show this message") do
puts opts
exit
end
end.parse!
-require 'rest-assured'
+RestAssured::Config.build(user_opts)
+require 'rest-assured'
RestAssured::Application.run!
View
15 db/migrate/20111013122857_create_requests.rb
@@ -0,0 +1,15 @@
+class CreateRequests < ActiveRecord::Migration
+ def self.up
+ create_table :requests do |t|
+ t.integer :double_id
+ t.text :params
+ t.text :body
+ t.text :rack_env
+ t.datetime :created_at
+ end
+ end
+
+ def self.down
+ drop_table :requests
+ end
+end
View
9 db/migrate/20111016174101_rename_method_to_verb.rb
@@ -0,0 +1,9 @@
+class RenameMethodToVerb < ActiveRecord::Migration
+ def self.up
+ rename_column :doubles, :method, :verb
+ end
+
+ def self.down
+ rename_column :doubles, :verb, :method
+ end
+end
View
9 db/migrate/20111021113953_add_status_to_doubles.rb
@@ -0,0 +1,9 @@
+class AddStatusToDoubles < ActiveRecord::Migration
+ def self.up
+ add_column :doubles, :status, :integer
+ end
+
+ def self.down
+ remove_column :doubles, :status
+ end
+end
View
74 features/command_line_options.feature
@@ -0,0 +1,74 @@
+Feature: command line options
+ In order to run rest-assured in different configurations (db params, port, etc)
+ As test developer
+ I need a way to specify those configurations.
+
+ Scenario Outline: specifying server port
+ When I start rest-assured with <option>
+ Then it should run on port <port>
+
+ Examples:
+ | option | port |
+ | -p 1234 | 1234 |
+ | --port 1235 | 1235 |
+ | | 4578 |
+
+ Scenario Outline: specifying log file
+ When I start rest-assured with <option>
+ Then the log file should be <logfile>
+
+ Examples:
+ | option | logfile |
+ | -l /tmp/rest-assured.log | /tmp/rest-assured.log |
+ | --logfile ./test.log | ./test.log |
+ | | ./rest-assured.log |
+
+ Scenario Outline: sqlite options
+ When I start rest-assured with <options>
+ Then database adapter should be sqlite and db file should be <dbfile>
+
+ Examples:
+ | options | dbfile |
+ | | ./rest-assured.db |
+ | -d /tmp/ratest.db | /tmp/ratest.db |
+ | --database /tmp/resta.db | /tmp/resta.db |
+
+ Scenario Outline: mysql options
+ When I start rest-assured with -a mysql <options>
+ Then database options should be:
+ | dbname | dbuser | dbpass | dbhost | dbport | dbencoding | dbsocket |
+ | <dbname> | <dbuser> | <dbpass> | <dbhost> | <dbport> | <dbencoding> | <dbsocket> |
+
+ Examples:
+ | options | dbname | dbuser | dbpass | dbhost | dbport | dbencoding | dbsocket |
+ | | rest_assured | root | | | | | |
+ | -d resta | resta | root | | | | | |
+ | --database resta | resta | root | | | | | |
+ | -u bob | rest_assured | bob | | | | | |
+ | --dbuser bob | rest_assured | bob | | | | | |
+ | --dbpass pswd | rest_assured | root | pswd | | | | |
+ | --dbhost remote | rest_assured | root | | remote | | | |
+ | --dbport 5555 | rest_assured | root | | | 5555 | | |
+ | --dbencoding utf16le | rest_assured | root | | | | utf16le | |
+ | --dbsocket /tmp/mysql.sock | rest_assured | root | | | | | /tmp/mysql.sock |
+
+ Scenario Outline: use ssl option
+ When I start rest-assured with <option>
+ Then rest-assured should "<use_ssl>"
+
+ Examples:
+ | option | use_ssl |
+ | | false |
+ | --ssl | true |
+
+ Scenario Outline: specifying ssl options
+ When I start rest-assured with <option>
+ Then ssl certificate used should be "<ssl_cert>" and ssl key should be "<ssl_key>"
+
+ Examples:
+ | option | ssl_cert | ssl_key |
+ | -c /tmp/mycert.crt | /tmp/mycert.crt | DEFAULT_KEY |
+ | --ssl_cert ./mycert.crt | ./mycert.crt | DEFAULT_KEY |
+ | | DEFAULT_CERT | DEFAULT_KEY |
+ | -k /tmp/mykey.key | DEFAULT_CERT | /tmp/mykey.key |
+ | --ssl_key ./mykey.key | DEFAULT_CERT | ./mykey.key |
View
24 features/persistence.feature
@@ -1,24 +0,0 @@
-Feature: Persistence
- In order to persist fixtrures/redirects between service restarts
- As a developer
- I want to be able to specify persistent storage
-
- Scenario: default storage
- Given I start service without --database option
- And I register "/api/something" as fullpath and "content" as response content
- And I restart service without --database option
- When I request "/api/something"
- Then I should get 404 in response status
-
- Scenario Outline: specify storage
- Given I start service with --database "<db>" option
- And I register "/api/something" as fullpath and "content" as response content
- And I restart service with --database "<db2>" option
- When I request "/api/something"
- Then I should get <status> in response status
-
- Examples:
- | db | db2 | status |
- | database.db | database.db | 200 |
- | /tmp/database.db | /tmp/database.db | 200 |
- | database.db | database2.db | 404 |
View
34 features/doubles_via_api.feature → features/rest_api/doubles.feature
@@ -4,28 +4,30 @@ Feature: use doubles via api
I want to mock rest services my app is consuming from
Scenario Outline: create double
- When I create a double with "<fullpath>" as fullpath, "<content>" as response content and "<method>" as request method
- Then there should be 1 double with "<fullpath>" as fullpath, "<content>" as response content and "<result_method>" as request method
+ When I create a double with "<fullpath>" as fullpath, "<content>" as response content, "<verb>" as request verb and status as "<status>"
+ Then there should be 1 double with "<fullpath>" as fullpath, "<content>" as response content, "<result_verb>" as request verb and status as "<result_status>"
Examples:
- | fullpath | content | method | result_method |
- | /api/something | created | POST | POST |
- | /api/sss | changed | PUT | PUT |
- | /api/asdfsf | removed | DELETE | DELETE |
- | /api/some | text content | GET | GET |
- | /api/some?a=3&b=dd | more content | | GET |
+ | fullpath | content | verb | result_verb | status | result_status |
+ | /api/something | created | POST | POST | 200 | 200 |
+ | /api/sss | changed | PUT | PUT | 201 | 201 |
+ | /api/asdfsf | removed | DELETE | DELETE | 300 | 300 |
+ | /api/some | text content | GET | GET | 303 | 303 |
+ | /api/some?a=3&b=dd | more content | | GET | | 200 |
+ | /api/empty | | POST | POST | | 200 |
Scenario Outline: request fullpath that matches double
- Given there is double with "<fullpath>" as fullpath, "<content>" as response content and "<method>" as request method
- When I "<method>" "<fullpath>"
- Then I should get "<content>" in response content
+ Given there is double with "<fullpath>" as fullpath, "<content>" as response content, "<verb>" as request verb and "<status>" as status
+ When I "<verb>" "<fullpath>"
+ Then I should get "<status>" as response status and "<content>" in response content
Examples:
- | fullpath | content | method |
- | /api/something | created | POST |
- | /api/sss | changed | PUT |
- | /api/asdfsf | removed | DELETE |
- | /api/some?a=3&b=dd | more content | GET |
+ | fullpath | content | verb | status |
+ | /api/something | created | POST | 200 |
+ | /api/sss | changed | PUT | 201 |
+ | /api/asdfsf | removed | DELETE | 202 |
+ | /api/some?a=3&b=dd | more content | GET | 203 |
+ | /other/api | | GET | 303 |
# current rule: last added double gets picked
Scenario Outline: request fullpath that matches multiple doubles
View
19 features/redirect_rules_via_api.feature → features/rest_api/redirects.feature
@@ -3,27 +3,30 @@ Feature: manage redirect rules
As a developer
I want to redirect to real api if there are no doubles for requested fullpath
- Background:
- Given there are no redirect rules
- And there are no doubles
-
Scenario: no redirect rules
+ Given blank slate
When I request "/api/something"
Then I should get 404
Scenario: add redirect rule
+ Given blank slate
When I register redirect with pattern "^/api" and uri "http://real.api.co.uk"
And I request "/api/something"
Then it should redirect to "http://real.api.co.uk/api/something"
Scenario: add second redirect that match the same request
- When I register redirect with pattern "/api/something" and uri "http://real.api.co.uk"
- And I register redirect with pattern "/api/some.*" and uri "http://real.com"
+ Given there is redirect with pattern "/api/something" and uri "http://real.api.co.uk"
+ When I register redirect with pattern "/api/some.*" and uri "http://real.com"
And I request "/api/something"
Then it should redirect to "http://real.api.co.uk/api/something"
Scenario: add second redirect that does not match the same request
- When I register redirect with pattern "/api/something" and uri "http://real.api.co.uk"
- And I register redirect with pattern "/api/some" and uri "http://real.com"
+ Given there is redirect with pattern "/api/something" and uri "http://real.api.co.uk"
+ When I register redirect with pattern "/api/some" and uri "http://real.com"
And I request "/api/someth"
Then it should redirect to "http://real.com/api/someth"
+
+ Scenario: clear redirects
+ Given there are some redirects
+ When I delete all redirects
+ Then there should be no redirects
View
15 features/ruby_api/verify_requests.feature
@@ -0,0 +1,15 @@
+Feature: check double's call history
+ In order to verify outcomes of my app that take form of http requests
+ As test developer
+ I want to be able to get double 'call history'
+
+ Scenario: no calls made to double
+ Given there is a double
+ When I request call history for that double
+ Then it should be empty
+
+ Scenario: some calls made to double
+ Given there is a double
+ When that double gets requested
+ And I request call history for that double
+ Then I should see history records for those requests
View
39 features/ruby_api/wait_for_requests.feature
@@ -0,0 +1,39 @@
+Feature: wait for requests on double to happen
+ In order to know when it is a good time to verify requests on a double
+ As test developer
+ I want to be able to wait until specified number of requests happen
+
+ Background:
+ Given I created a double:
+ """
+ @double = RestAssured::Double.create(:fullpath => '/some/api')
+ """
+
+ Scenario: succesfully wait for requests
+ When I wait for 3 requests:
+ """
+ @double.wait_for_requests(3)
+ """
+ And that double gets requested 3 times
+ Then it should let me through
+
+ Scenario: wait for requests that never come
+ When I wait for 3 requests:
+ """
+ @double.wait_for_requests(3)
+ """
+ And that double gets requested 2 times
+ Then it should wait for 5 seconds (default timeout)
+ And it should raise MoreRequestsExpected error after with the following message:
+ """
+ Expected 3 requests. Got 2.
+ """
+
+ @now
+ Scenario: custom timeout
+ When I wait for 3 requests:
+ """
+ @double.wait_for_requests(3, :timeout => 3)
+ """
+ And that double gets requested 2 times
+ Then it should wait for 3 seconds
View
47 features/step_definitions/command_line_options_steps.rb
@@ -0,0 +1,47 @@
+When /^I start rest\-assured with (.*)$/ do |options|
+ @app_config = fake_start_rest_assured(options)
+end
+
+Then /^it should run on port (\d+)$/ do |port|
+ @app_config[:port].should == port
+end
+
+Then /^the log file should be (.*)$/ do |logfile|
+ @app_config[:logfile].should == logfile
+ `rm #{logfile}`
+end
+
+Then /^database adapter should be sqlite and db file should be (.*)$/ do |dbfile|
+ @app_config[:db_config][:database].should == dbfile
+ @app_config[:db_config][:adapter].should == 'sqlite3'
+ `rm #{dbfile}`
+end
+
+Then /^database options should be:$/ do |table|
+ res = table.hashes.first
+
+ empty_to_nil = lambda do |string|
+ string.empty? ? nil : string
+ end
+
+ @app_config[:db_config][:adapter].should == 'mysql'
+ @app_config[:db_config][:database].should == res['dbname']
+ @app_config[:db_config][:user].should == res['dbuser']
+ @app_config[:db_config][:password].should == empty_to_nil[res['dbpass']]
+ @app_config[:db_config][:host].should == empty_to_nil[res['dbhost']]
+ @app_config[:db_config][:port].should == empty_to_nil[res['dbport']].try(:to_i)
+ @app_config[:db_config][:encoding].should == empty_to_nil[res['dbencoding']]
+ @app_config[:db_config][:socket].should == empty_to_nil[res['dbsocket']]
+end
+
+Then /^ssl certificate used should be "([^"]*)" and ssl key should be "([^"]*)"$/ do |ssl_cert, ssl_key|
+ ssl_cert = File.expand_path('../../../ssl/localhost.crt', __FILE__) if ssl_cert == 'DEFAULT_CERT'
+ ssl_key = File.expand_path('../../../ssl/localhost.key', __FILE__) if ssl_key == 'DEFAULT_KEY'
+
+ @app_config[:ssl_cert].should == ssl_cert
+ @app_config[:ssl_key].should == ssl_key
+end
+
+Then /^rest\-assured should "([^"]*)"$/ do |use|
+ @app_config[:use_ssl].to_s.should == use
+end
View
41 features/step_definitions/doubles_steps.rb
@@ -1,33 +1,37 @@
# REST api steps
Given /^there are no doubles$/ do
- Double.destroy_all
+ RestAssured::Models::Double.destroy_all
end
When /^I create a double with "([^"]*)" as fullpath and "([^"]*)" as response content$/ do |fullpath, content|
post '/doubles', { :fullpath => fullpath, :content => content }
last_response.should be_ok
end
-When /^I create a double with "([^"]*)" as fullpath, "([^"]*)" as response content and "([^"]*)" as request method$/ do |fullpath, content, method|
- post '/doubles', { :fullpath => fullpath, :content => content, :method => method }
+When /^I create a double with "([^""]*)" as fullpath, "([^""]*)" as response content, "([^""]*)" as request verb and status as "([^""]*)"$/ do |fullpath, content, verb, status|
+ post '/doubles', { :fullpath => fullpath, :content => content, :verb => verb, :status => status }
last_response.should be_ok
end
+Then /^I should get (#{CAPTURE_A_NUMBER}) in response status$/ do |status|
+ last_response.status.should == status
+end
+
Then /^there should be (#{CAPTURE_A_NUMBER}) double with "([^"]*)" as fullpath and "([^"]*)" as response content$/ do |n, fullpath, content|
- Double.where(:fullpath => fullpath, :content => content).count.should == 1
+ RestAssured::Models::Double.where(:fullpath => fullpath, :content => content).count.should == n
end
-Then /^there should be (#{CAPTURE_A_NUMBER}) double with "([^"]*)" as fullpath, "([^"]*)" as response content and "([^"]*)" as request method$/ do |n, fullpath, content, method|
- Double.where(:fullpath => fullpath, :content => content, :method => method).count.should == n
+Then /^there should be (#{CAPTURE_A_NUMBER}) double with "([^""]*)" as fullpath, "([^""]*)" as response content, "([^""]*)" as request verb and status as "(#{CAPTURE_A_NUMBER})"$/ do |n, fullpath, content, verb, status|
+ RestAssured::Models::Double.where(:fullpath => fullpath, :content => content, :verb => verb, :status => status).count.should == n
end
Given /^there is double with "([^"]*)" as fullpath and "([^"]*)" as response content$/ do |fullpath, content|
- Double.create(:fullpath => fullpath, :content => content)
+ RestAssured::Models::Double.create(:fullpath => fullpath, :content => content)
end
-Given /^there is double with "([^"]*)" as fullpath, "([^"]*)" as response content and "([^"]*)" as request method$/ do |fullpath, content, method|
- Double.create(:fullpath => fullpath, :content => content, :method => method)
+Given /^there is double with "([^"]*)" as fullpath, "([^"]*)" as response content, "([^"]*)" as request verb and "([^"]*)" as status$/ do |fullpath, content, verb, status|
+ RestAssured::Models::Double.create(:fullpath => fullpath, :content => content, :verb => verb, :status => status)
end
Given /^I register "([^"]*)" as fullpath and "([^"]*)" as response content$/ do |fullpath, content|
@@ -39,17 +43,18 @@
get fullpath
end
-When /^I "([^"]*)" "([^"]*)"$/ do |method, fullpath|
- send(method.downcase, fullpath)
+When /^I "([^"]*)" "([^"]*)"$/ do |verb, fullpath|
+ send(verb.downcase, fullpath)
end
-Then /^I should get "([^"]*)" in response content$/ do |content|
+Then /^I should get (?:"(#{CAPTURE_A_NUMBER})" as response status and )?"([^"]*)" in response content$/ do |status, content|
+ last_response.status.should == status if status.present?
last_response.body.should == content
end
Given /^there are some doubles$/ do
[['fullpath1', 'content1'], ['fullpath2', 'content2'], ['fullpath3', 'content3']].each do |double|
- Double.create(:fullpath => double[0], :content => double[1])
+ RestAssured::Models::Double.create(:fullpath => double[0], :content => double[1])
end
end
@@ -59,14 +64,14 @@
end
Then /^there should be no doubles$/ do
- Double.count.should == 0
+ RestAssured::Models::Double.count.should == 0
end
# UI steps
Given /^the following doubles exist:$/ do |doubles|
doubles.hashes.each do |row|
- Double.create(:fullpath => row['fullpath'], :description => row['description'], :content => row['content'])
+ RestAssured::Models::Double.create(:fullpath => row['fullpath'], :description => row['description'], :content => row['content'])
end
end
@@ -110,8 +115,8 @@
end
Given /^there are two doubles for the same fullpath$/ do
- @first = Double.create :fullpath => '/api/something', :content => 'some content'
- @second = Double.create :fullpath => '/api/something', :content => 'other content'
+ @first = RestAssured::Models::Double.create :fullpath => '/api/something', :content => 'some content'
+ @second = RestAssured::Models::Double.create :fullpath => '/api/something', :content => 'other content'
end
When /^I make (first|second) double active$/ do |ord|
@@ -121,6 +126,7 @@
end
Then /^(first|second) double should be served$/ do |ord|
+ sleep 0.1 # allow time for change to end up in the db
f = instance_variable_get('@' + ord)
get f.fullpath
last_response.body.should == f.content
@@ -141,3 +147,4 @@
Then /^I should be asked to confirm delete$/ do
page.driver.browser.switch_to.alert.accept
end
+
View
13 features/step_definitions/persistence_steps.rb
@@ -1,13 +0,0 @@
-Given /^I (?:re)?start service without \-\-database option$/ do
- AppConfig[:database] = ':memory:' #default value
- load 'rest-assured/init.rb'
-end
-
-Then /^I should get (#{CAPTURE_A_NUMBER}) in response status$/ do |status|
- last_response.status.should == status
-end
-
-Given /^I (?:re)?start service with \-\-database "([^"]*)" option$/ do |db_path|
- AppConfig[:database] = db_path
- load 'rest-assured/init.rb'
-end
View
29 features/step_definitions/redirect_rules_steps.rb
@@ -1,24 +1,28 @@
Given /^there are no redirect rules$/ do
- Redirect.destroy_all
+ RestAssured::Models::Redirect.destroy_all
end
Then /^I should get (\d+)$/ do |code|
last_response.status.should.to_s == code
end
-When /^I register redirect with pattern "([^"]*)" and uri "([^"]*)"$/ do |pattern, uri|
- post '/redirects', { :pattern => pattern, :to => uri }
+Given /^there is redirect with pattern "([^"]*)" and uri "([^"]*)"$/ do |pattern, url|
+ post '/redirects', { :pattern => pattern, :to => url }
last_response.should be_ok
end
+When /^I register redirect with pattern "([^"]*)" and uri "([^"]*)"$/ do |pattern, url|
+ Given %{there is redirect with pattern "#{pattern}" and uri "#{url}"}
+end
+
Then /^it should redirect to "([^"]*)"$/ do |real_api_url|
follow_redirect!
last_response.header['Location'].should == real_api_url
end
Given /^the following redirects exist:$/ do |redirects|
redirects.hashes.each do |row|
- Redirect.create(:pattern => row['pattern'], :to => row['to'])
+ RestAssured::Models::Redirect.create(:pattern => row['pattern'], :to => row['to'])
end
end
@@ -50,7 +54,7 @@
end
When /^I reorder second redirect to be the first one$/ do
- handler = find("#redirects #redirect_#{Redirect.last.id} td.handle")
+ handler = find("#redirects #redirect_#{RestAssured::Models::Redirect.last.id} td.handle")
target = find('#redirects thead')
handler.drag_to target
@@ -64,3 +68,18 @@
last_request.url.should == "#{url}#{missing_request}"
end
+
+Given /^blank slate$/ do
+end
+
+Given /^there are some redirects$/ do
+ RestAssured::Models::Redirect.create(:pattern => 'something', :to => 'somewhere')
+end
+
+When /^I delete all redirects$/ do
+ delete '/redirects/all'
+end
+
+Then /^there should be no redirects$/ do
+ RestAssured::Models::Redirect.count.should == 0
+end
View
69 features/step_definitions/ruby_api_steps.rb
@@ -0,0 +1,69 @@
+Given /^there is a double$/ do
+ @double = RestAssured::Double.create(:fullpath => '/some/path', :content => 'some content', :verb => 'POST')
+end
+
+When /^that double gets requested$/ do
+ post @double.fullpath, { :foo => 'bar' }.to_json, "CONTENT_TYPE" => "application/json"
+ post @double.fullpath, { :fooz => 'baaz'}, 'SOME_HEADER' => 'header_data'
+end
+
+When /^I request call history for that double$/ do
+ @requests = @double.reload.requests
+end
+
+Then /^I should see history records for those requests$/ do
+ @requests.first.body.should == { :foo => 'bar' }.to_json
+ JSON.parse( @requests.first.rack_env )["CONTENT_TYPE"].should == 'application/json'
+
+ JSON.parse( @requests.last.params ).should == { 'fooz' => 'baaz' }
+ JSON.parse( @requests.last.rack_env )["SOME_HEADER"].should == 'header_data'
+end
+
+Then /^it should be empty$/ do
+ @requests.size.should == 0
+end
+
+Given /^I created a double:$/ do |string|
+ # expected string is:
+ # @double = RestAssured::Double.create(:fullpath => '/some/api', :verb => 'POST')
+ eval string
+end
+
+When /^that double gets requested (#{CAPTURE_A_NUMBER}) times$/ do |num|
+ num.times do
+ sleep 0.5
+ send(@double.verb.downcase, @double.fullpath)
+ end
+end
+
+When /^I wait for (\d+) requests:$/ do |num, string|
+ # expected string
+ # @double.wait_for_requests(3)
+
+ @wait_start = Time.now
+ @t = Thread.new do
+ begin
+ eval string
+ rescue RestAssured::MoreRequestsExpected => e
+ @more_reqs_exc = e
+ end
+ end
+end
+
+Then /^it should let me through$/ do
+ @t.join
+ @more_reqs_exc.should == nil
+end
+
+Then /^it should wait for (#{CAPTURE_A_NUMBER}) seconds(?: \(default timeout\))?$/ do |timeout|
+ @t.join
+ wait_time = Time.now - @wait_start
+ #(timeout..(timeout+1)).should cover(wait_time) # cover() only avilable in 1.9
+ wait_time.should >= timeout
+ wait_time.should < timeout + 1
+end
+
+Then /^it should raise MoreRequestsExpected error after with the following message:$/ do |string|
+ @more_reqs_exc.should be_instance_of RestAssured::MoreRequestsExpected
+ @more_reqs_exc.message.should =~ /#{string}/
+end
View
27 features/support/env.rb
@@ -3,12 +3,13 @@
require 'spork'
Spork.prefork do
- require 'rspec/expectations'
+ require 'rspec'
require 'rack/test'
require 'capybara'
require 'capybara/firebug'
require 'capybara/cucumber'
require 'database_cleaner'
+ require File.dirname(__FILE__) + '/world_helpers'
ENV['RACK_ENV'] = 'test'
@@ -32,12 +33,31 @@ def browser.env=(env)
Capybara::Selenium::Driver.new(app, :browser => :firefox, :profile => profile)
end
- World(Capybara, Rack::Test::Methods, RackHeaderHack)
+ World(Capybara, Rack::Test::Methods, RackHeaderHack, WorldHelpers)
+
end
Spork.each_run do
+ require 'rest-assured/config'
+ RestAssured::Config.build(:adapter => 'mysql')
+
require 'rest-assured'
+ require 'rest-assured/client'
+ require File.expand_path('../test-server', __FILE__)
+
+ at_exit do
+ TestServer.stop
+ end
+
+ TestServer.start(:port => 9876)
+
+ while not TestServer.up?
+ puts 'Waiting for TestServer to come up...'
+ sleep 1
+ end
+
+ RestAssured::Client.config.server_address = 'http://localhost:9876'
def app
RestAssured::Application
@@ -55,7 +75,10 @@ def app
end
After do
+ sleep 0.1
DatabaseCleaner.clean
+
+ @t.join if @t.is_a?(Thread)
end
end
View
46 features/support/test-server.rb
@@ -0,0 +1,46 @@
+require 'net/http'
+
+# This is only needed till I get ActiveResource going through rack-test
+class TestServer
+ @pid_file = "./rest-assured.pid"
+
+ def self.start(opts = {})
+ @server_port = opts[:port] || 9876
+
+ print 'Starting TestServer server... '
+
+ p = Process.fork do
+ if get_pid
+ print "\nPrevious TestServer instance appears to be running. Will be using it."
+ else
+ Process.exec("bundle exec rest-assured -p #{@server_port} -a mysql")
+ end
+ end
+
+ Process.detach(p)
+ puts 'Done.'
+ end
+
+ def self.stop
+ print 'Shutting down TestServer server... '
+ Process.kill('TERM', get_pid.to_i) rescue puts( "Failed to kill TestServer server: #{$!}" )
+ puts 'Done.'
+ end
+
+ def self.server_address
+ "http://localhost:#{@server_port}"
+ end
+
+ def self.up?
+ Net::HTTP.new('localhost', @server_port).head('/')
+ true
+ rescue Errno::ECONNREFUSED
+ false
+ end
+
+ private
+
+ def self.get_pid
+ `ps -eo pid,args`.split("\n").grep( /rest-assured -p #{@server_port}/ ).map{|p| p.split.first }.first
+ end
+end
View
31 features/support/world_helpers.rb
@@ -0,0 +1,31 @@
+require 'yaml'
+require 'open3'
+
+module WorldHelpers
+ def fake_start_rest_assured(options)
+ rest_assured_exec = File.expand_path '../../../bin/rest-assured', __FILE__
+ code = File.read rest_assured_exec
+
+ code.sub!(/(.*)/, "\\1\nENV['RACK_ENV'] = 'production'")
+ code.sub!(/require 'rest-assured'/, '')
+ code.sub!(/RestAssured::Application.run!/, 'puts AppConfig.to_yaml')
+
+ new_exec = "#{rest_assured_exec}_temp"
+ File.open(new_exec, 'w') do |file|
+ file.write code
+ end
+
+ `chmod +x #{new_exec}`
+
+ # this is 1.9.X version. So much more useful than 1.8 (uncommented). Sigh...
+ #config_yaml, stderr_str, status = Open3.capture3({'RACK_ENV' => 'production'}, new_exec, *options.split(' '))
+ config_yaml = nil
+ Open3.popen3(new_exec, *options.split(' ')) do |stdin, stdout, stderr|
+ config_yaml = stdout.read
+ end
+
+ `rm #{new_exec}`
+
+ YAML.load(config_yaml)
+ end
+end
View
0 features/doubles_via_ui.feature → features/web_ui/doubles.feature
File renamed without changes.
View
0 features/redirect_rules_via_ui.feature → features/web_ui/redirects.feature
File renamed without changes.
View
0 lib/active_record/leak_connection_patch.rb → lib/active_record/leaky_connections_patch.rb
File renamed without changes.
View
40 lib/rest-assured.rb
@@ -1,28 +1,23 @@
require 'rubygems'
require 'sinatra/base'
+require 'sinatra/handler_options_patch'
require 'haml'
-require 'sass'
-#require 'sinatra/reloader'
require 'rack-flash'
require 'sinatra/partials'
-require 'rest-assured/init'
+require 'rest-assured/config'
require 'rest-assured/models/double'
require 'rest-assured/models/redirect'
+require 'rest-assured/models/request'
require 'rest-assured/routes/double'
require 'rest-assured/routes/redirect'
+require 'rest-assured/routes/response'
module RestAssured
class Application < Sinatra::Base
- set :environment, AppConfig[:environment]
- set :port, AppConfig[:port]
- enable :method_override
+ include Config
- Logger.class_eval do
- alias_method :write, :<<
- end
- enable :logging
- use Rack::CommonLogger, $app_logger
+ enable :method_override
enable :sessions
use Rack::Flash, :sweep => true
@@ -42,27 +37,10 @@ def browser?
include DoubleRoutes
include RedirectRoutes
- get '/css/base.css' do
- scss :base
- end
-
- %w{get post put delete}.each do |method|
- send method, /.*/ do
- Double.where(:fullpath => request.fullpath, :active => true, :method => method.upcase).first.try(:content) or try_redirect(request) or status 404
+ %w{get post put delete}.each do |verb|
+ send verb, /.*/ do
+ Response.perform(self)
end
end
-
- #configure(:development) do
- #register Sinatra::Reloader
- #end
-
- private
- def try_redirect(request)
- r = Redirect.ordered.find do |r|
- request.fullpath =~ /#{r.pattern}/
- end
-
- r && redirect( "#{r.to}#{request.fullpath}" )
- end
end
end
View
17 lib/rest-assured/client.rb
@@ -0,0 +1,17 @@
+require 'rest-assured/client/resources'
+
+module RestAssured
+ module Client
+ class Config
+ attr_reader :server_address
+
+ def server_address=(addr)
+ @server_address = Double.site = addr
+ end
+ end
+
+ def self.config
+ @config ||= Config.new
+ end
+ end
+end
View
18 lib/rest-assured/client/resources.rb
@@ -0,0 +1,18 @@
+require 'active_resource'
+
+module RestAssured
+ class MoreRequestsExpected < StandardError; end
+
+ class Double < ActiveResource::Base
+ def wait_for_requests(n, opts = {})
+ timeout = opts[:timeout] || 5
+
+ timeout.times do
+ sleep 1
+ reload
+ return if requests.count >= n
+ end
+ raise MoreRequestsExpected.new("Expected #{n} requests. Got #{requests.count}.")
+ end
+ end
+end
View
178 lib/rest-assured/config.rb
@@ -1,16 +1,162 @@
-AppConfig = {
- :port => 4578,
- :environment => ENV['RACK_ENV'] || 'production'
-}
-
-AppConfig[:database] = if AppConfig[:environment] == 'production'
- './rest-assured.db'
- else
- File.expand_path("../../../db/#{AppConfig[:environment]}.db", __FILE__)
- end
-
-AppConfig[:log_file] = if AppConfig[:environment] == 'production'
- './rest-assured.log'
- else
- File.expand_path("../../../#{AppConfig[:environment]}.log", __FILE__)
- end
+require 'logger'
+require 'active_record'
+require 'active_support/core_ext/kernel/reporting'
+require 'webrick'
+require 'webrick/https'
+
+module RestAssured
+ module Config
+ class ConfigHash < Hash
+ def initialize(default_values = {})
+ super()
+ self.merge!(default_values)
+ end
+
+ def method_missing(meth, *args)
+ meth = meth.to_s
+
+ if meth.sub!(/=/, '')
+ self[meth.to_sym] = args.first
+ else
+ self[meth.to_sym]
+ end
+ end
+ end
+
+ ::AppConfig = ConfigHash.new({
+ :port => 4578,
+ :environment => ENV['RACK_ENV'] || 'production',
+ :adapter => 'sqlite'
+ })
+
+ # this is meant to be called prior to include
+ def self.build(opts = {})
+ AppConfig.merge!(opts)
+
+ AppConfig.logfile ||= if AppConfig.environment == 'production'
+ './rest-assured.log'
+ else
+ File.expand_path("../../../#{AppConfig.environment}.log", __FILE__)
+ end
+ build_db_config
+ build_ssl_config
+ end
+
+ def self.included(klass)
+ init_logger
+ setup_db
+ setup_ssl(klass) if AppConfig.use_ssl
+
+ klass.set :port, AppConfig.port
+ klass.set :environment, AppConfig.environment
+
+ klass.enable :logging
+ klass.use Rack::CommonLogger, AppConfig.logger
+ end
+
+ private
+
+ def self.setup_ssl(klass)
+ ssl_config = {
+ :SSLEnable => true,
+ :SSLCertificate => OpenSSL::X509::Certificate.new( File.read( AppConfig.ssl_cert ) ),
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new( File.read( AppConfig.ssl_key ) ),
+ :SSLCertName => [ ["CN", WEBrick::Utils::getservername] ],
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE
+ }
+
+ klass.set :server, %[webrick]
+ klass.set :webrick, ssl_config
+ end
+
+ def self.setup_db
+ setup_db_logging
+ connect_db
+ migrate_db
+ end
+
+ def self.init_logger
+ Logger.class_eval do
+ alias_method :write, :<<
+ end
+
+ AppConfig.logger = Logger.new(AppConfig.logfile)
+ AppConfig.logger.level = Logger::DEBUG
+ end
+
+ def self.setup_db_logging
+ raise "Init logger first" unless AppConfig.logger
+
+ # active record logging is purely internal
+ # thus disabling it for production
+ ActiveRecord::Base.logger = if AppConfig.environment == 'production'
+ Logger.new(dev_null)
+ else
+ AppConfig.logger
+ end
+ end
+
+ def self.dev_null
+ test('e', '/dev/null') ? '/dev/null' : 'NUL:'
+ end
+
+ def self.connect_db
+ ActiveRecord::Base.establish_connection AppConfig.db_config
+ end
+
+ def self.migrate_db
+ migrate = lambda { ActiveRecord::Migrator.migrate(File.expand_path('../../../db/migrate', __FILE__)) }
+
+ if AppConfig[:environment] == 'production' && Kernel.respond_to?(:silence)
+ silence(:stdout, &migrate)
+ else
+ migrate.call
+ end
+ end
+
+ def self.build_db_config
+ AppConfig.db_config = if AppConfig.adapter =~ /sqlite/i
+ default_database = if AppConfig.environment == 'production'
+ './rest-assured.db'
+ else
+ File.expand_path("../../../db/#{AppConfig.environment}.db", __FILE__)
+ end
+ {
+ :adapter => 'sqlite3',
+ :database => AppConfig.database || default_database
+ }
+ elsif AppConfig.adapter =~ /mysql/i
+ default_database = if AppConfig.environment != 'production'
+ "rest_assured_#{AppConfig.environment}"
+ else
+ 'rest_assured'
+ end
+
+ opts = {
+ :adapter => 'mysql',
+ :reconnect => true,
+ :pool => 20,
+ :user => AppConfig.user || 'root',
+ :database => AppConfig.database || default_database
+ }
+
+ opts[:password] = AppConfig.dbpass if AppConfig.dbpass
+ opts[:host] = AppConfig.dbhost if AppConfig.dbhost
+ opts[:port] = AppConfig.dbport if AppConfig.dbport
+ opts[:encoding] = AppConfig.dbencoding if AppConfig.dbencoding
+ opts[:socket] = AppConfig.dbsocket if AppConfig.dbsocket
+ opts
+ else
+ raise "Unsupported db adapter '#{AppConfig.adapter}'. Valid adapters are sqlite and mysql"
+ end
+ end
+
+ def self.build_ssl_config
+ AppConfig.use_ssl ||= false
+ AppConfig.ssl_cert ||= File.expand_path('../../../ssl/localhost.crt', __FILE__)
+ AppConfig.ssl_key ||= File.expand_path('../../../ssl/localhost.key', __FILE__)
+ end
+ end
+end
+
+
View
25 lib/rest-assured/init.rb
@@ -1,25 +0,0 @@
-require 'active_record'
-require 'active_record/leak_connection_patch'
-require 'rest-assured/config'
-require 'logger'
-
-$app_logger = Logger.new(AppConfig[:log_file])
-$app_logger.level = Logger::DEBUG
-
-# active record logging is purely internal
-# thus disabling it for production
-ActiveRecord::Base.logger = if AppConfig[:environment] == 'production'
- Logger.new(test('e', '/dev/null') ? '/dev/null' : 'NUL:')
- else
- $app_logger
- end
-
-ActiveRecord::Base.establish_connection(
- :adapter => "sqlite3",
- :database => AppConfig[:database],
- :timeout => 10000
-)
-
-ActiveRecord::Migrator.migrate(
- File.expand_path('../../../db/migrate', __FILE__)
-)
View
57 lib/rest-assured/models/double.rb
@@ -1,32 +1,47 @@
-class Double < ActiveRecord::Base
- attr_accessible :fullpath, :content, :description, :method
+require 'net/http'
- METHODS = %w{GET POST PUT DELETE}
+module RestAssured
+ module Models
+ class Double < ActiveRecord::Base
+ attr_accessible :fullpath, :content, :description, :verb, :status
- validates_presence_of :fullpath, :content
- validates_inclusion_of :method, :in => METHODS
+ VERBS = %w{GET POST PUT DELETE}
+ STATUSES = Net::HTTPResponse::CODE_TO_OBJ.keys.map(&:to_i)
- before_save :toggle_active
- before_validation :set_method
- after_destroy :set_active
+ validates_presence_of :fullpath
+ validates_inclusion_of :verb, :in => VERBS
+ validates_inclusion_of :status, :in => STATUSES
- private
- def toggle_active
- ne = id ? '!=' : 'IS NOT'
+ before_save :toggle_active
+ before_validation :set_verb
+ before_validation :set_status
+ after_destroy :set_active
- if active && Double.where("fullpath = ? AND active = ? AND id #{ne} ?", fullpath, true, id).exists?
- Double.where("fullpath = ? AND id #{ne} ?", fullpath, id).update_all :active => false
+ has_many :requests, :dependent => :destroy
+
+ private
+ def toggle_active
+ ne = id ? '!=' : 'IS NOT'
+
+ if active && Double.where("fullpath = ? AND active = ? AND id #{ne} ?", fullpath, true, id).exists?
+ Double.where("fullpath = ? AND id #{ne} ?", fullpath, id).update_all :active => false
+ end
end
- end
- def set_method
- self.method = 'GET' unless method.present?
- end
+ def set_verb
+ self.verb = 'GET' unless verb.present?
+ end
+
+ def set_status
+ self.status = 200 unless status.present?
+ end
- def set_active
- if active && f = Double.where(:fullpath => fullpath).last
- f.active = true
- f.save
+ def set_active
+ if active && f = Double.where(:fullpath => fullpath).last
+ f.active = true
+ f.save
+ end
end
end
+ end
end
View
64 lib/rest-assured/models/redirect.rb
@@ -1,37 +1,39 @@
-class Redirect < ActiveRecord::Base
- attr_accessible :pattern, :to, :position
-
- validates_presence_of :pattern, :to
- validates_uniqueness_of :position, :allow_blank => true
-
- scope :ordered, order('position')
-
- before_create :assign_position
-
- def self.update_order(ordered_redirect_ids)
- success = true
-
- transaction do
- begin
- update_all :position => nil
-
- ordered_redirect_ids.each_with_index do |r_id, idx|
- r = find(r_id)
- r.position = idx
- r.save!
+module RestAssured
+ module Models
+ class Redirect < ActiveRecord::Base
+ attr_accessible :pattern, :to, :position
+
+ validates_presence_of :pattern, :to
+
+ scope :ordered, order('position')
+
+ before_create :assign_position
+
+ def self.update_order(ordered_redirect_ids)
+ success = true
+
+ transaction do
+ begin
+ ordered_redirect_ids.each_with_index do |r_id, idx|
+ r = find(r_id)
+ r.position = idx
+ r.save!
+ end
+ rescue => e
+ # TODO log exception
+ puts e.inspect
+ success = false
+ raise ActiveRecord::Rollback
+ end
end
- rescue
- # TODO log exception
- success = false
- raise ActiveRecord::Rollback
+ success
end
- end
- success
- end
- private
+ private
- def assign_position
- self.position = ( self.class.maximum(:position) || -1 ) + 1
+ def assign_position
+ self.position = ( self.class.maximum(:position) || -1 ) + 1
+ end
end
+ end
end
View
16 lib/rest-assured/models/request.rb
@@ -0,0 +1,16 @@
+module RestAssured
+ module Models
+ class Request < ActiveRecord::Base
+ belongs_to :double
+
+ validates_presence_of :rack_env
+
+ after_create :save_created_at
+
+ private
+ def save_created_at
+ self.created_at = Time.now
+ end
+ end
+ end
+end
View
36 lib/rest-assured/routes/double.rb
@@ -1,3 +1,5 @@
+require 'json'
+
module RestAssured
module DoubleRoutes
def self.included(router)
@@ -6,18 +8,28 @@ def self.included(router)
end
router.get '/doubles' do
- @doubles = Double.all
+ @doubles = Models::Double.all
haml :'doubles/index'
end
router.get '/doubles/new' do
- @double = Double.new
+ @double = Models::Double.new
haml :'doubles/new'
end
- router.post '/doubles' do
- f = { :fullpath => params['fullpath'], :content => params['content'], :description => params['description'], :method => params['method'] }
- @double = Double.create(params['double'] || f)
+ router.get '/doubles/:id.json' do |id|
+ begin
+ double = Models::Double.find(id)
+ body double.to_json(:include => :requests)
+ rescue ActiveRecord::RecordNotFound
+ status 404
+ end
+ end
+
+ router.post /^\/doubles(\.json)?$/ do |passes_json|
+ f = { :fullpath => params['fullpath'], :content => params['content'], :description => params['description'], :verb => params['verb'], :status => params['status'] }
+
+ @double = Models::Double.create(passes_json ? JSON.parse(request.body.read)['double'] : ( params['double'] || f ))
if browser?
if @double.errors.blank?
@@ -29,19 +41,21 @@ def self.included(router)
end
else
if @double.errors.present?
- status 400
- body @double.errors.full_messages.join("\n")
+ status 422
+ body @double.errors.to_json
+ else
+ body @double.to_json
end
end
end
router.get %r{/doubles/(\d+)/edit} do |id|
- @double = Double.find(id)
+ @double = Models::Double.find(id)
haml :'doubles/edit'
end
router.put %r{/doubles/(\d+)} do |id|
- @double = Double.find(id)
+ @double = Models::Double.find(id)
if request.xhr?
if params['active']
@@ -63,14 +77,14 @@ def self.included(router)
end
router.delete %r{/doubles/(\d+)} do |id|
- if Double.destroy(id)
+ if Models::Double.destroy(id)
flash[:notice] = 'Double deleted'
redirect '/doubles'
end
end
router.delete '/doubles/all' do
- status Double.delete_all ? 200 : 500
+ status Models::Double.delete_all ? 200 : 500
end
end
end
View
17 lib/rest-assured/routes/redirect.rb
@@ -2,17 +2,17 @@ module RestAssured
module RedirectRoutes
def self.included(router)
router.get '/redirects' do
- @redirects = Redirect.ordered
+ @redirects = Models::Redirect.ordered
haml :'redirects/index'
end
router.get '/redirects/new' do
- @redirect = Redirect.new
+ @redirect = Models::Redirect.new
haml :'redirects/new'
end
router.post '/redirects' do
- @redirect = Redirect.create(params['redirect'] || { :pattern => params['pattern'], :to => params['to'] })
+ @redirect = Models::Redirect.create(params['redirect'] || { :pattern => params['pattern'], :to => params['to'] })
if browser?
if @redirect.errors.blank?
@@ -31,12 +31,12 @@ def self.included(router)
end
router.get %r{/redirects/(\d+)/edit} do |id|
- @redirect = Redirect.find(id)
+ @redirect = Models::Redirect.find(id)
haml :'redirects/edit'
end
router.put %r{/redirects/(\d+)} do |id|
- @redirect = Redirect.find(id)
+ @redirect = Models::Redirect.find(id)
@redirect.update_attributes(params['redirect'])
@@ -51,7 +51,7 @@ def self.included(router)
router.put '/redirects/reorder' do
if params['redirect']
- if Redirect.update_order(params['redirect'])
+ if Models::Redirect.update_order(params['redirect'])
'Changed'
else
'Crumps! It broke'
@@ -60,12 +60,15 @@ def self.included(router)
end
router.delete %r{/redirects/(\d+)} do |id|
- if Redirect.destroy(id)
+ if Models::Redirect.destroy(id)
flash[:notice] = 'Redirect deleted'
redirect '/redirects'
end
end
+ router.delete '/redirects/all' do
+ status Models::Redirect.delete_all ? 200 : 500
+ end
end
end
end
View
22 lib/rest-assured/routes/response.rb
@@ -0,0 +1,22 @@
+module RestAssured
+ class Response
+ def self.perform(app)
+ request = app.request
+
+ if d = Models::Double.where(:fullpath => request.fullpath, :active => true, :verb => request.request_method).first
+ request.body.rewind
+ body = request.body.read #without temp variable ':body = > body' is always nil. mistery
+ env = request.env.except('rack.input', 'rack.errors', 'rack.logger')
+
+ d.requests.create!(:rack_env => env.to_json, :body => body, :params => request.params.to_json)
+
+ app.body d.content
+ app.status d.status
+ elsif r = Models::Redirect.ordered.find { |r| request.fullpath =~ /#{r.pattern}/ }
+ app.redirect( "#{r.to}#{request.fullpath}" )
+ else
+ app.status 404
+ end
+ end
+ end
+end
View
2 lib/rest-assured/version.rb
@@ -1,3 +1,3 @@
module RestAssured
- VERSION = '0.1.4'
+ VERSION = '0.2.0'
end
View
25 lib/sinatra/handler_options_patch.rb
@@ -0,0 +1,25 @@
+module Sinatra
+ class Base
+ class << self
+ def run!(options={})
+ set options
+ handler = detect_rack_handler
+ handler_name = handler.name.gsub(/.*::/, '')
+ # handler specific options use the lower case handler name as hash key, if present
+ handler_opts = begin
+ send(handler_name.downcase)
+ rescue NoMethodError
+ {}
+ end
+ puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
+ "on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
+ handler.run self, handler_opts.merge(:Host => bind, :Port => port) do |server|
+ [:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } }
+ set :running, true
+ end
+ rescue Errno::EADDRINUSE => e
+ puts "== Someone is already performing on port #{port}!"
+ end
+ end
+ end
+end
View
0 views/base.scss → public/css/base.css
File renamed without changes.
View
4 rest-assured.gemspec
@@ -22,10 +22,8 @@ Gem::Specification.new do |s|
s.add_dependency 'sinatra', '>= 1.3.1'
s.add_dependency 'rack-flash', '>= 0.1.2'
- #s.add_dependency 'sinatra-reloader'
s.add_dependency 'haml', '>= 3.1.3'
- s.add_dependency 'sass', '>= 3.1.8'
s.add_dependency 'activerecord', '~> 3.1.0'
- s.add_dependency 'sqlite3', '>= 1.3.4'
+ s.add_dependency 'activeresource', '~> 3.1.0'
end
View
83 spec/client/resource_double_spec.rb
@@ -0,0 +1,83 @@
+require 'uri'
+require File.expand_path('../../spec_helper', __FILE__)
+
+module RestAssured
+ describe Double do
+ before do
+ @orig_addr = RestAssured::Client.config.server_address
+ end
+
+ after do
+ RestAssured::Client.config.server_address = @orig_addr
+ end
+
+ it { should be_kind_of ActiveResource::Base }
+
+ it 'knows where rest-assured server is' do
+ RestAssured::Client.config.server_address = 'http://localhost:1234'
+ Double.site.should == URI.parse('http://localhost:1234')
+ end
+
+ it 'creates new double' do
+ d = Double.create :fullpath => '/some/api', :content => 'content'
+ Models::Double.where(:fullpath => d.fullpath, :content => d.content).should exist
+ end
+
+ it 'finds exising double' do
+ d = Models::Double.create :fullpath => '/some/api', :content => 'content'
+
+ dd = Double.find(d.id)
+
+ dd.fullpath.should == d.fullpath
+ dd.content.should == d.content
+ end
+
+ it 'shows request history' do
+ d = Models::Double.create :fullpath => '/some/api', :content => 'content'
+ d.requests << Models::Request.create(:rack_env => 'rack_env json', :body => 'body', :params => 'params')
+ d.requests << Models::Request.create(:rack_env => 'different rack_env', :body => 'other body', :params => 'more params')
+
+ dd = Double.find(d.id)
+ dd.requests.size.should == 2
+ dd.requests.first.rack_env.should == 'rack_env json'
+ dd.requests.first.params.should == 'params'
+ dd.requests.last.body.should == 'other body'
+ end
+
+ context 'when waits requests' do
+ after do
+ @t.join if @t.respond_to?(:join)
+ end
+
+ it 'waits for specified number of requests' do
+ d = Models::Double.create :fullpath => '/some/api', :content => 'content'
+ dd = Double.find(d.id)
+
+ @t = Thread.new do
+ 3.times do
+ sleep 1
+ d.requests << Models::Request.create(:rack_env => 'rack_env json', :body => 'body', :params => 'params')
+ end
+ end
+
+ dd.wait_for_requests(2)
+
+ dd.requests.count.should >= 2
+ end
+
+ it 'raises exception if requests have not happened within timeout' do
+ d = Models::Double.create :fullpath => '/some/api', :content => 'content'
+ dd = Double.find(d.id)
+ dd.stub(:sleep)
+
+ @t = Thread.new do
+ 2.times do
+ d.requests << Models::Request.create(:rack_env => 'rack_env json', :body => 'body', :params => 'params')
+ end
+ end
+
+ lambda { dd.wait_for_requests(3) }.should raise_error(MoreRequestsExpected, 'Expected 3 requests. Got 2.')
+ end
+ end
+ end
+end
View
133 spec/config_spec.rb
@@ -0,0 +1,133 @@
+require File.expand_path('../../lib/rest-assured/config', __FILE__)
+require 'rack'
+require 'openssl'
+