trailblazer-test 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +115 -0
- data/.rubocop.yml +17 -0
- data/.travis.yml +16 -0
- data/CHANGES.md +8 -0
- data/Gemfile +4 -0
- data/README.md +211 -0
- data/Rakefile +13 -0
- data/lib/trailblazer/test.rb +12 -0
- data/lib/trailblazer/test/assertions.rb +37 -0
- data/lib/trailblazer/test/deprecation/operation/assertions.rb +42 -0
- data/lib/trailblazer/test/deprecation/operation/helper.rb +28 -0
- data/lib/trailblazer/test/operation/assertions.rb +89 -0
- data/lib/trailblazer/test/operation/helper.rb +38 -0
- data/lib/trailblazer/test/operation/policy_assertions.rb +13 -0
- data/lib/trailblazer/test/version.rb +5 -0
- data/trailblazer-test.gemspec +29 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 02bdfafb7d0c3648980cf33724a89d9954428fbd8466fd0f58df7fe0366b8d25
|
4
|
+
data.tar.gz: 2753cb581aa889d898f5b23bff1daa44e802150e877087d32faf7e3a46357f90
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9e999f603f7944601d56d17c18e1cbfebe43234fa539e5c7ce9176ca5cf3e1aa2e211bcc1097569b372776020a9b63c6fb86a0f8a350968bea4b0dc755a2eafb
|
7
|
+
data.tar.gz: 4701ebcfbb62202dcac8bbcc13149779c63add90ab9471484f1495a5a3a7ca682ee2e17c9210e8c70543f0707939edb3c4bda9d2de46283a54e9e91e5f0a024f
|
data/.gitignore
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.5.0
|
3
|
+
DisplayCopNames: true
|
4
|
+
Layout/CaseIndentation:
|
5
|
+
IndentOneStep: true
|
6
|
+
Layout/FirstArrayElementLineBreak:
|
7
|
+
Enabled: true
|
8
|
+
Layout/FirstHashElementLineBreak:
|
9
|
+
Enabled: true
|
10
|
+
Layout/FirstMethodArgumentLineBreak:
|
11
|
+
Enabled: true
|
12
|
+
Layout/FirstMethodParameterLineBreak:
|
13
|
+
Enabled: true
|
14
|
+
Layout/MultilineAssignmentLayout:
|
15
|
+
Enabled: true
|
16
|
+
EnforcedStyle: same_line
|
17
|
+
Layout/SpaceInsideHashLiteralBraces:
|
18
|
+
EnforcedStyle: no_space
|
19
|
+
Metrics/LineLength:
|
20
|
+
Max: 130
|
21
|
+
Metrics/ParameterLists:
|
22
|
+
Max: 5
|
23
|
+
Naming/VariableNumber:
|
24
|
+
EnforcedStyle: snake_case
|
25
|
+
Style/AndOr:
|
26
|
+
EnforcedStyle: conditionals
|
27
|
+
Style/AutoResourceCleanup:
|
28
|
+
Enabled: true
|
29
|
+
Style/CollectionMethods:
|
30
|
+
Enabled: true
|
31
|
+
Style/Documentation:
|
32
|
+
Enabled: false
|
33
|
+
Style/EmptyLiteral:
|
34
|
+
Enabled: false
|
35
|
+
Style/EmptyMethod:
|
36
|
+
EnforcedStyle: expanded
|
37
|
+
Style/FormatStringToken:
|
38
|
+
EnforcedStyle: template
|
39
|
+
Style/ImplicitRuntimeError:
|
40
|
+
Enabled: true
|
41
|
+
Style/MethodCalledOnDoEndBlock:
|
42
|
+
Enabled: true
|
43
|
+
Style/MethodDefParentheses:
|
44
|
+
EnforcedStyle: require_parentheses
|
45
|
+
Style/MissingElse:
|
46
|
+
Enabled: true
|
47
|
+
EnforcedStyle: case
|
48
|
+
Style/NumericLiterals:
|
49
|
+
Enabled: false
|
50
|
+
Style/OptionHash:
|
51
|
+
Enabled: true
|
52
|
+
Style/PercentLiteralDelimiters:
|
53
|
+
PreferredDelimiters:
|
54
|
+
"%w": "[]"
|
55
|
+
"%W": "[]"
|
56
|
+
"%i": "[]"
|
57
|
+
"%I": "[]"
|
58
|
+
"%r": "()"
|
59
|
+
Style/ReturnNil:
|
60
|
+
Enabled: true
|
61
|
+
Style/SafeNavigation:
|
62
|
+
Enabled: false
|
63
|
+
Style/Send:
|
64
|
+
Enabled: true
|
65
|
+
Style/SignalException:
|
66
|
+
EnforcedStyle: semantic
|
67
|
+
Style/StringLiterals:
|
68
|
+
EnforcedStyle: double_quotes
|
69
|
+
Style/StringLiteralsInInterpolation:
|
70
|
+
EnforcedStyle: double_quotes
|
71
|
+
Style/StringMethods:
|
72
|
+
Enabled: true
|
73
|
+
Style/SymbolArray:
|
74
|
+
Enabled: true
|
75
|
+
# this allows in rspec to have expect { } with multiple lines
|
76
|
+
Style/BlockDelimiters:
|
77
|
+
EnforcedStyle: braces_for_chaining
|
78
|
+
Layout/EndOfLine:
|
79
|
+
Enabled: false
|
80
|
+
# don't need these checks in test folders
|
81
|
+
Metrics/ModuleLength:
|
82
|
+
Exclude:
|
83
|
+
- "spec/**/*"
|
84
|
+
- "test/**/*"
|
85
|
+
Metrics/BlockLength:
|
86
|
+
Exclude:
|
87
|
+
- "spec/**/*"
|
88
|
+
- "test/**/*"
|
89
|
+
- "*.gemspec" # definitely not in the gemspec
|
90
|
+
Metrics/MethodLength:
|
91
|
+
Max: 20
|
92
|
+
Lint/UnreachableCode:
|
93
|
+
Description: 'Unreachable code.'
|
94
|
+
Enabled: false
|
95
|
+
Lint/Void:
|
96
|
+
Enabled: false
|
97
|
+
Layout/AlignHash:
|
98
|
+
EnforcedLastArgumentHashStyle: ignore_implicit
|
99
|
+
Metrics/AbcSize:
|
100
|
+
Max: 25
|
101
|
+
Style/LambdaCall:
|
102
|
+
Enabled: false
|
103
|
+
Style/Semicolon:
|
104
|
+
Enabled: false
|
105
|
+
Naming/UncommunicativeMethodParamName:
|
106
|
+
Enabled: false
|
107
|
+
Style/ClassAndModuleChildren:
|
108
|
+
Enabled: false
|
109
|
+
Layout/LeadingCommentSpace:
|
110
|
+
Exclude:
|
111
|
+
- 'test/docs/**/*'
|
112
|
+
Layout/AlignHash:
|
113
|
+
EnforcedHashRocketStyle: table
|
114
|
+
Style/FrozenStringLiteralComment:
|
115
|
+
Enabled: false
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
inherit_from:
|
2
|
+
- https://raw.githubusercontent.com/trailblazer/meta/master/rubocop.yml
|
3
|
+
|
4
|
+
Style/SignalException:
|
5
|
+
Exclude:
|
6
|
+
- lib/trailblazer/test/operation/assertions.rb
|
7
|
+
- lib/trailblazer/test/operation/helper.rb
|
8
|
+
- lib/trailblazer/test/deprecation/operation/helper.rb
|
9
|
+
|
10
|
+
Metrics/ParameterLists:
|
11
|
+
Exclude:
|
12
|
+
- lib/trailblazer/test/operation/assertions.rb
|
13
|
+
|
14
|
+
Metrics/LineLength:
|
15
|
+
Exclude:
|
16
|
+
- lib/trailblazer/test/operation/assertions.rb
|
17
|
+
|
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
language: ruby
|
2
|
+
before_install:
|
3
|
+
- gem install bundler
|
4
|
+
matrix:
|
5
|
+
include:
|
6
|
+
- rvm: 2.1
|
7
|
+
gemfile: Gemfile
|
8
|
+
- rvm: 2.2
|
9
|
+
gemfile: Gemfile
|
10
|
+
- rvm: 2.3.1
|
11
|
+
gemfile: Gemfile
|
12
|
+
- rvm: 2.4.1
|
13
|
+
gemfile: Gemfile
|
14
|
+
- rvm: 2.5.0
|
15
|
+
gemfile: Gemfile
|
16
|
+
script: bundle exec rake test && rake rubocop
|
data/CHANGES.md
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
# Trailblazer::Test
|
2
|
+
|
3
|
+
[](https://travis-ci.org/trailblazer/trailblazer-test)
|
4
|
+
[](http://badge.fury.io/rb/trailblazer-test)
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'trailblazer-test'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install trailblazer-test
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Add in your test `_helper` the following modules:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
include Trailblazer::Test::Assertions
|
28
|
+
include Trailblazer::Test::Operation::Assertions
|
29
|
+
```
|
30
|
+
|
31
|
+
If you are using Trailblazer v2.0 you need to add also:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require "trailblazer/test/deprecation/operation/assertions"
|
35
|
+
|
36
|
+
include Trailblazer::Test::Deprecation::Operation::Assertions # in your test class
|
37
|
+
```
|
38
|
+
|
39
|
+
To be able to test an operation we need 3 auxiliary methods which have to be defined at the start of your tests:
|
40
|
+
* `default_params` (**required**): hash of params which will be always passed to the operation unless overriden by `params` or `ctx`
|
41
|
+
* `expected_attrs` (**required**): hash always used to assert model attributes
|
42
|
+
* `default_options` (**required if using `ctx`**): hash of options which will be always passed to the operation unless overriden by `ctx`
|
43
|
+
|
44
|
+
We are also providing 2 helper methods:
|
45
|
+
* `params(new_params)`
|
46
|
+
* `ctx(new_params, options)`
|
47
|
+
|
48
|
+
Those will merge params and options for you and return the final inputs which then can be passed to the operation under testing.
|
49
|
+
|
50
|
+
Pass `deep_merge: false` to the helper methods to disable the default deep merging of params and options.
|
51
|
+
|
52
|
+
*Same API for Trailblazer v2.0 and v2.1.*
|
53
|
+
|
54
|
+
Finally, using the built-in assertions you are able to test your operations in a fast and easy way:
|
55
|
+
* `assert_pass` -> your operation is successful and model has the correct attributes
|
56
|
+
* `assert_fail` -> your operation fails and returns some specific errors
|
57
|
+
* `assert_policy_fail` -> your operation fails because policy fails
|
58
|
+
|
59
|
+
#### params
|
60
|
+
|
61
|
+
`params` accepts one argument which is merged into `default_params`.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
let(:default_params) { { title: 'My title' } }
|
65
|
+
|
66
|
+
params(artist: 'My Artist') -> { params: { title: 'My title', artist: 'My Artist' } }
|
67
|
+
params(title: 'Other one') -> { params: { title: 'Other one' } }
|
68
|
+
```
|
69
|
+
|
70
|
+
#### ctx
|
71
|
+
|
72
|
+
`ctx` accepts 2 arguments, first one will be merged into the `default_params` and the second one will be merged into `default_options`
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
let(:default_params) { { title: 'My title' } }
|
76
|
+
let(:default_options) { { current_user: 'me' } }
|
77
|
+
|
78
|
+
ctx(artist: 'My Artist') -> { params: { title: 'My title', artist: 'My Artist' }, current_user: 'me' }
|
79
|
+
ctx({title: 'Other one'}, current_user: 'you') -> { params: { title: 'Other one' }, current_user: 'you' }
|
80
|
+
```
|
81
|
+
|
82
|
+
### assert_pass
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
assert_pass(operation, ctx, expected_attributes)
|
86
|
+
```
|
87
|
+
|
88
|
+
Example:
|
89
|
+
```ruby
|
90
|
+
let(:default_params) { { band: 'The Chats'} }
|
91
|
+
let(:default_options) { { current_user: user} }
|
92
|
+
let(:expected_attrs) { { band: 'The Chats'} }
|
93
|
+
|
94
|
+
it { assert_pass MyOp, ctx(title: 'Smoko'), title: 'Smoko' }
|
95
|
+
```
|
96
|
+
|
97
|
+
Pass `deep_merge: false` to disable the deep merging of the third argument `expected_attributes` and the auxiliary method `expected_attrs`.
|
98
|
+
|
99
|
+
It's also possible to test in a more detailed way using a block:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
assert_pass MyOp, ctx(title: 'Smoko'), {} do |result|
|
103
|
+
assert_equal "Smoko", result[:model].title
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
### assert_fail
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
assert_fail(operation, ctx)
|
111
|
+
```
|
112
|
+
|
113
|
+
Example:
|
114
|
+
```ruby
|
115
|
+
let(:default_params) { { band: 'The Chats'} }
|
116
|
+
let(:default_options) { { current_user: user} }
|
117
|
+
let(:expected_attrs) { { band: 'The Chats'} }
|
118
|
+
|
119
|
+
it { assert_fail MyOp, ctx(title: 'Smoko') }
|
120
|
+
```
|
121
|
+
|
122
|
+
This will just test that the operation fails instead passing `expected_errors` as an array of symbols will also test that specific attribute has an error:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
assert_fail MyOp, ctx(band: 'Justing Beaver'), expected_errors: [:band] # definitely wrong!!!!
|
126
|
+
```
|
127
|
+
|
128
|
+
Using the block here will allow to test the error message:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
assert_fail MyOp, ctx(band: 'Justing Beaver') do |result|
|
132
|
+
assert_equal 'You cannot listen Justing Beaver', result['contract.default'].errors.messages[:band]
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
Change contract name using `contract_name`.
|
137
|
+
|
138
|
+
*We will improve this part and allowing to the test message directly without using a block*
|
139
|
+
|
140
|
+
|
141
|
+
### assert_policy_fail
|
142
|
+
|
143
|
+
Add this in your test file to be able to use it:
|
144
|
+
```ruby
|
145
|
+
include Trailblazer::Test::Operation::PolicyAssertions
|
146
|
+
```
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
assert_policy_fail(operation, ctx)
|
150
|
+
```
|
151
|
+
|
152
|
+
This will test that the operation fails due to a policy failure.
|
153
|
+
|
154
|
+
Example:
|
155
|
+
```ruby
|
156
|
+
let(:default_params) { { band: 'The Chats'} }
|
157
|
+
let(:default_options) { { current_user: user} }
|
158
|
+
|
159
|
+
it { assert_policy_fail MyOp, ctx({title: 'Smoko'}, current_user: another) }
|
160
|
+
```
|
161
|
+
Change policy name using `policy_name`.
|
162
|
+
|
163
|
+
## Test Setup
|
164
|
+
|
165
|
+
It is obviously crucial to test your operation in the correct test enviroment calling operation instead of using `FactoryBot` or simply `Model.create`.
|
166
|
+
|
167
|
+
To do so we provide 2 helper methods:
|
168
|
+
* `call`: will call the operation and **will not raise** an error in case of failure
|
169
|
+
* `factory`: will call the operation and **will raise** an error in case of failure returning also the trace and a validate error message in case exists
|
170
|
+
|
171
|
+
### Usage
|
172
|
+
|
173
|
+
Add this in your test `_helper.rb`:
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
include Trailblazer::Test::Operation::Helper
|
177
|
+
```
|
178
|
+
|
179
|
+
In case you use are Trailblazer v2.0, you need to add this instead:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
require "trailblazer/test/deprecation/operation/helper"
|
183
|
+
|
184
|
+
include Trailblazer::Test::Deprecation::Operation::Helper
|
185
|
+
```
|
186
|
+
|
187
|
+
*Same API for both Trailblazer v2.0 and v2.1*
|
188
|
+
|
189
|
+
Examples:
|
190
|
+
```ruby
|
191
|
+
# call
|
192
|
+
let(:user) { call(User::Create, params: params)[:model] }
|
193
|
+
|
194
|
+
# call with block
|
195
|
+
let(:user) do
|
196
|
+
call User::Create, params: params do |result|
|
197
|
+
# run some code to reproduce some async jobs (for example)
|
198
|
+
end[:model]
|
199
|
+
end
|
200
|
+
|
201
|
+
# factory - this will raise an error if User::Create fails
|
202
|
+
let(:user) { factory(User::Create, params: params)[:model] }
|
203
|
+
|
204
|
+
# factory - this will raise an error if User::Create fails
|
205
|
+
let(:user) do
|
206
|
+
factory User::Create, params: params do |result|
|
207
|
+
# this block will be yield only if User::Create is successful
|
208
|
+
# run some code to reproduce some async jobs (for example)
|
209
|
+
end[:model]
|
210
|
+
end
|
211
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
require "rubocop/rake_task"
|
4
|
+
|
5
|
+
RuboCop::RakeTask.new(:rubocop)
|
6
|
+
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
8
|
+
t.libs << "test"
|
9
|
+
t.libs << "lib"
|
10
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
11
|
+
end
|
12
|
+
|
13
|
+
task default: :test
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "trailblazer/test/version"
|
2
|
+
|
3
|
+
module Trailblazer
|
4
|
+
module Test
|
5
|
+
# Your code goes here...
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require "trailblazer/test/assertions"
|
10
|
+
require "trailblazer/test/operation/helper"
|
11
|
+
require "trailblazer/test/operation/assertions"
|
12
|
+
require "trailblazer/test/operation/policy_assertions"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# module MiniTest::Assertions
|
2
|
+
module Trailblazer
|
3
|
+
module Test
|
4
|
+
# Evaluate value if it's a lambda, and let the caller know whether we need an
|
5
|
+
# assert_equal or an assert.
|
6
|
+
def self.expected(asserted, value, actual)
|
7
|
+
value.is_a?(Proc) ? [value.(actual: actual, asserted: asserted), false] : [value, true]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Read the actual value from the asserted object (e.g. a model).
|
11
|
+
def self.actual(asserted, reader, name)
|
12
|
+
reader ? asserted.public_send(reader, name) : asserted.public_send(name)
|
13
|
+
end
|
14
|
+
|
15
|
+
module Assertions
|
16
|
+
module_function
|
17
|
+
|
18
|
+
# tuples = defaults.merge(overrides) # FIXME: merge with above!
|
19
|
+
|
20
|
+
# Test if all `tuples` values on `asserted` match the expected values.
|
21
|
+
# @param asserted Object Object that exposes attributes to test
|
22
|
+
# @param tuples Hash Key/value attribute pairs to test
|
23
|
+
# @param options Hash Default :reader is `asserted.{name}`,
|
24
|
+
# TODO: test err msgs!
|
25
|
+
def assert_exposes(asserted, tuples, reader: nil)
|
26
|
+
tuples.each do |k, v|
|
27
|
+
actual = Test.actual(asserted, reader, k)
|
28
|
+
expected, is_eq = Test.expected(asserted, v, actual)
|
29
|
+
|
30
|
+
is_eq ? assert_equal(expected, actual, "Property [#{k}] mismatch") : assert(expected, "Actual: #{actual.inspect}.")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
# Trailblazer::Operation::Result.infect_an_assertion :assert_result_matches, :must_match, :do_not_flip
|
37
|
+
# Object.infect_an_assertion :assert_exposes, :must_expose, :do_not_flip
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Trailblazer::Test
|
2
|
+
module Deprecation
|
3
|
+
module Operation
|
4
|
+
module Assertions
|
5
|
+
include Trailblazer::Test::Operation::Assertions
|
6
|
+
|
7
|
+
# @needs default_params
|
8
|
+
# @needs default_options
|
9
|
+
|
10
|
+
def params(default_params: self.default_params, deep_merge: true, **new_params)
|
11
|
+
[merge_for(default_params, new_params, deep_merge), {}]
|
12
|
+
end
|
13
|
+
|
14
|
+
def ctx(new_params, *options)
|
15
|
+
# need *options to allow user to do something like:
|
16
|
+
# ctx({yeah: 'nah'}, "current_user" => Object, some: 'other' )
|
17
|
+
|
18
|
+
# this is not greate but seems necessary using *options
|
19
|
+
new_options = options.first || {}
|
20
|
+
deep_merge = new_options[:deep_merge].nil? ? true : deep_merge
|
21
|
+
new_options.delete(:deep_merge)
|
22
|
+
|
23
|
+
ctx = merge_for(_default_options, options.first || {}, deep_merge)
|
24
|
+
[merge_for(params[0], new_params, deep_merge), ctx]
|
25
|
+
end
|
26
|
+
|
27
|
+
def _default_options(options: default_options)
|
28
|
+
options
|
29
|
+
end
|
30
|
+
|
31
|
+
# compatibility call for TRB 2.0
|
32
|
+
def _call_operation(operation_class, *args)
|
33
|
+
operation_class.(args[0][0], args[0][1])
|
34
|
+
end
|
35
|
+
|
36
|
+
def _model(result)
|
37
|
+
result["model"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Trailblazer::Test
|
2
|
+
module Deprecation
|
3
|
+
module Operation
|
4
|
+
module Helper
|
5
|
+
def call(operation_class, *args, &block)
|
6
|
+
call!(operation_class, args, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def factory(operation_class, *args, &block)
|
10
|
+
call!(operation_class, args, raise_on_failure: true, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @private
|
14
|
+
def call!(operation_class, args, raise_on_failure: false)
|
15
|
+
operation_class.(*args).tap do |result|
|
16
|
+
unless result.success?
|
17
|
+
yield result if block_given?
|
18
|
+
|
19
|
+
raise OperationFailedError, "factory(#{operation_class}) failed." if raise_on_failure
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class OperationFailedError < RuntimeError; end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "hashie"
|
2
|
+
|
3
|
+
module Trailblazer::Test::Operation
|
4
|
+
module Assertions
|
5
|
+
# @needs default_params
|
6
|
+
# @needs default_options
|
7
|
+
# @needs expected_attrs
|
8
|
+
|
9
|
+
def params(default_params: self.default_params, deep_merge: true, **new_params)
|
10
|
+
{params: merge_for(default_params, new_params, deep_merge)}
|
11
|
+
end
|
12
|
+
|
13
|
+
def ctx(new_params, default_options: self.default_options, deep_merge: true, **options)
|
14
|
+
new_params = merge_for(params[:params], new_params, deep_merge)
|
15
|
+
new_options = merge_for(default_options, options, deep_merge)
|
16
|
+
|
17
|
+
{params: new_params, **new_options}
|
18
|
+
end
|
19
|
+
|
20
|
+
def assert_pass(operation_class, operation_inputs, expected_attributes, default_attributes: expected_attrs, deep_merge: true, &block)
|
21
|
+
expected_attributes = merge_for(default_attributes, expected_attributes, deep_merge)
|
22
|
+
|
23
|
+
assert_pass_with_model(operation_class, operation_inputs, expected_model_attributes: expected_attributes, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def assert_fail(operation_class, operation_inputs, expected_errors: nil, contract_name: "default", &block)
|
27
|
+
assert_fail_with_model(operation_class, operation_inputs, expected_errors: expected_errors, contract_name: contract_name, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# @private
|
31
|
+
# TODO: test expected_attributes default param and explicit!
|
32
|
+
def assert_pass_with_model(operation_class, operation_inputs, expected_model_attributes: {}, &user_block)
|
33
|
+
_assert_call(operation_class, operation_inputs, user_block: user_block) do |result|
|
34
|
+
assert_equal true, result.success?
|
35
|
+
assert_exposes(_model(result), expected_model_attributes)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @private
|
40
|
+
def assert_fail_with_model(operation_class, operation_inputs, expected_errors: nil, contract_name: raise, &user_block)
|
41
|
+
_assert_call(operation_class, operation_inputs, user_block: user_block) do |result|
|
42
|
+
assert_equal true, result.failure?
|
43
|
+
|
44
|
+
raise ExpectedErrorsTypeError, "expected_errors has to be an Array" unless expected_errors.is_a?(Array)
|
45
|
+
|
46
|
+
# only test _if_ errors are present, not the content.
|
47
|
+
errors = result["contract.#{contract_name}"].errors.messages # TODO: this will soon change with the operation Errors object.
|
48
|
+
|
49
|
+
assert_equal expected_errors.sort, errors.keys.sort
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @private
|
54
|
+
def _assert_call(operation_class, operation_inputs, user_block: raise)
|
55
|
+
result = _call_operation(operation_class, operation_inputs)
|
56
|
+
|
57
|
+
return user_block.call(result) if user_block # DISCUSS: result or model?
|
58
|
+
|
59
|
+
yield(result)
|
60
|
+
|
61
|
+
result
|
62
|
+
end
|
63
|
+
|
64
|
+
# @private
|
65
|
+
class ExpectedErrorsTypeError < RuntimeError; end
|
66
|
+
|
67
|
+
# @private
|
68
|
+
class CtxHash < Hash
|
69
|
+
include Hashie::Extensions::DeepMerge
|
70
|
+
end
|
71
|
+
|
72
|
+
# @private
|
73
|
+
def merge_for(dest, source, deep_merge)
|
74
|
+
return dest.merge(source) unless deep_merge
|
75
|
+
|
76
|
+
CtxHash[dest].deep_merge(CtxHash[source])
|
77
|
+
end
|
78
|
+
|
79
|
+
# @private
|
80
|
+
def _call_operation(operation_class, operation_inputs)
|
81
|
+
operation_class.(operation_inputs)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @private
|
85
|
+
def _model(result)
|
86
|
+
result[:model]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Trailblazer::Test::Operation
|
2
|
+
module Helper
|
3
|
+
def call(operation_class, **args, &block)
|
4
|
+
call!(operation_class, args, &block)
|
5
|
+
end
|
6
|
+
|
7
|
+
def factory(operation_class, **args, &block)
|
8
|
+
call!(operation_class, args.merge(raise_on_failure: true), &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @private
|
12
|
+
def call!(operation_class, raise_on_failure: false, **args)
|
13
|
+
operation_class.trace(**args).tap do |result|
|
14
|
+
unless result.success?
|
15
|
+
|
16
|
+
msg = "factory(#{operation_class}) has failed"
|
17
|
+
|
18
|
+
unless result["contract.default"].nil? # should we allow to change contract name?
|
19
|
+
if result["contract.default"].errors.messages.any?
|
20
|
+
msg += " due to validation errors: #{result["contract.default"].errors.messages}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if raise_on_failure
|
25
|
+
result.wtf?
|
26
|
+
raise OperationFailedError, msg
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
yield result if block_given?
|
31
|
+
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class OperationFailedError < RuntimeError; end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Trailblazer::Test::Operation
|
2
|
+
module PolicyAssertions
|
3
|
+
include Assertions
|
4
|
+
# @needs params_pass
|
5
|
+
# @needs options_pass
|
6
|
+
def assert_policy_fail(operation_class, ctx, policy_name: "default")
|
7
|
+
_assert_call(operation_class, ctx, user_block: nil) do |result|
|
8
|
+
assert_equal true, result.failure?
|
9
|
+
assert_equal true, result["result.policy.#{policy_name}"].failure?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "trailblazer/test/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "trailblazer-test"
|
7
|
+
spec.version = Trailblazer::Test::VERSION
|
8
|
+
spec.authors = ["Nick Sutterer"]
|
9
|
+
spec.email = ["apotonick@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Assertions, matchers, and helpers to test Trailblazer code."
|
12
|
+
spec.description = "Assertions, matchers, and helpers to test Trailblazer code."
|
13
|
+
spec.homepage = "http://trailblazer.to"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r(^exe/)) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "hashie"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
25
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rubocop"
|
28
|
+
spec.add_development_dependency "simplecov"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trailblazer-test
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nick Sutterer
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-12-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hashie
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.15'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.15'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Assertions, matchers, and helpers to test Trailblazer code.
|
98
|
+
email:
|
99
|
+
- apotonick@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml"
|
106
|
+
- ".rubocop.yml"
|
107
|
+
- ".travis.yml"
|
108
|
+
- CHANGES.md
|
109
|
+
- Gemfile
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- lib/trailblazer/test.rb
|
113
|
+
- lib/trailblazer/test/assertions.rb
|
114
|
+
- lib/trailblazer/test/deprecation/operation/assertions.rb
|
115
|
+
- lib/trailblazer/test/deprecation/operation/helper.rb
|
116
|
+
- lib/trailblazer/test/operation/assertions.rb
|
117
|
+
- lib/trailblazer/test/operation/helper.rb
|
118
|
+
- lib/trailblazer/test/operation/policy_assertions.rb
|
119
|
+
- lib/trailblazer/test/version.rb
|
120
|
+
- trailblazer-test.gemspec
|
121
|
+
homepage: http://trailblazer.to
|
122
|
+
licenses: []
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 2.7.7
|
141
|
+
signing_key:
|
142
|
+
specification_version: 4
|
143
|
+
summary: Assertions, matchers, and helpers to test Trailblazer code.
|
144
|
+
test_files: []
|