vhs 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +143 -0
- data/Rakefile +2 -0
- data/bin/vhs +13 -0
- data/lib/vhs.rb +132 -0
- data/lib/vhs/cassetter.rb +34 -0
- data/lib/vhs/cli/cassettes.rb +81 -0
- data/lib/vhs/cli/cli.rb +77 -0
- data/lib/vhs/cli/config.rb +28 -0
- data/lib/vhs/configuration.rb +20 -0
- data/lib/vhs/loader.rb +51 -0
- data/lib/vhs/templates/rspec.rb +25 -0
- data/lib/vhs/templates/vhs.yml +9 -0
- data/lib/vhs/typhoeus_stub.rb +21 -0
- data/lib/vhs/version.rb +4 -0
- data/vhs.gemspec +29 -0
- metadata +135 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f4413b91dbf665f7dc22e284aa3a2fb437a878df
|
4
|
+
data.tar.gz: d294ac9dca430168860996f470c096cf52128601
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9ac9c88b1e4ff0e975c74969d966b717f42d30b6cafeb37123bad39b3b426a4c9b311991d231f49f87f1d8d35c4d589d6618edcc52d5216e96d4a9438fa85582
|
7
|
+
data.tar.gz: 2e3f011bbbb2bfc70d900f8f98157059d6bbf8e36c2581c4975c0589cbec3ab2fd3740a180f064e281c22d028fffeccf3f8e0341251b5a789d20a6826318e092
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
## 0.3.0
|
2
|
+
* VHS.turn_off methods ejects all cassettes before turning off to avoid VCR
|
3
|
+
from raising exceptions when there are active cassettes
|
4
|
+
* CLI command clean created to remove dirt cassettes
|
5
|
+
* Config options log_read and log_write added to separate both loggings
|
6
|
+
|
7
|
+
## 0.2.0
|
8
|
+
* requests matching changed to all [:method, :uri, :body, :params] to fix PUT
|
9
|
+
and POST requests
|
10
|
+
* cassette CLI command list error fixed
|
11
|
+
|
12
|
+
## 0.1.0
|
13
|
+
* first working version without POST and PUT requests
|
14
|
+
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Joaquin Rivera Padron
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# VHS
|
2
|
+
|
3
|
+
VHS stubs all the API calls performed in your test suite, without you needing
|
4
|
+
to change your tests.
|
5
|
+
|
6
|
+
VHS makes an intelligent handling of
|
7
|
+
[VCR](https://source.xing.com/joaquin-rivera/vcr) cassettes to
|
8
|
+
stub every API call that your test suite does. It
|
9
|
+
does so by hooking into Typhoeus and determining the correct
|
10
|
+
cassette for the request being performed.
|
11
|
+
|
12
|
+
On a first run if your API call has no cassette saved the real
|
13
|
+
call is made. After that the real call will not be executed and
|
14
|
+
the cassette will be used instead.
|
15
|
+
|
16
|
+
That means: faster and more reliable tests without worrying for
|
17
|
+
stubbing what is not the scope of your test.
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'vhs'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
$ bundle
|
30
|
+
|
31
|
+
Configure VHS:
|
32
|
+
|
33
|
+
$ vhs config cli
|
34
|
+
|
35
|
+
This copies a `.vhs.yml` file to your root with configuration options for VHS,
|
36
|
+
check it out to see the options. It is also gitignored so you can configure VHS
|
37
|
+
at will without affecting your teammates.
|
38
|
+
|
39
|
+
Now configure RSpec to use VHS:
|
40
|
+
|
41
|
+
$ vhs config rspec
|
42
|
+
|
43
|
+
This copies a `spec/support/vhs.rb` file, make sure you include in your spec_helper.
|
44
|
+
|
45
|
+
Now you are ready to run your specs using VHS.
|
46
|
+
|
47
|
+
## Usage
|
48
|
+
|
49
|
+
After you run your spec suite, the cassettes used by your specs are saved under
|
50
|
+
the directory `spec/fixtures/vhs`.
|
51
|
+
|
52
|
+
To know whether all of the API calls succeeded you can list the error ones using:
|
53
|
+
|
54
|
+
$ vhs list error
|
55
|
+
|
56
|
+
And update them accordingly
|
57
|
+
|
58
|
+
$ vhs update error
|
59
|
+
|
60
|
+
Check the help of both commands to know the posibilities at your disposal.
|
61
|
+
|
62
|
+
## Fixing specs suite
|
63
|
+
|
64
|
+
TimeBandits break the usage of VHS, to make it work, you need to remove lines like
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
RESTApi.run_with_time_bandit(TimeBandits::CustomConsumers::Search)
|
68
|
+
```
|
69
|
+
|
70
|
+
TODO add details here and in wiki pages about conflicts and ways of fixing them
|
71
|
+
|
72
|
+
## Turning VHS off
|
73
|
+
|
74
|
+
If you have already stubbed calls to `RESTApi` in your specs, you
|
75
|
+
can turn VHS off using the provided rspec tag at the level of `describe`,
|
76
|
+
`context`, or `it` blocks like this:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
# some spec file
|
80
|
+
describe XYZ, vhs: false do
|
81
|
+
|
82
|
+
context 'a spec context', vhs: false do
|
83
|
+
|
84
|
+
it 'is an spec example', vhs: false do
|
85
|
+
```
|
86
|
+
|
87
|
+
or leave VHS running for part of your spec, and just turn it off on the exact
|
88
|
+
place you need to:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
# some spec file
|
92
|
+
it 'is an spec stubbing RESTApi call' do
|
93
|
+
# code goes here
|
94
|
+
VHS.turn_off
|
95
|
+
# stub as needed
|
96
|
+
RESTApi.stub(:get, %r{/rest/users/users/})
|
97
|
+
VHS.turn_on
|
98
|
+
# from here on VHS takes over again
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
## Add cassettes to git
|
103
|
+
|
104
|
+
It makes sense to add the cassettes to your repo once the test suite is green.
|
105
|
+
|
106
|
+
But before you do so, check that your cassetttes have a fixed url value (i.e.
|
107
|
+
your test sandbox's rest url).
|
108
|
+
To ready them for anyone running the specs the cassettes need to be dynamized,
|
109
|
+
i.e. to replace that fixed sandbox rest url with the value in
|
110
|
+
`AppConfig.sandbox.rest_url`.
|
111
|
+
|
112
|
+
You can dymanize your cassettes running:
|
113
|
+
|
114
|
+
$ vhs dynamize
|
115
|
+
|
116
|
+
After that add the cassettes to your git repo and your team can enjoy your fast
|
117
|
+
test suite.
|
118
|
+
|
119
|
+
## Clean up after suite run
|
120
|
+
|
121
|
+
After the cassettes are added to the repo, it can be the case that running your
|
122
|
+
suite still generates new cassettes. To remove them easily use the command
|
123
|
+
|
124
|
+
$ vhs clean
|
125
|
+
|
126
|
+
It will remove every cassette not added or commited to git.
|
127
|
+
|
128
|
+
## TODO
|
129
|
+
- Try in in jenkins
|
130
|
+
- Provide a wiki pages with specs
|
131
|
+
- Try it with test unit
|
132
|
+
- write specs!
|
133
|
+
- clean up code here and in VCR
|
134
|
+
- fix the CLI cassettes update command
|
135
|
+
|
136
|
+
## Contributing
|
137
|
+
|
138
|
+
1. Fork it ( https://github.com/[my-github-username]/vhs/fork )
|
139
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
140
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
141
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
142
|
+
5. Create a new Pull Request
|
143
|
+
|
data/Rakefile
ADDED
data/bin/vhs
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
lib = Pathname.new(__FILE__).dirname.join('..', 'lib').expand_path
|
5
|
+
$LOAD_PATH.unshift lib.to_s
|
6
|
+
|
7
|
+
begin require 'rubygems'; rescue LoadError; end
|
8
|
+
|
9
|
+
require 'vhs'
|
10
|
+
require 'vhs/cli/cli'
|
11
|
+
|
12
|
+
VHS::CLI::CLI.start
|
13
|
+
|
data/lib/vhs.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'typhoeus'
|
2
|
+
require 'vcr'
|
3
|
+
|
4
|
+
require 'vhs/version'
|
5
|
+
require 'vhs/cassetter'
|
6
|
+
require 'vhs/configuration'
|
7
|
+
require 'vhs/loader'
|
8
|
+
|
9
|
+
module VHS
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# Public: Modify VHS configuration
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
# VHS.configure do |vhs|
|
16
|
+
# vhs.api_host = AppConfig.sandbox.rest_url
|
17
|
+
# end
|
18
|
+
def configure
|
19
|
+
yield configuration
|
20
|
+
end
|
21
|
+
|
22
|
+
# Accessor for VHS::Configuration
|
23
|
+
def configuration
|
24
|
+
@configuration ||= Configuration.new
|
25
|
+
end
|
26
|
+
alias :config :configuration
|
27
|
+
|
28
|
+
def load
|
29
|
+
require "vhs/typhoeus_stub"
|
30
|
+
@active = true
|
31
|
+
end
|
32
|
+
|
33
|
+
def turned_on?
|
34
|
+
@active
|
35
|
+
end
|
36
|
+
|
37
|
+
def turn_on
|
38
|
+
#TODO
|
39
|
+
# - add a param to leave VCR on?
|
40
|
+
# - allow to pass a block to run with VHS on?
|
41
|
+
VCR.turn_on!
|
42
|
+
@active = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def turn_off
|
46
|
+
#TODO
|
47
|
+
# - add a param to leave VCR on?
|
48
|
+
# - allow to pass a block to run with VHS off?
|
49
|
+
|
50
|
+
# we need to save all cassettes before turning off, to avoid VCR exceptions
|
51
|
+
eject_all_cassettes
|
52
|
+
|
53
|
+
VCR.turn_off!
|
54
|
+
@active = false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: forces the update of cassettes.
|
58
|
+
def cassette_force_updates
|
59
|
+
@cassette_forced_update = true
|
60
|
+
end
|
61
|
+
|
62
|
+
def cassette_forced_updates?
|
63
|
+
@cassette_forced_update
|
64
|
+
end
|
65
|
+
|
66
|
+
# Loads a cassette for the request unless one is already loaded.
|
67
|
+
def load_cassette(request)
|
68
|
+
if turned_on?
|
69
|
+
cassette_name = VCR.cassette_name request
|
70
|
+
request_cassette = find_cassette cassette_name
|
71
|
+
|
72
|
+
if request_cassette.nil?
|
73
|
+
VCR.insert_cassette cassette_name, cassette_options
|
74
|
+
|
75
|
+
puts "~ [vhs] Loaded cassette #{ cassette_name }" if config.log_load
|
76
|
+
elsif config.log_load
|
77
|
+
puts "~ [vhs] Existing cassette #{ cassette_name }"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#TODO move cassette options to spec config file?
|
83
|
+
def cassette_options
|
84
|
+
{
|
85
|
+
erb: { api_host: config.api_host },
|
86
|
+
allow_playback_repeats: true,
|
87
|
+
record: cassette_forced_updates? ? :all : :once,
|
88
|
+
match_requests_on: [:method, :uri, :body, :params]
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def write_cassette(request)
|
93
|
+
#TODO if turned_on?
|
94
|
+
cassette_name = VCR.cassette_name request
|
95
|
+
request_cassette = find_cassette cassette_name
|
96
|
+
|
97
|
+
if request_cassette
|
98
|
+
puts "~ [vhs] Wrote cassette #{ cassette_name }" if config.log_write
|
99
|
+
request_cassette.eject
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def cassette_update(cassette_filename)
|
104
|
+
Cassetter.update cassette_filename
|
105
|
+
end
|
106
|
+
|
107
|
+
def eject_all_cassettes
|
108
|
+
VCR.send(:cassettes).each do |cassette|
|
109
|
+
cassette.eject
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
#TODO move to Cassetter, together with many methods here, and let this module
|
114
|
+
# just proxy methods to classes
|
115
|
+
def remove_cassettes
|
116
|
+
#eject_all_cassettes
|
117
|
+
VCR.remove_cassettes
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
#TODO the logic of find_cassette and cassette_name is duplicated in VCR
|
123
|
+
#OPTIMIZE this uses VCR private method
|
124
|
+
def find_cassette(cassette_name)
|
125
|
+
#TODO do not use VCR private :cassettes method
|
126
|
+
VCR.send(:cassettes).select do |c|
|
127
|
+
c.name == cassette_name
|
128
|
+
end.first
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class VHS::Cassetter
|
4
|
+
|
5
|
+
#FIXME this is broken as of version 0.2.0 due to requests with Tempfiles!
|
6
|
+
|
7
|
+
#TODO
|
8
|
+
# - write this as an instance method
|
9
|
+
# - allow to pass cassette with or w/o VCR.configuration.cassette_library_dir
|
10
|
+
def self.update(cassette_filename)
|
11
|
+
cassette_yaml = VCR::Cassette::Reader.new(cassette_filename, { api_host: VHS::config.api_host }).read
|
12
|
+
cassette_hash = YAML.load cassette_yaml #FIXME params are getting lost here
|
13
|
+
cassette_request = cassette_hash.first[:request]
|
14
|
+
|
15
|
+
request_options = {
|
16
|
+
body: cassette_request[:body],
|
17
|
+
method: cassette_request[:method],
|
18
|
+
headers: { Accept: 'application/json' } # other headers make the servers raise
|
19
|
+
}
|
20
|
+
if cassette_request.respond_to? :params
|
21
|
+
request_options[:params] = cassette_request[:params]
|
22
|
+
end
|
23
|
+
|
24
|
+
VHS.cassette_force_updates # forces cassette to be recorded
|
25
|
+
|
26
|
+
request = Typhoeus::Request.new cassette_request[:uri], request_options
|
27
|
+
|
28
|
+
hydra = Typhoeus::Hydra.new
|
29
|
+
hydra.queue request
|
30
|
+
hydra.run
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'vhs/loader'
|
2
|
+
|
3
|
+
module VHS
|
4
|
+
module CLI
|
5
|
+
class Cassettes
|
6
|
+
#TODO add success, redirects, etc as mnemonics for status codes
|
7
|
+
|
8
|
+
# Public: returns a string with a list, \n separated, of all cassettes
|
9
|
+
def all_str
|
10
|
+
`#{ all_cassettes_cmd }`
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: returns an array with all cassettes
|
14
|
+
def all
|
15
|
+
`#{ all_cassettes_cmd } | #{ rm_trailing_chars_cmd }`.split(/\n/)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Public: returns a string with a list, \n separated, of cassettes with
|
19
|
+
# HTTP code not 2xx
|
20
|
+
def error_str
|
21
|
+
`#{ error_cassettes_cmd }`
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: returns an array with all cassettes with HTTP code not 2xx
|
25
|
+
def error
|
26
|
+
`#{ error_cassettes_cmd } | #{ rm_trailing_chars_cmd }`.split(/\n/)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: returns a string with a list, \n separated, of cassettes with
|
30
|
+
# HTTP code matching the regexp <code>
|
31
|
+
def regexp_str(code)
|
32
|
+
`#{ all_cassettes_cmd(code) }`
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: returns an array of cassettes with code matching the regexp <code>
|
36
|
+
def regexp(code)
|
37
|
+
`#{ all_cassettes_cmd(code) } | #{ rm_trailing_chars_cmd }`.split(/\n/)
|
38
|
+
end
|
39
|
+
|
40
|
+
def clean
|
41
|
+
`#{ rm_cassettes_not_in_git_cmd }`
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Private: finds all cassettes.
|
47
|
+
# - code: regexp for the HTTP status code, if not passed all cassettes
|
48
|
+
# are found
|
49
|
+
# Returns a list, \n separated, of cassettes with code behind
|
50
|
+
def all_cassettes_cmd(code = '')
|
51
|
+
"grep -E '^ +code: #{ code }' #{ path } -R | #{ rm_trailing_colon_cmd }"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Private: finds all cassettes which HTTP code is not 2xx.
|
55
|
+
# Returns a list, \n separated, of cassettes with code behind
|
56
|
+
def error_cassettes_cmd
|
57
|
+
"grep -E '^ +code:' #{ path } -R | grep -v ' code: 2..' | #{ rm_trailing_colon_cmd }"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Private: removes everything from output leaving just filenames.
|
61
|
+
def rm_trailing_chars_cmd
|
62
|
+
"sed 's/\.yml.*/.yml/g'"
|
63
|
+
end
|
64
|
+
|
65
|
+
def rm_trailing_colon_cmd
|
66
|
+
"sed 's/\.yml:/.yml/g'"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Private: removes cassettes that are not commited or added to git.
|
70
|
+
def rm_cassettes_not_in_git_cmd
|
71
|
+
"git status #{path} | grep -v 'modified' | grep -v 'new file' | grep '#{path}' | awk '{ print $1 }' | xargs rm"
|
72
|
+
end
|
73
|
+
|
74
|
+
def path
|
75
|
+
@path ||= Loader.new.cassettes_path
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
data/lib/vhs/cli/cli.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
require 'vhs/cli/cassettes'
|
4
|
+
require 'vhs/cli/config'
|
5
|
+
require 'vhs/loader'
|
6
|
+
|
7
|
+
module VHS
|
8
|
+
module CLI
|
9
|
+
|
10
|
+
class CLI < Thor
|
11
|
+
include Thor::Actions
|
12
|
+
|
13
|
+
#TODO add aliases
|
14
|
+
desc 'config SUBCOMMAND ARGS', 'Configures VHS for test frameworks'
|
15
|
+
subcommand 'config', Config
|
16
|
+
|
17
|
+
desc 'list CASSETTE', 'List your cassettes. CASSETTE values are: all (default), error, a regexp to match an HTTP status code (must start with a digit)'
|
18
|
+
def list(cassette = 'all')
|
19
|
+
case cassette
|
20
|
+
when 'all'
|
21
|
+
puts Cassettes.new.all_str
|
22
|
+
#when 'success' #TODO
|
23
|
+
#when 'redirect'
|
24
|
+
when 'error'
|
25
|
+
puts Cassettes.new.error_str
|
26
|
+
else
|
27
|
+
puts Cassettes.new.regexp_str(cassette)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'update CASSETTE', 'Updates cassettes. CASSETTE values are: all (default), a filepath to a cassette, error, a regexp to match an HTTP status code (must start with a digit)'
|
32
|
+
def update(cassette = 'all')
|
33
|
+
cassettes = case cassette
|
34
|
+
when 'all'
|
35
|
+
puts 'Updating all cassettes'
|
36
|
+
Cassettes.new.all
|
37
|
+
when 'error'
|
38
|
+
puts 'Updating error cassettes'
|
39
|
+
Cassettes.new.error
|
40
|
+
when /\A\d.?.?/
|
41
|
+
puts "Updating cassettes by regexp #{ cassette }"
|
42
|
+
Cassettes.new.regexp(cassette)
|
43
|
+
else
|
44
|
+
[cassette] # it is a filename
|
45
|
+
end
|
46
|
+
|
47
|
+
Loader.load
|
48
|
+
cassettes.each do |cassette|
|
49
|
+
puts "Updating cassette #{ cassette }"
|
50
|
+
VHS.cassette_update cassette
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#TODO move to XING dynamizers
|
55
|
+
desc 'dynamize', 'Replaces api urls in cassettes with <%= api_host %>'
|
56
|
+
def dynamize
|
57
|
+
Cassettes.new.all.each do |cassette|
|
58
|
+
gsub_file cassette, /uri: http:\/\/.*.env.xing.com:3007\/rest/, 'uri: <%= api_host %>'
|
59
|
+
end
|
60
|
+
puts 'Cassettes have being dynamized'
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'clean', 'Removes all cassettes not added to git'
|
64
|
+
def clean
|
65
|
+
Cassettes.new.clean
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def self.source_root
|
71
|
+
@source_root ||= Loader.new.cassettes_path
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module VHS
|
2
|
+
class Config < Thor
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
desc 'cli', 'Copies .vhs.yml configuration file'
|
6
|
+
def cli
|
7
|
+
template 'vhs.yml', '.vhs.yml'
|
8
|
+
append_to_file '.gitignore', '.vhs.yml'
|
9
|
+
puts "VHS CLI configured"
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'rspec DESTINATION_PATH', 'Configures rspec to use VHS'
|
13
|
+
method_option :destination_path, default: 'spec/support/vhs.rb'
|
14
|
+
def rspec
|
15
|
+
destination_path = options[:destination_path]
|
16
|
+
copy_file 'rspec.rb', destination_path
|
17
|
+
puts "VHS configured for RSpec"
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def self.source_root
|
23
|
+
File.join File.dirname(__FILE__), '../templates/'
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class VHS::Configuration
|
2
|
+
# Should VHS be turned on or off. Defaults to true = on.
|
3
|
+
attr_accessor :turned_on
|
4
|
+
|
5
|
+
# The sandbox host your test suite calls. Must be set, lib/vhs.rb shows how.
|
6
|
+
attr_accessor :api_host
|
7
|
+
|
8
|
+
# Should VHS log cassettes load. Defaults to false.
|
9
|
+
attr_accessor :log_load
|
10
|
+
|
11
|
+
# Should VHS log cassettes write. Defaults to false.
|
12
|
+
attr_accessor :log_write
|
13
|
+
|
14
|
+
# Configuration defaults
|
15
|
+
def initialize
|
16
|
+
#TODO use this one on turn_on method
|
17
|
+
@turned_on = true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
data/lib/vhs/loader.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module VHS
|
2
|
+
class Loader
|
3
|
+
|
4
|
+
def self.load
|
5
|
+
new.load
|
6
|
+
end
|
7
|
+
|
8
|
+
# Public: configures VHS from .vhs.yml config file and loads it.
|
9
|
+
def load
|
10
|
+
configure_vcr
|
11
|
+
configure_vhs
|
12
|
+
VHS.load
|
13
|
+
end
|
14
|
+
|
15
|
+
#TODO move it to config class?
|
16
|
+
def cassettes_path
|
17
|
+
config['vcr']['cassette_library_dir']
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def config
|
23
|
+
@config ||= begin
|
24
|
+
pwd = "#{ `pwd` }".gsub(/\n/, '')
|
25
|
+
YAML.load_file "#{ pwd }/.vhs.yml"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def configure_vcr
|
30
|
+
VCR.configure do |vcr|
|
31
|
+
config['vcr'].each do |key, value|
|
32
|
+
if vcr.respond_to? "#{ key }="
|
33
|
+
vcr.send "#{ key }=", value
|
34
|
+
else
|
35
|
+
vcr.send key, value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def configure_vhs
|
42
|
+
VHS.configure do |vhs|
|
43
|
+
config['vhs'].each do |key, value|
|
44
|
+
vhs.send "#{ key }=", value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
RSpec.configure do |rspec|
|
2
|
+
# using `vhs: false` as tag in context, describe and it blocks turns off VHS
|
3
|
+
rspec.around(:example, vhs: false) do |example|
|
4
|
+
VHS.turn_off
|
5
|
+
example.run
|
6
|
+
VHS.turn_on
|
7
|
+
end
|
8
|
+
|
9
|
+
#TODO move this to xing-vhs or so
|
10
|
+
rspec.before(:each) do
|
11
|
+
# disconnects Typhoeus response_token check as it break VHS stubs
|
12
|
+
allow_any_instance_of(RESTApi::Request).to receive(:check_response_token).and_return(true)
|
13
|
+
end
|
14
|
+
|
15
|
+
rspec.after(:each) do
|
16
|
+
#TODO
|
17
|
+
# - check if this is still needed
|
18
|
+
# - benchmark whether it is expensive in the general case
|
19
|
+
# - find more cleanup strategies
|
20
|
+
VHS.remove_cassettes
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
VHS::Loader.load
|
25
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# this file is almost a copy pasta of vcr/library_hooks/typhoeus.rb
|
2
|
+
|
3
|
+
# TODO: add Typhoeus::Hydra.register_stub_finder API to Typhoeus
|
4
|
+
# so we can use that instead of monkey-patching it.
|
5
|
+
Typhoeus::Hydra::Stubbing::SharedMethods.class_eval do
|
6
|
+
alias_method :find_stub_from_request_vcr, :find_stub_from_request
|
7
|
+
|
8
|
+
def find_stub_from_request(request)
|
9
|
+
#TODO add turn_on? check here
|
10
|
+
VHS.load_cassette request
|
11
|
+
find_stub_from_request_vcr request
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
::Typhoeus::Hydra.after_request_before_on_complete do |request|
|
16
|
+
unless VCR.library_hooks.disabled?(:typhoeus) || request.response.mock?
|
17
|
+
#TODO add turn_on? check here
|
18
|
+
VHS.write_cassette request
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
data/lib/vhs/version.rb
ADDED
data/vhs.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'vhs/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "vhs"
|
8
|
+
spec.version = VHS::VERSION
|
9
|
+
spec.authors = ["Joaquin Rivera Padron"]
|
10
|
+
spec.email = ["joaquin.rivera@xing.com"]
|
11
|
+
spec.summary = %q{Automatic stubs of all API calls in your test suite.}
|
12
|
+
spec.description = %q{Intelligent handling of VCR cassettes to automatically record and replay all API calls in your tests.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
#TODO do not add the *.sh files, only the ruby ones
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency "thor", "~> 0.19.1"
|
23
|
+
spec.add_runtime_dependency "typhoeus", "~> 0.2.1" #XING's old libraries
|
24
|
+
spec.add_runtime_dependency "vcr", "2.0.0.beta1.vhs.pre.0.2.0"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
end
|
29
|
+
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vhs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joaquin Rivera Padron
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: thor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.19.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.19.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: typhoeus
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.2.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.2.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: vcr
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.0.0.beta1.vhs.pre.0.2.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.0.0.beta1.vhs.pre.0.2.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
description: Intelligent handling of VCR cassettes to automatically record and replay
|
84
|
+
all API calls in your tests.
|
85
|
+
email:
|
86
|
+
- joaquin.rivera@xing.com
|
87
|
+
executables:
|
88
|
+
- vhs
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- ".gitignore"
|
93
|
+
- CHANGELOG.md
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- bin/vhs
|
99
|
+
- lib/vhs.rb
|
100
|
+
- lib/vhs/cassetter.rb
|
101
|
+
- lib/vhs/cli/cassettes.rb
|
102
|
+
- lib/vhs/cli/cli.rb
|
103
|
+
- lib/vhs/cli/config.rb
|
104
|
+
- lib/vhs/configuration.rb
|
105
|
+
- lib/vhs/loader.rb
|
106
|
+
- lib/vhs/templates/rspec.rb
|
107
|
+
- lib/vhs/templates/vhs.yml
|
108
|
+
- lib/vhs/typhoeus_stub.rb
|
109
|
+
- lib/vhs/version.rb
|
110
|
+
- vhs.gemspec
|
111
|
+
homepage: ''
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
metadata: {}
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
requirements: []
|
130
|
+
rubyforge_project:
|
131
|
+
rubygems_version: 2.4.3
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: Automatic stubs of all API calls in your test suite.
|
135
|
+
test_files: []
|