trailblazer-endpoint 0.0.1 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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