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
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
YjQzMTBmMmMzZmIyYTQ4ZGRmZmZlM2ZiZTI1MjMwNjJmNGJkY2Q1Nw==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d6f76493d9ca390d0cfc2cd573d4b41b1c8f643d
|
4
|
+
data.tar.gz: b755938abe8ce6e126f3649ed6de117ce2f51527
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
YzU2Y2E2MTg2ZGI2Njc3NTQ0N2I2YTU2YmFhZDc4NzdiZDk3ZTI5ZTFkN2Ix
|
11
|
-
ZGU5ZjJhMzc4NDkwM2U4NDljNmUyNmU5YjY3YzY2NzZjM2M2NDE=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
YTAwZjBlMzAwY2NmOTFlMmU1NGEwYzE4ODI3NWJmNjc0ZTIxNzNiMTAzMTNh
|
14
|
-
MTIxYzZiYTU4NzM2YzE5ZWZlNGFlMmIzNTIxOTYzN2U2NDNjMzIzNThmZDRm
|
15
|
-
N2NkZmJiZWIxMDQ5ZDdkMWEwNzI0ZThmYjg4OGFiMmM2Njg5YjI=
|
6
|
+
metadata.gz: 2b92b0ebeb0a4864ecc2288c25ec8208e78edd3a514f1dd7630e667e981103af499a01c015b739e1e441266adb8dccd9660a4912de5c42d2dc6ac6e8756346b8
|
7
|
+
data.tar.gz: f35b2ac7a411bc5d0ed4236fdb6dcbb91c5299ed2afff097fa5b74084d546b25010d90cbbc6ed7c65884d625f027f866053e7e1ef14b2e18688a879ed4a41002
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# 0.0.2
|
2
|
+
|
3
|
+
* fixed the bug with using the default values in child cassette builders when parent cassette builders overrided them.
|
4
|
+
* internal improvements helping in developing new features.
|
5
|
+
* improved documentation.
|
6
|
+
* added default values:
|
7
|
+
- `get` is a default method for a request;
|
8
|
+
- `200` is a default status of a response;
|
9
|
+
- `UTF-8` is a default encoding for a body of the request and response.
|
10
|
+
- `json` is a default serializer.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -6,6 +6,13 @@
|
|
6
6
|
|
7
7
|
Do you hate fixtures? I do as well. The purpose of this library is to make your life easier when your application works with external API and you use VCR to stub that API.
|
8
8
|
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- Describes VCR cassettes with Ruby.
|
12
|
+
- Describes entities for a response body of VCR cassettes with [FactoryGirl](/thoughtbot/factory_girl).
|
13
|
+
- Posibility to inherit VCR cassettes (actually cassette builders describing VCR cassettes, but the effect is the same).
|
14
|
+
- Serializers to serialize a response body to a format supported by your API.
|
15
|
+
|
9
16
|
## Installation
|
10
17
|
|
11
18
|
**Requirments**:
|
@@ -29,7 +36,7 @@ To create default directories' structure and the config file with default option
|
|
29
36
|
|
30
37
|
It will create `cassette_builders` directory in your `spec` folder and `.whisperer.yml` file in your root directory of the project.
|
31
38
|
|
32
|
-
If you want to create
|
39
|
+
If you want to create the config file only, you need to execute:
|
33
40
|
|
34
41
|
$ rake whisperer:config:create
|
35
42
|
|
@@ -58,7 +65,7 @@ Whisperer.define(:arya_stark) do
|
|
58
65
|
|
59
66
|
body do
|
60
67
|
encoding 'UTF-8'
|
61
|
-
factory 'arya_stark'
|
68
|
+
factory 'arya_stark'
|
62
69
|
end
|
63
70
|
end
|
64
71
|
|
@@ -87,6 +94,7 @@ You can use multiple factories to generate collection for your response:
|
|
87
94
|
```ruby
|
88
95
|
body do
|
89
96
|
factories ['robb_stark', 'ned_stark'] # again we provide only names of factories
|
97
|
+
serializer :json_multiple
|
90
98
|
end
|
91
99
|
```
|
92
100
|
|
@@ -109,15 +117,16 @@ body do
|
|
109
117
|
)
|
110
118
|
end
|
111
119
|
|
112
|
-
raw_data factories
|
120
|
+
raw_data factories
|
121
|
+
serializer :json_multiple
|
113
122
|
end
|
114
123
|
```
|
115
124
|
|
116
|
-
It is very useful, when you need generate dynamically instances of a factory.
|
125
|
+
It is very useful, when you need to generate dynamically instances of a factory.
|
117
126
|
|
118
127
|
#### Inheritance in cassette builders
|
119
128
|
|
120
|
-
If you need to generate almost the same VCR cassette, but with a bit
|
129
|
+
If you need to generate almost the same VCR cassette, but with a bit different data, you can do it via inheritance:
|
121
130
|
|
122
131
|
```ruby
|
123
132
|
Whisperer.define(:robb_stark, parent: :arya_stark) do
|
@@ -186,6 +195,8 @@ Placeholder is a simple class inheriting `OpenStruct` class:
|
|
186
195
|
|
187
196
|
It decouples factories from your application.
|
188
197
|
|
198
|
+
**Note:** If you use own models instead of `OpenStruct` objects for defining factories, you have to implement `attributes` method returning a hash with attributes for your models. Otherwise, the serializers provided by this gem will use all instance variables of your models for serializing them.
|
199
|
+
|
189
200
|
### Serializers for a response body
|
190
201
|
|
191
202
|
When an external API is subbed with VCR, API response has some format like Json, XML or any other formats. Whisperer supports possibility to convert factories into a format your external API uses. Such mechanism is provided by **serializers** which are used along with building a response body. Whisperer has only 2 serializers:
|
@@ -246,13 +257,65 @@ Now, it can be used as any other serializer:
|
|
246
257
|
end
|
247
258
|
```
|
248
259
|
|
260
|
+
### Sub directories to save cassettes
|
261
|
+
|
262
|
+
By default all generated cassettes are saved in one directory. It is not convenient when you have a lot of cassettes there. Therefore, there is an option to define own subpath in a cassette builder, that subpath will be used for saving a cassette:
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
Whisperer.define(:robb_stark) do
|
266
|
+
response do
|
267
|
+
body do
|
268
|
+
factory 'robb_stark'
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
sub_path 'starks'
|
273
|
+
end
|
274
|
+
```
|
275
|
+
|
276
|
+
If you don't change the path to your cassette, the cassette from the example will be saved in `spec/cassettes/vcr_cassettes/starks` directory. It helps you to structure your cassettes.
|
277
|
+
|
278
|
+
### Default values of cassette builders
|
279
|
+
|
280
|
+
There are attributes which you can omit and the gem will provide default values for them. All values listed in the following example are default and you can omit them:
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
Whisperer.define(:arya_stark) do
|
284
|
+
request do
|
285
|
+
method :get
|
286
|
+
|
287
|
+
body do
|
288
|
+
encoding 'UTF-8'
|
289
|
+
string ''
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
response do
|
294
|
+
status do
|
295
|
+
code 200
|
296
|
+
message 'OK'
|
297
|
+
end
|
298
|
+
|
299
|
+
body do
|
300
|
+
encoding 'UTF-8'
|
301
|
+
serializer :json
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
```
|
306
|
+
|
307
|
+
Also, there are attributes which are automatically calculated if you don't specify values for them:
|
308
|
+
|
309
|
+
- **recorded_at** - it gets a date of generating a cassette;
|
310
|
+
- **content_length** header of a response` - the gem calculates this value based on a response body.
|
311
|
+
|
249
312
|
### Configuration
|
250
313
|
|
251
314
|
You can configure Whisperer through `.whisperer.yml` which should be created in a root directory of your project. It gives you following options:
|
252
315
|
|
253
|
-
- generate_to - the path to save generated cassettes
|
254
|
-
- builders_matcher - the pattern to find builders
|
255
|
-
- factories_matcher - the pattern to find factories
|
316
|
+
- **generate_to** - the path to save generated cassettes
|
317
|
+
- **builders_matcher** - the pattern to find builders
|
318
|
+
- **factories_matcher** - the pattern to find factories
|
256
319
|
|
257
320
|
Example of such file:
|
258
321
|
|
@@ -270,7 +333,7 @@ To generate cassettes based on cassette builders, you need to launch command:
|
|
270
333
|
|
271
334
|
This command will generate new cassettes and re-generate all existing cassettes for VCR.
|
272
335
|
|
273
|
-
To generate
|
336
|
+
To generate one particular cassette, you can use this command
|
274
337
|
|
275
338
|
$ rake whisperer:cassettes:generate[cassette_builder]
|
276
339
|
|
@@ -284,6 +347,14 @@ Manual creation of cassette builders is painful. There is a command which can he
|
|
284
347
|
|
285
348
|
It creates a sample for you in the directory with cassette builders, you need to edit it only.
|
286
349
|
|
350
|
+
## Code examples
|
351
|
+
|
352
|
+
There is a repository with [examples](https://github.com/dnesteryuk/whisperer_example) of the Whisperer gem usage. It will help you to start using it.
|
353
|
+
|
354
|
+
## Resources
|
355
|
+
|
356
|
+
[Introduction into Whisperer](http://nesteryuk.info/2014/11/16/whisperer-introduction.html)
|
357
|
+
|
287
358
|
## Contributing
|
288
359
|
|
289
360
|
1. Fork it
|
data/TODO.md
CHANGED
@@ -1,47 +1,42 @@
|
|
1
|
-
## Release 0.0.
|
1
|
+
## Release 0.0.3
|
2
2
|
|
3
|
-
1.
|
4
|
-
|
5
|
-
- factories for requests
|
3
|
+
1. Find the way to disable altering the existing cassettes by VCR while launching tests.
|
4
|
+
2. Add a helper method to generate a collection of factories to avoid such code:
|
6
5
|
|
7
|
-
|
6
|
+
```ruby
|
7
|
+
factory = FactoryGirl.build(:event)
|
8
8
|
|
9
|
-
|
9
|
+
factories = [factory]
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
# ...
|
14
|
-
end
|
11
|
+
6.times do |t|
|
12
|
+
i = (t + 2).to_s
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
factories << FactoryGirl.build(
|
15
|
+
:event,
|
16
|
+
id: factory.id + i
|
17
|
+
|
18
|
+
)
|
19
|
+
end
|
20
|
+
```
|
21
|
+
which is duplicated in a few builders.
|
22
|
+
3. Default record which will be used only for inheriting, something:
|
19
23
|
|
20
|
-
|
21
|
-
|
24
|
+
```ruby
|
25
|
+
Whisperer.define(:default_for_my_api) do
|
22
26
|
end
|
23
27
|
```
|
24
28
|
|
25
|
-
|
29
|
+
it won't be used for generating a cassette, only for inheriting.
|
30
|
+
4. Add DSL which will allow us to define any helper method for the body DSL. It should allow our users to extend functionality of the gem.
|
31
|
+
5. Add DSL to define the way how we can read attributes from models which are used in factories if it is not `OpenStruct` model.
|
32
|
+
6. `Whisperer::Serializers::Json#fetch_attrs` method should be extracted to own class since it may be useful for a custom serializers.
|
33
|
+
7. Move the documentation to the wiki.
|
26
34
|
|
27
|
-
|
28
|
-
3. The Whisperer::Config.load method is too complex.
|
29
|
-
4. Create rake task for generating factories based on Vcr responses.
|
30
|
-
5. Try to find better way for defining dynamic attributes for headers, it doesn't work when you write:
|
35
|
+
## Release 0.1.0
|
31
36
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
headers: {
|
36
|
-
content_length: 10
|
37
|
-
}
|
38
|
-
}
|
39
|
-
)
|
40
|
-
```
|
37
|
+
1. Create rake task for generating factories based on Vcr responses.
|
38
|
+
|
39
|
+
## Think about possibile features
|
41
40
|
|
42
|
-
|
43
|
-
|
44
|
-
8. Think about the issue with touching Whisperer::cassette_records, it is not ok
|
45
|
-
9. `Whisperer::generate` and `Whisperer::generate_all` receive cassette records twice. Also, it is needless to check existence of a cassette record if it is passed from `generate_all` to `generate`.
|
46
|
-
10. Serializers must be stored similar to preprocessors (in the own module/class).
|
47
|
-
11. Check whether we can use a real model instead of OpenStruct while describing factories.
|
41
|
+
1. Do we need to expose preprocessors or may be extend their usage (unregister, apply only for a certain cassettes)?
|
42
|
+
2. We can use factories for building request body, shold we consider this option as a real feature?
|
data/lib/whisperer.rb
CHANGED
@@ -9,81 +9,50 @@ require 'whisperer/placeholder'
|
|
9
9
|
require 'whisperer/dsl'
|
10
10
|
require 'whisperer/helpers'
|
11
11
|
|
12
|
+
require 'whisperer/storage'
|
12
13
|
require 'whisperer/generator'
|
14
|
+
require 'whisperer/merger'
|
13
15
|
|
14
16
|
require 'whisperer/convertors/hash'
|
15
17
|
require 'whisperer/convertors/interaction'
|
16
18
|
|
19
|
+
require 'whisperer/serializers'
|
17
20
|
require 'whisperer/serializers/json'
|
18
21
|
require 'whisperer/serializers/json_multiple'
|
19
22
|
|
20
23
|
require 'whisperer/preprocessors'
|
24
|
+
require 'whisperer/preprocessors/default_values'
|
21
25
|
require 'whisperer/preprocessors/content_length'
|
22
26
|
require 'whisperer/preprocessors/response_body'
|
23
27
|
|
24
28
|
module Whisperer
|
25
|
-
@cassette_records = ThreadSafe::Hash.new
|
26
|
-
@serializers = ThreadSafe::Hash.new
|
27
|
-
|
28
29
|
class << self
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
def define(name, options = {}, &block)
|
33
|
-
dsl = Dsl.build
|
34
|
-
dsl.instance_eval &block
|
35
|
-
record = dsl.container
|
36
|
-
|
37
|
-
if options[:parent]
|
38
|
-
original_record = cassette_records[options[:parent]]
|
39
|
-
|
40
|
-
if original_record.nil?
|
41
|
-
raise ArgumentError.new("Parent record \"#{options[:parent]}\" is not declared.")
|
42
|
-
else
|
43
|
-
record.merge!(original_record)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
cassette_records[name.to_sym] = record
|
48
|
-
end
|
49
|
-
|
50
|
-
# Returns true if at least one factory is defined, otherwise returns false.
|
51
|
-
def defined_any?
|
52
|
-
cassette_records.size > 0
|
30
|
+
def define(*args, &block)
|
31
|
+
Storage.define(*args, &block)
|
53
32
|
end
|
54
33
|
|
55
34
|
def generate(name)
|
56
35
|
name = name.to_sym
|
57
36
|
|
58
|
-
unless
|
59
|
-
raise
|
37
|
+
unless container = Storage.cassette_record(name)
|
38
|
+
raise NoCassetteRecordError.new("There is not cassette builder with \"#{name}\" name.")
|
60
39
|
end
|
61
40
|
|
62
|
-
container = cassette_records[name]
|
63
|
-
|
64
41
|
Generator.generate(container, name)
|
65
42
|
end
|
66
43
|
|
67
44
|
def generate_all
|
68
|
-
if defined_any?
|
69
|
-
cassette_records.each do |name, container|
|
70
|
-
generate(name)
|
45
|
+
if Storage.defined_any?
|
46
|
+
Storage.cassette_records.each do |name, container|
|
47
|
+
Generator.generate(container, name)
|
71
48
|
end
|
72
49
|
else
|
73
|
-
raise
|
50
|
+
raise NoCassetteRecordError.new('Cassette builders are not found.')
|
74
51
|
end
|
75
52
|
end
|
76
53
|
|
77
|
-
def serializer(name)
|
78
|
-
unless serializers[name]
|
79
|
-
raise ArgumentError.new("There is not serializer registered with \"#{name}\" name")
|
80
|
-
end
|
81
|
-
|
82
|
-
serializers[name]
|
83
|
-
end
|
84
|
-
|
85
54
|
def register_serializer(name, class_name)
|
86
|
-
|
55
|
+
Serializers.register(name, class_name)
|
87
56
|
end
|
88
57
|
|
89
58
|
def register_preprocessor(name, class_name)
|
@@ -91,12 +60,13 @@ module Whisperer
|
|
91
60
|
end
|
92
61
|
end
|
93
62
|
|
94
|
-
class
|
63
|
+
class NoCassetteRecordError < ArgumentError; end
|
95
64
|
end
|
96
65
|
|
97
66
|
|
98
|
-
Whisperer.register_serializer(:json,
|
67
|
+
Whisperer.register_serializer(:json, Whisperer::Serializers::Json)
|
99
68
|
Whisperer.register_serializer(:json_multiple, Whisperer::Serializers::JsonMultiple)
|
100
69
|
|
101
|
-
Whisperer
|
102
|
-
Whisperer
|
70
|
+
Whisperer.register_preprocessor(:default_values, Whisperer::Preprocessors::DefaultValues)
|
71
|
+
Whisperer.register_preprocessor(:response_body, Whisperer::Preprocessors::ResponseBody)
|
72
|
+
Whisperer.register_preprocessor(:content_length, Whisperer::Preprocessors::ContentLength)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Whisperer
|
2
|
+
class Merger
|
3
|
+
extend Helpers
|
4
|
+
|
5
|
+
add_builder 'merge'
|
6
|
+
|
7
|
+
def initialize(child, parent)
|
8
|
+
@child, @parent = child, parent
|
9
|
+
end
|
10
|
+
|
11
|
+
def merge
|
12
|
+
merge_attrs(@parent, @child)
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
def merge_attrs(source, holder)
|
17
|
+
source.attributes.each do |attr, val|
|
18
|
+
if val.respond_to?(:attributes)
|
19
|
+
merge_attrs(val, holder[attr])
|
20
|
+
else
|
21
|
+
# We need to make sure that such attribute is declared
|
22
|
+
# for a record, otherwise, it cannot be written.
|
23
|
+
if holder.class.attribute_set[attr].nil?
|
24
|
+
holder.attribute(attr, source.class.attribute_set[attr])
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_info = holder.class.attribute_set[attr]
|
28
|
+
|
29
|
+
if holder[attr].nil? || holder[attr].respond_to?(:is_default)
|
30
|
+
holder[attr] = val
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module Whisperer
|
4
|
+
module Preprocessors
|
5
|
+
class DefaultValues < Base
|
6
|
+
def process
|
7
|
+
fill_default_vals(@record)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def fill_default_vals(holder)
|
12
|
+
holder.attributes.each do |attr, val|
|
13
|
+
if val.respond_to?(:attributes)
|
14
|
+
fill_default_vals(val)
|
15
|
+
else
|
16
|
+
holder[attr] = val.to_default if val.respond_to?(:is_default)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end # class DefaultValues
|
21
|
+
end # module Preprocessors
|
22
|
+
end # module Whisperer
|