trailblazer-test 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +20 -0
- data/CHANGES.md +8 -0
- data/Gemfile +7 -0
- data/README.md +24 -190
- data/Rakefile +0 -3
- data/lib/trailblazer/test/assertion/assert_exposes.rb +73 -0
- data/lib/trailblazer/test/assertion/assert_fail.rb +58 -0
- data/lib/trailblazer/test/assertion/assert_pass.rb +101 -0
- data/lib/trailblazer/test/assertion.rb +114 -0
- data/lib/trailblazer/test/context.rb +5 -0
- data/lib/trailblazer/test/endpoint.rb +42 -0
- data/lib/trailblazer/test/helper/mock_step.rb +19 -0
- data/lib/trailblazer/test/suite/assert.rb +90 -0
- data/lib/trailblazer/test/suite/ctx.rb +51 -0
- data/lib/trailblazer/test/suite.rb +58 -0
- data/lib/trailblazer/test/testing.rb +62 -0
- data/lib/trailblazer/test/version.rb +1 -1
- data/lib/trailblazer/test.rb +13 -4
- data/trailblazer-test.gemspec +11 -3
- metadata +91 -19
- data/.rubocop-https---raw-githubusercontent-com-trailblazer-meta-master-rubocop-yml +0 -115
- data/.rubocop.yml +0 -17
- data/.travis.yml +0 -16
- data/lib/trailblazer/test/assertions.rb +0 -37
- data/lib/trailblazer/test/deprecation/operation/assertions.rb +0 -42
- data/lib/trailblazer/test/deprecation/operation/helper.rb +0 -28
- data/lib/trailblazer/test/operation/assertions.rb +0 -89
- data/lib/trailblazer/test/operation/helper.rb +0 -38
- data/lib/trailblazer/test/operation/policy_assertions.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25326dbdb940038e49c6830ac6c4c833583c13db2d5f11782cac2b457f0836cb
|
4
|
+
data.tar.gz: e0639ac0cacc534a105138840e15f1019c9136c30042d8ef5d28c80eeb4800fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00332de32685126953eff6bfc0f1384f497e431e4ec1038a2f33a04cd3b37f762c8612c6834757a81095633ceaf55c748a799bf925ce27c923b5a391e8d2481d
|
7
|
+
data.tar.gz: a09347b703bf1eb6298f63b3e0c3b4481590e92b29b3d8abfb7e192e28a1986a94e2eafb1ff1d18792fc9693c94bcc4cad0cbec922ff9aff637e8ac2cc12fb4f
|
@@ -0,0 +1,20 @@
|
|
1
|
+
## This file is managed by Terraform.
|
2
|
+
## Do not modify this file directly, as it may be overwritten.
|
3
|
+
## Please open an issue instead.
|
4
|
+
name: CI
|
5
|
+
on: [push, pull_request]
|
6
|
+
jobs:
|
7
|
+
test:
|
8
|
+
strategy:
|
9
|
+
fail-fast: false
|
10
|
+
matrix:
|
11
|
+
# commenting out 2.7 because of dry.
|
12
|
+
ruby: ['3.0', '3.1', '3.2', "3.3", "head", "jruby"]
|
13
|
+
runs-on: ubuntu-latest
|
14
|
+
steps:
|
15
|
+
- uses: actions/checkout@v3
|
16
|
+
- uses: ruby/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
ruby-version: ${{ matrix.ruby }}
|
19
|
+
bundler-cache: true
|
20
|
+
- run: bundle exec rake
|
data/CHANGES.md
CHANGED
data/Gemfile
CHANGED
@@ -2,3 +2,10 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in trailblazer-test.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
# gem "reform-rails", path: "../reform-rails"
|
7
|
+
|
8
|
+
gem "trailblazer-endpoint", path: "../trailblazer-endpoint"
|
9
|
+
# gem "trailblazer-core-utils", path: "../trailblazer-core-utils"
|
10
|
+
# gem "trailblazer-endpoint", github: "trailblazer/trailblazer-endpoint"
|
11
|
+
gem "ostruct"
|
data/README.md
CHANGED
@@ -1,211 +1,45 @@
|
|
1
1
|
# Trailblazer::Test
|
2
2
|
|
3
|
-
|
4
|
-
[](http://badge.fury.io/rb/trailblazer-test)
|
3
|
+
_Assertions and helpers for operation unit tests._
|
5
4
|
|
6
|
-
|
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`.
|
5
|
+
The [comprehensive docs are here](https://trailblazer.to/2.1/docs/test/).
|
137
6
|
|
138
|
-
|
7
|
+
Read our introducing blog post for a better overview.
|
139
8
|
|
9
|
+
## Installation
|
140
10
|
|
141
|
-
|
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.
|
11
|
+
Add the following line to your project's `Gemfile`.
|
153
12
|
|
154
|
-
Example:
|
155
13
|
```ruby
|
156
|
-
|
157
|
-
let(:default_options) { { current_user: user} }
|
158
|
-
|
159
|
-
it { assert_policy_fail MyOp, ctx({title: 'Smoko'}, current_user: another) }
|
14
|
+
gem "trailblazer-test", ">= 1.0.0", "< 2.0.0"
|
160
15
|
```
|
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
16
|
|
167
|
-
|
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
|
17
|
+
## Overview
|
170
18
|
|
171
|
-
|
19
|
+
This gem adds the following assertions and helpers:
|
172
20
|
|
173
|
-
|
21
|
+
* `#assert_pass` to test an operation terminating with success.
|
22
|
+
* `#assert_fail` to assert validation errors and the like.
|
23
|
+
* `#mock_step` helping the replace steps with stubs.
|
174
24
|
|
175
|
-
|
176
|
-
include Trailblazer::Test::Operation::Helper
|
177
|
-
```
|
25
|
+
## Example
|
178
26
|
|
179
|
-
|
27
|
+
An example test case checking if an operation passed and created a model could look as follows.
|
180
28
|
|
181
29
|
```ruby
|
182
|
-
|
30
|
+
# test/operation/memo_test.rb
|
183
31
|
|
184
|
-
|
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
|
32
|
+
require "test_helper"
|
200
33
|
|
201
|
-
|
202
|
-
|
34
|
+
class MemoOperationTest < Minitest::Spec
|
35
|
+
Trailblazer::Test.module!(self) # install our helpers.
|
203
36
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
37
|
+
it "passes with valid input" do
|
38
|
+
# ...
|
39
|
+
assert_pass Memo::Operation::Create, input,
|
40
|
+
content: "Stock up beer",
|
41
|
+
persisted?: true,
|
42
|
+
id: ->(asserted:, **) { asserted.id > 0 }
|
43
|
+
end
|
210
44
|
end
|
211
45
|
```
|
data/Rakefile
CHANGED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
module Test
|
3
|
+
module Assertion
|
4
|
+
module AssertExposes
|
5
|
+
# Test if all `tuples` values on `asserted` match the expected values.
|
6
|
+
# @param asserted Object Object that exposes attributes to test
|
7
|
+
# @param tuples Hash Key/value attribute pairs to test
|
8
|
+
# @param options Hash Default :reader is `asserted.{name}`,
|
9
|
+
# TODO: test err msgs!
|
10
|
+
def assert_exposes(asserted, expected=nil, reader: nil, **options)
|
11
|
+
expected = options.any? ? options : expected # allow passing {expected} as kwargs, too.
|
12
|
+
|
13
|
+
_assert_exposes_for(asserted, expected, reader: reader)
|
14
|
+
end
|
15
|
+
|
16
|
+
# def assert_exposes_hash(asserted, expected)
|
17
|
+
# _assert_exposes_for(asserted, expected, reader: :[])
|
18
|
+
# end
|
19
|
+
|
20
|
+
# @private
|
21
|
+
def _assert_exposes_for(asserted, expected, **options)
|
22
|
+
passed, matches, last_failed = Assert.assert_attributes(asserted, expected, **options) do |_matches, last_failed|
|
23
|
+
name, expected_value, actual_value, _passed, is_eq, error_msg = last_failed
|
24
|
+
|
25
|
+
is_eq ? assert_equal(expected_value, actual_value, error_msg) : assert(expected_value, error_msg)
|
26
|
+
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
|
30
|
+
return true
|
31
|
+
end
|
32
|
+
|
33
|
+
module Assert
|
34
|
+
module_function
|
35
|
+
|
36
|
+
# Yields {block} if tuples don't match/failed.
|
37
|
+
def assert_attributes(asserted, expected, reader: false, &block)
|
38
|
+
passed, matches, last_failed = match_tuples(asserted, expected, reader: reader)
|
39
|
+
|
40
|
+
yield matches, last_failed unless passed
|
41
|
+
|
42
|
+
return passed, matches, last_failed
|
43
|
+
end
|
44
|
+
|
45
|
+
# Test if all properties match using our own {#test_equal}.
|
46
|
+
# @private
|
47
|
+
def match_tuples(asserted, expected, reader:)
|
48
|
+
passed = true # goes {false} if one or more attributes didn't match.
|
49
|
+
|
50
|
+
matches = expected.collect do |k, v|
|
51
|
+
actual = Test::Assertion.actual(asserted, reader, k)
|
52
|
+
expected, is_eq = Test::Assertion.expected(asserted, v, actual)
|
53
|
+
|
54
|
+
is_eq ?
|
55
|
+
[k, expected, actual, passed &= test_equal(expected, actual), is_eq, "Property [#{k}] mismatch"] :
|
56
|
+
[k, expected, actual, passed &= test_true(expected, actual), is_eq, "Actual: #{actual.inspect}."]
|
57
|
+
end
|
58
|
+
|
59
|
+
[passed, matches, matches.find { |k, v, actual, passed, *| !passed }]
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_equal(expected, actual)
|
63
|
+
expected == actual
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_true(expected, actual)
|
67
|
+
!! expected
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
module Test
|
3
|
+
module Assertion
|
4
|
+
module AssertFail
|
5
|
+
module_function
|
6
|
+
|
7
|
+
extend AssertPass::Utils
|
8
|
+
|
9
|
+
# {expected_errors} can be nil when using the {#assert_fail} block syntax.
|
10
|
+
def call(activity, ctx, expected_errors=nil, test:, invoke:, **kws)
|
11
|
+
signal, ctx, _ = invoke.(activity, ctx)
|
12
|
+
|
13
|
+
assert_fail_with_model(signal, ctx, expected_errors: expected_errors, test: test, operation: activity, **kws)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @private
|
17
|
+
def assert_fail_with_model(signal, ctx, test:, **options)
|
18
|
+
assert_after_call(ctx, **options) do |ctx|
|
19
|
+
|
20
|
+
test.assert_equal *arguments_for_assert_fail(signal), error_message_for_assert_fail_after_call(signal, ctx, **options)
|
21
|
+
|
22
|
+
if options[:expected_errors]
|
23
|
+
# TODO: allow error messages from somewhere else.
|
24
|
+
# only test _if_ errors are present, not the content.
|
25
|
+
colored_errors = AssertPass::Errors.colored_errors_for(ctx)
|
26
|
+
|
27
|
+
test.assert_equal *arguments_for_assert_contract_errors(signal, ctx, contract_name: :default, **options), "Actual contract errors: #{colored_errors}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def arguments_for_assert_fail(signal)
|
33
|
+
return false, Assertion::SUCCESS_TERMINI.include?(signal.to_h[:semantic]) # FIXME: same logic as in {#assert_pass}.
|
34
|
+
end
|
35
|
+
|
36
|
+
def arguments_for_assert_contract_errors(signal, ctx, contract_name:, expected_errors:, **)
|
37
|
+
with_messages = expected_errors.is_a?(Hash)
|
38
|
+
|
39
|
+
raise ExpectedErrorsTypeError, "expected_errors has to be an Array or Hash" unless expected_errors.is_a?(Array) || with_messages # TODO: test me!
|
40
|
+
|
41
|
+
errors = ctx[:"contract.#{contract_name}"].errors.messages # TODO: this will soon change with the operation Errors object.
|
42
|
+
|
43
|
+
if with_messages
|
44
|
+
expected_errors = expected_errors.collect { |k, v| [k, Array(v)] }.to_h
|
45
|
+
|
46
|
+
return expected_errors, errors
|
47
|
+
else
|
48
|
+
return expected_errors.sort, errors.keys.sort
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def error_message_for_assert_fail_after_call(signal, ctx, operation:, **)
|
53
|
+
%{{#{operation}} didn't fail, it passed}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
module Test
|
3
|
+
module Assertion
|
4
|
+
module AssertPass
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def call(activity, ctx, invoke:, **options)
|
8
|
+
signal, ctx, _ = invoke.(activity, ctx)
|
9
|
+
|
10
|
+
assert_pass_with_model(signal, ctx, operation: activity, **options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def assert_pass_with_model(signal, ctx, **options)
|
14
|
+
assert_after_call(ctx, **options) do |ctx|
|
15
|
+
Passed.new.call(signal, ctx, **options)
|
16
|
+
PassedWithAttributes.new.call(signal, ctx, **options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Passed
|
21
|
+
# Check if the operation terminates on {:success}.
|
22
|
+
# @semi-public Used in rspec-trailblazer
|
23
|
+
def call(signal, ctx, **options)
|
24
|
+
expected_outcome, actual_outcome = arguments_for_assertion(signal)
|
25
|
+
error_msg = error_message(signal, ctx, **options) # DISCUSS: compute error message before there was an error?
|
26
|
+
|
27
|
+
outcome = assertion(expected_outcome, actual_outcome, error_msg, **options)
|
28
|
+
return outcome, error_msg
|
29
|
+
end
|
30
|
+
|
31
|
+
# What needs to be compared?
|
32
|
+
def arguments_for_assertion(signal)
|
33
|
+
return true, Assertion::SUCCESS_TERMINI.include?(signal.to_h[:semantic])
|
34
|
+
end
|
35
|
+
|
36
|
+
def error_message(signal, ctx, operation:, **)
|
37
|
+
colored_errors = Errors.colored_errors_for(ctx)
|
38
|
+
|
39
|
+
%{{#{operation}} failed: #{colored_errors}} # FIXME: only if contract's there!
|
40
|
+
end
|
41
|
+
|
42
|
+
def assertion(expected_outcome, actual_outcome, error_msg, test:, **)
|
43
|
+
test.assert_equal(
|
44
|
+
expected_outcome,
|
45
|
+
actual_outcome,
|
46
|
+
error_msg
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @semi-public Used in rspec-trailblazer
|
52
|
+
class PassedWithAttributes
|
53
|
+
def call(signal, ctx, **options)
|
54
|
+
model = model_for(ctx, **options)
|
55
|
+
|
56
|
+
outcome, error_msg = assertion(ctx, **options, model: model)
|
57
|
+
return outcome, error_msg
|
58
|
+
end
|
59
|
+
|
60
|
+
# DISCUSS: should we default options like {:model_at} here?
|
61
|
+
def model_for(ctx, model_at: :model, **)
|
62
|
+
ctx[model_at]
|
63
|
+
end
|
64
|
+
|
65
|
+
def assertion(ctx, model:, expected_model_attributes:, test:, **)
|
66
|
+
test.assert_exposes(model, expected_model_attributes)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
module Utils
|
71
|
+
# @private
|
72
|
+
def assert_after_call(ctx, user_block:, **kws)
|
73
|
+
yield(ctx)
|
74
|
+
|
75
|
+
user_block.call(ctx) if user_block
|
76
|
+
|
77
|
+
ctx
|
78
|
+
end
|
79
|
+
end # Utils
|
80
|
+
|
81
|
+
module Errors
|
82
|
+
module_function
|
83
|
+
|
84
|
+
def colored_errors_for(ctx)
|
85
|
+
# TODO: generic errors object "finding"
|
86
|
+
errors =
|
87
|
+
if ctx[:"contract.default"]
|
88
|
+
ctx[:"contract.default"].errors.messages.inspect
|
89
|
+
else
|
90
|
+
""
|
91
|
+
end
|
92
|
+
|
93
|
+
colored_errors = %{\e[33m#{errors}\e[0m}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
extend Utils
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
module Test
|
3
|
+
# Top-level entry points for end users.
|
4
|
+
# These methods expose the end user syntax, not the logic.
|
5
|
+
module Assertion
|
6
|
+
def self.module!(receiver, activity: false, suite: false, spec: true)
|
7
|
+
modules = [Helper::MockStep, AssertExposes]
|
8
|
+
if suite
|
9
|
+
modules += [Suite, Suite::Spec] if spec
|
10
|
+
modules += [Suite, Suite::Test] if suite && !spec
|
11
|
+
else
|
12
|
+
modules += [Assertion]
|
13
|
+
end
|
14
|
+
|
15
|
+
modules += [Assertion::Activity] if activity
|
16
|
+
|
17
|
+
receiver.include(*modules.reverse)
|
18
|
+
end
|
19
|
+
|
20
|
+
SUCCESS_TERMINI = [:success, :pass_fast] # DISCUSS: where should this be defined?
|
21
|
+
|
22
|
+
# @private
|
23
|
+
# Invoker for Operation
|
24
|
+
def self.invoke_operation(operation, ctx)
|
25
|
+
result = operation.call(ctx)
|
26
|
+
|
27
|
+
return result.terminus, result # translate the holy {Operation::Result} object back to a normal "circuit interface" return value.
|
28
|
+
end
|
29
|
+
|
30
|
+
# @private
|
31
|
+
# Invoker with debugging for Operation
|
32
|
+
def self.invoke_operation_with_wtf(operation, ctx)
|
33
|
+
result = operation.wtf?(ctx)
|
34
|
+
|
35
|
+
return result.terminus, result
|
36
|
+
end
|
37
|
+
|
38
|
+
# Evaluate value if it's a lambda, and let the caller know whether we need an
|
39
|
+
# assert_equal or an assert.
|
40
|
+
def self.expected(asserted, value, actual)
|
41
|
+
value.is_a?(Proc) ? [value.(actual: actual, asserted: asserted), false] : [value, true]
|
42
|
+
end
|
43
|
+
|
44
|
+
# # Read the actual value from the asserted object (e.g. a model).
|
45
|
+
def self.actual(asserted, reader, name)
|
46
|
+
reader ? asserted.public_send(reader, name) : asserted.public_send(name)
|
47
|
+
end
|
48
|
+
|
49
|
+
# DISCUSS: move to Assertion::Minitest?
|
50
|
+
# Test case instance method. Specific to Minitest.
|
51
|
+
def assert_pass(activity, options, invoke: Assertion.method(:invoke_operation), model_at: :model, **kws, &block)
|
52
|
+
# DISCUSS: {:model_at} and {:invoke_method} block actual attributes.
|
53
|
+
AssertPass.(activity, options,
|
54
|
+
test: self,
|
55
|
+
user_block: block,
|
56
|
+
expected_model_attributes: kws,
|
57
|
+
model_at: model_at,
|
58
|
+
invoke: invoke,
|
59
|
+
) # Forward {#assert_pass} to {AssertPass.call} or wherever your implementation sits.
|
60
|
+
end
|
61
|
+
|
62
|
+
# DISCUSS: move to Assertion::Minitest?
|
63
|
+
# Test case instance method. Specific to Minitest.
|
64
|
+
def assert_fail(activity, options, *args, invoke: Assertion.method(:invoke_operation), **kws, &block)
|
65
|
+
AssertFail.(activity, options, *args, test: self, user_block: block, invoke: invoke, **kws) # Forward {#assert_fail} to {AssertFail.call} or wherever your implementation sits.
|
66
|
+
end
|
67
|
+
|
68
|
+
def assert_pass?(*args, **options, &block)
|
69
|
+
assert_pass(*args, **options, invoke: Assertion.method(:invoke_operation_with_wtf), &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
def assert_fail?(*args, **options, &block)
|
73
|
+
assert_fail(*args, **options, invoke: Assertion.method(:invoke_operation_with_wtf), &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Assertions for Activity, not for Operation.
|
77
|
+
module Activity
|
78
|
+
def self.invoke_activity(activity, ctx)
|
79
|
+
signal, (ctx, _) = activity.call([ctx, {}]) # call with circuit interface. https://trailblazer.to/2.1/docs/operation/#operation-internals-circuit-interface
|
80
|
+
|
81
|
+
return signal, ctx
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.invoke_activity_with_task_wrap(activity, ctx)
|
85
|
+
signal, (ctx, _) = ::Trailblazer::Activity::TaskWrap.invoke(activity, [ctx, {}]) # call with circuit interface. https://trailblazer.to/2.1/docs/operation/#operation-internals-circuit-interface
|
86
|
+
|
87
|
+
return signal, ctx
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.invoke_activity_with_tracing(activity, ctx)
|
91
|
+
signal, (ctx, _) = Developer::Wtf.invoke(activity, [ctx, {}])
|
92
|
+
|
93
|
+
return signal, ctx
|
94
|
+
end
|
95
|
+
|
96
|
+
def assert_pass(*args, invoke: Activity.method(:invoke_activity_with_task_wrap), **options, &block)
|
97
|
+
super(*args, **options, invoke: invoke, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
def assert_fail(*args, invoke: Activity.method(:invoke_activity_with_task_wrap), **options, &block)
|
101
|
+
super(*args, **options, invoke: invoke, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
def assert_pass?(*args, **options, &block)
|
105
|
+
assert_pass(*args, **options, invoke: Activity.method(:invoke_activity_with_tracing), &block)
|
106
|
+
end
|
107
|
+
|
108
|
+
def assert_fail?(*args, **options, &block)
|
109
|
+
assert_fail(*args, **options, invoke: Activity.method(:invoke_activity_with_tracing), &block)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|