trailblazer-test 0.1.0 → 1.0.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 +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
|