cassette_explorer 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Guardfile +42 -0
- data/LICENSE.txt +22 -0
- data/README.md +35 -0
- data/bin/cassette_explorer +13 -0
- data/cassette_explorer.gemspec +35 -0
- data/lib/cassette_explorer.rb +20 -0
- data/lib/cassette_explorer/page.rb +54 -0
- data/lib/cassette_explorer/reader.rb +32 -0
- data/lib/cassette_explorer/server.rb +105 -0
- data/lib/cassette_explorer/template.rb +14 -0
- data/lib/cassette_explorer/templates/index.erb +87 -0
- data/lib/cassette_explorer/version.rb +3 -0
- data/spec/cassette_explorer/page_spec.rb +5 -0
- data/spec/cassette_explorer/server_spec.rb +65 -0
- data/spec/fixtures/vcr_cassettes/first_cassette.yml +1589 -0
- data/spec/fixtures/vcr_cassettes/fourth_cassette.yml +3659 -0
- data/spec/fixtures/vcr_cassettes/second_cassette.yml +48 -0
- data/spec/fixtures/vcr_cassettes/with_path/third_cassette.yml +1795 -0
- data/spec/spec_helper.rb +53 -0
- metadata +256 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 565db288bd62dcbd44e712bd0e5f6f84af54e656
|
4
|
+
data.tar.gz: 7ee23f6229a282aa669f4f82d769605050eb57e0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 74d975de6175f17c9367567b575373bad68d8d67de142514b0ed5ffd0f89c3de02718c7495b11afd4d81a2508a308bb55db5446783f31b721c6a67a9fbec0bf9
|
7
|
+
data.tar.gz: ac84df6d4aace1d43583ceac36dcbea757933888f1fafc9301d5dbc0f6e11815267b08fbeb72dd9c4eadcb0ca56e77c0fd8f6d6fd5724aeb091fabb79fc52f06
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
5
|
+
# directories %w(app lib config test spec features) \
|
6
|
+
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
7
|
+
|
8
|
+
## Note: if you are using the `directories` clause above and you are not
|
9
|
+
## watching the project directory ('.'), then you will want to move
|
10
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
11
|
+
#
|
12
|
+
# $ mkdir config
|
13
|
+
# $ mv Guardfile config/
|
14
|
+
# $ ln -s config/Guardfile .
|
15
|
+
#
|
16
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
17
|
+
|
18
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
19
|
+
# rspec may be run, below are examples of the most common uses.
|
20
|
+
# * bundler: 'bundle exec rspec'
|
21
|
+
# * bundler binstubs: 'bin/rspec'
|
22
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
23
|
+
# installed the spring binstubs per the docs)
|
24
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
25
|
+
# * 'just' rspec: 'rspec'
|
26
|
+
|
27
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
28
|
+
require "guard/rspec/dsl"
|
29
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
30
|
+
|
31
|
+
# Feel free to open issues for suggestions and improvements
|
32
|
+
|
33
|
+
# RSpec files
|
34
|
+
rspec = dsl.rspec
|
35
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
36
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
37
|
+
watch(rspec.spec_files)
|
38
|
+
|
39
|
+
# Ruby files
|
40
|
+
ruby = dsl.ruby
|
41
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
42
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Zequez
|
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,35 @@
|
|
1
|
+
# CassetteExplorer
|
2
|
+
|
3
|
+
CasetteExplorer is a command line utility that allow you to mount a webserver to load the
|
4
|
+
[VCR](https://github.com/vcr/vcr) cassettes in the browser.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
```
|
9
|
+
$ gem install cassette_explorer
|
10
|
+
```
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
```
|
15
|
+
$ cassette_explorer -h
|
16
|
+
Usage: cassette_explorer /path/to/vcr/cassettes/directory [options]
|
17
|
+
-r, --no-replace-urls Replace relative URLs with absolute URLs. Default: true
|
18
|
+
-f, --load-in-iframe Default the views to load in an iframe (it can be toggled from the page). Default: false
|
19
|
+
-w, --watch Watches the cassettes for changes. Default: true
|
20
|
+
-p, --port PORT Port to mount the server
|
21
|
+
-h, --help Prints this help
|
22
|
+
```
|
23
|
+
|
24
|
+
## TODO
|
25
|
+
|
26
|
+
- Write better tests
|
27
|
+
- Make the view a folder tree
|
28
|
+
|
29
|
+
## Contributing
|
30
|
+
|
31
|
+
1. Fork it ( https://github.com/Zequez/cassette_explorer/fork )
|
32
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
33
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
34
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
35
|
+
5. Create a new Pull Request
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
4
|
+
begin
|
5
|
+
require File.dirname(THIS_FILE) + '/../lib/cassette_explorer'
|
6
|
+
rescue LoadError
|
7
|
+
require 'cassette_explorer'
|
8
|
+
end
|
9
|
+
|
10
|
+
server = CassetteExplorer::Server.from_command_line(ARGV)
|
11
|
+
if server
|
12
|
+
server.run
|
13
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cassette_explorer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'cassette_explorer'
|
8
|
+
spec.version = CassetteExplorer::VERSION
|
9
|
+
spec.authors = ['Zequez']
|
10
|
+
spec.email = ['zequez@gmail.com']
|
11
|
+
spec.summary = %q{VCR Cassette Explorer}
|
12
|
+
spec.description = %q{Mounts a webserver to serve VCR cassettes for visualization on your browser}
|
13
|
+
spec.homepage = 'http://github.com/Zequez/cassette_explorer'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = 'cassette_explorer'
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'listen', '~> 3.0'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
24
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.3'
|
26
|
+
spec.add_development_dependency 'guard', '~> 2.13'
|
27
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.6'
|
28
|
+
spec.add_development_dependency 'webmock', '~> 1.21'
|
29
|
+
spec.add_development_dependency 'vcr', '~> 2.9'
|
30
|
+
spec.add_development_dependency 'typhoeus', '~> 0.7'
|
31
|
+
spec.add_development_dependency 'nokogiri', '~> 1.6'
|
32
|
+
spec.add_development_dependency 'capybara', '~> 2.4'
|
33
|
+
spec.add_development_dependency 'selenium-webdriver', '~> 2.46'
|
34
|
+
spec.add_development_dependency 'poltergeist', '~> 1.6'
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
|
3
|
+
|
4
|
+
require 'webrick'
|
5
|
+
require 'yaml'
|
6
|
+
require 'erb'
|
7
|
+
require 'listen'
|
8
|
+
require 'optparse'
|
9
|
+
require 'ostruct'
|
10
|
+
require 'base64'
|
11
|
+
|
12
|
+
require "cassette_explorer/version"
|
13
|
+
require "cassette_explorer/page"
|
14
|
+
require "cassette_explorer/reader"
|
15
|
+
require "cassette_explorer/template"
|
16
|
+
require "cassette_explorer/server"
|
17
|
+
|
18
|
+
module CassetteExplorer
|
19
|
+
ROOT = Pathname.new(__dir__) + '..'
|
20
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module CassetteExplorer
|
2
|
+
class Page
|
3
|
+
attr_reader :request
|
4
|
+
|
5
|
+
def initialize(request)
|
6
|
+
@request = request
|
7
|
+
end
|
8
|
+
|
9
|
+
def method
|
10
|
+
@request['request']['method'].upcase
|
11
|
+
end
|
12
|
+
|
13
|
+
def status
|
14
|
+
@request['response']['status']['code']
|
15
|
+
end
|
16
|
+
|
17
|
+
def title
|
18
|
+
@title ||= html_raw.scan(/<title>([^<]+)<\/title>/).flatten.first
|
19
|
+
end
|
20
|
+
|
21
|
+
def html(relative_urls = false)
|
22
|
+
if relative_urls
|
23
|
+
html_with_relative_urls
|
24
|
+
else
|
25
|
+
html_raw
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def url
|
30
|
+
@request['request']['uri']
|
31
|
+
end
|
32
|
+
|
33
|
+
def uri
|
34
|
+
@uri ||= URI.parse(url)
|
35
|
+
end
|
36
|
+
|
37
|
+
def html_raw
|
38
|
+
body = @request['response']['body']
|
39
|
+
|
40
|
+
if body['base64_string']
|
41
|
+
html = Base64.decode64 body['base64_string']
|
42
|
+
else
|
43
|
+
html = body['string']
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def html_with_relative_urls
|
48
|
+
base_path = uri.scheme + '://' + uri.host + uri.path.sub(/[^\/]+$/, '')
|
49
|
+
html_raw
|
50
|
+
.gsub('href="./', "href=\"#{base_path}")
|
51
|
+
.gsub('src="./', "src=\"#{base_path}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CassetteExplorer
|
2
|
+
class Reader
|
3
|
+
def initialize(path)
|
4
|
+
@path = File.expand_path(path)
|
5
|
+
end
|
6
|
+
|
7
|
+
def read
|
8
|
+
|
9
|
+
files = Dir["#{@path}/**/**"]
|
10
|
+
|
11
|
+
sections = {}
|
12
|
+
|
13
|
+
files.reject{ |f| File.directory? f }.each do |file|
|
14
|
+
section_name = section_name_from_file_path(file)
|
15
|
+
sections[section_name] ||= []
|
16
|
+
|
17
|
+
data = YAML.load_file(file)
|
18
|
+
if data['http_interactions']
|
19
|
+
data['http_interactions'].each do |request|
|
20
|
+
sections[section_name].push Page.new(request)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
sections
|
26
|
+
end
|
27
|
+
|
28
|
+
def section_name_from_file_path(file_path)
|
29
|
+
file_path.gsub(@path, '')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module CassetteExplorer
|
2
|
+
class Server
|
3
|
+
def self.from_command_line(argv)
|
4
|
+
options = {}
|
5
|
+
|
6
|
+
parser = OptionParser.new do |opts|
|
7
|
+
opts.banner = 'Usage: cassette_explorer /path/to/vcr/cassettes/directory [options]'
|
8
|
+
|
9
|
+
opts.on('-r', '--no-replace-urls', 'Replace relative URLs with absolute URLs. Default: true') do |v|
|
10
|
+
options[:relative_urls] = !v
|
11
|
+
end
|
12
|
+
|
13
|
+
opts.on(
|
14
|
+
'-f',
|
15
|
+
'--load-in-iframe',
|
16
|
+
'Default the views to load in an iframe (it can be toggled from the page). Default: false'
|
17
|
+
) do |v|
|
18
|
+
options[:load_in_iframe] = v
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on('-w', '--watch', 'Watches the cassettes for changes. Default: true') do |v|
|
22
|
+
options[:watch] = v
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on('-p', '--port PORT', 'Port to mount the server') do |v|
|
26
|
+
options[:port] = v
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on('-h', '--help', 'Prints this help') do |v|
|
30
|
+
options[:help] = v
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
parser.parse!(argv)
|
36
|
+
raise OptionParser::InvalidOption if argv.size != 1
|
37
|
+
options[:path] = argv.pop
|
38
|
+
rescue OptionParser::InvalidOption
|
39
|
+
options[:help] = true
|
40
|
+
end
|
41
|
+
|
42
|
+
if options[:help]
|
43
|
+
puts parser.help
|
44
|
+
else
|
45
|
+
new options
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(options = {})
|
50
|
+
@options = {
|
51
|
+
port: 2332,
|
52
|
+
path: './spec/fixtures/vcr_cassettes/',
|
53
|
+
template: __dir__ + '/templates/index.erb',
|
54
|
+
relative_urls: true,
|
55
|
+
watch: true,
|
56
|
+
load_in_iframe: false
|
57
|
+
}.merge(options)
|
58
|
+
|
59
|
+
@reader = Reader.new(@options[:path])
|
60
|
+
end
|
61
|
+
|
62
|
+
def run(blocking = true)
|
63
|
+
@sections = @reader.read
|
64
|
+
|
65
|
+
listener = Listen.to(@options[:path]) do
|
66
|
+
puts 'Change detected, reloading cassettes'
|
67
|
+
@sections = @reader.read
|
68
|
+
end
|
69
|
+
|
70
|
+
listener.start # doesn't block
|
71
|
+
|
72
|
+
server = WEBrick::HTTPServer.new Port: @options[:port]
|
73
|
+
server.mount_proc '/' do |req, res|
|
74
|
+
url = req.query['url']
|
75
|
+
file = req.query['file']
|
76
|
+
data = req.query['data']
|
77
|
+
|
78
|
+
if file and url
|
79
|
+
section = @sections[file]
|
80
|
+
page = section.detect{ |p| p.url == url }
|
81
|
+
|
82
|
+
if data
|
83
|
+
res.body = YAML::dump(page.request)
|
84
|
+
else
|
85
|
+
res.body = page.html(@options[:relative_urls])
|
86
|
+
end
|
87
|
+
else
|
88
|
+
res.body = render_index(@sections)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if blocking
|
93
|
+
server.start
|
94
|
+
else
|
95
|
+
Thread.new do
|
96
|
+
server.start
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def render_index(data)
|
102
|
+
Template.new(files: data, load_in_iframe: @options[:load_in_iframe]).render(@options[:template])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module CassetteExplorer
|
2
|
+
class Template < OpenStruct
|
3
|
+
include ERB::Util
|
4
|
+
|
5
|
+
def render(template_file)
|
6
|
+
template = File.read template_file
|
7
|
+
ERB.new(template).result(binding)
|
8
|
+
end
|
9
|
+
|
10
|
+
def page_path(file, page)
|
11
|
+
"?file=#{u file}&url=#{u page.url}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<title>List of pages</title>
|
4
|
+
<style>
|
5
|
+
body {
|
6
|
+
margin: 0;
|
7
|
+
font-family: monospace;
|
8
|
+
font-size: 16px;
|
9
|
+
line-height: 24px;
|
10
|
+
}
|
11
|
+
|
12
|
+
.file {
|
13
|
+
color: grey;
|
14
|
+
}
|
15
|
+
|
16
|
+
.method {
|
17
|
+
font-weight: bold;
|
18
|
+
}
|
19
|
+
|
20
|
+
.method.get {
|
21
|
+
color: green;
|
22
|
+
}
|
23
|
+
|
24
|
+
.method.post {
|
25
|
+
color: red;
|
26
|
+
}
|
27
|
+
|
28
|
+
.link {
|
29
|
+
|
30
|
+
}
|
31
|
+
|
32
|
+
.status {
|
33
|
+
font-weight: bold;
|
34
|
+
color: red;
|
35
|
+
}
|
36
|
+
|
37
|
+
.status.status-200 {
|
38
|
+
color: green;
|
39
|
+
}
|
40
|
+
|
41
|
+
.iframe-toggle-container {
|
42
|
+
position: absolute;
|
43
|
+
top: 0;
|
44
|
+
right: 0;
|
45
|
+
}
|
46
|
+
|
47
|
+
iframe {
|
48
|
+
width: 100%;
|
49
|
+
height: 1000px;
|
50
|
+
border: 0;
|
51
|
+
}
|
52
|
+
</style>
|
53
|
+
<body>
|
54
|
+
<ul>
|
55
|
+
<% files.each_pair do |file, pages| %>
|
56
|
+
<li class="file"><%= file %></li>
|
57
|
+
<ul>
|
58
|
+
<% pages.each do |page| %>
|
59
|
+
<li>
|
60
|
+
<a class="interaction-link" href="<%=page_path file, page%>&data">Data</a>
|
61
|
+
<span class="method <%= page.method.downcase %>"><%= page.method %></span>
|
62
|
+
<a class="link" href="<%=page_path file, page%>"><%= page.url %></a>
|
63
|
+
<span class="status status-<%= page.status %>">=> <%= page.status %></span>
|
64
|
+
<span class="title"><%= page.title and %Q{"#{page.title}"} %></span>
|
65
|
+
</li>
|
66
|
+
<% end %>
|
67
|
+
</ul>
|
68
|
+
<% end %>
|
69
|
+
</ul>
|
70
|
+
<label class="iframe-toggle-container">
|
71
|
+
Load in iframe
|
72
|
+
<input type="checkbox" id="iframe-toggle" <%=load_in_iframe ? 'checked' : ''%>/>
|
73
|
+
</label>
|
74
|
+
<iframe src=""></iframe>
|
75
|
+
|
76
|
+
<script>
|
77
|
+
var iframe = document.getElementsByTagName('iframe')[0];
|
78
|
+
var iframeToggle = document.getElementById('iframe-toggle');
|
79
|
+
document.body.addEventListener('click', function(ev){
|
80
|
+
if (iframeToggle.checked && ev.target.tagName == 'A') {
|
81
|
+
iframe.src = ev.target.href;
|
82
|
+
ev.preventDefault();
|
83
|
+
}
|
84
|
+
});
|
85
|
+
</script>
|
86
|
+
</body>
|
87
|
+
</html>
|