whisperer 0.0.1
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 +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
|