whisperer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +13 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +293 -0
- data/Rakefile +1 -0
- data/TODO.md +47 -0
- data/lib/whisperer.rb +102 -0
- data/lib/whisperer/config.rb +40 -0
- data/lib/whisperer/convertors/hash.rb +39 -0
- data/lib/whisperer/convertors/interaction.rb +21 -0
- data/lib/whisperer/dsl.rb +19 -0
- data/lib/whisperer/dsl/base.rb +47 -0
- data/lib/whisperer/dsl/body.rb +33 -0
- data/lib/whisperer/dsl/headers.rb +20 -0
- data/lib/whisperer/dsl/request.rb +15 -0
- data/lib/whisperer/dsl/response.rb +15 -0
- data/lib/whisperer/dsl/response/status.rb +10 -0
- data/lib/whisperer/generator.rb +43 -0
- data/lib/whisperer/helpers.rb +16 -0
- data/lib/whisperer/placeholder.rb +14 -0
- data/lib/whisperer/preprocessors.rb +19 -0
- data/lib/whisperer/preprocessors/base.rb +13 -0
- data/lib/whisperer/preprocessors/content_length.rb +17 -0
- data/lib/whisperer/preprocessors/response_body.rb +23 -0
- data/lib/whisperer/record.rb +48 -0
- data/lib/whisperer/record/body.rb +15 -0
- data/lib/whisperer/record/headers.rb +22 -0
- data/lib/whisperer/record/request.rb +16 -0
- data/lib/whisperer/record/response.rb +18 -0
- data/lib/whisperer/record/response/status.rb +10 -0
- data/lib/whisperer/samples/cassette_builder.rb +24 -0
- data/lib/whisperer/serializers/base.rb +24 -0
- data/lib/whisperer/serializers/json.rb +33 -0
- data/lib/whisperer/serializers/json_multiple.rb +14 -0
- data/lib/whisperer/tasks/whisperer.rake +88 -0
- data/lib/whisperer/version.rb +3 -0
- data/spec/cassette_builders/arya_stark.rb +26 -0
- data/spec/cassette_builders/bran_stark.rb +19 -0
- data/spec/cassette_builders/empty_robb_stark.rb +20 -0
- data/spec/cassette_builders/robb_stark.rb +30 -0
- data/spec/cassette_builders/robb_stark_without_content_length.rb +20 -0
- data/spec/cassette_builders/sansa_stark.rb +9 -0
- data/spec/cassette_builders/starks.rb +31 -0
- data/spec/cassette_builders/wolfs.rb +7 -0
- data/spec/cassettes/empty_robb_stark.yml +22 -0
- data/spec/cassettes/girls/arya_stark.yml +24 -0
- data/spec/cassettes/robb_stark.yml +28 -0
- data/spec/cassettes/robb_stark_without_content_length.yml +22 -0
- data/spec/cassettes/sansa_stark.yml +24 -0
- data/spec/cassettes/starks.yml +28 -0
- data/spec/cassettes/wolfs.yml +28 -0
- data/spec/factories/arya_stark.rb +7 -0
- data/spec/factories/bran_stark.rb +7 -0
- data/spec/factories/ned_stark.rb +7 -0
- data/spec/factories/robb_stark.rb +7 -0
- data/spec/factories/sansa_stark.rb +7 -0
- data/spec/integration/whisperer_spec.rb +51 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/spec_integration_helper.rb +6 -0
- data/spec/support/cassettes.rb +16 -0
- data/spec/support/custom_serializer.rb +5 -0
- data/spec/unit/config_spec.rb +94 -0
- data/spec/unit/convertors/hash_spec.rb +59 -0
- data/spec/unit/convertors/interaction_spec.rb +46 -0
- data/spec/unit/dsl/base_spec.rb +99 -0
- data/spec/unit/dsl/body_spec.rb +73 -0
- data/spec/unit/dsl/headers_spec.rb +31 -0
- data/spec/unit/dsl/request_spec.rb +4 -0
- data/spec/unit/dsl/response_spec.rb +4 -0
- data/spec/unit/dsl/status_spec.rb +4 -0
- data/spec/unit/dsl_spec.rb +4 -0
- data/spec/unit/generator_spec.rb +77 -0
- data/spec/unit/helpers_spec.rb +38 -0
- data/spec/unit/preprocessors/content_length_spec.rb +39 -0
- data/spec/unit/preprocessors/response_body_spec.rb +55 -0
- data/spec/unit/preprocessors_spec.rb +8 -0
- data/spec/unit/record/headers_spec.rb +21 -0
- data/spec/unit/record/response_spec.rb +11 -0
- data/spec/unit/record_spec.rb +77 -0
- data/spec/unit/serializers/base_spec.rb +19 -0
- data/spec/unit/serializers/json_multiple_spec.rb +24 -0
- data/spec/unit/serializers/json_spec.rb +37 -0
- data/spec/unit/whisperer_spec.rb +189 -0
- data/whisperer.gemspec +28 -0
- metadata +277 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Whisperer
|
4
|
+
# This config class is used only for Rake tasks.
|
5
|
+
# The puspose of it is to allow third users to defile options in Yaml file
|
6
|
+
# for generating cassettes and then it will be picked up
|
7
|
+
# while generating cassettes.
|
8
|
+
class Config
|
9
|
+
include Virtus.model
|
10
|
+
|
11
|
+
attribute :path_to_builders, String, default: 'spec/cassette_builders'
|
12
|
+
|
13
|
+
attribute :generate_to, String, default: 'spec/cassettes'
|
14
|
+
attribute :factories_matcher, String, default: './spec/factories/**/*.rb'
|
15
|
+
attribute :builders_matcher, String, default: -> (c, attr) { "./#{c.path_to_builders}/**/*.rb" }
|
16
|
+
|
17
|
+
def self.load(file_name = nil)
|
18
|
+
raw_config = {}
|
19
|
+
|
20
|
+
if file_name && File.exists?(file_name)
|
21
|
+
raw_config = YAML.load(File.read(file_name)) || {}
|
22
|
+
end
|
23
|
+
|
24
|
+
config = new(raw_config)
|
25
|
+
|
26
|
+
VCR.configure do |c|
|
27
|
+
c.cassette_library_dir = config.generate_to
|
28
|
+
end
|
29
|
+
|
30
|
+
config
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns yaml record with configuration options.
|
34
|
+
def to_yml
|
35
|
+
"generate_to: '#{generate_to}'\n" <<
|
36
|
+
"builders_matcher: '#{builders_matcher}'\n" <<
|
37
|
+
"factories_matcher: '#{factories_matcher}'"
|
38
|
+
end
|
39
|
+
end # class Config
|
40
|
+
end # module Whisperer
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
|
3
|
+
module Whisperer
|
4
|
+
module Convertors
|
5
|
+
class Hash
|
6
|
+
extend Helpers
|
7
|
+
|
8
|
+
add_builder :convert
|
9
|
+
|
10
|
+
def initialize(obj)
|
11
|
+
@obj = obj
|
12
|
+
end
|
13
|
+
|
14
|
+
# Converts the current object with all related objects to hash
|
15
|
+
def convert
|
16
|
+
to_hash(@obj)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def to_hash(val)
|
21
|
+
new_attrs, attrs = {}, val.to_hash
|
22
|
+
|
23
|
+
attrs.each do |attr, val|
|
24
|
+
new_attrs[prepare_key(attr)] = if val.respond_to?(:to_hash)
|
25
|
+
to_hash(val)
|
26
|
+
else
|
27
|
+
val
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
new_attrs
|
32
|
+
end
|
33
|
+
|
34
|
+
def prepare_key(key)
|
35
|
+
key.to_s
|
36
|
+
end
|
37
|
+
end # class Hash
|
38
|
+
end # module Convertors
|
39
|
+
end # module Whisperer
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Whisperer
|
2
|
+
module Convertors
|
3
|
+
|
4
|
+
# Converts
|
5
|
+
class Interaction
|
6
|
+
extend Helpers
|
7
|
+
|
8
|
+
add_builder :convert
|
9
|
+
|
10
|
+
def initialize(container)
|
11
|
+
@container = container
|
12
|
+
end
|
13
|
+
|
14
|
+
def convert
|
15
|
+
hash = Whisperer::Convertors::Hash.convert(@container)
|
16
|
+
|
17
|
+
VCR::HTTPInteraction.from_hash(hash)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative 'dsl/base'
|
2
|
+
|
3
|
+
require_relative 'record'
|
4
|
+
|
5
|
+
module Whisperer
|
6
|
+
class Dsl < BaseDsl
|
7
|
+
link_container_class Whisperer::Record
|
8
|
+
|
9
|
+
link_dsl 'request'
|
10
|
+
link_dsl 'response'
|
11
|
+
|
12
|
+
add_writer 'recorded_at'
|
13
|
+
|
14
|
+
add_writer 'sub_path'
|
15
|
+
end # class Dsl
|
16
|
+
end # module Whisperer
|
17
|
+
|
18
|
+
require 'whisperer/dsl/request'
|
19
|
+
require 'whisperer/dsl/response'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Whisperer
|
2
|
+
class BaseDsl
|
3
|
+
class << self
|
4
|
+
attr_reader :container_class
|
5
|
+
|
6
|
+
def link_dsl(name)
|
7
|
+
define_method(name) do |&block|
|
8
|
+
class_name = Whisperer::Dsl.const_get(name.capitalize)
|
9
|
+
|
10
|
+
sub_dsl = class_name.new(
|
11
|
+
@container.public_send(name)
|
12
|
+
)
|
13
|
+
|
14
|
+
sub_dsl.instance_eval &block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def link_container_class(val)
|
19
|
+
@container_class = val
|
20
|
+
end
|
21
|
+
|
22
|
+
def build
|
23
|
+
if self.container_class
|
24
|
+
new(
|
25
|
+
self.container_class.new
|
26
|
+
)
|
27
|
+
else
|
28
|
+
raise ArgumentError.new(
|
29
|
+
'You should associate a container (model) with this dsl class, before building it'
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_writer(name)
|
35
|
+
define_method(name) do |val|
|
36
|
+
@container.public_send("#{name}=", val)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :container
|
42
|
+
|
43
|
+
def initialize(container)
|
44
|
+
@container = container
|
45
|
+
end
|
46
|
+
end # class BaseDsl
|
47
|
+
end # module Whisperer
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module Whisperer
|
4
|
+
class Dsl
|
5
|
+
class Body < BaseDsl
|
6
|
+
add_writer 'encoding'
|
7
|
+
add_writer 'string'
|
8
|
+
|
9
|
+
def factory(name, *args)
|
10
|
+
model = FactoryGirl.build(name)
|
11
|
+
|
12
|
+
raw_data(model, *args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def factories(names, *args)
|
16
|
+
models = names.map do |name|
|
17
|
+
FactoryGirl.build(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
raw_data(models, *args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def raw_data(data_obj, options = {})
|
24
|
+
@container.data_obj = data_obj
|
25
|
+
@container.serializer_opts = options
|
26
|
+
end
|
27
|
+
|
28
|
+
def serializer(name)
|
29
|
+
@container.serializer = name.to_sym
|
30
|
+
end
|
31
|
+
end # class Body
|
32
|
+
end # class Dsl
|
33
|
+
end # module Whisperer
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module Whisperer
|
4
|
+
class Dsl
|
5
|
+
class Headers < BaseDsl
|
6
|
+
def respond_to?(meth_id)
|
7
|
+
@container.respond_to?(meth_id)
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
def method_missing(meth_id, *args)
|
12
|
+
unless @container.respond_to?(meth_id)
|
13
|
+
@container.attribute(meth_id, String)
|
14
|
+
end
|
15
|
+
|
16
|
+
@container.public_send("#{meth_id}=", *args)
|
17
|
+
end
|
18
|
+
end # class Header
|
19
|
+
end # class Dsl
|
20
|
+
end # module Whisperer
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require_relative 'headers'
|
3
|
+
require_relative 'body'
|
4
|
+
|
5
|
+
module Whisperer
|
6
|
+
class Dsl
|
7
|
+
class Request < BaseDsl
|
8
|
+
link_dsl 'headers'
|
9
|
+
link_dsl 'body'
|
10
|
+
|
11
|
+
add_writer 'uri'
|
12
|
+
add_writer 'method'
|
13
|
+
end # class Request
|
14
|
+
end # module Dsl
|
15
|
+
end # module Whisperer
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
require_relative 'headers'
|
4
|
+
require_relative 'body'
|
5
|
+
require_relative 'response/status'
|
6
|
+
|
7
|
+
module Whisperer
|
8
|
+
class Dsl
|
9
|
+
class Response < BaseDsl
|
10
|
+
link_dsl 'headers'
|
11
|
+
link_dsl 'body'
|
12
|
+
link_dsl 'status'
|
13
|
+
end # class Response
|
14
|
+
end # class Dsl
|
15
|
+
end # module Whisperer
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Whisperer
|
2
|
+
class Generator
|
3
|
+
extend Helpers
|
4
|
+
|
5
|
+
add_builder 'generate'
|
6
|
+
|
7
|
+
attr_reader :record
|
8
|
+
|
9
|
+
def initialize(record, name)
|
10
|
+
@record, @name = record, name
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate
|
14
|
+
Preprocessors.process!(record)
|
15
|
+
|
16
|
+
interaction = Convertors::Interaction.convert(record)
|
17
|
+
|
18
|
+
self.uniq_cassette!
|
19
|
+
|
20
|
+
cassette = VCR::Cassette.new("#{record.sub_path}/#{@name}")
|
21
|
+
cassette.record_http_interaction(
|
22
|
+
interaction
|
23
|
+
)
|
24
|
+
|
25
|
+
cassette.eject
|
26
|
+
|
27
|
+
File.read(path_to_cassette)
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
def path_to_cassette
|
32
|
+
"#{VCR.configuration.cassette_library_dir}/#{record.sub_path}/#{@name}.yml"
|
33
|
+
end
|
34
|
+
|
35
|
+
def uniq_cassette!
|
36
|
+
if File.exists?(path_to_cassette)
|
37
|
+
File.unlink(
|
38
|
+
path_to_cassette
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# This class is a fake model to define factories
|
2
|
+
# since FactoryGirl requires a module.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# FactoryGirl.define do
|
7
|
+
# factory :ned_stark, class: Placeholder do
|
8
|
+
# first_name 'Ned'
|
9
|
+
# last_name 'Stark'
|
10
|
+
# group 'member'
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
Placeholder = Class.new(OpenStruct)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Whisperer
|
2
|
+
module Preprocessors
|
3
|
+
@preprocessors = ThreadSafe::Hash.new
|
4
|
+
|
5
|
+
class << self
|
6
|
+
attr_reader :preprocessors
|
7
|
+
|
8
|
+
def register(name, class_name)
|
9
|
+
preprocessors[name] = class_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def process!(container)
|
13
|
+
preprocessors.each do |name, class_names|
|
14
|
+
class_names.process(container)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
# This class calculates a content length for a response body
|
4
|
+
# if it is not defined in a cassette record.
|
5
|
+
module Whisperer
|
6
|
+
module Preprocessors
|
7
|
+
class ContentLength < Base
|
8
|
+
def process
|
9
|
+
headers = @record.response.headers
|
10
|
+
|
11
|
+
if headers.content_length.nil?
|
12
|
+
headers.content_length = @record.response.body.string.size
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end # class ContentLength
|
16
|
+
end # module Preprocessors
|
17
|
+
end # module Whisperer
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module Whisperer
|
4
|
+
module Preprocessors
|
5
|
+
class ResponseBody < Base
|
6
|
+
def process
|
7
|
+
body = @record.response.body
|
8
|
+
|
9
|
+
unless body.data_obj.nil?
|
10
|
+
body.string = serializer_class(body.serializer).serialize(
|
11
|
+
body.data_obj,
|
12
|
+
body.serializer_opts
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
def serializer_class(name)
|
19
|
+
Whisperer.serializer(name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative 'record/request'
|
2
|
+
require_relative 'record/response'
|
3
|
+
|
4
|
+
module Whisperer
|
5
|
+
class Record
|
6
|
+
include Virtus.model
|
7
|
+
|
8
|
+
attribute :request, Whisperer::Request, default: proc { Whisperer::Request.new }
|
9
|
+
attribute :response, Whisperer::Response, default: proc { Whisperer::Response.new }
|
10
|
+
attribute :http_version, String, default: ''
|
11
|
+
attribute :recorded_at, String, default: proc { Time.now.httpdate }
|
12
|
+
attribute :sub_path, String
|
13
|
+
|
14
|
+
def merge!(model)
|
15
|
+
merge_attrs(model, self)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
def merge_attrs(item, container)
|
20
|
+
item.attributes.each do |attr, val|
|
21
|
+
if val.respond_to?(:attributes)
|
22
|
+
merge_attrs(val, container[attr])
|
23
|
+
else
|
24
|
+
# We need to make sure that such attribute is declared
|
25
|
+
# for a record, otherwise, it cannot be written.
|
26
|
+
if container.class.attribute_set[attr].nil?
|
27
|
+
container.attribute(attr, item.class.attribute_set[attr])
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_info = container.class.attribute_set[attr]
|
31
|
+
|
32
|
+
is_default = false
|
33
|
+
|
34
|
+
unless attr_info.nil?
|
35
|
+
def_val = attr_info.default_value
|
36
|
+
def_val = def_val.call if def_val.respond_to?(:call)
|
37
|
+
|
38
|
+
is_default = def_val == container[attr]
|
39
|
+
end
|
40
|
+
|
41
|
+
if container[attr].nil? || is_default
|
42
|
+
container[attr] = val
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end # class Record
|
48
|
+
end # module Whisperer
|