trailblazer-endpoint 0.0.1 → 0.0.6

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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -0
  3. data/Appraisals +5 -0
  4. data/CHANGES.md +26 -0
  5. data/README.md +40 -5
  6. data/Rakefile +7 -1
  7. data/gemfiles/rails_app.gemfile +12 -0
  8. data/lib/trailblazer/endpoint.rb +29 -14
  9. data/lib/trailblazer/endpoint/adapter.rb +30 -121
  10. data/lib/trailblazer/endpoint/builder.rb +1 -1
  11. data/lib/trailblazer/endpoint/controller.rb +223 -0
  12. data/lib/trailblazer/endpoint/dsl.rb +29 -0
  13. data/lib/trailblazer/endpoint/options.rb +92 -0
  14. data/lib/trailblazer/endpoint/protocol.rb +5 -8
  15. data/lib/trailblazer/endpoint/version.rb +1 -1
  16. data/test/adapter/api_test.rb +6 -11
  17. data/test/adapter/web_test.rb +2 -5
  18. data/test/config_test.rb +128 -0
  19. data/test/docs/controller_test.rb +339 -58
  20. data/test/endpoint_test.rb +1 -1
  21. data/test/rails-app/.gitignore +8 -2
  22. data/test/rails-app/.ruby-version +1 -0
  23. data/test/rails-app/Gemfile +15 -9
  24. data/test/rails-app/Gemfile.lock +162 -121
  25. data/test/rails-app/app/concepts/app/api/v1/representer/errors.rb +16 -0
  26. data/test/rails-app/app/concepts/auth/jwt.rb +35 -0
  27. data/test/rails-app/app/concepts/auth/operation/authenticate.rb +32 -0
  28. data/test/rails-app/app/concepts/auth/operation/policy.rb +9 -0
  29. data/test/rails-app/app/concepts/song/cell/create.rb +4 -0
  30. data/test/rails-app/app/concepts/song/cell/new.rb +4 -0
  31. data/test/rails-app/app/concepts/song/operation/create.rb +17 -0
  32. data/test/rails-app/app/concepts/song/operation/show.rb +10 -0
  33. data/test/rails-app/app/concepts/song/representer.rb +5 -0
  34. data/test/rails-app/app/concepts/song/view/create.erb +1 -0
  35. data/test/rails-app/app/concepts/song/view/new.erb +1 -0
  36. data/test/rails-app/app/controllers/api/v1/songs_controller.rb +41 -0
  37. data/test/rails-app/app/controllers/application_controller.rb +8 -1
  38. data/test/rails-app/app/controllers/application_controller/api.rb +107 -0
  39. data/test/rails-app/app/controllers/application_controller/web.rb +44 -0
  40. data/test/rails-app/app/controllers/auth_controller.rb +44 -0
  41. data/test/rails-app/app/controllers/home_controller.rb +5 -0
  42. data/test/rails-app/app/controllers/songs_controller.rb +71 -13
  43. data/test/rails-app/app/models/song.rb +3 -0
  44. data/test/rails-app/app/models/user.rb +7 -0
  45. data/test/rails-app/bin/bundle +114 -0
  46. data/test/rails-app/bin/rails +4 -0
  47. data/test/rails-app/bin/rake +4 -0
  48. data/test/rails-app/bin/setup +33 -0
  49. data/test/rails-app/config/application.rb +26 -3
  50. data/test/rails-app/config/credentials.yml.enc +1 -0
  51. data/test/rails-app/config/database.yml +2 -2
  52. data/test/rails-app/config/environments/development.rb +7 -17
  53. data/test/rails-app/config/environments/production.rb +28 -23
  54. data/test/rails-app/config/environments/test.rb +8 -12
  55. data/test/rails-app/config/initializers/application_controller_renderer.rb +6 -4
  56. data/test/rails-app/config/initializers/cors.rb +16 -0
  57. data/test/rails-app/config/initializers/trailblazer.rb +2 -0
  58. data/test/rails-app/config/locales/en.yml +11 -1
  59. data/test/rails-app/config/master.key +1 -0
  60. data/test/rails-app/config/routes.rb +16 -4
  61. data/test/rails-app/db/schema.rb +15 -0
  62. data/test/rails-app/test/controllers/api_songs_controller_test.rb +87 -0
  63. data/test/rails-app/test/controllers/songs_controller_test.rb +80 -147
  64. data/test/rails-app/test/test_helper.rb +7 -1
  65. data/test/test_helper.rb +0 -2
  66. data/trailblazer-endpoint.gemspec +2 -1
  67. metadata +70 -22
  68. data/test/rails-app/config/initializers/cookies_serializer.rb +0 -5
  69. data/test/rails-app/config/initializers/new_framework_defaults.rb +0 -24
  70. data/test/rails-app/config/initializers/session_store.rb +0 -3
  71. data/test/rails-app/config/secrets.yml +0 -22
  72. data/test/rails-app/test/helpers/.keep +0 -0
  73. data/test/rails-app/test/integration/.keep +0 -0
  74. data/test/rails-app/test/mailers/.keep +0 -0
  75. data/test/rails-app/vendor/assets/javascripts/.keep +0 -0
  76. data/test/rails-app/vendor/assets/stylesheets/.keep +0 -0
@@ -1,92 +1,373 @@
1
1
  require "test_helper"
2
2
 
3
3
  class DocsControllerTest < Minitest::Spec
4
- it "what" do
5
- endpoint "view?" do |ctx|
6
- # 200, success
7
- return
4
+ class ApplicationController
5
+ def self.options_for_endpoint(ctx, controller:, **)
6
+ {
7
+ find_process_model: true,
8
+ **controller.instance_variable_get(:@params)[:params],
9
+ }
10
+ end
11
+
12
+ def self.request_options(ctx, **)
13
+ {
14
+ request: true,
15
+ }
16
+ end
17
+
18
+ def self.options_for_flow_options(ctx, **)
19
+ {
20
+ }
21
+ end
22
+
23
+ def self.options_for_block_options(ctx, controller:, **)
24
+ {
25
+ success_block: ->(ctx, seq:, **) { controller.instance_exec { render seq << :success_block } },
26
+ failure_block: ->(ctx, seq:, **) { controller.instance_exec { render seq << :failure_block } },
27
+ protocol_failure_block: ->(ctx, seq:, **) { controller.instance_exec { render seq << :protocol_failure_block } }
28
+ }
8
29
  end
9
30
 
10
- # 422
11
- # but also 404 etc
31
+
32
+ extend Trailblazer::Endpoint::Controller
33
+
34
+ # include Trailblazer::Endpoint::Controller::InstanceMethods # {#endpoint_for}
35
+ include Trailblazer::Endpoint::Controller::InstanceMethods::DSL # {#endpoint}
36
+
37
+ include Trailblazer::Endpoint::Controller::Rails
38
+ include Trailblazer::Endpoint::Controller::Rails::Process
39
+
40
+ directive :options_for_endpoint, method(:options_for_endpoint), method(:request_options)
41
+ directive :options_for_flow_options, method(:options_for_flow_options)
42
+ directive :options_for_block_options, method(:options_for_block_options)
43
+
44
+ def process(action_name, **params)
45
+ @params = params
46
+ send_action(action_name)
47
+ @render
48
+ end
49
+
50
+ def render(text)
51
+ @render = text
52
+ end
53
+
54
+
55
+
56
+
57
+ Protocol = Class.new(Trailblazer::Endpoint::Protocol) do
58
+ include T.def_steps(:authenticate, :policy)
59
+ end
60
+
61
+ endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web, domain_ctx_filter: Trailblazer::Endpoint.domain_ctx_filter([:current_user, :process_model]), scope_domain_ctx: true
12
62
  end
13
63
 
64
+ class HtmlController < ApplicationController
65
+ private def endpoint_for(*)
66
+ protocol = Class.new(Trailblazer::Endpoint::Protocol) do
67
+ include T.def_steps(:authenticate, :policy)
68
+ end
69
+
70
+ endpoint =
71
+ Trailblazer::Endpoint.build(
72
+ domain_activity: Minitest::Spec.new(nil).activity, # FIXME
73
+ protocol: protocol,
74
+ adapter: Trailblazer::Endpoint::Adapter::Web,
75
+ scope_domain_ctx: true,
76
+
77
+ ) do
78
+ {Output(:not_found) => Track(:not_found)}
79
+ end
80
+ end
81
+
82
+ def self.options_for_domain_ctx(ctx, seq:, controller:, **)
83
+ {
84
+ current_user: "Yo",
85
+ seq: seq,
86
+ **controller.instance_variable_get(:@params)[:params],
87
+ }
88
+ end
89
+
90
+ directive :options_for_domain_ctx, method(:options_for_domain_ctx)
14
91
 
15
- class Controller
16
- def initialize(endpoint, activity)
17
- @___activity = activity
18
- @endpoint = endpoint
19
- @seq = []
92
+ private def _endpoint(action, seq: [], &block)
93
+ endpoint(action, seq: seq, &block)
20
94
  end
21
95
 
22
- def view(params)
23
- endpoint "view?", params do |ctx|
24
- @seq << :success
25
- # 200, success
26
- return
96
+ # all standard routes are user-defined
97
+ def view
98
+ _endpoint "view?" do |ctx, seq:, **|
99
+ render "success" + ctx[:current_user] + seq.inspect
100
+ end.failure do |ctx, seq:, **|
101
+ render "failure" + ctx[:current_user] + seq.inspect
102
+
103
+ end.protocol_failure do |ctx, seq:, **|
104
+ render "protocol_failure" + ctx[:current_user] + seq.inspect
27
105
  end
106
+ end
28
107
 
29
- @seq << :failure
30
- # 422
31
- # but also 404 etc
108
+ # standard use-case: only success
109
+ def show
110
+ _endpoint "view?" do |ctx, seq:, **|
111
+ render "success" + ctx[:current_user] + seq.inspect
112
+ end
32
113
  end
33
114
 
34
- def call(action, **params)
35
- send(action, **params)
36
- @seq
115
+ # standard use case: {success} and {failure}
116
+ def update
117
+ _endpoint "view?" do |ctx, seq:, **|
118
+ render "success" + ctx[:current_user] + seq.inspect
119
+ end.Or do |ctx, seq:, **|
120
+ render "Fail!" + ctx[:current_user] + seq.inspect
121
+ end
37
122
  end
38
123
 
39
- private def endpoint(action, params, &block)
40
- ctx = Trailblazer::Endpoint.advance_from_controller(@endpoint,
41
- event_name: "",
42
- success_block: block,
43
- failure_block: ->(*) { return },
44
- protocol_failure_block: ->(*) { @seq << 401 and return },
124
+ end # HtmlController
125
+
126
+ it "what" do
127
+ # success
128
+ controller = HtmlController.new
129
+ controller.process(:view, params: {}).must_equal %{successYo[:authenticate, :policy, :model, :validate]}
45
130
 
46
- collaboration: @___activity,
47
- domain_ctx: {},
48
- success_id: "fixme",
49
- flow_options: {},
131
+ # failure
132
+ controller = HtmlController.new
133
+ controller.process(:view, params: {validate: false}).must_equal %{failureYo[:authenticate, :policy, :model, :validate]}
50
134
 
51
- **params,
135
+ # protocol_failure
136
+ controller = HtmlController.new
137
+ controller.process(:view, params: {authenticate: false}).must_equal %{protocol_failureYo[:authenticate]}
138
+ end
52
139
 
53
- # DISCUSS: do we really like that fuzzy API? if yes, why do we need {additional_endpoint_options} or whatever it's called?
54
- seq: @seq,
55
- )
140
+ it "only success_block is user-defined" do
141
+ # success
142
+ controller = HtmlController.new
143
+ controller.process(:show, params: {}).must_equal %{successYo[:authenticate, :policy, :model, :validate]}
144
+
145
+ # failure
146
+ controller = HtmlController.new
147
+ # from controller-default
148
+ controller.process(:show, params: {validate: false}).must_equal [:authenticate, :policy, :model, :validate, :failure_block]
149
+
150
+ # protocol_failure
151
+ controller = HtmlController.new
152
+ # from controller-default
153
+ controller.process(:show, params: {authenticate: false}).must_equal [:authenticate, :protocol_failure_block]
154
+ end
155
+
156
+ it "success/Or" do
157
+ # success
158
+ controller = HtmlController.new
159
+ controller.process(:update, params: {}).must_equal %{successYo[:authenticate, :policy, :model, :validate]}
160
+
161
+ # failure
162
+ controller = HtmlController.new
163
+ # from controller-default
164
+ controller.process(:update, params: {validate: false}).must_equal %{Fail!Yo[:authenticate, :policy, :model, :validate]}
165
+
166
+ # protocol_failure
167
+ controller = HtmlController.new
168
+ # from controller-default
169
+ controller.process(:update, params: {authenticate: false}).must_equal [:authenticate, :protocol_failure_block]
170
+ end
171
+
172
+
173
+ # Test if {domain_ctx} is automatically wrapped via Context() so that we can use string-keys.
174
+ # TODO: test if aliases etc are properly passed.
175
+ class OptionsController < HtmlController
176
+ def self.options_for_domain_ctx(ctx, seq:, controller:, **)
177
+ {
178
+ "contract.params" => Object, # string-key should usually break if not wrapped
179
+ }
180
+ end
181
+
182
+ def self.options_for_endpoint(ctx, controller:, **)
183
+ {
184
+ current_user: "Yogi",
185
+ process_model: Class,
186
+ }
187
+ end
188
+
189
+ directive :options_for_domain_ctx, method(:options_for_domain_ctx)
190
+ directive :options_for_endpoint, method(:options_for_endpoint), inherit: false
191
+
192
+ def view
193
+ _endpoint "view?" do |ctx, seq:, **|
194
+ render "success" + ctx["contract.params"].to_s + seq.inspect
195
+ end
56
196
  end
197
+ end # OptionsController
198
+
199
+ it "allows string keys in {domain_ctx} since it gets automatically Ctx()-wrapped" do
200
+ controller = OptionsController.new
201
+ controller.process(:view, params: {}).must_equal %{successObject[:authenticate, :policy, :model, :validate]}
57
202
  end
58
203
 
59
- it "injected {return} interrupts the controller action" do
60
- protocol = Class.new(Trailblazer::Endpoint::Protocol)do
61
- include T.def_steps(:authenticate, :policy)
204
+
205
+ # copy from {endpoint_ctx} to {domain_ctx}
206
+ class DomainContextController < ApplicationController
207
+ private def _endpoint(action, seq: [], **options, &block)
208
+ endpoint(action, seq: seq, **options, &block)
62
209
  end
63
210
 
64
- endpoint =
65
- Trailblazer::Endpoint.build(
66
- domain_activity: activity,
67
- protocol: protocol,
68
- adapter: Trailblazer::Endpoint::Adapter::Web,
69
- scope_domain_ctx: false,
211
+ Activity = Class.new(Trailblazer::Activity::Railway) do
212
+ step :check
70
213
 
71
- ) do
72
- {Output(:not_found) => Track(:not_found)}
214
+ def check(ctx, current_user:, seq:, process_model:, **)
215
+ seq << :check
216
+ ctx[:message] = "#{current_user} / #{process_model}"
217
+ end
73
218
  end
74
219
 
75
- # 200
76
- seq = Controller.new(endpoint, activity).call(:view)
77
- seq.must_equal [:authenticate, :policy, :model, :validate, :success] # the {return} works.
220
+ endpoint "view?", domain_activity: Activity
221
+ endpoint "show?", domain_activity: Activity
222
+
78
223
 
79
- # 401
80
- seq = Controller.new(endpoint, activity).call(:view, authenticate: false)
81
- seq.must_equal [:authenticate, :policy, :model, :validate, :success]
224
+ def self.options_for_domain_ctx(ctx, seq:, controller:, **)
225
+ {
226
+ seq: seq,
227
+ }
228
+ end
229
+
230
+ def self.options_for_endpoint(ctx, controller:, **)
231
+ {
232
+ current_user: "Yogi",
233
+ process_model: Class,
234
+ something: true,
235
+ }
236
+ end
237
+
238
+ directive :options_for_domain_ctx, method(:options_for_domain_ctx)
239
+ directive :options_for_endpoint, method(:options_for_endpoint), inherit: false
240
+
241
+ def view
242
+ _endpoint "view?" do |ctx, seq:, **|
243
+ render "success" + ctx[:message].to_s + seq.inspect
244
+ end
245
+ end
246
+
247
+ def show
248
+ # override existing domain_ctx
249
+ # make options here available in steps
250
+ _endpoint "show?", options_for_domain_ctx: {params: {id: 1}, seq: []} do |ctx, seq:, params:, **|
251
+ render "success" + ctx[:message].to_s + seq.inspect + params.inspect
252
+ end
253
+ end
254
+
255
+ def create
256
+ # add endpoint_options
257
+ _endpoint "show?", policy: false do |ctx, seq:, params:, **|
258
+ render "success" + ctx[:message].to_s + seq.inspect + params.inspect
259
+ end
260
+ end
261
+
262
+ # todo: test overriding endp options
263
+ # _endpoint "show?", params: {id: 1}, process_model: "it's me!" do |ctx, seq:, params:, process_model:, **|
264
+ end # DomainContextController
265
+
266
+ it "{:current_user} and {:process_model} are made available in {domain_ctx}" do
267
+ controller = DomainContextController.new
268
+ controller.process(:view, params: {}).must_equal %{successYogi / Class[:authenticate, :policy, :check]}
269
+ end
270
+
271
+ it "{:seq} is overridden, {:params} made available, in {domain_ctx}" do
272
+ controller = DomainContextController.new
273
+ # note that {seq} is not shared anymore
274
+ controller.process(:show, params: {}).must_equal %{successYogi / Class[:check]{:id=>1}}
275
+ end
276
+
277
+ it "allows passing {endpoint_options} directly" do
278
+ controller = DomainContextController.new
279
+ controller.process(:create, params: {}).must_equal [:authenticate, :policy, :protocol_failure_block]
280
+ end
281
+
282
+
283
+ # Test without DSL
284
+ class BasicController
285
+ extend Trailblazer::Endpoint::Controller
286
+
287
+ directive :options_for_block_options, Trailblazer::Endpoint::Controller.method(:options_for_block_options)
288
+
289
+ def endpoint(name, &block)
290
+ action_options = {seq: []}
291
+
292
+ Trailblazer::Endpoint::Controller.advance_endpoint_for_controller(endpoint: endpoint_for(name), block_options: self.class.options_for(:options_for_block_options, {controller: self}), config_source: self.class, **action_options)
293
+ end
294
+
295
+ def head(status)
296
+ @status = status
297
+ end
298
+ end
299
+
300
+ class RodaController < BasicController
301
+ endpoint("show?", protocol: ApplicationController::Protocol, adapter: Trailblazer::Endpoint::Adapter::Web, domain_activity: Class.new(Trailblazer::Activity::Railway) { def save(*); true; end; step :save })
302
+
303
+ def show
304
+ endpoint "show?"
305
+ @status
306
+ end
82
307
  end
83
308
 
84
309
  it "what" do
85
- endpoint "view?" do |ctx|
86
- # 200, success
87
- return
88
- end.Or() do |ctx|
89
- # Only 422
310
+ RodaController.new.show.must_equal 200
311
+ end
312
+ end
313
+
314
+ class ControllerEndpointMethodTest < Minitest::Spec
315
+ # Test {Controller::endpoint}
316
+
317
+ class Protocol < Trailblazer::Endpoint::Protocol
318
+ def policy(*); true; end
319
+ def authenticate(*); true; end
320
+ end
321
+
322
+ class BasicController
323
+ include Trailblazer::Endpoint::Controller.module(api: true, application_controller: true)
324
+
325
+ directive :options_for_block_options, Trailblazer::Endpoint::Controller.method(:options_for_block_options)
326
+
327
+ endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web
328
+
329
+ def head(status)
330
+ @status = status
331
+ end
332
+
333
+ def self.options_for_block_options(ctx, controller:, **)
334
+ {
335
+ success_block: ->(ctx, endpoint_ctx:, **) { controller.head("#{ctx[:op]}") },
336
+ failure_block: ->(ctx, status:, **) { },
337
+ protocol_failure_block: ->(ctx, status:, **) { }
338
+ }
339
+ end
340
+
341
+ directive :options_for_block_options, method(:options_for_block_options)
342
+ end
343
+
344
+ class RodaController < BasicController
345
+ class Create < Trailblazer::Activity::Railway
346
+ def save(ctx, **); ctx[:op] = self.class; end;
347
+ step :save
348
+ end
349
+ class Update < Create
350
+ end
351
+
352
+ # {Controller::endpoint}: {:domain_activity} defaults to {name} when not given
353
+ endpoint Create # class {name}s are ok
354
+ endpoint :update, domain_activity: Update # symbol {name} is ok
355
+
356
+ def show
357
+ endpoint Create
358
+ @status
359
+ end
360
+
361
+ def update
362
+ endpoint :update
363
+ @status
90
364
  end
91
365
  end
366
+
367
+
368
+ it "what" do
369
+ RodaController.new.show.must_equal %{ControllerEndpointMethodTest::RodaController::Create}
370
+ RodaController.new.update.must_equal %{ControllerEndpointMethodTest::RodaController::Update}
371
+ end
92
372
  end
373
+
@@ -261,7 +261,7 @@ class EndpointTest < Minitest::Spec
261
261
  class MyApiAdapter < Trailblazer::Endpoint::Adapter::API
262
262
  # example how to add your own step to a certain path
263
263
  # FIXME: :after doesn't work
264
- step :my_401_handler, before: :_401_status, magnetic_to: :_401, Output(:success) => Track(:_401), Output(:failure) => Track(:_401)
264
+ step :my_401_handler, before: :_401_status, magnetic_to: :_401, Output(:success) => Track(:_401)
265
265
 
266
266
  # def render_success(ctx, **)
267
267
  # ctx[:json] = %{#{ctx[:representer]}.new(#{ctx[:model]})}
@@ -10,6 +10,7 @@
10
10
  # Ignore the default SQLite database.
11
11
  /db/*.sqlite3
12
12
  /db/*.sqlite3-journal
13
+ /db/*.sqlite3-*
13
14
 
14
15
  # Ignore all logfiles and tempfiles.
15
16
  /log/*
@@ -17,5 +18,10 @@
17
18
  !/log/.keep
18
19
  !/tmp/.keep
19
20
 
20
- # Ignore Byebug command history file.
21
- .byebug_history
21
+ # Ignore pidfiles, but keep the directory.
22
+ /tmp/pids/*
23
+ !/tmp/pids/
24
+ !/tmp/pids/.keep
25
+
26
+ # Ignore master key for decrypting credentials and more.
27
+ # /config/master.key