light-service-ext 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/CHANGELOG.md +9 -1
  4. data/README.md +193 -5
  5. data/dev/setup.rb +5 -3
  6. data/lib/light-service-ext/application_action.rb +0 -21
  7. data/lib/light-service-ext/application_context.rb +29 -1
  8. data/lib/light-service-ext/application_validator_action.rb +2 -2
  9. data/lib/light-service-ext/configuration.rb +28 -0
  10. data/lib/light-service-ext/constants.rb +1 -1
  11. data/lib/light-service-ext/error_info.rb +11 -11
  12. data/lib/light-service-ext/version.rb +1 -1
  13. data/lib/light-service-ext/with_error_handler.rb +26 -0
  14. data/lib/light-service-ext.rb +35 -13
  15. data/light-service-ext.gemspec +2 -1
  16. data/spec/light-service-ext/application_action_spec.rb +39 -0
  17. data/spec/light-service-ext/application_context_spec.rb +189 -0
  18. data/spec/{light_service_ext → light-service-ext}/application_organizer_spec.rb +9 -2
  19. data/spec/light-service-ext/configuration_spec.rb +66 -0
  20. data/spec/{light_service_ext → light-service-ext}/error_info_spec.rb +18 -7
  21. data/spec/light-service-ext/with_error_handler_spec.rb +62 -0
  22. data/spec/light_service_ext_spec.rb +67 -1
  23. data/spec/spec_helper.rb +7 -3
  24. metadata +42 -22
  25. data/spec/light_service_ext/application_action_spec.rb +0 -77
  26. data/spec/light_service_ext/application_context_spec.rb +0 -68
  27. /data/spec/{light_service_ext → light-service-ext}/all_actions_complete_action_spec.rb +0 -0
  28. /data/spec/{light_service_ext → light-service-ext}/application_contract_spec.rb +0 -0
  29. /data/spec/{light_service_ext → light-service-ext}/application_validator_action_spec.rb +0 -0
  30. /data/spec/{light_service_ext → light-service-ext}/around_action_execute_extension_spec.rb +0 -0
  31. /data/spec/{light_service_ext → light-service-ext}/context_error_spec.rb +0 -0
  32. /data/spec/{light_service_ext → light-service-ext}/regex_spec.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 337b734063a899dc19d0a5d6591b127015473b682cd98a3d4c0454e3425ccdef
4
- data.tar.gz: 17bd81121eb9be17e5f81bb050e86d43edf82eb358bfe2034cfd1f27ddde1475
3
+ metadata.gz: a72952c78aab0b82bbce081bd1c2ab955ef264a9fbb5186a9ead3a429eaa504e
4
+ data.tar.gz: 43cb1863bac420a7150573a0e24cc141bd2384e36cfc4440dbd12f2fce276e93
5
5
  SHA512:
6
- metadata.gz: 6315693fe915515d1c1def2c80af6bc6b68b7f6268fb4cb9e3303da344d8284b95719ab219fc3c601c646f2169edfd894f222770f269c7e422b748ff1945e398
7
- data.tar.gz: 3249c9ef1fecde4b3312685fc2b68a46a1f41466bf89f81ce4db00f6bf9327cbe70c8b2c475c23c48dd24f8df486f0d618a0604fd367c663a5cdbf39f3f4776a
6
+ metadata.gz: cabc217b43f33aa0771de27613eeecd83f425786e93946ed14d716523814a4af7045d464ae16841b6d3e2db20a8b0f4d780190363ad130bf934c100b962ba1f1
7
+ data.tar.gz: 5ab3b2151ebb98e80b65a24717a628d7e9f60f206b25c258a05d32e3d57c85e832d99b92905f4883e2d770e9240da85e611771b2497d17f7c87687e670b82838
data/.rubocop.yml CHANGED
@@ -236,3 +236,9 @@ RSpec/MultipleMemoizedHelpers:
236
236
 
237
237
  RSpec/PredicateMatcher:
238
238
  Enabled: false
239
+
240
+ RSpec/FilePath:
241
+ Enabled: false
242
+
243
+ RSpec/SubjectStub:
244
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2023-02-07
3
+ ## [0.1.0] - 2023-02-15
4
4
 
5
5
  - Initial release
6
+
7
+ ## [0.1.1] - 2023-02-15
8
+
9
+ - Fixing issue with with use of `relative_path` inside of `light-service-ext.gemspec`
10
+
11
+ ## [0.1.2] - YYYY-MM-DD
12
+
13
+ - Updates `README.md` with detailed information on features provided
data/README.md CHANGED
@@ -1,8 +1,11 @@
1
- # LightServiceExt
1
+ ![LightService](https://raw.githubusercontent.com/adomokos/light-service/master/resources/light-service.png)
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/light-service-ext`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ # Light Service Extensions
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ Aims to enhance [light-service](https://github.com/adomokos/light-service) to enhance this powerful and flexible service skeleton framework with an emphasis on simplicity
6
+
7
+ ## Console
8
+ run `bin/console` for an interactive prompt.
6
9
 
7
10
  ## Installation
8
11
 
@@ -20,9 +23,194 @@ Or install it yourself as:
20
23
 
21
24
  $ gem install light-service-ext
22
25
 
23
- ## Usage
26
+ ## ApplicationContext
27
+
28
+ > Adds useful defaults to the organizer/orchestrator context
29
+ - `:input` ~> values originally provided to organizer get moved here for better isolation
30
+ - `:params`
31
+ - stores values `filtered` and `mapped` from original `input`
32
+ - outcomes/return values provided by any action that implements `LightServiceExt::ApplicationAction`
33
+ - `:errors`
34
+ - validation errors processed by `LightServiceExt::ApplicationValidatorAction` [dry-validation](https://github.com/dry-rb/dry-validation) contract
35
+ - manually added by an action e.g. `{ errors: { email: 'not found' } }`
36
+ - `:successful_actions` ~> provides a list of actions processed mostly useful for debugging purposes
37
+ - `:api_responses` ~> contains a list of external API interactions mostly for recording/debugging purposes
38
+ - `:allow_raise_on_failure` ~> determines whether or not to throw a `RaiseOnContextError` error up the stack in the case of validation errors and/or captured exceptions
39
+ - `:outcome` denotes the current status of the organizer with one of the following flags:
40
+ - `LightServiceExt::Outcome::COMPLETE`
41
+
42
+ Example
43
+
44
+ ````ruby
45
+ input = { order: order }
46
+ overrides = {} # optionally override `params`, `errors` and `allow_raise_on_failure`
47
+ LightServiceExt::ApplicationContext.make_with_defaults(input, overrides)
48
+
49
+ # => { input: { order: order },
50
+ # params: {},
51
+ # errors: {},
52
+ # successful_actions: [],
53
+ # api_responses: [],
54
+ # allow_raise_on_failure: true
55
+ # }
56
+ ````
57
+
58
+ #### Useful methods
59
+
60
+ - `.add_params(**params)`
61
+ - Adds given args to context's `params` field
62
+ - e.g. `add_params(user_id: 1) # => { params: { user_id: 1 } }`
63
+ - `.add_errors(**errors)`
64
+ - Adds given args to to context's `errors` field
65
+ - Fails and returns from current action/organizer's context
66
+ - e.g. `add_to_errors(email: 'not found') # => { errors: { email: 'not found' } }`
67
+
68
+
69
+ ## ApplicationOrganizer
70
+
71
+ > Adds the following support
72
+
73
+ ### Useful methods
74
+
75
+ - `.reduce_if_success(<list of actions>)` prevents execution of action/step in the case of context failure or `:errors` present
76
+ - `.with_context(&block)` calls given block with `:ctx` argument
77
+ - `.execute_if` ~> Useful if you want the current `Organizer` to act as a `Orchestrator` and call another organizer
78
+ - *ONLY* modifies the current organizer/orchestrator's as a result of executing `organizer_or_action_class_or_proc` if manually applied by a given `result_callback` Proc
79
+ - Executed `steps` do modify the current organizer/orchestrator's context without the need for manual intervention
80
+ - Arguments:
81
+ - `condition_block` (required) ~> given block is called with current `context` argument
82
+ - `organizer_or_action_class_or_proc` (required) ~> only executed if `condition_block` evaluates to `true`
83
+ - must be one of `ApplicationOrganizer`, `ApplicationAction`, `Proc`
84
+ - `apply_ctx_transform` (optional)
85
+ - given block is called prior to `organizer_or_action_class_or_proc` being executed
86
+ - e.g. `apply_ctx_transform: -> (context) { context[:params][:user_id] = record(context)&.id }`
87
+ - returned value gets passed to `organizer_or_action_class_or_proc` call
88
+ - `result_callback` (optional)
89
+ - given block is called after `organizer_or_action_class_or_proc` has been executed
90
+ - Useful in the case where you want to augment the current organizer's context based on the context returned from the `organizer_or_action_class_or_proc` call
91
+ - e.g. `result_callback: -> (ctx:, result:) { ctx[:params] = result[:params] }`
92
+ - `ctx:` represents the main `organizer/orchestrator's` context
93
+ - `result:` represents the context returned from the executed `organizer_or_action_class_or_proc`
94
+ - `steps` (optional) ~> calls current `organizer/orchestrator's` actions/steps and called once `organizer_or_action_class_or_proc` has been processed
95
+ - *PLEASE NOTE* called regardless of the result from the `organizer_or_action_class_or_proc` call unless you *manually* fail the current context or add `:errors`
96
+
97
+ #### Error Handling
98
+ > Provided by `.with_error_handler`
99
+
100
+ - Records errors via `issue_error_report!` into context as exemplified below:
101
+ ```ruby
102
+ {
103
+ errors: {
104
+ base: "some-exception-message",
105
+ internal_only: {
106
+ type: 'ArgumentError',
107
+ message: "`user_id` must be a number",
108
+ exception: "ArgumentError : `user_id` must be a number",
109
+ backtrace: [], # filtered backtrace via `[ActiveSupport::BacktraceCleaner](https://api.rubyonrails.org/classes/ActiveSupport/BacktraceCleaner.html)`
110
+ error: original_captured_exception
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ - Captures `model validation` exceptions and record the messages to the organizer's `:errors` context field
117
+ - Supports the following exceptions by default
118
+ - `ActiveRecord::Errors`
119
+ - `ActiveModel::Errors`
120
+ - Raises any non validation errors up the stack
121
+
122
+ #### API Responses
123
+ - records api responses set by an action's `:api_response` context field
124
+ - Stored inside of the organizer's `:api_responses` field
125
+
126
+ #### Retrieve Record
127
+ > Allows for a block to be defined on an organizer in order to retrieve the model record
128
+
129
+ Example
130
+
131
+ ```ruby
132
+ class TaxCalculator < LightServiceExt::ApplicationOrganizer
133
+ self.retrieve_record = -> (ctx:) { User.find_by(email: ctx.params[:email]) }
134
+
135
+ def self.call(input:)
136
+ user = record(ctx: input) # `.record` method executes proc provided to `retrieve_record`
137
+ input = { user: user }.merge(user: user)
138
+ reduce_with({ input: input }, steps)
139
+ end
140
+ end
141
+ ```
142
+
143
+ #### Failing The Context
144
+ - Prevents further action's been executed in the following scenarios:
145
+ - All actions complete determined by organizer's `:outcome` context field set to `LightServiceExt::Outcome::COMPLETE`
146
+
147
+ ### ApplicationAction
148
+
149
+ #### Useful methods
150
+ - TODO
151
+
152
+ #### Invoked Action
153
+ - *NOTE* Action's `executed` block gets called by the underlying `LightService::Action`
154
+ - this means in order to call your action's methods you need to invoke it from `invoked_action:` instead of `self`
155
+ - `invoked_action:` added to current action's context before it gets executed
156
+ - Consist of an instance of the current action that implements `LightServiceExt::ApplicationAction`
157
+
158
+ ## ApplicationContract
159
+
160
+ - Enhances `Dry::Validation::Contract` with the following methods:
161
+ - `#keys` ~> returns names of params defined
162
+ - `#t` ~> returns translation messages in context with the current organizer
163
+ - Arguments:
164
+ - `key` e.g. :not_found
165
+ - `base_path:` e.g. :user
166
+ - `**opts` options passed into underlying Rails i18n translate call
167
+ - E.g. `t(:not_found, base_path: 'business_create', scope: 'user')` would execute
168
+ - => `I18n.t('business_create.user.not_found', opts.except(:scope))`
169
+
170
+ ## ApplicationValidatorAction
171
+
172
+ > Responsible for mapping, filtering and validating the context `input:` field
173
+
174
+ - `executed` block does the following:
175
+ - Appends `params:` field to the current context with the mapped and filtered values
176
+ - Appends errors returned from a `ApplicationContract` [dry-validation](https://github.com/dry-rb/dry-validation) contract to the current context's `errors:` field
177
+ - *NOTE* fails current context if `errors:` present
178
+
179
+ ##### Useful Accessors
180
+
181
+ - `.contract_class` ~> sets the [dry-validation](https://github.com/dry-rb/dry-validation) contract to be applied by the current validator action
182
+ - `.params_mapper_class` ~> sets the mapper class that must implement `.map_from(context)` and return mapped `:input` values
183
+
184
+ ## ContextError
185
+
186
+ > Provides all the information related to an exception/validation errors captured by the current organizer
187
+
188
+ #### Useful methods
189
+ - `#error_info` ~> `ErrorInfo` instance
190
+ - `#context` ~> state of context provided
191
+ - `#error` ~> original exception
192
+ - `#message` ~> summarizes which action failed etc.
193
+
194
+ ## ErrorInfo
195
+ - Summarize captured exception
196
+
197
+ #### Useful accessors
198
+ - `non_fatal_errors` ~> takes a list of error class names considered to be non fatal exceptions
199
+
200
+ #### Useful methods
201
+ - `#error` ~> captured exception
202
+ - `#type` ~> exception class name e.g. `ArgumentError`
203
+ - `#message` ~> error message
204
+ - `title` ~> combined error class name and error message e.g. `ArgumentError : email must be present`
205
+ - `#fatal_error?`
206
+ - `#error_summary` ~> summarizes exception with message and cleaned backtrace via `ActiveSupport::BacktraceCleaner`
207
+
208
+ ## Regex
24
209
 
25
- TODO: Write usage instructions here
210
+ #### Useful methods
211
+ - `.match?(type, value)` e.g. `LightServiceExt::Regex.match?(email:, 'email@domain.com')`
212
+ - supported `type`:
213
+ - :email
26
214
 
27
215
  ## Development
28
216
 
data/dev/setup.rb CHANGED
@@ -5,8 +5,10 @@
5
5
  require 'json'
6
6
  require 'light-service'
7
7
  require 'dry-validation'
8
+ require 'rspec/core'
8
9
 
9
- Dir.glob("lib/**/*.rb").each do |f|
10
- require File.join(__dir__, '..', f)
11
- end
10
+ require 'active_support/core_ext/array'
11
+ require 'active_support/configurable'
12
12
 
13
+ require File.expand_path("../../lib/light-service-ext", Pathname.new(__FILE__).realpath)
14
+ require File.expand_path("../../spec/spec_helper", Pathname.new(__FILE__).realpath)
@@ -4,27 +4,6 @@ module LightServiceExt
4
4
  class ApplicationAction
5
5
  extend LightService::Action
6
6
 
7
- class << self
8
- def add_params(ctx, **params)
9
- add_to_context(ctx, :params, **params)
10
- end
11
-
12
- def add_errors(ctx, **errors)
13
- add_to_context(ctx, :errors, **errors)
14
-
15
- ctx.fail_and_return! if ctx[:errors].present?
16
- end
17
-
18
- private
19
-
20
- def add_to_context(ctx, key, **args)
21
- return if ctx.nil?
22
-
23
- ctx[key].merge!(args.dup)
24
- nil
25
- end
26
- end
27
-
28
7
  def self.inherited(base)
29
8
  base.singleton_class.prepend AroundActionExecuteExtension
30
9
  super
@@ -13,10 +13,38 @@ module LightServiceExt
13
13
  private
14
14
 
15
15
  def default_attrs
16
- { errors: {}, params: {}, successful_actions: [], api_responses: [], allow_raise_on_failure: true }.freeze
16
+ { errors: {}, params: {}, successful_actions: [], api_responses: [],
17
+ allow_raise_on_failure: LightServiceExt.config.allow_raise_on_failure?,
18
+ internal_only: { error_info: nil } }.freeze
17
19
  end
18
20
  end
19
21
 
22
+ def add_params(**params)
23
+ return if params.blank?
24
+
25
+ self[:params].merge!(params.dup)
26
+ end
27
+
28
+ def add_internal_only(**attrs)
29
+ return if attrs.blank?
30
+
31
+ self[:internal_only].merge!(attrs.dup)
32
+ end
33
+
34
+ def add_errors(**errors)
35
+ return if errors.blank?
36
+
37
+ self[:errors].merge!(errors.dup)
38
+ nil
39
+ end
40
+
41
+ def add_errors!(**errors)
42
+ return if errors.blank?
43
+
44
+ add_errors(**errors)
45
+ fail_and_return!
46
+ end
47
+
20
48
  def invoked_action
21
49
  self[:invoked_action]
22
50
  end
@@ -7,8 +7,8 @@ module LightServiceExt
7
7
 
8
8
  executed do |context|
9
9
  validator = map_and_validate_inputs(context)
10
- add_params(context, **validator.to_h)
11
- add_errors(context, **validator.errors.to_h.transform_values(&:first))
10
+ context.add_params(**validator.to_h)
11
+ context.add_errors!(**validator.errors.to_h.transform_values(&:first))
12
12
  end
13
13
 
14
14
  class << self
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LightServiceExt
4
+ class Configuration
5
+ include ActiveSupport::Configurable
6
+
7
+ config_accessor(:allow_raise_on_failure, default: true)
8
+ config_accessor(:non_fatal_error_classes, default: [])
9
+ config_accessor(:default_non_fatal_error_classes) { ['Rails::ActiveRecordError'.safe_constantize] }
10
+ config_accessor(:logger) { (defined? Rails.logger).nil? ? Logger.new($stdout) : Rails.logger }
11
+
12
+ def allow_raise_on_failure?
13
+ !!allow_raise_on_failure
14
+ end
15
+
16
+ def non_fatal_errors
17
+ (default_non_fatal_error_classes + non_fatal_error_classes).compact.uniq.map(&:to_s).freeze
18
+ end
19
+
20
+ def fatal_error?(exception)
21
+ !non_fatal_errors.exclude?(exception.class.name)
22
+ end
23
+
24
+ def non_fatal_error?(exception)
25
+ non_fatal_errors.include?(exception.class.name)
26
+ end
27
+ end
28
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module LightServiceExt
4
4
  module Outcome
5
- COMPLETE = "all_steps_complete"
5
+ COMPLETE = :all_steps_complete
6
6
  end
7
7
  end
@@ -29,6 +29,13 @@ module LightServiceExt
29
29
  TEXT
30
30
  end
31
31
 
32
+ def errors
33
+ model = error && (error.try(:model) || error.try(:record))
34
+ return model.errors.messages.transform_values(&:first) if model.present?
35
+
36
+ { base: message }
37
+ end
38
+
32
39
  def to_h
33
40
  {
34
41
  type: type,
@@ -36,7 +43,8 @@ module LightServiceExt
36
43
  exception: title,
37
44
  backtrace: clean_backtrace[0, 3]&.join("\n"),
38
45
  error: error,
39
- fatal_error?: fatal_error?
46
+ fatal_error?: fatal_error?,
47
+ errors: errors
40
48
  }
41
49
  end
42
50
 
@@ -45,7 +53,7 @@ module LightServiceExt
45
53
  end
46
54
 
47
55
  def clean_backtrace
48
- @clean_backtrace ||= if defined? Rails
56
+ @clean_backtrace ||= if defined? Rails.backtrace_cleaner
49
57
  Rails.backtrace_cleaner.clean(backtrace || [])
50
58
  else
51
59
  backtrace || []
@@ -53,15 +61,7 @@ module LightServiceExt
53
61
  end
54
62
 
55
63
  def non_fatal_error?
56
- error.nil? || self.class.non_fatal_errors.map(&:to_s).include?(type)
57
- end
58
-
59
- class << self
60
- attr_writer :non_fatal_errors
61
-
62
- def non_fatal_errors
63
- @non_fatal_errors ||= []
64
- end
64
+ error.nil? || LightServiceExt.config.non_fatal_error?(error)
65
65
  end
66
66
  end
67
67
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LightServiceExt
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/AbcSize
4
+
5
+ module LightServiceExt
6
+ module WithErrorHandler
7
+ def with_error_handler(ctx:)
8
+ @result = yield || ApplicationContext.make_with_defaults
9
+ rescue Rails::ActiveRecordError => e
10
+ error_info = ErrorInfo.new(e, fatal: false)
11
+ ctx.add_internal_only(error_info: error_info)
12
+ ctx.add_errors(**error_info.errors)
13
+
14
+ LightServiceExt.config.logger.error(error_info.error_summary)
15
+
16
+ ctx.fail!
17
+ ctx
18
+ rescue StandardError => e
19
+ error_info = ErrorInfo.new(e, fatal: false)
20
+ LightServiceExt.config.logger.error(error_info.error_summary)
21
+
22
+ raise
23
+ end
24
+ end
25
+ end
26
+ # rubocop:enable Metrics/AbcSize
@@ -1,16 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "light-service-ext/version"
4
- require "light-service-ext/constants"
5
- require "light-service-ext/regex"
6
- require "light-service-ext/error_info"
7
- require "light-service-ext/context_error"
8
- require "light-service-ext/application_context"
9
- require "light-service-ext/application_contract"
10
- require "light-service-ext/around_action_execute_extension"
11
- require "light-service-ext/application_action"
12
- require "light-service-ext/all_actions_complete_action"
13
- require "light-service-ext/application_validator_action"
14
- require "light-service-ext/application_organizer"
3
+ %w[
4
+ version
5
+ constants
6
+ regex
7
+ error_info
8
+ context_error
9
+ configuration
10
+ with_error_handler
11
+ application_context
12
+ application_contract
13
+ around_action_execute_extension
14
+ application_action
15
+ all_actions_complete_action
16
+ application_validator_action
17
+ application_organizer
18
+ ].each do |filename|
19
+ require File.expand_path("../light-service-ext/#{filename}", Pathname.new(__FILE__).realpath)
20
+ end
15
21
 
16
- module LightServiceExt; end
22
+
23
+
24
+ module LightServiceExt
25
+ class << self
26
+ def config
27
+ self.configuration
28
+ end
29
+
30
+ def configuration
31
+ @configuration ||= Configuration.new
32
+ end
33
+
34
+ def configure
35
+ yield configuration
36
+ end
37
+ end
38
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/light-service-ext/version"
3
+ require File.expand_path('../lib/light-service-ext/version', __FILE__)
4
4
 
5
5
  Gem::Specification.new do |gem|
6
6
  gem.authors = ["Desmond O'Leary"]
@@ -26,6 +26,7 @@ Gem::Specification.new do |gem|
26
26
  gem.add_runtime_dependency 'dry-struct', '~> 1.6'
27
27
  gem.add_runtime_dependency 'dry-validation', '~> 1.10'
28
28
  gem.add_runtime_dependency 'json', '~> 2.6', '>= 2.6.3'
29
+ gem.add_runtime_dependency 'activesupport', '>= 5'
29
30
 
30
31
  gem.add_development_dependency("rake", "~> 13.0.6")
31
32
  gem.add_development_dependency("rspec", "~> 3.12.0")
@@ -0,0 +1,39 @@
1
+ module LightServiceExt
2
+ RSpec.describe ApplicationAction do
3
+ FakeApplicationAction = Class.new(described_class) do
4
+ executed do |context|
5
+ value = context.dig(:input, :callback).call
6
+ context.add_params(value: value)
7
+ context.add_errors!(value: value)
8
+ end
9
+ end
10
+
11
+ let(:organizer_class) do
12
+ Class.new(ApplicationOrganizer) do
13
+ def self.steps
14
+ [FakeApplicationAction]
15
+ end
16
+ end
17
+ end
18
+
19
+ let(:value) { 'some-value' }
20
+ let(:callback) { -> { value } }
21
+ let(:input) { { callback: callback } }
22
+ let(:ctx) do
23
+ LightService::Testing::ContextFactory
24
+ .make_from(organizer_class)
25
+ .for(FakeApplicationAction)
26
+ .with(callback: callback)
27
+ end
28
+
29
+ subject(:context) do
30
+ FakeApplicationAction.execute(ctx)
31
+ end
32
+
33
+ it 'adds value returned by callback to params' do
34
+ expect(context.keys).to include(:input, :errors, :params)
35
+
36
+ expect(context[:params]).to eql({ value: value })
37
+ end
38
+ end
39
+ end