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
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NDliMDRjMTM4ZDRiMDMyZDIwMTc1Y2U1NjBiZWNjNzdhYWM2ZjY3ZQ==
5
- data.tar.gz: !binary |-
6
- YjQzMTBmMmMzZmIyYTQ4ZGRmZmZlM2ZiZTI1MjMwNjJmNGJkY2Q1Nw==
2
+ SHA1:
3
+ metadata.gz: d6f76493d9ca390d0cfc2cd573d4b41b1c8f643d
4
+ data.tar.gz: b755938abe8ce6e126f3649ed6de117ce2f51527
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- MzllODJkNDVmYzgyZTQzYzYzZjBhMWM4YmE2NmMzOWEwOGU0YmI2ZTdmMTEw
10
- YzU2Y2E2MTg2ZGI2Njc3NTQ0N2I2YTU2YmFhZDc4NzdiZDk3ZTI5ZTFkN2Ix
11
- ZGU5ZjJhMzc4NDkwM2U4NDljNmUyNmU5YjY3YzY2NzZjM2M2NDE=
12
- data.tar.gz: !binary |-
13
- YTAwZjBlMzAwY2NmOTFlMmU1NGEwYzE4ODI3NWJmNjc0ZTIxNzNiMTAzMTNh
14
- MTIxYzZiYTU4NzM2YzE5ZWZlNGFlMmIzNTIxOTYzN2U2NDNjMzIzNThmZDRm
15
- N2NkZmJiZWIxMDQ5ZDdkMWEwNzI0ZThmYjg4OGFiMmM2Njg5YjI=
6
+ metadata.gz: 2b92b0ebeb0a4864ecc2288c25ec8208e78edd3a514f1dd7630e667e981103af499a01c015b739e1e441266adb8dccd9660a4912de5c42d2dc6ac6e8756346b8
7
+ data.tar.gz: f35b2ac7a411bc5d0ed4236fdb6dcbb91c5299ed2afff097fa5b74084d546b25010d90cbbc6ed7c65884d625f027f866053e7e1ef14b2e18688a879ed4a41002
@@ -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
@@ -4,4 +4,5 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'rspec', '~> 3.1'
7
- gem 'rake'
7
+ gem 'rake'
8
+
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 only the config file, you need to execute:
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', :json
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, :json_multiple
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 differ data, you can do it via inheritance:
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 only on particular cassette, you can use this command
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
1
+ ## Release 0.0.3
2
2
 
3
- 1. Add info to doc:
4
- - subpath for generating cassettes
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
- ## Release 0.0.2
6
+ ```ruby
7
+ factory = FactoryGirl.build(:event)
8
8
 
9
- 1. Think about the better way for inheriting serializes, now it looks like:
9
+ factories = [factory]
10
10
 
11
- ```ruby
12
- class Whisperer::Serializers::Base
13
- # ...
14
- end
11
+ 6.times do |t|
12
+ i = (t + 2).to_s
15
13
 
16
- class Whisperer::Serializers::Json < Whisperer::Serializers::Base
17
- # ...
18
- end
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
- class Whisperer::Serializers::JsonMultiple < Whisperer::Serializers::Json
21
- # ...
24
+ ```ruby
25
+ Whisperer.define(:default_for_my_api) do
22
26
  end
23
27
  ```
24
28
 
25
- If an user wants to inherit `Whisperer::Serializers::JsonMultiple` it will look even more crazy.
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
- 2. In most cases if we have a serializer for one single factory, we need a serializer for multiple factories. We need to write code which will create a multiple serializer automatically.
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
- ```ruby
33
- Whisperer::Record.new(
34
- response: {
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
- 6. Refactore Whisperer::Record#merge_attrs! method, it should be moved to some another class
43
- 7. Refactore Whisperer.define method, it is another responsibility which should not leave in this module.
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?
@@ -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
- attr_reader :cassette_records
30
- attr_reader :serializers
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 cassette_records[name]
59
- raise NocassetteRecordError.new("There is not cassette builder with \"#{name}\" name.")
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 NocassetteRecordError.new('cassette builders are not found.')
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
- serializers[name] = class_name
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 NocassetteRecordError < ArgumentError; end
63
+ class NoCassetteRecordError < ArgumentError; end
95
64
  end
96
65
 
97
66
 
98
- Whisperer.register_serializer(:json, Whisperer::Serializers::Json)
67
+ Whisperer.register_serializer(:json, Whisperer::Serializers::Json)
99
68
  Whisperer.register_serializer(:json_multiple, Whisperer::Serializers::JsonMultiple)
100
69
 
101
- Whisperer::register_preprocessor(:response_body, Whisperer::Preprocessors::ResponseBody)
102
- Whisperer::register_preprocessor(:content_length, Whisperer::Preprocessors::ContentLength)
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