fixturama 0.1.0 → 0.2.0

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/README.md +32 -4
  4. data/fixturama.gemspec +1 -1
  5. data/lib/fixturama.rb +48 -20
  6. data/lib/fixturama/changes.rb +71 -0
  7. data/lib/fixturama/changes/base.rb +28 -0
  8. data/lib/fixturama/changes/chain.rb +64 -0
  9. data/lib/fixturama/changes/chain/actions.rb +31 -0
  10. data/lib/fixturama/changes/chain/arguments.rb +59 -0
  11. data/lib/fixturama/changes/chain/raise_action.rb +35 -0
  12. data/lib/fixturama/changes/chain/return_action.rb +33 -0
  13. data/lib/fixturama/changes/const.rb +30 -0
  14. data/lib/fixturama/changes/request.rb +91 -0
  15. data/lib/fixturama/changes/request/response.rb +62 -0
  16. data/lib/fixturama/changes/request/responses.rb +22 -0
  17. data/lib/fixturama/changes/seed.rb +39 -0
  18. data/lib/fixturama/config.rb +5 -0
  19. data/lib/fixturama/fixture_error.rb +31 -0
  20. data/lib/fixturama/loader.rb +1 -0
  21. data/lib/fixturama/loader/context.rb +1 -0
  22. data/lib/fixturama/loader/value.rb +1 -0
  23. data/spec/fixturama/seed_fixture/_spec.rb +1 -0
  24. data/spec/fixturama/seed_fixture/seed.yml +0 -5
  25. metadata +34 -34
  26. data/lib/fixturama/seed.rb +0 -15
  27. data/lib/fixturama/stubs.rb +0 -79
  28. data/lib/fixturama/stubs/chain.rb +0 -90
  29. data/lib/fixturama/stubs/chain/actions.rb +0 -39
  30. data/lib/fixturama/stubs/chain/actions/raise.rb +0 -21
  31. data/lib/fixturama/stubs/chain/actions/return.rb +0 -23
  32. data/lib/fixturama/stubs/chain/arguments.rb +0 -86
  33. data/lib/fixturama/stubs/const.rb +0 -43
  34. data/lib/fixturama/stubs/request.rb +0 -98
  35. data/lib/fixturama/stubs/request/response.rb +0 -43
  36. data/lib/fixturama/stubs/request/responses.rb +0 -20
  37. data/lib/fixturama/utils.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bfb62ba99d427f8a3f03b6d46a0fb7e6712dd31bfff6da7c17fb075cfa930f0
4
- data.tar.gz: fa1b0359c0681a73723a915dd499945602b350b3b802b68e6962a66753a5d208
3
+ metadata.gz: ed87b651886697aaea8b0a79ddbf5b875372ca382926f3dc07d8f3bec2a4d07d
4
+ data.tar.gz: 76248c5bb6b75d335c80387d11b6446cee23829bf1ca8f6fb319c12a1b5f0e52
5
5
  SHA512:
6
- metadata.gz: 987b0cf303e78879b18278d85ace7cf26261e6c76000b0081571cb40a3dcf763435cb5560de122bf3a466b9c781684c8ed8d183697fbd1b84aa619c8409e34c8
7
- data.tar.gz: d85486458d90dbff1e3f2136c5862e081aa1a1861bb5e6320fb68bbc258d11ce1e978de5603c900080ef719809e97a4d0bf793a46c8f90af33e7a6626d258835
6
+ metadata.gz: 20607f5c8bc2ed14ac8bef4bf207495946a02f4a39e2d6d77661b4fa154c01b3f4072e62aaf9083509369eb4b9fe01b2c9dfe1aa4e87b7bf2bb9a3b31b0a1827
7
+ data.tar.gz: baf0b2716a960d8885dda3dde622009ebf8a1335912358898283b42b7beea04137e9d2ee1bcc58d305b8cdf14c21f3bdfa4442674a4dbb780aad61486b0e2682
@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
+ ## [0.2.0] - [2020-02-17]
9
+
10
+ ### Added
11
+
12
+ - Stubbing and seeding from the same source file via the `call_fixture` method (nepalez)
13
+
14
+ ```yaml
15
+ # ./changes.yml
16
+ ---
17
+ - type: user
18
+ params:
19
+ id: 1
20
+
21
+ - const: DEFAULT_USER_ID
22
+ value: 1
23
+
24
+ - url: https://example.com/users/default
25
+ method: get
26
+ responses:
27
+ - body:
28
+ id: 1
29
+ name: Andrew
30
+ ```
31
+
32
+ ```ruby
33
+ before { call_fixture "#{__dir__}/changes.yml" }
34
+ ```
35
+
8
36
  ## [0.1.0] - [2020-02-09]
9
37
 
10
38
  ### Added
@@ -200,3 +228,4 @@ This is a first public release with features extracted from production app.
200
228
  [0.0.6]: https://github.com/nepalez/fixturama/compare/v0.0.5...v0.0.6
201
229
  [0.0.7]: https://github.com/nepalez/fixturama/compare/v0.0.6...v0.0.7
202
230
  [0.1.0]: https://github.com/nepalez/fixturama/compare/v0.0.7...v0.1.0
231
+ [0.2.0]: https://github.com/nepalez/fixturama/compare/v0.1.0...v0.2.0
data/README.md CHANGED
@@ -124,16 +124,17 @@ Use the `count: 2` key to create more objects at once.
124
124
 
125
125
  ### Stubbing
126
126
 
127
- Another opinionated format we use for stubs (`stub_fixture`). The gem supports stubbing both message chains and constants.
127
+ The gem supports stubbing message chains, constants and http requests with the following keys.
128
128
 
129
129
  For message chains:
130
130
 
131
131
  - `class` for stubbed class
132
132
  - `chain` for messages chain
133
133
  - `arguments` (optional) for specific arguments
134
- - `actions` for an array of actions for consecutive invocations of the chain
135
-
136
- Every action either `return` some value, or `raise` some exception
134
+ - `actions` for an array of actions for consecutive invocations of the chain with keys
135
+ - `return` for a value to be returned
136
+ - `raise` for an exception to be risen
137
+ - `repeate` for a number of invocations with this action
137
138
 
138
139
  For constants:
139
140
 
@@ -152,6 +153,7 @@ For http requests:
152
153
  - `status`
153
154
  - `body`
154
155
  - `headers`
156
+ - `repeate` for the number of times this response should be returned before switching to the next one
155
157
 
156
158
  ```yaml
157
159
  # ./stubs.yml
@@ -179,6 +181,7 @@ For http requests:
179
181
  - <%= profile_id %>
180
182
  actions:
181
183
  - return: true
184
+ repeate: 1 # this is the default value
182
185
  - raise: ActiveRecord::RecordNotFound
183
186
 
184
187
  - const: NOTIFIER_TIMEOUT_SEC
@@ -192,6 +195,7 @@ For http requests:
192
195
  password: bar
193
196
  responses:
194
197
  - status: 200 # for the first call
198
+ repeate: 1 # this is the default value, but you can set another one
195
199
  - status: 404 # for any other call
196
200
 
197
201
  - uri: htpps://example.com/foo # exact string!
@@ -231,6 +235,30 @@ I find it especially helpful when I need to check different edge cases. Instead
231
235
 
232
236
  Looking at the spec I can easily figure out the "structure" of expectation, while looking at fixtures I can check the concrete corner cases.
233
237
 
238
+ ## Single Source of Changes
239
+
240
+ If you will, you can list all stubs and seeds at the one single file like
241
+
242
+ ```yaml
243
+ # ./changes.yml
244
+ ---
245
+ - type: user
246
+ params:
247
+ id: 1
248
+ name: Andrew
249
+
250
+ - const: DEFAULT_USER_ID
251
+ value: 1
252
+ ```
253
+
254
+ This fixture can be applied via `call_fixture` method just like we did above with `seed_fixture` and `stub_fixture`:
255
+
256
+ ```ruby
257
+ before { call_fixture "#{__dir__}/changes.yml" }
258
+ ```
259
+
260
+ In fact, since the `v0.2.0` all those methods are just the aliases of the `call_fixture`.
261
+
234
262
  ## License
235
263
 
236
264
  The gem is available as open source under the terms of the [MIT License][license].
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "fixturama"
3
- gem.version = "0.1.0"
3
+ gem.version = "0.2.0"
4
4
  gem.author = "Andrew Kozin (nepalez)"
5
5
  gem.email = "andrew.kozin@gmail.com"
6
6
  gem.homepage = "https://github.com/nepalez/fixturama"
@@ -6,44 +6,72 @@ require "rspec"
6
6
  require "webmock/rspec"
7
7
  require "yaml"
8
8
 
9
+ #
10
+ # A set of helpers to prettify specs with fixtures
11
+ #
9
12
  module Fixturama
13
+ require_relative "fixturama/fixture_error"
10
14
  require_relative "fixturama/config"
11
- require_relative "fixturama/utils"
12
15
  require_relative "fixturama/loader"
13
- require_relative "fixturama/stubs"
14
- require_relative "fixturama/seed"
16
+ require_relative "fixturama/changes"
15
17
 
18
+ # Set the initial value for database-generated IDs
19
+ # @param [#to_i] value
20
+ # @return [Fixturama]
16
21
  def self.start_ids_from(value)
17
22
  Config.start_ids_from(value)
23
+ self
18
24
  end
19
25
 
20
- def stub_fixture(path, **opts)
21
- items = load_fixture(path, **opts)
22
- raise "The fixture should contain an array" unless items.is_a?(Array)
26
+ # @!method read_fixture(path, options)
27
+ # Read the text content of the fixture
28
+ # @param [#to_s] path The path to the fixture file
29
+ # @param [Hash<Symbol, _>] options
30
+ # The list of options to be accessible in the fixture
31
+ # @return [String]
32
+ def read_fixture(path, **options)
33
+ content = File.read(path)
34
+ hashie = Hashie::Mash.new(options)
35
+ bindings = hashie.instance_eval { binding }
23
36
 
24
- items.each { |item| fixturama_stubs.add(item) }
25
- fixturama_stubs.apply(self)
37
+ ERB.new(content).result(bindings)
26
38
  end
27
39
 
28
- def seed_fixture(path, **opts)
29
- Array(load_fixture(path, **opts)).each { |item| Seed.call(item) }
40
+ # @!method load_fixture(path, options)
41
+ # Load data from a fixture
42
+ # @param (see #read_fixture)
43
+ # @return [Object]
44
+ def load_fixture(path, **options)
45
+ Loader.new(path, options).call
30
46
  end
31
47
 
32
- def load_fixture(path, **opts)
33
- Loader.new(path, opts).call
48
+ # @!method call_fixture(path, options)
49
+ # Stub different objects and seed the database from a fixture
50
+ # @param (see #read_fixture)
51
+ # @return [RSpec::Core::Example] the current example
52
+ def call_fixture(path, **options)
53
+ items = Array load_fixture(path, **options)
54
+ items.each { |item| changes.add(item) }
55
+ tap { changes.call(self) }
56
+ rescue FixtureError => err
57
+ raise err.with_file(path)
34
58
  end
35
59
 
36
- def read_fixture(path, **opts)
37
- content = File.read(path)
38
- hashie = Hashie::Mash.new(opts)
39
- bindings = hashie.instance_eval { binding }
60
+ # @!method seed_fixture(path, options)
61
+ # The alias for the +call_fixture+
62
+ # @param (see #call_fixture)
63
+ # @return (see #call_fixture)
64
+ alias seed_fixture call_fixture
40
65
 
41
- ERB.new(content).result(bindings)
42
- end
66
+ # @!method stub_fixture(path, options)
67
+ # The alias for the +call_fixture+
68
+ # @param (see #call_fixture)
69
+ # @return (see #call_fixture)
70
+ alias stub_fixture call_fixture
43
71
 
44
72
  private
45
73
 
46
- def fixturama_stubs
47
- @fixturama_stubs ||= Stubs.new
74
+ def changes
75
+ @changes ||= Changes.new
48
76
  end
49
77
  end
@@ -0,0 +1,71 @@
1
+ module Fixturama
2
+ #
3
+ # @private
4
+ # Registry of changes (stubs and seeds)
5
+ #
6
+ class Changes
7
+ require_relative "changes/base"
8
+ require_relative "changes/chain"
9
+ require_relative "changes/const"
10
+ require_relative "changes/request"
11
+ require_relative "changes/seed"
12
+
13
+ # Match option keys to the type of an item
14
+ TYPES = {
15
+ actions: Chain,
16
+ arguments: Chain,
17
+ basic_auth: Request,
18
+ body: Request,
19
+ chain: Chain,
20
+ class: Chain,
21
+ const: Const,
22
+ count: Seed,
23
+ headers: Request,
24
+ http_method: Request,
25
+ object: Chain,
26
+ params: Seed,
27
+ query: Request,
28
+ response: Request,
29
+ responses: Request,
30
+ traits: Seed,
31
+ type: Seed,
32
+ uri: Request,
33
+ url: Request,
34
+ value: Const,
35
+ }.freeze
36
+
37
+ # Adds new change to the registry
38
+ # @param [Hash] options
39
+ # @return [Fixturama::Changes]
40
+ # @raise [Fixturama::FixtureError] if the options cannot be processed
41
+ def add(options)
42
+ options = Hash(options).transform_keys(&:to_sym)
43
+ types = options.keys.map { |key| TYPES[key] }.compact.uniq
44
+ raise "Wrong count" unless types.count == 1
45
+
46
+ @changes << types.first.new(options)
47
+ self
48
+ rescue FixtureError => err
49
+ raise err
50
+ rescue StandardError => err
51
+ raise FixtureError.new("an operation", options, err)
52
+ end
53
+
54
+ # Apply all registered changes to the RSpec example
55
+ # @param [RSpec::Core::Example] example
56
+ # @return [self]
57
+ def call(example)
58
+ @changes
59
+ .group_by(&:key)
60
+ .values
61
+ .map { |changes| changes.reduce :merge }
62
+ .each { |change| change.call(example) }
63
+ end
64
+
65
+ private
66
+
67
+ def initialize
68
+ @changes = []
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,28 @@
1
+ class Fixturama::Changes
2
+ #
3
+ # @private
4
+ # @abstract
5
+ # Base class for changes downloaded from a fixture
6
+ #
7
+ class Base
8
+ # @!attribute [r] key The key identifier of the change
9
+ # @return [String]
10
+ alias key hash
11
+
12
+ # Merge the other change into the current one
13
+ # @param [Fixturama::Changes::Base] other
14
+ # @return [Fixturama::Changes::Base]
15
+ def merge(other)
16
+ # By default just take the other change if applicable
17
+ other.class == self.class && other.key == key ? other : self
18
+ end
19
+
20
+ # @abstract
21
+ # Call the corresponding change (either a stub or a seed)
22
+ # @param [RSpec::Core::Example] _example The RSpec example
23
+ # @return [self]
24
+ def call(_example)
25
+ self
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,64 @@
1
+ class Fixturama::Changes
2
+ #
3
+ # @private
4
+ # Stub a chain of messages
5
+ #
6
+ class Chain < Base
7
+ require_relative "chain/raise_action"
8
+ require_relative "chain/return_action"
9
+ require_relative "chain/actions"
10
+ require_relative "chain/arguments"
11
+
12
+ def key
13
+ @key ||= ["chain", @receiver.name, *@messages].join(".")
14
+ end
15
+
16
+ def merge(other)
17
+ return self unless other.class == self.class && other.key == key
18
+
19
+ tap { @arguments = (other.arguments | arguments).sort_by(&:order) }
20
+ end
21
+
22
+ def call(example)
23
+ call_action = example.send(:receive_message_chain, *@messages) do |*real|
24
+ action = arguments.find { |expected| expected.match?(*real) }
25
+ action ? action.call : raise("Unexpected arguments: #{real}")
26
+ end
27
+
28
+ example.send(:allow, @receiver).to call_action
29
+ end
30
+
31
+ protected
32
+
33
+ attr_reader :arguments
34
+
35
+ private
36
+
37
+ def initialize(**options)
38
+ @receiver = receiver_from(options)
39
+ @messages = messages_from(options)
40
+ @arguments = [Arguments.new(options)]
41
+ end
42
+
43
+ def receiver_from(options)
44
+ case options.slice(:class, :object).keys
45
+ when %i[class] then Kernel.const_get(options[:class])
46
+ when %i[object] then Object.send(:eval, options[:object])
47
+ else raise
48
+ end
49
+ rescue StandardError => err
50
+ raise Fixturama::FixtureError.new("a stabbed object", options, err)
51
+ end
52
+
53
+ def messages_from(options)
54
+ case value = options[:chain]
55
+ when Array then value.map(&:to_sym)
56
+ when String then [value.to_sym]
57
+ when Symbol then value
58
+ else raise
59
+ end
60
+ rescue StandardError => err
61
+ raise Fixturama::FixtureError.new("a messages chain", options, err)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ class Fixturama::Changes::Chain
2
+ #
3
+ # @private
4
+ # Keep arguments of a message chain along with the corresponding actions
5
+ #
6
+ class Actions
7
+ def next
8
+ @list.count > 1 ? @list.pop : @list.first
9
+ end
10
+
11
+ private
12
+
13
+ def initialize(*list)
14
+ list = [{ return: nil }] if list.empty?
15
+
16
+ @list = list.flatten.reverse.flat_map do |item|
17
+ action = build(item)
18
+ [action] * action.repeat
19
+ end
20
+ end
21
+
22
+ def build(item)
23
+ item = Hash(item).transform_keys(&:to_sym)
24
+ case item.slice(:return, :raise).keys
25
+ when %i[return] then ReturnAction.new(item)
26
+ when %i[raise] then RaiseAction.new(item)
27
+ else raise Fixturama::FixtureError.new("an action", item)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,59 @@
1
+ class Fixturama::Changes::Chain
2
+ #
3
+ # @private
4
+ # Keep a set of arguments along with the corresponding actions to be done
5
+ #
6
+ class Arguments
7
+ # @return [Array<Object>] the collection of arguments
8
+ attr_reader :arguments
9
+
10
+ # Order of comparing this set of arguments with the actual ones
11
+ # @return [Integer]
12
+ def order
13
+ -arguments.count
14
+ end
15
+
16
+ # Compare definitions by sets of arguments
17
+ # @param [Fixturama::Changes::Chain::Arguments] other
18
+ # @return [Boolean]
19
+ def ==(other)
20
+ other.arguments == arguments
21
+ end
22
+
23
+ # If actual arguments are covered by the current ones
24
+ def match?(*args, **opts)
25
+ return false if arguments.count > args.count + 1
26
+
27
+ arguments.first(args.count).zip(args).each do |(expected, actual)|
28
+ return false unless actual == expected
29
+ end
30
+
31
+ if arguments.count > args.count
32
+ Hash(arguments.last).transform_keys(&:to_sym).each do |k, v|
33
+ return false unless opts[k] == v
34
+ end
35
+ end
36
+
37
+ true
38
+ end
39
+
40
+ # Call the corresponding action if actual arguments are matched
41
+ def call
42
+ @actions.next.call
43
+ end
44
+
45
+ private
46
+
47
+ def initialize(options)
48
+ @arguments = extract(options, :arguments)
49
+ @actions = Actions.new(*extract(options, :actions))
50
+ end
51
+
52
+ def extract(options, key)
53
+ return [] unless options.key?(key)
54
+
55
+ source = options[key]
56
+ source.is_a?(Array) ? source : [source]
57
+ end
58
+ end
59
+ end