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.
- checksums.yaml +5 -13
- data/CHANGELOG.md +10 -0
- data/Gemfile +2 -1
- data/README.md +80 -9
- data/TODO.md +30 -35
- data/lib/whisperer.rb +18 -48
- data/lib/whisperer/merger.rb +36 -0
- data/lib/whisperer/preprocessors/default_values.rb +22 -0
- data/lib/whisperer/preprocessors/response_body.rb +2 -2
- data/lib/whisperer/record.rb +2 -35
- data/lib/whisperer/record/body.rb +3 -3
- data/lib/whisperer/record/default_value.rb +13 -0
- data/lib/whisperer/record/headers.rb +6 -1
- data/lib/whisperer/record/request.rb +1 -1
- data/lib/whisperer/record/response.rb +1 -6
- data/lib/whisperer/record/response/status.rb +2 -2
- data/lib/whisperer/serializers.rb +21 -0
- data/lib/whisperer/serializers/json.rb +26 -1
- data/lib/whisperer/serializers/json_multiple.rb +1 -1
- data/lib/whisperer/storage.rb +41 -0
- data/lib/whisperer/tasks/whisperer.rake +3 -3
- data/lib/whisperer/version.rb +1 -1
- data/spec/cassettes/empty_robb_stark.yml +2 -2
- data/spec/cassettes/girls/arya_stark.yml +2 -2
- data/spec/cassettes/robb_stark.yml +2 -2
- data/spec/cassettes/robb_stark_without_content_length.yml +2 -2
- data/spec/cassettes/sansa_stark.yml +1 -1
- data/spec/cassettes/starks.yml +2 -2
- data/spec/cassettes/wolfs.yml +2 -2
- data/spec/unit/merger_spec.rb +98 -0
- data/spec/unit/preprocessors/content_length_spec.rb +9 -5
- data/spec/unit/preprocessors/default_values_spec.rb +26 -0
- data/spec/unit/preprocessors/response_body_spec.rb +4 -4
- data/spec/unit/record/headers_spec.rb +13 -1
- data/spec/unit/record_spec.rb +0 -73
- data/spec/unit/serializers/json_spec.rb +60 -12
- data/spec/unit/serializers_spec.rb +24 -0
- data/spec/unit/storage_spec.rb +82 -0
- data/spec/unit/whisperer_spec.rb +33 -103
- 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.
|
19
|
+
Whisperer::Serializers.fetch(name)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
data/lib/whisperer/record.rb
CHANGED
@@ -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
|
@@ -2,9 +2,14 @@ module Whisperer
|
|
2
2
|
class Headers
|
3
3
|
include Virtus.model
|
4
4
|
|
5
|
-
def initialize(
|
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,12 +6,7 @@ module Whisperer
|
|
6
6
|
class Response
|
7
7
|
include Virtus.model
|
8
8
|
|
9
|
-
attribute :headers,
|
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
|
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
|
@@ -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('
|
57
|
-
rescue Whisperer::
|
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::
|
70
|
+
rescue Whisperer::NoCassetteRecordError => error
|
71
71
|
puts Rainbow(error.message).red
|
72
72
|
end
|
73
73
|
end
|
data/lib/whisperer/version.rb
CHANGED
@@ -4,7 +4,7 @@ http_interactions:
|
|
4
4
|
method: get
|
5
5
|
uri: http://example.com/users/2
|
6
6
|
body:
|
7
|
-
encoding:
|
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
|
-
-
|
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:
|
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
|
-
-
|
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:
|
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
|
-
-
|
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:
|
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
|
-
-
|
16
|
+
- 58
|
17
17
|
body:
|
18
18
|
encoding: UTF-8
|
19
19
|
string: '{"first_name":"Robb","last_name":"Stark","group":"member"}'
|
data/spec/cassettes/starks.yml
CHANGED
@@ -4,7 +4,7 @@ http_interactions:
|
|
4
4
|
method: get
|
5
5
|
uri: http://example.com/users
|
6
6
|
body:
|
7
|
-
encoding:
|
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
|
-
-
|
18
|
+
- 57
|
19
19
|
Content-Type:
|
20
20
|
- application/json;charset=utf-8
|
21
21
|
X-Content-Type-Options:
|
data/spec/cassettes/wolfs.yml
CHANGED
@@ -4,7 +4,7 @@ http_interactions:
|
|
4
4
|
method: get
|
5
5
|
uri: http://example.com/members
|
6
6
|
body:
|
7
|
-
encoding:
|
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
|
-
-
|
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
|