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