trailblazer 2.1.0.rc1 → 2.1.0.rc12

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,294 +0,0 @@
1
- require "test_helper"
2
- require "trailblazer/operation/contract"
3
-
4
- # contract Constant # new
5
- # contract Constant, inherit: true # extend existing
6
- # contract do end # extend existing || new
7
- # contract Constant do .. end # new, extend new
8
-
9
- class DslContractTest < MiniTest::Spec
10
- module Call
11
- def call(params)
12
- validate(params, model: model=OpenStruct.new) { contract.sync }
13
- model
14
- end
15
-
16
- def self.included(includer)
17
- includer.step Trailblazer::Operation::Model( OpenStruct, :new )
18
- includer.step Trailblazer::Operation::Contract::Build()
19
- includer.step Trailblazer::Operation::Contract::Validate()
20
- includer.step Trailblazer::Operation::Contract::Persist( method: :sync )
21
- # includer.> ->(op, *) { op["x"] = [] }
22
- end
23
- end
24
-
25
- # ---
26
- # Operation::["contract.default.class"]
27
- # Operation::["contract.default.class"]=
28
- class Create < Trailblazer::Operation
29
- include Contract
30
- self["contract.default.class"] = String
31
- end
32
-
33
- # reader method.
34
- # no subclassing.
35
- it { Create["contract.default.class"].must_equal String }
36
-
37
- class CreateOrFind < Create
38
- end
39
-
40
- # no inheritance with setter.
41
- it { CreateOrFind["contract.default.class"].must_equal nil }
42
-
43
- # ---
44
- # Op::contract Constant
45
- class Update < Trailblazer::Operation
46
- class IdContract < Reform::Form
47
- property :id
48
- end
49
-
50
- extend Contract::DSL
51
- contract IdContract
52
-
53
- include Call
54
- end
55
-
56
- # UT: subclasses contract.
57
- it { Update["contract.default.class"].superclass.must_equal Update::IdContract }
58
- # IT: only knows `id`.
59
- it { Update.(params: {id: 1, title: "Coaster"})[:model].inspect.must_equal %{#<OpenStruct id=1>} }
60
-
61
- # Op::contract with inheritance
62
- # no ::contract call.
63
- class Upgrade < Update
64
- end
65
-
66
- # UT: subclasses contract but doesn't share with parent.
67
- it { Upgrade["contract.default.class"].superclass.must_equal Update::IdContract }
68
- it { Upgrade["contract.default.class"].wont_equal Update["contract.default.class"] }
69
- # IT: only knows `id`.
70
- it { Upgrade.(params: {id: 1, title: "Coaster"})[:model].inspect.must_equal %{#<OpenStruct id=1>} }
71
-
72
- # ::contract B overrides old A contract.
73
- # this makes sure when calling contract(Constant), the old class gets wiped and is replaced with the new constant.
74
- class Upsert < Update
75
- class TitleContract < Reform::Form
76
- property :title
77
- end
78
-
79
- contract TitleContract
80
- end
81
-
82
- # UT: subclasses contract.
83
- it { Upsert["contract.default.class"].superclass.must_equal Upsert::TitleContract }
84
- # IT: only knows `title`.
85
- it { Upsert.(params: {id: 1, title: "Coaster"})[:model].inspect.must_equal %{#<OpenStruct title="Coaster">} }
86
-
87
- # ::contract B do ..end overrides and extends new.
88
- # using a constant will wipe out the existing class.
89
- class Upside < Update
90
- contract Upsert::TitleContract do
91
- property :id
92
- end
93
- end
94
-
95
- # UT: subclasses contract.
96
- it { Upside["contract.default.class"].superclass.must_equal Upsert::TitleContract }
97
- # IT: only knows `title`.
98
- it { Upside.(params: {id: 1, title: "Coaster"})[:model].inspect.must_equal %{#<OpenStruct title="Coaster", id=1>} }
99
-
100
-
101
-
102
- #---
103
- # contract do .. end
104
- # (with block)
105
- class Delete < Trailblazer::Operation
106
- include Call
107
- extend Contract::DSL
108
- contract do
109
- property :title
110
- end
111
- end
112
-
113
- # UT: contract path is "contract.default.class"
114
- it { Delete["contract.default.class"].definitions.keys.must_equal ["title"] }
115
- # IT: knows `title`.
116
- it { Delete.(params: {id: 1, title: "Coaster"})[:model].inspect.must_equal %{#<OpenStruct title=\"Coaster\">} }
117
-
118
- class Wipe < Trailblazer::Operation
119
- extend Contract::DSL
120
-
121
- self["x"] = contract do end
122
- end
123
- # UT: ::contract returns form class
124
- it { Wipe["x"].superclass.must_equal Reform::Form }
125
-
126
- # subsequent calls merge.
127
- class Remove < Trailblazer::Operation
128
- extend Contract::DSL
129
- include Call
130
-
131
- contract do
132
- property :title
133
- end
134
-
135
- contract do
136
- property :id
137
- end
138
- end
139
-
140
- # IT: knows `title` and `id`, since contracts get merged.
141
- it { Remove.(params: {id: 1, title: "Coaster"})[:model].inspect.must_equal %{#<OpenStruct title=\"Coaster\", id=1>} }
142
-
143
-
144
-
145
-
146
-
147
-
148
- # Operation::["contract.default.class"]
149
- # Operation::["contract.default.class"]=
150
- describe %{Operation::["contract.default.class"]} do
151
-
152
- class Update2 < Trailblazer::Operation
153
- self["contract.default.class"] = String
154
- end
155
-
156
- it { Update2["contract.default.class"].must_equal String }
157
- end
158
-
159
- describe "inheritance across operations" do
160
- # inheritance
161
- class Operation < Trailblazer::Operation
162
- extend Contract::DSL
163
- contract do
164
- property :title
165
- property :band
166
- end
167
-
168
- class JSON < self
169
- contract do # inherit Contract
170
- property :genre, validates: {presence: true}
171
- property :band, virtual: true
172
- end
173
- end
174
-
175
- class XML < self
176
- end
177
- end
178
-
179
- # inherits subclassed Contract.
180
- it { Operation["contract.default.class"].wont_equal Operation::JSON["contract.default.class"] }
181
- it { Operation::XML["contract.default.class"].superclass.must_equal Reform::Form }
182
-
183
- it do
184
- form = Operation["contract.default.class"].new(OpenStruct.new)
185
- form.validate({})#.must_equal true
186
- form.errors.to_s.must_equal "{}"
187
-
188
- form = Operation::JSON["contract.default.class"].new(OpenStruct.new)
189
- form.validate({})#.must_equal true
190
- form.errors.to_s.must_equal "{:genre=>[\"can't be blank\"]}"
191
- end
192
-
193
- # allows overriding options
194
- it do
195
- form = Operation::JSON["contract.default.class"].new(song = OpenStruct.new)
196
- form.validate({genre: "Punkrock", band: "Osker"}).must_equal true
197
- form.sync
198
-
199
- song.genre.must_equal "Punkrock"
200
- song.band.must_equal nil
201
- end
202
- end
203
-
204
- describe "Op.contract CommentForm" do
205
- class SongForm < Reform::Form
206
- property :songTitle, validates: {presence: true}
207
- end
208
-
209
- class OpWithExternalContract < Trailblazer::Operation
210
- extend Contract::DSL
211
- contract SongForm
212
- include Call
213
- end
214
-
215
- it { OpWithExternalContract.(params: {"songTitle"=> "Monsterparty"})["contract.default"].songTitle.must_equal "Monsterparty" }
216
- end
217
-
218
- describe "Op.contract CommentForm do .. end" do
219
- class DifferentSongForm < Reform::Form
220
- property :songTitle, validates: {presence: true}
221
- end
222
-
223
- class OpNotExtendingContract < Trailblazer::Operation
224
- extend Contract::DSL
225
- contract DifferentSongForm
226
- include Call
227
- end
228
-
229
- class OpExtendingContract < Trailblazer::Operation
230
- extend Contract::DSL
231
- contract DifferentSongForm do
232
- property :genre
233
- end
234
- include Call
235
- end
236
-
237
- # this operation copies DifferentSongForm and shouldn't have `genre`.
238
- it do
239
- contract = OpNotExtendingContract.(params: {"songTitle"=>"Monsterparty", "genre"=>"Punk"})["contract.default"]
240
- contract.songTitle.must_equal "Monsterparty"
241
- assert_raises(NoMethodError) { contract.genre }
242
- end
243
-
244
- # this operation copies DifferentSongForm and extends it with the property `genre`.
245
- it do
246
- contract = OpExtendingContract.(params: {"songTitle"=>"Monsterparty", "genre"=>"Punk"})["contract.default"]
247
- contract.songTitle.must_equal "Monsterparty"
248
- contract.genre.must_equal "Punk"
249
- end
250
-
251
- # of course, the original contract wasn't modified, either.
252
- it do
253
- assert_raises(NoMethodError) { DifferentSongForm.new(OpenStruct.new).genre }
254
- end
255
- end
256
-
257
- describe "Op.contract :name, Form" do
258
- class Follow < Trailblazer::Operation
259
- ParamsForm = Class.new
260
-
261
- extend Contract::DSL
262
- contract :params, ParamsForm
263
- end
264
-
265
- it { Follow["contract.params.class"].superclass.must_equal Follow::ParamsForm }
266
- end
267
-
268
- describe "Op.contract :name do..end" do
269
- class Unfollow < Trailblazer::Operation
270
- extend Contract::DSL
271
- contract :params do
272
- property :title
273
- end
274
- end
275
-
276
- it { Unfollow["contract.params.class"].superclass.must_equal Reform::Form }
277
- end
278
-
279
- # multiple ::contract calls.
280
- describe "multiple ::contract calls" do
281
- class Star < Trailblazer::Operation
282
- extend Contract::DSL
283
- contract do
284
- property :title
285
- end
286
-
287
- contract do
288
- property :id
289
- end
290
- end
291
-
292
- it { Star["contract.default.class"].definitions.keys.must_equal ["title", "id"] }
293
- end
294
- end
@@ -1,100 +0,0 @@
1
- # require 'test_helper'
2
- # require "trailblazer/operation/module"
3
- # require "trailblazer/operation/callback"
4
- # require "trailblazer/operation/contract"
5
-
6
- # class OperationModuleTest < MiniTest::Spec
7
- # Song = Struct.new(:name, :artist)
8
- # Artist = Struct.new(:id, :full_name)
9
-
10
- # class Create < Trailblazer::Operation
11
- # include Trailblazer::Operation::Callback
12
- # include Contract::Explicit
13
-
14
- # contract do
15
- # property :name
16
- # property :artist, populate_if_empty: Artist do
17
- # property :id
18
- # end
19
- # end
20
-
21
- # callback do
22
- # on_change :notify_me!
23
- # end
24
-
25
- # attr_reader :model
26
- # def call(params)
27
- # self["model"] = Song.new
28
-
29
- # validate(params, model: self["model"]) do
30
- # contract.sync
31
-
32
- # dispatch!
33
- # end
34
-
35
- # self
36
- # end
37
-
38
- # def dispatched
39
- # self["dispatched"] ||= []
40
- # end
41
-
42
- # private
43
- # def notify_me!(*)
44
- # dispatched << :notify_me!
45
- # end
46
- # end
47
-
48
-
49
- # module SignedIn
50
- # include Trailblazer::Operation::Module
51
-
52
- # contract do
53
- # property :artist, inherit: true do
54
- # property :full_name
55
-
56
- # puts definitions.inspect
57
- # end
58
- # end
59
-
60
- # callback do
61
- # on_change :notify_you!
62
- # end
63
-
64
- # def notify_you!(*)
65
- # dispatched << :notify_you!
66
- # end
67
- # end
68
-
69
-
70
- # class Update < Create
71
- # callback do
72
- # on_change :notify_them!
73
- # end
74
-
75
- # include SignedIn
76
-
77
- # def notify_them!(*)
78
- # dispatched << :notify_them!
79
- # end
80
- # end
81
-
82
-
83
- # it do
84
- # op = Create.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
85
-
86
- # op["dispatched"].must_equal [:notify_me!]
87
- # op["model"].name.must_equal "Feelings"
88
- # op["model"].artist.id.must_equal 1
89
- # op["model"].artist.full_name.must_be_nil # property not declared.
90
- # end
91
-
92
- # it do
93
- # op = Update.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
94
-
95
- # op["dispatched"].must_equal [:notify_me!, :notify_them!, :notify_you!]
96
- # op["model"].name.must_equal "Feelings"
97
- # op["model"].artist.id.must_equal 1
98
- # op["model"].artist.full_name.must_equal "The Offspring" # property declared via Module.
99
- # end
100
- # end
@@ -1,159 +0,0 @@
1
- require "test_helper"
2
-
3
- class VariablesTest < Minitest::Spec
4
- # Nested op copies values and modifies/amplifies some of them.
5
- class Whistleblower < Trailblazer::Operation
6
- step ->(options, public_opinion:, **) { options["edward.public_opinion"] = public_opinion.upcase } , id: "edward.public_opinion"
7
- step ->(options, secret:, **) { options["edward.secret"] = secret } , id: "edward.secret"
8
- step ->(options, rumours:, **) { rumours.nil? ? rumours : options["edward.rumours"] = rumours*2 } , id: "edward.test"
9
- pass ->(options, **) { options["edward.public_knowledge"] = options["public_knowledge"] }, id: "edward.read.public_knowledge"
10
- end
11
-
12
- =begin
13
- Everything public. options are simply passed on.
14
-
15
- Both Org and Edward write and read from the same Context instance.
16
- =end
17
- class OpenOrganization < Trailblazer::Operation
18
- step ->(options, **) { options["rumours"] = "Bla" }
19
- step ->(options, **) { options["secret"] = "Psst!" }
20
-
21
- step Nested( Whistleblower )
22
-
23
- step ->(options, **) { options["org.rumours"] = options["edward.rumours"] } # what can we see from Edward?
24
- step ->(options, **) { options["org.secret"] = options["edward.secret"] } # what can we see from Edward?
25
- end
26
-
27
- it do
28
- result = OpenOrganization.("public_opinion" => "Freedom!", "public_knowledge" => true)
29
-
30
- result.inspect("public_opinion", "rumours", "secret", "edward.public_opinion", "edward.secret", "edward.rumours", "edward.public_knowledge").
31
- must_equal %{<Result:true ["Freedom!", "Bla", "Psst!", "FREEDOM!", "Psst!", "BlaBla", true] >}
32
- end
33
-
34
- #---
35
- #- simply passes on the context
36
- class ConsideringButOpenOrganization < Trailblazer::Operation
37
- step ->(options, **) { options["rumours"] = "Bla" }
38
- step ->(options, **) { options["secret"] = "Psst!" }
39
-
40
- step Nested( Whistleblower, input: :input! )
41
-
42
- step ->(options, **) { options["org.rumours"] = options["edward.rumours"] } # what can we see from Edward?
43
- step ->(options, **) { options["org.secret"] = options["edward.secret"] } # what can we see from Edward?
44
-
45
- def input!(options, **)
46
- options
47
- end
48
- end
49
-
50
- it do
51
- result = ConsideringButOpenOrganization.("public_opinion" => "Freedom!")
52
-
53
- result.inspect("public_opinion", "rumours", "secret", "edward.public_opinion", "edward.secret", "edward.rumours").
54
- must_equal %{<Result:true ["Freedom!", "Bla", "Psst!", "FREEDOM!", "Psst!", "BlaBla"] >}
55
- end
56
-
57
- =begin
58
- Explicitely passes allowed variables, only.
59
-
60
- Edward can only read those three variables and can't see "public_knowledge" or others.
61
- =end
62
- class ProtectedOrganization < ConsideringButOpenOrganization
63
- def input!(options, **)
64
- {
65
- "public_opinion" => options["public_opinion"],
66
- "secret" => 0,
67
- "rumours" => 0.0
68
- }
69
- end
70
- end
71
-
72
- it do
73
- result = ProtectedOrganization.( {}, "public_opinion" => "Freedom!", "public_knowledge" => true )
74
-
75
- result.inspect("public_opinion", "rumours", "secret", "edward.public_opinion", "edward.secret", "edward.rumours", "edward.public_knowledge").
76
- must_equal %{<Result:true ["Freedom!", "Bla", "Psst!", "FREEDOM!", 0, 0.0, nil] >}
77
- end
78
-
79
- #---
80
- #- new ctx
81
- =begin
82
- :input produces a differing value for an existing key "secret"
83
- this should only be visible in the nested activity, and the original
84
- "secret" must be what it was before (unless :output would change that.)
85
-
86
- NOTE: i decided it's better to not even allow Context#merge because it
87
- defeats the idea of hiding information via :input and overcomplicates
88
- the scoping.
89
- =end
90
- class EncryptedOrganization < ConsideringButOpenOrganization
91
- def input!(options, **)
92
- options.merge( "secret" => options["secret"]+"XxX" )
93
- end
94
- end
95
-
96
- it do
97
- skip "no options.merge until we know we actually need it"
98
- result = EncryptedOrganization.("public_opinion" => "Freedom!")
99
-
100
- result.inspect("public_opinion", "rumours", "secret", "edward.public_opinion", "edward.secret", "edward.rumours").
101
- must_equal %{<Result:true ["Freedom!", "Bla", "Psst!", "FREEDOM!", "Psst!XxX", "BlaBla"] >}
102
- end
103
-
104
- # TODO write to options and get error
105
-
106
- =begin
107
- Simply passes on the context to Edward, but applies an :output filter,
108
- so that Org can't see several nested values such as "edward.public_knowledge"
109
- or "edward.rumours" (see steps in #read-section).
110
- =end
111
- class DiscreetOrganization < Trailblazer::Operation
112
- step ->(options, **) { options["rumours"] = "Bla" }, id: "set.rumours"
113
- step ->(options, **) { options["secret"] = "Psst!" }, id: "set.secret"
114
-
115
- step Nested( Whistleblower, input: :input!, output: :output! )
116
-
117
- #read-section
118
- pass ->(options, **) { options["org.rumours"] = options["edward.rumours"] }, id: "read.edward.rumours" # what can we see from Edward?
119
- step ->(options, **) { options["org.secret"] = options["edward.secret"] }, id: "read.edward.secret" # what can we see from Edward?
120
-
121
- def input!(options, **)
122
- options
123
- end
124
-
125
- def output!(options, **)
126
- {
127
- "out.keys" => options.keys,
128
- "out.rumours" => options["edward.rumours"].slice(0..2),
129
- "out.secret" => options["edward.secret"].reverse,
130
- }
131
- end
132
- end
133
-
134
- it do
135
- result = DiscreetOrganization.("public_opinion" => "Freedom!", "public_knowledge" => true)
136
-
137
- result.inspect("public_opinion", "rumours", "secret", "edward.public_opinion", "edward.secret", "edward.rumours", "out.keys", "out.rumours", "out.secret", "org.rumours", "org.secret", "public_knowledge", "edward.public_knowledge").
138
- must_equal %{<Result:false [\"Freedom!\", \"Bla\", \"Psst!\", nil, nil, nil, [\"edward.public_opinion\", \"edward.secret\", \"edward.rumours\", \"edward.public_knowledge\", \"rumours\", \"secret\", \"public_opinion\", \"public_knowledge\"], \"Bla\", \"!tssP\", nil, nil, true, nil] >}
139
- end
140
-
141
- it "with tracing" do
142
- result = DiscreetOrganization.trace("public_opinion" => "Freedom!")
143
-
144
- result.wtf.gsub(/0x\w+/, "").gsub(/\d+/, "").must_equal %{`-- VariablesTest::DiscreetOrganization
145
- |-- Start.default
146
- |-- set.rumours
147
- |-- set.secret
148
- |-- Nested(VariablesTest::Whistleblower)
149
- | |-- Start.default
150
- | |-- edward.public_opinion
151
- | |-- edward.secret
152
- | |-- edward.test
153
- | |-- edward.read.public_knowledge
154
- | `-- End.success
155
- |-- read.edward.rumours
156
- |-- read.edward.secret
157
- `-- End.failure}
158
- end
159
- end