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.
Files changed (88) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +7 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +293 -0
  8. data/Rakefile +1 -0
  9. data/TODO.md +47 -0
  10. data/lib/whisperer.rb +102 -0
  11. data/lib/whisperer/config.rb +40 -0
  12. data/lib/whisperer/convertors/hash.rb +39 -0
  13. data/lib/whisperer/convertors/interaction.rb +21 -0
  14. data/lib/whisperer/dsl.rb +19 -0
  15. data/lib/whisperer/dsl/base.rb +47 -0
  16. data/lib/whisperer/dsl/body.rb +33 -0
  17. data/lib/whisperer/dsl/headers.rb +20 -0
  18. data/lib/whisperer/dsl/request.rb +15 -0
  19. data/lib/whisperer/dsl/response.rb +15 -0
  20. data/lib/whisperer/dsl/response/status.rb +10 -0
  21. data/lib/whisperer/generator.rb +43 -0
  22. data/lib/whisperer/helpers.rb +16 -0
  23. data/lib/whisperer/placeholder.rb +14 -0
  24. data/lib/whisperer/preprocessors.rb +19 -0
  25. data/lib/whisperer/preprocessors/base.rb +13 -0
  26. data/lib/whisperer/preprocessors/content_length.rb +17 -0
  27. data/lib/whisperer/preprocessors/response_body.rb +23 -0
  28. data/lib/whisperer/record.rb +48 -0
  29. data/lib/whisperer/record/body.rb +15 -0
  30. data/lib/whisperer/record/headers.rb +22 -0
  31. data/lib/whisperer/record/request.rb +16 -0
  32. data/lib/whisperer/record/response.rb +18 -0
  33. data/lib/whisperer/record/response/status.rb +10 -0
  34. data/lib/whisperer/samples/cassette_builder.rb +24 -0
  35. data/lib/whisperer/serializers/base.rb +24 -0
  36. data/lib/whisperer/serializers/json.rb +33 -0
  37. data/lib/whisperer/serializers/json_multiple.rb +14 -0
  38. data/lib/whisperer/tasks/whisperer.rake +88 -0
  39. data/lib/whisperer/version.rb +3 -0
  40. data/spec/cassette_builders/arya_stark.rb +26 -0
  41. data/spec/cassette_builders/bran_stark.rb +19 -0
  42. data/spec/cassette_builders/empty_robb_stark.rb +20 -0
  43. data/spec/cassette_builders/robb_stark.rb +30 -0
  44. data/spec/cassette_builders/robb_stark_without_content_length.rb +20 -0
  45. data/spec/cassette_builders/sansa_stark.rb +9 -0
  46. data/spec/cassette_builders/starks.rb +31 -0
  47. data/spec/cassette_builders/wolfs.rb +7 -0
  48. data/spec/cassettes/empty_robb_stark.yml +22 -0
  49. data/spec/cassettes/girls/arya_stark.yml +24 -0
  50. data/spec/cassettes/robb_stark.yml +28 -0
  51. data/spec/cassettes/robb_stark_without_content_length.yml +22 -0
  52. data/spec/cassettes/sansa_stark.yml +24 -0
  53. data/spec/cassettes/starks.yml +28 -0
  54. data/spec/cassettes/wolfs.yml +28 -0
  55. data/spec/factories/arya_stark.rb +7 -0
  56. data/spec/factories/bran_stark.rb +7 -0
  57. data/spec/factories/ned_stark.rb +7 -0
  58. data/spec/factories/robb_stark.rb +7 -0
  59. data/spec/factories/sansa_stark.rb +7 -0
  60. data/spec/integration/whisperer_spec.rb +51 -0
  61. data/spec/spec_helper.rb +25 -0
  62. data/spec/spec_integration_helper.rb +6 -0
  63. data/spec/support/cassettes.rb +16 -0
  64. data/spec/support/custom_serializer.rb +5 -0
  65. data/spec/unit/config_spec.rb +94 -0
  66. data/spec/unit/convertors/hash_spec.rb +59 -0
  67. data/spec/unit/convertors/interaction_spec.rb +46 -0
  68. data/spec/unit/dsl/base_spec.rb +99 -0
  69. data/spec/unit/dsl/body_spec.rb +73 -0
  70. data/spec/unit/dsl/headers_spec.rb +31 -0
  71. data/spec/unit/dsl/request_spec.rb +4 -0
  72. data/spec/unit/dsl/response_spec.rb +4 -0
  73. data/spec/unit/dsl/status_spec.rb +4 -0
  74. data/spec/unit/dsl_spec.rb +4 -0
  75. data/spec/unit/generator_spec.rb +77 -0
  76. data/spec/unit/helpers_spec.rb +38 -0
  77. data/spec/unit/preprocessors/content_length_spec.rb +39 -0
  78. data/spec/unit/preprocessors/response_body_spec.rb +55 -0
  79. data/spec/unit/preprocessors_spec.rb +8 -0
  80. data/spec/unit/record/headers_spec.rb +21 -0
  81. data/spec/unit/record/response_spec.rb +11 -0
  82. data/spec/unit/record_spec.rb +77 -0
  83. data/spec/unit/serializers/base_spec.rb +19 -0
  84. data/spec/unit/serializers/json_multiple_spec.rb +24 -0
  85. data/spec/unit/serializers/json_spec.rb +37 -0
  86. data/spec/unit/whisperer_spec.rb +189 -0
  87. data/whisperer.gemspec +28 -0
  88. 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,10 @@
1
+ require_relative '../base'
2
+
3
+ module Whisperer
4
+ class Dsl
5
+ class Status < BaseDsl
6
+ add_writer 'message'
7
+ add_writer 'code'
8
+ end # class Status
9
+ end # class Dsl
10
+ 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,16 @@
1
+ module Whisperer
2
+ module Helpers
3
+ def add_builder(meth_id)
4
+ mod = Module.new
5
+
6
+ mod.instance_eval do
7
+ define_method(meth_id) do |*args|
8
+ obj = new(*args)
9
+ obj.public_send(meth_id)
10
+ end
11
+ end
12
+
13
+ extend mod
14
+ end
15
+ end
16
+ 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,13 @@
1
+ module Whisperer
2
+ module Preprocessors
3
+ class Base
4
+ extend Helpers
5
+
6
+ add_builder :process
7
+
8
+ def initialize(record)
9
+ @record = record
10
+ end
11
+ end
12
+ end
13
+ 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