whisperer 0.0.1 → 0.0.2

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