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
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
|