whisperer 0.0.1 → 0.0.2

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 (40) hide show
  1. checksums.yaml +5 -13
  2. data/CHANGELOG.md +10 -0
  3. data/Gemfile +2 -1
  4. data/README.md +80 -9
  5. data/TODO.md +30 -35
  6. data/lib/whisperer.rb +18 -48
  7. data/lib/whisperer/merger.rb +36 -0
  8. data/lib/whisperer/preprocessors/default_values.rb +22 -0
  9. data/lib/whisperer/preprocessors/response_body.rb +2 -2
  10. data/lib/whisperer/record.rb +2 -35
  11. data/lib/whisperer/record/body.rb +3 -3
  12. data/lib/whisperer/record/default_value.rb +13 -0
  13. data/lib/whisperer/record/headers.rb +6 -1
  14. data/lib/whisperer/record/request.rb +1 -1
  15. data/lib/whisperer/record/response.rb +1 -6
  16. data/lib/whisperer/record/response/status.rb +2 -2
  17. data/lib/whisperer/serializers.rb +21 -0
  18. data/lib/whisperer/serializers/json.rb +26 -1
  19. data/lib/whisperer/serializers/json_multiple.rb +1 -1
  20. data/lib/whisperer/storage.rb +41 -0
  21. data/lib/whisperer/tasks/whisperer.rake +3 -3
  22. data/lib/whisperer/version.rb +1 -1
  23. data/spec/cassettes/empty_robb_stark.yml +2 -2
  24. data/spec/cassettes/girls/arya_stark.yml +2 -2
  25. data/spec/cassettes/robb_stark.yml +2 -2
  26. data/spec/cassettes/robb_stark_without_content_length.yml +2 -2
  27. data/spec/cassettes/sansa_stark.yml +1 -1
  28. data/spec/cassettes/starks.yml +2 -2
  29. data/spec/cassettes/wolfs.yml +2 -2
  30. data/spec/unit/merger_spec.rb +98 -0
  31. data/spec/unit/preprocessors/content_length_spec.rb +9 -5
  32. data/spec/unit/preprocessors/default_values_spec.rb +26 -0
  33. data/spec/unit/preprocessors/response_body_spec.rb +4 -4
  34. data/spec/unit/record/headers_spec.rb +13 -1
  35. data/spec/unit/record_spec.rb +0 -73
  36. data/spec/unit/serializers/json_spec.rb +60 -12
  37. data/spec/unit/serializers_spec.rb +24 -0
  38. data/spec/unit/storage_spec.rb +82 -0
  39. data/spec/unit/whisperer_spec.rb +33 -103
  40. metadata +26 -12
@@ -7,7 +7,7 @@ module Whisperer
7
7
  body = @record.response.body
8
8
 
9
9
  unless body.data_obj.nil?
10
- body.string = serializer_class(body.serializer).serialize(
10
+ body.string = serializer_class(body.serializer.to_sym).serialize(
11
11
  body.data_obj,
12
12
  body.serializer_opts
13
13
  )
@@ -16,7 +16,7 @@ module Whisperer
16
16
 
17
17
  protected
18
18
  def serializer_class(name)
19
- Whisperer.serializer(name)
19
+ Whisperer::Serializers.fetch(name)
20
20
  end
21
21
  end
22
22
  end
@@ -1,3 +1,4 @@
1
+ require_relative 'record/default_value'
1
2
  require_relative 'record/request'
2
3
  require_relative 'record/response'
3
4
 
@@ -8,41 +9,7 @@ module Whisperer
8
9
  attribute :request, Whisperer::Request, default: proc { Whisperer::Request.new }
9
10
  attribute :response, Whisperer::Response, default: proc { Whisperer::Response.new }
10
11
  attribute :http_version, String, default: ''
11
- attribute :recorded_at, String, default: proc { Time.now.httpdate }
12
+ attribute :recorded_at, String, default: proc { DefaultValue.new(Time.now.httpdate) }
12
13
  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
14
  end # class Record
48
15
  end # module Whisperer
@@ -4,12 +4,12 @@ module Whisperer
4
4
  class Body
5
5
  include Virtus.model
6
6
 
7
- attribute :encoding, String
8
- attribute :string, String
7
+ attribute :encoding, String, default: proc { DefaultValue.new('UTF-8') }
8
+ attribute :string, String, default: proc { DefaultValue.new('') }
9
9
 
10
10
  # Attributes which are not part of Vcr response
11
11
  attribute :data_obj, Object
12
- attribute :serializer, Symbol, default: proc { :json }
12
+ attribute :serializer, Symbol, default: proc { DefaultValue.new(:json) }
13
13
  attribute :serializer_opts, Hash, default: {}
14
14
  end # class Body
15
15
  end # module Whisperer
@@ -0,0 +1,13 @@
1
+ module Whisperer
2
+ class DefaultValue
3
+ def initialize(val)
4
+ @val = val
5
+ end
6
+
7
+ def is_default; true; end
8
+
9
+ def to_sym; @val; end
10
+
11
+ def to_default; @val; end
12
+ end
13
+ end
@@ -2,9 +2,14 @@ module Whisperer
2
2
  class Headers
3
3
  include Virtus.model
4
4
 
5
- def initialize(*args)
5
+ def initialize(attrs = {})
6
6
  extend Virtus.model
7
7
 
8
+ attrs.each do |attr, val|
9
+ self.attribute(attr.to_sym, Object)
10
+ self.public_send("#{attr}=", val)
11
+ end
12
+
8
13
  super
9
14
  end
10
15
 
@@ -6,7 +6,7 @@ module Whisperer
6
6
  include Virtus.model
7
7
 
8
8
  attribute :uri, String
9
- attribute :method, Symbol
9
+ attribute :method, Symbol, default: proc { DefaultValue.new(:get) }
10
10
  attribute :headers, Whisperer::Headers, default: proc {
11
11
  Whisperer::Headers.new
12
12
  }
@@ -6,12 +6,7 @@ module Whisperer
6
6
  class Response
7
7
  include Virtus.model
8
8
 
9
- attribute :headers, Whisperer::Headers, default: proc {
10
- header = Whisperer::Headers.new
11
- header.attribute(:content_length, String)
12
- header
13
- }
14
-
9
+ attribute :headers, Headers, default: proc { Headers.new(content_length: nil) }
15
10
  attribute :body, Body, default: proc { Body.new }
16
11
  attribute :status, Status, default: proc { Status.new }
17
12
  end # class Response
@@ -3,8 +3,8 @@ module Whisperer
3
3
  class Status
4
4
  include Virtus.model
5
5
 
6
- attribute :code, Integer, default: 200
7
- attribute :message, String, default: 'OK'
6
+ attribute :code, Integer, default: proc { DefaultValue.new(200) }
7
+ attribute :message, String, default: proc { DefaultValue.new('OK') }
8
8
  end # class Status
9
9
  end # class Response
10
10
  end # module Whisperer
@@ -0,0 +1,21 @@
1
+ module Whisperer
2
+ module Serializers
3
+ @serializers = ThreadSafe::Hash.new
4
+
5
+ class << self
6
+ attr_reader :serializers
7
+
8
+ def fetch(name)
9
+ unless serializers[name]
10
+ raise ArgumentError.new("There is not serializer registered with \"#{name}\" name")
11
+ end
12
+
13
+ serializers[name]
14
+ end
15
+
16
+ def register(name, class_name)
17
+ serializers[name] = class_name
18
+ end
19
+ end
20
+ end
21
+ end
@@ -19,7 +19,7 @@ module Whisperer
19
19
 
20
20
  protected
21
21
  def prepare_data
22
- @obj.marshal_dump
22
+ fetch_attrs(@obj)
23
23
  end
24
24
 
25
25
  # This method returns give data as it is.
@@ -28,6 +28,31 @@ module Whisperer
28
28
  def post_prepare_data(data)
29
29
  data
30
30
  end
31
+
32
+ def fetch_attrs(obj)
33
+ # When an OpenStruct object is given
34
+ if obj.respond_to?(:marshal_dump)
35
+ obj.marshal_dump
36
+ # When an object has the attributes method
37
+ elsif obj.respond_to?(:attributes)
38
+ obj.attributes
39
+ # When a pure ruby object is given
40
+ else
41
+ obj.instance_variables.each_with_object({}) do |attr, memo|
42
+ name = attr[1..-1]
43
+
44
+ # If there is an accessor method to read a value,
45
+ # we should use it.
46
+ memo[name] = if obj.respond_to?(name)
47
+ obj.public_send(name)
48
+ else
49
+ obj.instance_variable_get(attr)
50
+ end
51
+
52
+ memo
53
+ end
54
+ end
55
+ end
31
56
  end # class Json
32
57
  end # module Serializers
33
58
  end # module Whisperer
@@ -6,7 +6,7 @@ module Whisperer
6
6
  protected
7
7
  def prepare_data
8
8
  @obj.map do |item|
9
- item.marshal_dump
9
+ fetch_attrs(item)
10
10
  end
11
11
  end
12
12
  end # class JsonMultiple
@@ -0,0 +1,41 @@
1
+ module Whisperer
2
+ class Storage
3
+ @cassette_records = ThreadSafe::Hash.new
4
+
5
+ class << self
6
+ attr_accessor :cassette_records
7
+ private :cassette_records=
8
+
9
+ def define(name, options = {}, &block)
10
+ dsl = Dsl.build
11
+ dsl.instance_eval &block
12
+ record = dsl.container
13
+
14
+ if options[:parent]
15
+ original_record = cassette_record(options[:parent])
16
+
17
+ if original_record.nil?
18
+ raise ArgumentError.new("Parent record \"#{options[:parent]}\" is not declared.")
19
+ else
20
+ Merger.merge(record, original_record)
21
+ end
22
+ end
23
+
24
+ cassette_records[name.to_sym] = record
25
+ end
26
+
27
+ # Returns true if at least one factory is defined, otherwise returns false.
28
+ def defined_any?
29
+ cassette_records.size > 0
30
+ end
31
+
32
+ def cassette_record(name)
33
+ cassette_records[name]
34
+ end
35
+
36
+ def reset_storage
37
+ @cassette_records.clear
38
+ end
39
+ end
40
+ end
41
+ end
@@ -53,8 +53,8 @@ namespace :whisperer do
53
53
  begin
54
54
  Whisperer.generate_all
55
55
 
56
- puts Rainbow('cassettes are generated').green
57
- rescue Whisperer::NocassetteRecordError => error
56
+ puts Rainbow('Cassettes are generated').green
57
+ rescue Whisperer::NoCassetteRecordError => error
58
58
  puts Rainbow("Any cassette builder was found. Please, make sure you define at least one (We are looking for it like: #{config.builders_matcher}).").yellow
59
59
  end
60
60
  end
@@ -67,7 +67,7 @@ namespace :whisperer do
67
67
  Whisperer::generate(name)
68
68
 
69
69
  puts Rainbow("The cassette '#{name}' is generated").green
70
- rescue Whisperer::NocassetteRecordError => error
70
+ rescue Whisperer::NoCassetteRecordError => error
71
71
  puts Rainbow(error.message).red
72
72
  end
73
73
  end
@@ -1,3 +1,3 @@
1
1
  module Whisperer
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -4,7 +4,7 @@ http_interactions:
4
4
  method: get
5
5
  uri: http://example.com/users/2
6
6
  body:
7
- encoding: US-ASCII
7
+ encoding: UTF-8
8
8
  string: ''
9
9
  headers: {}
10
10
  response:
@@ -13,7 +13,7 @@ http_interactions:
13
13
  message: OK
14
14
  headers:
15
15
  Content-Length:
16
- - '14'
16
+ - 14
17
17
  body:
18
18
  encoding: UTF-8
19
19
  string: '{"empty":true}'
@@ -4,7 +4,7 @@ http_interactions:
4
4
  method: get
5
5
  uri: http://example.com/users/1
6
6
  body:
7
- encoding: US-ASCII
7
+ encoding: UTF-8
8
8
  string: ''
9
9
  headers: {}
10
10
  response:
@@ -13,7 +13,7 @@ http_interactions:
13
13
  message: OK
14
14
  headers:
15
15
  Content-Length:
16
- - '58'
16
+ - 58
17
17
  Content-Type:
18
18
  - application/json;charset=utf-8
19
19
  body:
@@ -4,7 +4,7 @@ http_interactions:
4
4
  method: get
5
5
  uri: http://example.com/users/1
6
6
  body:
7
- encoding: US-ASCII
7
+ encoding: UTF-8
8
8
  string: ''
9
9
  headers:
10
10
  Accept:
@@ -15,7 +15,7 @@ http_interactions:
15
15
  message: OK
16
16
  headers:
17
17
  Content-Length:
18
- - '57'
18
+ - 57
19
19
  Content-Type:
20
20
  - application/json;charset=utf-8
21
21
  X-Content-Type-Options:
@@ -4,7 +4,7 @@ http_interactions:
4
4
  method: get
5
5
  uri: http://example.com/users/2
6
6
  body:
7
- encoding: US-ASCII
7
+ encoding: UTF-8
8
8
  string: ''
9
9
  headers: {}
10
10
  response:
@@ -13,7 +13,7 @@ http_interactions:
13
13
  message: OK
14
14
  headers:
15
15
  Content-Length:
16
- - '58'
16
+ - 58
17
17
  body:
18
18
  encoding: UTF-8
19
19
  string: '{"first_name":"Robb","last_name":"Stark","group":"member"}'
@@ -4,7 +4,7 @@ http_interactions:
4
4
  method: get
5
5
  uri: http://example.com/users/2
6
6
  body:
7
- encoding: US-ASCII
7
+ encoding: UTF-8
8
8
  string: ''
9
9
  headers:
10
10
  Accept:
@@ -4,7 +4,7 @@ http_interactions:
4
4
  method: get
5
5
  uri: http://example.com/users
6
6
  body:
7
- encoding: US-ASCII
7
+ encoding: UTF-8
8
8
  string: ''
9
9
  headers:
10
10
  Accept:
@@ -15,7 +15,7 @@ http_interactions:
15
15
  message: OK
16
16
  headers:
17
17
  Content-Length:
18
- - '57'
18
+ - 57
19
19
  Content-Type:
20
20
  - application/json;charset=utf-8
21
21
  X-Content-Type-Options:
@@ -4,7 +4,7 @@ http_interactions:
4
4
  method: get
5
5
  uri: http://example.com/members
6
6
  body:
7
- encoding: US-ASCII
7
+ encoding: UTF-8
8
8
  string: ''
9
9
  headers:
10
10
  Accept:
@@ -15,7 +15,7 @@ http_interactions:
15
15
  message: OK
16
16
  headers:
17
17
  Content-Length:
18
- - '57'
18
+ - 57
19
19
  Content-Type:
20
20
  - application/json;charset=utf-8
21
21
  X-Content-Type-Options:
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ describe Whisperer::Merger do
4
+ describe '#merge' do
5
+ let(:default_parent) {
6
+ r = Whisperer::Record.new(
7
+ request: {
8
+ uri: 'http://google.com',
9
+ },
10
+ response: {
11
+ body: {
12
+ encoding: 'UTF-8',
13
+ string: 'test'
14
+ }
15
+ },
16
+ recorded_at: 'test data'
17
+ )
18
+
19
+ r.request.headers.attribute(:content_length, Integer)
20
+ r.request.headers.attribute(:accept, String)
21
+
22
+ r.request.headers.content_length = 100
23
+ r.request.headers.accept = 'javascript'
24
+
25
+ r.response.body.serializer = :test_serializer
26
+ r
27
+ }
28
+
29
+ let(:default_child) {
30
+ r = Whisperer::Record.new(
31
+ request: {
32
+ headers: {
33
+ content_length: 50
34
+ }
35
+ },
36
+ response: {
37
+ body: {
38
+ encoding: 'UTF-16'
39
+ }
40
+ }
41
+ )
42
+
43
+ r.request.headers.attribute(:content_length, Integer)
44
+
45
+ r.request.headers.content_length = 50
46
+ r
47
+ }
48
+
49
+ let(:parent) { default_parent }
50
+ let(:child) { default_child }
51
+
52
+ subject { Whisperer::Merger.new(child, parent) }
53
+
54
+ before do
55
+ subject.merge
56
+ end
57
+
58
+ it 'has a correct content length header for the request' do
59
+ expect(child.request.headers.content_length).to eq(50)
60
+ end
61
+
62
+ it 'has an accept header for the request' do
63
+ expect(child.request.headers.accept).to eq('javascript')
64
+ end
65
+
66
+ it 'has an url' do
67
+ expect(child.request.uri).to eq('http://google.com')
68
+ end
69
+
70
+ it 'has a correct encoding for the body of the response' do
71
+ expect(child.response.body.encoding).to eq('UTF-16')
72
+ end
73
+
74
+ it 'has a string for the body of the response' do
75
+ expect(child.response.body.string).to eq('test')
76
+ end
77
+
78
+ it 'has a correct serializer for the body of the response' do
79
+ expect(child.response.body.serializer).to eq(:test_serializer)
80
+ end
81
+
82
+ it 'has a correct recorded_at value' do
83
+ expect(child.recorded_at).to eq('test data')
84
+ end
85
+
86
+ context 'when the default value for the serializer is used again' do
87
+ let(:child) {
88
+ r = default_child
89
+ r.response.body.serializer = :json
90
+ r
91
+ }
92
+
93
+ it 'has the newly defined value' do
94
+ expect(child.response.body.serializer).to eq(:json)
95
+ end
96
+ end
97
+ end
98
+ end