whisperer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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