trailblazer-endpoint 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES.md +3 -0
- data/Gemfile +16 -0
- data/README.md +11 -0
- data/Rakefile +10 -0
- data/lib/trailblazer-endpoint.rb +1 -0
- data/lib/trailblazer/endpoint.rb +133 -0
- data/lib/trailblazer/endpoint/adapter.rb +197 -0
- data/lib/trailblazer/endpoint/builder.rb +56 -0
- data/lib/trailblazer/endpoint/protocol.rb +122 -0
- data/lib/trailblazer/endpoint/rails.rb +27 -0
- data/lib/trailblazer/endpoint/version.rb +5 -0
- data/test/adapter/api_test.rb +78 -0
- data/test/adapter/representable_test.rb +7 -0
- data/test/adapter/web_test.rb +40 -0
- data/test/benchmark/skill_resolver_benchmark.rb +43 -0
- data/test/docs/controller_test.rb +92 -0
- data/test/docs/endpoint_test.rb +54 -0
- data/test/endpoint_test.rb +908 -0
- data/test/rails-app/.gitignore +21 -0
- data/test/rails-app/Gemfile +17 -0
- data/test/rails-app/Gemfile.lock +157 -0
- data/test/rails-app/README.md +24 -0
- data/test/rails-app/Rakefile +6 -0
- data/test/rails-app/app/controllers/application_controller.rb +3 -0
- data/test/rails-app/app/controllers/songs_controller.rb +26 -0
- data/test/rails-app/app/models/application_record.rb +3 -0
- data/test/rails-app/app/models/concerns/.keep +0 -0
- data/test/rails-app/config.ru +5 -0
- data/test/rails-app/config/application.rb +15 -0
- data/test/rails-app/config/boot.rb +3 -0
- data/test/rails-app/config/database.yml +25 -0
- data/test/rails-app/config/environment.rb +5 -0
- data/test/rails-app/config/environments/development.rb +54 -0
- data/test/rails-app/config/environments/production.rb +86 -0
- data/test/rails-app/config/environments/test.rb +42 -0
- data/test/rails-app/config/initializers/application_controller_renderer.rb +6 -0
- data/test/rails-app/config/initializers/backtrace_silencers.rb +7 -0
- data/test/rails-app/config/initializers/cookies_serializer.rb +5 -0
- data/test/rails-app/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/rails-app/config/initializers/inflections.rb +16 -0
- data/test/rails-app/config/initializers/mime_types.rb +4 -0
- data/test/rails-app/config/initializers/new_framework_defaults.rb +24 -0
- data/test/rails-app/config/initializers/session_store.rb +3 -0
- data/test/rails-app/config/initializers/wrap_parameters.rb +14 -0
- data/test/rails-app/config/locales/en.yml +23 -0
- data/test/rails-app/config/routes.rb +6 -0
- data/test/rails-app/config/secrets.yml +22 -0
- data/test/rails-app/db/seeds.rb +7 -0
- data/test/rails-app/log/.keep +0 -0
- data/test/rails-app/test/controllers/.keep +0 -0
- data/test/rails-app/test/controllers/songs_controller_test.rb +156 -0
- data/test/rails-app/test/fixtures/.keep +0 -0
- data/test/rails-app/test/fixtures/files/.keep +0 -0
- data/test/rails-app/test/helpers/.keep +0 -0
- data/test/rails-app/test/integration/.keep +0 -0
- data/test/rails-app/test/mailers/.keep +0 -0
- data/test/rails-app/test/models/.keep +0 -0
- data/test/rails-app/test/test_helper.rb +10 -0
- data/test/rails-app/tmp/.keep +0 -0
- data/test/rails-app/vendor/assets/javascripts/.keep +0 -0
- data/test/rails-app/vendor/assets/stylesheets/.keep +0 -0
- data/test/test_helper.rb +34 -0
- data/trailblazer-endpoint.gemspec +26 -0
- metadata +236 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
require "trailblazer/endpoint"
|
2
|
+
|
3
|
+
module Trailblazer::Endpoint::Handlers
|
4
|
+
# Generic matcher handlers for a Rails API backend.
|
5
|
+
#
|
6
|
+
# Note that the path mechanics are experimental. PLEASE LET US KNOW WHAT
|
7
|
+
# YOU NEED/HOW YOU DID IT: https://gitter.im/trailblazer/chat
|
8
|
+
class Rails
|
9
|
+
def initialize(controller, options)
|
10
|
+
@controller = controller
|
11
|
+
@path = options[:path]
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :controller
|
15
|
+
|
16
|
+
def call
|
17
|
+
->(m) do
|
18
|
+
m.not_found { |result| controller.head 404 }
|
19
|
+
m.unauthenticated { |result| controller.head 401 }
|
20
|
+
m.present { |result| controller.render json: result["representer.serializer.class"].new(result['model']), status: 200 }
|
21
|
+
m.created { |result| controller.head 201, location: "#{@path}/#{result["model"].id}" }#, result["representer.serializer.class"].new(result["model"]).to_json
|
22
|
+
m.success { |result| controller.head 200, location: "#{@path}/#{result["model"].id}" }
|
23
|
+
m.invalid { |result| controller.render json: result["representer.errors.class"].new(result['result.contract.default'].errors).to_json, status: 422 }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class AdapterAPITest < Minitest::Spec
|
4
|
+
it "Adapter::API" do
|
5
|
+
protocol = Class.new(Trailblazer::Endpoint::Protocol) do # DISCUSS: what to to with authenticate and policy?
|
6
|
+
include T.def_steps(:authenticate, :policy)
|
7
|
+
end
|
8
|
+
|
9
|
+
endpoint =
|
10
|
+
Trailblazer::Endpoint.build(
|
11
|
+
domain_activity: activity,
|
12
|
+
protocol: protocol, # do we cover all usual routes?
|
13
|
+
adapter: Trailblazer::Endpoint::Adapter::API,
|
14
|
+
scope_domain_ctx: false,
|
15
|
+
) do
|
16
|
+
|
17
|
+
|
18
|
+
{Output(:not_found) => Track(:not_found)}
|
19
|
+
end
|
20
|
+
|
21
|
+
# success
|
22
|
+
assert_route endpoint, {}, :authenticate, :policy, :model, :validate, :success, status: 200
|
23
|
+
# authentication error
|
24
|
+
assert_route endpoint, {authenticate: false}, :authenticate, :fail_fast, status: 401 # fail_fast == protocol error
|
25
|
+
# policy error
|
26
|
+
assert_route endpoint, {policy: false}, :authenticate, :policy, :fail_fast, status: 403 # fail_fast == protocol error
|
27
|
+
# (domain) not_found err
|
28
|
+
assert_route endpoint, {model: false}, :authenticate, :policy, :model, :fail_fast, status: 404 # fail_fast == protocol error
|
29
|
+
# (domain) validation err
|
30
|
+
assert_route endpoint, {validate: false}, :authenticate, :policy, :model, :validate, :failure, status: 422
|
31
|
+
end
|
32
|
+
|
33
|
+
it "Adapter::API with error_handlers" do
|
34
|
+
protocol = Class.new(Trailblazer::Endpoint::Protocol) do # DISCUSS: what to to with authenticate and policy?
|
35
|
+
include T.def_steps(:authenticate, :policy)
|
36
|
+
end
|
37
|
+
|
38
|
+
adapter = Trailblazer::Endpoint::Adapter::API
|
39
|
+
adapter = Trailblazer::Endpoint::Adapter::API.insert_error_handler_steps(adapter)
|
40
|
+
adapter.include(Trailblazer::Endpoint::Adapter::API::Errors::Handlers)
|
41
|
+
|
42
|
+
# puts Trailblazer::Developer.render(adapter)
|
43
|
+
|
44
|
+
endpoint =
|
45
|
+
Trailblazer::Endpoint.build(
|
46
|
+
domain_activity: activity,
|
47
|
+
protocol: protocol, # do we cover all usual routes?
|
48
|
+
adapter: adapter,
|
49
|
+
scope_domain_ctx: false,
|
50
|
+
|
51
|
+
) do
|
52
|
+
|
53
|
+
|
54
|
+
{Output(:not_found) => Track(:not_found)}
|
55
|
+
end
|
56
|
+
|
57
|
+
class TestErrors < Struct.new(:message)
|
58
|
+
def ==(b)
|
59
|
+
b.message == message
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# success
|
64
|
+
assert_route endpoint, ctx.merge({}), :authenticate, :policy, :model, :validate, :success, status: 200, errors: TestErrors.new(nil)
|
65
|
+
# authentication error
|
66
|
+
assert_route endpoint, ctx.merge({authenticate: false}), :authenticate, :fail_fast, status: 401, errors: TestErrors.new("Authentication credentials were not provided or are invalid.") # fail_fast == protocol error
|
67
|
+
# policy error
|
68
|
+
assert_route endpoint, ctx.merge({policy: false}), :authenticate, :policy, :fail_fast, status: 403, errors: TestErrors.new("Action not allowed due to a policy setting.") # fail_fast == protocol error
|
69
|
+
# (domain) not_found err
|
70
|
+
assert_route endpoint, ctx.merge({model: false}), :authenticate, :policy, :model, :fail_fast, status: 404, errors: TestErrors.new(nil) # fail_fast == protocol error
|
71
|
+
# (domain) validation err
|
72
|
+
assert_route endpoint, ctx.merge({validate: false}), :authenticate, :policy, :model, :validate, :failure, status: 422, errors: TestErrors.new("The submitted data is invalid.")
|
73
|
+
end
|
74
|
+
|
75
|
+
def ctx
|
76
|
+
{errors: TestErrors.new}
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class AdapterWebTest < Minitest::Spec
|
4
|
+
it "AdapterWeb / perfect 2.1 OP scenario (with {not_found} terminus" do
|
5
|
+
activity = Class.new(Trailblazer::Activity::Railway) do
|
6
|
+
step :model, Output(:failure) => End(:not_found)
|
7
|
+
step :validate
|
8
|
+
|
9
|
+
include T.def_steps(:validate, :model)
|
10
|
+
end
|
11
|
+
|
12
|
+
protocol = Class.new(Trailblazer::Endpoint::Protocol) do # DISCUSS: what to to with authenticate and policy?
|
13
|
+
include T.def_steps(:authenticate, :policy)
|
14
|
+
end
|
15
|
+
|
16
|
+
endpoint =
|
17
|
+
Trailblazer::Endpoint.build(
|
18
|
+
domain_activity: activity,
|
19
|
+
protocol: protocol, # do we cover all usual routes?
|
20
|
+
adapter: Trailblazer::Endpoint::Adapter::Web,
|
21
|
+
scope_domain_ctx: false,
|
22
|
+
) do
|
23
|
+
|
24
|
+
|
25
|
+
{Output(:not_found) => Track(:not_found)}
|
26
|
+
end
|
27
|
+
|
28
|
+
# success
|
29
|
+
assert_route(endpoint, {}, :authenticate, :policy, :model, :validate, :success)
|
30
|
+
# authentication error
|
31
|
+
assert_route endpoint, {authenticate: false}, :authenticate, :fail_fast # fail_fast == protocol error
|
32
|
+
# policy error
|
33
|
+
assert_route endpoint, {policy: false}, :authenticate, :policy, :fail_fast # fail_fast == protocol error
|
34
|
+
# (domain) not_found err
|
35
|
+
assert_route endpoint, {model: false}, :authenticate, :policy, :model, :fail_fast # fail_fast == protocol error
|
36
|
+
# (domain) validation err
|
37
|
+
assert_route endpoint, {validate: false}, :authenticate, :policy, :model, :validate, :failure
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "trailblazer/operation"
|
2
|
+
require "benchmark/ips"
|
3
|
+
|
4
|
+
initialize_hash = {}
|
5
|
+
10.times do |i|
|
6
|
+
initialize_hash["bla_#{i}"] = i
|
7
|
+
end
|
8
|
+
|
9
|
+
normal_container = {}
|
10
|
+
50.times do |i|
|
11
|
+
normal_container["xbla_#{i}"] = i
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
Benchmark.ips do |x|
|
16
|
+
x.report(:merge) {
|
17
|
+
attrs = normal_container.merge(initialize_hash)
|
18
|
+
10.times do |i|
|
19
|
+
attrs["bla_8"]
|
20
|
+
end
|
21
|
+
10.times do |i|
|
22
|
+
attrs["xbla_1"]
|
23
|
+
end
|
24
|
+
}
|
25
|
+
|
26
|
+
x.report(:resolver) {
|
27
|
+
attrs = Trailblazer::Skill::Resolver.new(initialize_hash, normal_container)
|
28
|
+
|
29
|
+
10.times do |i|
|
30
|
+
attrs["bla_8"]
|
31
|
+
end
|
32
|
+
10.times do |i|
|
33
|
+
attrs["xbla_1"]
|
34
|
+
end
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Warming up --------------------------------------
|
39
|
+
# merge 3.974k i/100ms
|
40
|
+
# resolver 6.593k i/100ms
|
41
|
+
# Calculating -------------------------------------
|
42
|
+
# merge 39.678k (± 9.1%) i/s - 198.700k in 5.056653s
|
43
|
+
# resolver 68.928k (± 6.4%) i/s - 342.836k in 5.001610s
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class DocsControllerTest < Minitest::Spec
|
4
|
+
it "what" do
|
5
|
+
endpoint "view?" do |ctx|
|
6
|
+
# 200, success
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
# 422
|
11
|
+
# but also 404 etc
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
class Controller
|
16
|
+
def initialize(endpoint, activity)
|
17
|
+
@___activity = activity
|
18
|
+
@endpoint = endpoint
|
19
|
+
@seq = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def view(params)
|
23
|
+
endpoint "view?", params do |ctx|
|
24
|
+
@seq << :success
|
25
|
+
# 200, success
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
@seq << :failure
|
30
|
+
# 422
|
31
|
+
# but also 404 etc
|
32
|
+
end
|
33
|
+
|
34
|
+
def call(action, **params)
|
35
|
+
send(action, **params)
|
36
|
+
@seq
|
37
|
+
end
|
38
|
+
|
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 },
|
45
|
+
|
46
|
+
collaboration: @___activity,
|
47
|
+
domain_ctx: {},
|
48
|
+
success_id: "fixme",
|
49
|
+
flow_options: {},
|
50
|
+
|
51
|
+
**params,
|
52
|
+
|
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
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "injected {return} interrupts the controller action" do
|
60
|
+
protocol = Class.new(Trailblazer::Endpoint::Protocol)do
|
61
|
+
include T.def_steps(:authenticate, :policy)
|
62
|
+
end
|
63
|
+
|
64
|
+
endpoint =
|
65
|
+
Trailblazer::Endpoint.build(
|
66
|
+
domain_activity: activity,
|
67
|
+
protocol: protocol,
|
68
|
+
adapter: Trailblazer::Endpoint::Adapter::Web,
|
69
|
+
scope_domain_ctx: false,
|
70
|
+
|
71
|
+
) do
|
72
|
+
{Output(:not_found) => Track(:not_found)}
|
73
|
+
end
|
74
|
+
|
75
|
+
# 200
|
76
|
+
seq = Controller.new(endpoint, activity).call(:view)
|
77
|
+
seq.must_equal [:authenticate, :policy, :model, :validate, :success] # the {return} works.
|
78
|
+
|
79
|
+
# 401
|
80
|
+
seq = Controller.new(endpoint, activity).call(:view, authenticate: false)
|
81
|
+
seq.must_equal [:authenticate, :policy, :model, :validate, :success]
|
82
|
+
end
|
83
|
+
|
84
|
+
it "what" do
|
85
|
+
endpoint "view?" do |ctx|
|
86
|
+
# 200, success
|
87
|
+
return
|
88
|
+
end.Or() do |ctx|
|
89
|
+
# Only 422
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class DocsEndpointTest < Minitest::Spec
|
4
|
+
|
5
|
+
# Show how handlers can be put onto a specific path, e.g. {handle_not_authenticated}.
|
6
|
+
module A
|
7
|
+
module Pro
|
8
|
+
module Endpoint
|
9
|
+
class Protocol < Trailblazer::Endpoint::Protocol
|
10
|
+
|
11
|
+
# put {handle_not_authorized} on the respective protocol path.
|
12
|
+
|
13
|
+
# we currently have to squeeze those handlers using the {:before} option, otherwise the path's End is placed before the handler in the sequence. A grouping feature could help.
|
14
|
+
|
15
|
+
step :handle_not_authenticated, magnetic_to: :not_authenticated, Output(:success) => Track(:not_authenticated), Output(:failure) => Track(:not_authenticated)
|
16
|
+
step :handle_not_authorized, magnetic_to: :not_authorized, Output(:success) => Track(:not_authorized), Output(:failure) => Track(:not_authorized)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "allows to insert a (handle_not_authenticated} path step" do
|
23
|
+
Trailblazer::Developer.render(A::Pro::Endpoint::Protocol).must_equal %{
|
24
|
+
#<Start/:default>
|
25
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=authenticate>
|
26
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=authenticate>
|
27
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=handle_not_authenticated>
|
28
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=policy>
|
29
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=policy>
|
30
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=handle_not_authorized>
|
31
|
+
{Trailblazer::Activity::Right} => Trailblazer::Endpoint::Protocol::Noop
|
32
|
+
Trailblazer::Endpoint::Protocol::Noop
|
33
|
+
{#<Trailblazer::Activity::End semantic=:failure>} => #<End/:failure>
|
34
|
+
{#<Trailblazer::Activity::End semantic=:success>} => #<End/:success>
|
35
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=handle_not_authenticated>
|
36
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Endpoint::Protocol::Failure/:not_authenticated>
|
37
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Endpoint::Protocol::Failure/:not_authenticated>
|
38
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=handle_not_authorized>
|
39
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Endpoint::Protocol::Failure/:not_authorized>
|
40
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Endpoint::Protocol::Failure/:not_authorized>
|
41
|
+
#<End/:success>
|
42
|
+
|
43
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:invalid_data>
|
44
|
+
|
45
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:not_found>
|
46
|
+
|
47
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:not_authorized>
|
48
|
+
|
49
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:not_authenticated>
|
50
|
+
|
51
|
+
#<End/:failure>
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,908 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module Trailblazer
|
4
|
+
class Endpoint_ < Trailblazer::Activity::Railway
|
5
|
+
|
6
|
+
|
7
|
+
class PolicyChain < Trailblazer::Activity::Railway
|
8
|
+
step :is_root?, Output(:success) => End(:success) # bypass policy chain
|
9
|
+
# step :a?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# TODO: document :track_color
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
class Basic_EndpointTest < Minitest::Spec
|
19
|
+
it "{Protocol} is very simple and has no handlers" do
|
20
|
+
protocol = Class.new(Trailblazer::Endpoint::Protocol)
|
21
|
+
|
22
|
+
Trailblazer::Developer.render(protocol).must_equal %{
|
23
|
+
#<Start/:default>
|
24
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=authenticate>
|
25
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=authenticate>
|
26
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Endpoint::Protocol::Failure/:not_authenticated>
|
27
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=policy>
|
28
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=policy>
|
29
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Endpoint::Protocol::Failure/:not_authorized>
|
30
|
+
{Trailblazer::Activity::Right} => Trailblazer::Endpoint::Protocol::Noop
|
31
|
+
Trailblazer::Endpoint::Protocol::Noop
|
32
|
+
{#<Trailblazer::Activity::End semantic=:failure>} => #<End/:failure>
|
33
|
+
{#<Trailblazer::Activity::End semantic=:success>} => #<End/:success>
|
34
|
+
#<End/:success>
|
35
|
+
|
36
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:invalid_data>
|
37
|
+
|
38
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:not_found>
|
39
|
+
|
40
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:not_authorized>
|
41
|
+
|
42
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:not_authenticated>
|
43
|
+
|
44
|
+
#<End/:failure>
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
it "{Protocol::Standard} has handlers for 401, 403, 422" do
|
49
|
+
protocol = Class.new(Trailblazer::Endpoint::Protocol::Standard)
|
50
|
+
|
51
|
+
Trailblazer::Developer.render(protocol).must_equal %{
|
52
|
+
#<Start/:default>
|
53
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=authenticate>
|
54
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=authenticate>
|
55
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=handle_not_authenticated>
|
56
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=policy>
|
57
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=policy>
|
58
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=handle_not_authorized>
|
59
|
+
{Trailblazer::Activity::Right} => Trailblazer::Endpoint::Protocol::Noop
|
60
|
+
Trailblazer::Endpoint::Protocol::Noop
|
61
|
+
{#<Trailblazer::Activity::End semantic=:failure>} => #<End/:failure>
|
62
|
+
{#<Trailblazer::Activity::End semantic=:success>} => #<End/:success>
|
63
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=handle_not_authenticated>
|
64
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Endpoint::Protocol::Failure/:not_authenticated>
|
65
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Endpoint::Protocol::Failure/:not_authenticated>
|
66
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=handle_not_authorized>
|
67
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Endpoint::Protocol::Failure/:not_authorized>
|
68
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Endpoint::Protocol::Failure/:not_authorized>
|
69
|
+
#<End/:success>
|
70
|
+
|
71
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:invalid_data>
|
72
|
+
|
73
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:not_found>
|
74
|
+
|
75
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:not_authorized>
|
76
|
+
|
77
|
+
#<Trailblazer::Endpoint::Protocol::Failure/:not_authenticated>
|
78
|
+
|
79
|
+
#<End/:failure>
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
it "{Adapter::Web} has status setters for 401, 403, 422" do
|
84
|
+
protocol = Class.new(Trailblazer::Endpoint::Adapter::Web)
|
85
|
+
|
86
|
+
Trailblazer::Developer.render(protocol).must_equal %{
|
87
|
+
#<Start/:default>
|
88
|
+
{Trailblazer::Activity::Right} => Trailblazer::Endpoint::Protocol
|
89
|
+
Trailblazer::Endpoint::Protocol
|
90
|
+
{#<Trailblazer::Activity::End semantic=:failure>} => #<End/:failure>
|
91
|
+
{#<Trailblazer::Activity::End semantic=:success>} => #<End/:success>
|
92
|
+
{#<Trailblazer::Endpoint::Protocol::Failure semantic=:not_authorized>} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=_403_status>
|
93
|
+
{#<Trailblazer::Endpoint::Protocol::Failure semantic=:not_found>} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=_404_status>
|
94
|
+
{#<Trailblazer::Endpoint::Protocol::Failure semantic=:not_authenticated>} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=_401_status>
|
95
|
+
{#<Trailblazer::Endpoint::Protocol::Failure semantic=:invalid_data>} => #<End/:failure>
|
96
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=_403_status>
|
97
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=protocol_failure>
|
98
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=_404_status>
|
99
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=protocol_failure>
|
100
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=_401_status>
|
101
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=protocol_failure>
|
102
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=protocol_failure>
|
103
|
+
{Trailblazer::Activity::Left} => #<End/:fail_fast>
|
104
|
+
{Trailblazer::Activity::Right} => #<End/:fail_fast>
|
105
|
+
#<End/:success>
|
106
|
+
|
107
|
+
#<End/:pass_fast>
|
108
|
+
|
109
|
+
#<End/:fail_fast>
|
110
|
+
|
111
|
+
#<End/:failure>
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
it "{Adapter::API} has status setters for 401, 403, 422, error handlers (message)" do
|
116
|
+
protocol = Class.new(Trailblazer::Endpoint::Adapter::API)
|
117
|
+
|
118
|
+
Trailblazer::Developer.render(protocol).must_equal %{
|
119
|
+
#<Start/:default>
|
120
|
+
{Trailblazer::Activity::Right} => Trailblazer::Endpoint::Protocol
|
121
|
+
Trailblazer::Endpoint::Protocol
|
122
|
+
{#<Trailblazer::Activity::End semantic=:failure>} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=failure_render_config>
|
123
|
+
{#<Trailblazer::Activity::End semantic=:success>} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=success_render_config>
|
124
|
+
{#<Trailblazer::Endpoint::Protocol::Failure semantic=:not_authorized>} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=_403_status>
|
125
|
+
{#<Trailblazer::Endpoint::Protocol::Failure semantic=:not_found>} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=_404_status>
|
126
|
+
{#<Trailblazer::Endpoint::Protocol::Failure semantic=:not_authenticated>} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=_401_status>
|
127
|
+
{#<Trailblazer::Endpoint::Protocol::Failure semantic=:invalid_data>} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=failure_render_config>
|
128
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=_403_status>
|
129
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=render_protocol_failure_config>
|
130
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=_404_status>
|
131
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=protocol_failure>
|
132
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=_401_status>
|
133
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=_401_error_message>
|
134
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=_401_error_message>
|
135
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=render_protocol_failure_config>
|
136
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=failure_render_config>
|
137
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=failure_config_status>
|
138
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=failure_config_status>
|
139
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=failure_config_status>
|
140
|
+
{Trailblazer::Activity::Left} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=render_failure>
|
141
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=render_failure>
|
142
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=render_failure>
|
143
|
+
{Trailblazer::Activity::Left} => #<End/:failure>
|
144
|
+
{Trailblazer::Activity::Right} => #<End/:failure>
|
145
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=success_render_config>
|
146
|
+
{Trailblazer::Activity::Left} => #<End/:failure>
|
147
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=success_render_status>
|
148
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=success_render_status>
|
149
|
+
{Trailblazer::Activity::Left} => #<End/:failure>
|
150
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=render_success>
|
151
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=render_success>
|
152
|
+
{Trailblazer::Activity::Left} => #<End/:failure>
|
153
|
+
{Trailblazer::Activity::Right} => #<End/:success>
|
154
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=render_protocol_failure_config>
|
155
|
+
{Trailblazer::Activity::Left} => #<End/:failure>
|
156
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=render_protocol_failure>
|
157
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=render_protocol_failure>
|
158
|
+
{Trailblazer::Activity::Right} => #<Trailblazer::Activity::TaskBuilder::Task user_proc=protocol_failure>
|
159
|
+
#<Trailblazer::Activity::TaskBuilder::Task user_proc=protocol_failure>
|
160
|
+
{Trailblazer::Activity::Right} => #<End/:fail_fast>
|
161
|
+
#<End/:success>
|
162
|
+
|
163
|
+
#<End/:pass_fast>
|
164
|
+
|
165
|
+
#<End/:fail_fast>
|
166
|
+
|
167
|
+
#<End/:failure>
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
class EndpointTest < Minitest::Spec
|
174
|
+
# policies
|
175
|
+
# policy.success?
|
176
|
+
# invoke
|
177
|
+
# Workflow::Advance [or simple OP]
|
178
|
+
# success? [standardized "result" object that holds end signal and ctx]
|
179
|
+
|
180
|
+
# controller [this code must be executed in the controller instance, but should be "Rails independent"]
|
181
|
+
# "injectable" policy (maybe also on controller level, configurable)
|
182
|
+
|
183
|
+
# if success yield
|
184
|
+
# Or.()
|
185
|
+
|
186
|
+
|
187
|
+
# test with authenticated user
|
188
|
+
# without user but for a "free" action
|
189
|
+
|
190
|
+
module Model
|
191
|
+
def model(ctx, seq:, model:true, **)
|
192
|
+
ctx[:model] = Struct.new(:name).new("Yo") if model
|
193
|
+
seq << :model
|
194
|
+
model
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
# Example OP with three termini
|
200
|
+
class Create < Trailblazer::Activity::Railway
|
201
|
+
include T.def_steps(:model, :validate, :save, :cc_check)
|
202
|
+
|
203
|
+
step :model, Output(:failure) => End(:not_found)
|
204
|
+
step :cc_check, Output(:failure) => End(:cc_invalid)
|
205
|
+
step :validate, Output(:failure) => End(:my_validation_error)
|
206
|
+
step :save
|
207
|
+
|
208
|
+
include Model
|
209
|
+
end
|
210
|
+
|
211
|
+
# Represents a classic FastTrack OP without additional ends.
|
212
|
+
# Implicit termini:
|
213
|
+
# model => not_found
|
214
|
+
# cc_check => cc_invalid
|
215
|
+
# validate => invalid_data
|
216
|
+
class LegacyCreate < Trailblazer::Activity::FastTrack
|
217
|
+
include T.def_steps(:my_policy, :model, :validate, :save, :cc_check)
|
218
|
+
|
219
|
+
step :my_policy
|
220
|
+
step :model
|
221
|
+
step :cc_check, fail_fast: true
|
222
|
+
step :validate
|
223
|
+
step :save
|
224
|
+
include Model
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
# we want to define API and Protocol somewhere application-wide in an explicit file.
|
229
|
+
# the domain OP/wiring we want via the endpoint builder.
|
230
|
+
|
231
|
+
module MyTest
|
232
|
+
# This implements the actual authentication, policies, etc.
|
233
|
+
class Protocol < Trailblazer::Endpoint::Protocol
|
234
|
+
# include EndpointTest::T.def_steps(:authenticate, :handle_not_authenticated, :policy, :handle_not_authorized, :handle_not_found)
|
235
|
+
|
236
|
+
[:authenticate, :handle_not_authenticated, :policy, :handle_not_authorized, :handle_not_found].each do |name|
|
237
|
+
define_method(name) do |ctx, **|
|
238
|
+
ctx[:domain_ctx][:seq] << name
|
239
|
+
return false if ctx[name] === false
|
240
|
+
true
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
# TODO: how can we make this better overridable in the endpoint generator?
|
246
|
+
def success?(ctx, domain_activity_return_signal:, domain_ctx:, **)
|
247
|
+
|
248
|
+
# FIXME: stupid test to see if we can read {:domain_activity_return_signal}.
|
249
|
+
ctx[:_domain_activity_return_signal] = domain_activity_return_signal
|
250
|
+
|
251
|
+
|
252
|
+
return Trailblazer::Endpoint::Protocol::Bridge::NotFound if domain_ctx[:model] === false
|
253
|
+
return Trailblazer::Endpoint::Protocol::Bridge::NotAuthorized if domain_ctx[:my_policy] === false
|
254
|
+
# for all other cases, the return value doesn't matter in {fail}.
|
255
|
+
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
class MyApiAdapter < Trailblazer::Endpoint::Adapter::API
|
262
|
+
# example how to add your own step to a certain path
|
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)
|
265
|
+
|
266
|
+
# def render_success(ctx, **)
|
267
|
+
# ctx[:json] = %{#{ctx[:representer]}.new(#{ctx[:model]})}
|
268
|
+
# end
|
269
|
+
|
270
|
+
def failure_config_status(ctx, **)
|
271
|
+
# DISCUSS: this is a bit like "success?" or a matcher.
|
272
|
+
if ctx[:domain_ctx][:validate] === false
|
273
|
+
ctx[:status] = 422
|
274
|
+
else
|
275
|
+
ctx[:status] = 200 # DISCUSS: this is the usual return code for application/domain errors, I guess?
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# how/where would we configure each endpoint? (per action)
|
280
|
+
# class Endpoint
|
281
|
+
# representer ...
|
282
|
+
# message ...
|
283
|
+
|
284
|
+
def my_401_handler(ctx, domain_ctx:, errors:, **)
|
285
|
+
errors.message = "No token"
|
286
|
+
|
287
|
+
domain_ctx[:seq] << :my_401_handler
|
288
|
+
end
|
289
|
+
end # MyApiAdapter
|
290
|
+
|
291
|
+
api_create_endpoint =
|
292
|
+
Trailblazer::Endpoint.build(
|
293
|
+
adapter: MyApiAdapter,
|
294
|
+
protocol: Class.new(MyTest::Protocol) do # FIXME: why do we have to open a new class to inject the handler method?
|
295
|
+
def handle_invalid_data(ctx, errors:, **) # FIXME: should this be part of the library?
|
296
|
+
errors.message = "The submitted data is invalid."
|
297
|
+
# DISCUSS: here, we could "translate" the {contract.errors}.
|
298
|
+
end
|
299
|
+
|
300
|
+
def handle_not_authorized(ctx, errors:, **)
|
301
|
+
errors.message = "Action not allowed due to a policy."
|
302
|
+
end
|
303
|
+
|
304
|
+
step :handle_not_authorized, magnetic_to: :not_authorized, Output(:success) => Track(:not_authorized), Output(:failure) => Track(:not_authorized), before: "End.not_authorized"
|
305
|
+
end,
|
306
|
+
domain_activity: Create,
|
307
|
+
) do
|
308
|
+
### PROTOCOL ###
|
309
|
+
|
310
|
+
step :handle_invalid_data, magnetic_to: :invalid_data, Output(:success) => Track(:invalid_data)
|
311
|
+
|
312
|
+
# these are arguments for the Protocol.domain_activity
|
313
|
+
{
|
314
|
+
# wire a non-standardized application error to its semantical pendant.
|
315
|
+
Output(:my_validation_error) => Track(:invalid_data), # non-protocol, "application" output
|
316
|
+
# Output(:not_found) => Track(:not_found),
|
317
|
+
|
318
|
+
# wire an unknown end to failure.
|
319
|
+
Output(:cc_invalid) => Track(:failure), # application error.
|
320
|
+
|
321
|
+
Output(:not_found) => _Path(semantic: :not_found) do # _Path will use {End(:not_found)} and thus reuse the terminus already created in Protocol.
|
322
|
+
step :handle_not_found # FIXME: don't require steps in path!
|
323
|
+
end
|
324
|
+
}
|
325
|
+
end
|
326
|
+
|
327
|
+
api_legacy_create_endpoint =
|
328
|
+
Trailblazer::Endpoint.build(
|
329
|
+
# DISCUSS: how do we implement a 201 route?
|
330
|
+
adapter: Class.new(MyApiAdapter) { def success_render_status(ctx, **)
|
331
|
+
ctx[:status] = 201
|
332
|
+
end },
|
333
|
+
protocol: Trailblazer::Endpoint::Protocol::Bridge.insert(MyTest::Protocol).class_eval do
|
334
|
+
def handle_not_authorized(ctx, errors:, **)
|
335
|
+
errors.message = "Action not allowed due to a policy."
|
336
|
+
end
|
337
|
+
|
338
|
+
step :handle_not_authorized, magnetic_to: :not_authorized, Output(:success) => Track(:not_authorized), Output(:failure) => Track(:not_authorized), before: "End.not_authorized"
|
339
|
+
|
340
|
+
self
|
341
|
+
end,
|
342
|
+
domain_activity: LegacyCreate,
|
343
|
+
) do
|
344
|
+
|
345
|
+
|
346
|
+
# Implicit termini:
|
347
|
+
# model => not_found
|
348
|
+
# cc_check => cc_invalid
|
349
|
+
# validate => invalid_data
|
350
|
+
|
351
|
+
### PROTOCOL ###
|
352
|
+
# these are arguments for the Protocol.domain_activity
|
353
|
+
{
|
354
|
+
Output(:fail_fast) => Track(:failure),
|
355
|
+
# TODO: pass_fast test
|
356
|
+
# TODO: do we want to wire those ends to an ongoing "binary" protocol?
|
357
|
+
|
358
|
+
# wire a non-standardized application error to its semantical pendant.
|
359
|
+
# Output(:my_validation_error) => Track(:invalid_data), # non-protocol, "application" output
|
360
|
+
# Output(:not_found) => Track(:not_found),
|
361
|
+
|
362
|
+
# wire an unknown end to failure.
|
363
|
+
# Output(:cc_invalid) => Track(:failure), # application error.
|
364
|
+
|
365
|
+
# Output(:not_found) => _Path(semantic: :not_found) do # _Path will use {End(:not_found)} and thus reuse the terminus already created in Protocol.
|
366
|
+
# step :handle_not_found # FIXME: don't require steps in path!
|
367
|
+
# end
|
368
|
+
}
|
369
|
+
end
|
370
|
+
|
371
|
+
###############3 TODO #######################
|
372
|
+
# test to wire 411 to another track (existing, known, automatically wired)
|
373
|
+
# test wiring an unknown terminus like "cc_not_accepted" to "failure"
|
374
|
+
|
375
|
+
|
376
|
+
# TODO: should we also add a 411 route per default?
|
377
|
+
# how do implement #success? ? in Protocol for sure
|
378
|
+
|
379
|
+
|
380
|
+
# Here we test overriding an entire "endpoint", we want to replace {authenticate} and remove {policy} and the actual {activity}.
|
381
|
+
class Gemauth < api_create_endpoint
|
382
|
+
step Subprocess(
|
383
|
+
MyTest::Protocol,
|
384
|
+
patch: {[] => ->(*) {
|
385
|
+
step nil, delete: :policy
|
386
|
+
step nil, delete: :domain_activity
|
387
|
+
step :gemserver_authenticate, replace: :authenticate, id: :authenticate, inherit: true
|
388
|
+
|
389
|
+
def gemserver_authenticate(ctx, domain_ctx:, gemserver_authenticate:true, **)
|
390
|
+
return false if ctx[:gemserver_authenticate] === false
|
391
|
+
|
392
|
+
domain_ctx[:seq] << :gemserver_authenticate
|
393
|
+
domain_ctx[:model] = Struct.new(:name).new("Gemserver says yes.")
|
394
|
+
end
|
395
|
+
}
|
396
|
+
}), replace: :protocol, inherit: true, id: :protocol
|
397
|
+
end
|
398
|
+
|
399
|
+
|
400
|
+
# step Invoke(), Output(:failure) => Track(:render_fail), Output(:my_validation_error) => ...
|
401
|
+
|
402
|
+
# Invoke(Create, )
|
403
|
+
|
404
|
+
|
405
|
+
# for login form, etc
|
406
|
+
# endpoint, skip: [:authenticate]
|
407
|
+
|
408
|
+
# workflow always terminates on wait events/termini => somewhere, we need to interpret that
|
409
|
+
# OP ends on terminus
|
410
|
+
|
411
|
+
class Errors < Struct.new(:message, :errors) # FIXME: extract
|
412
|
+
end
|
413
|
+
require "json"
|
414
|
+
class ErrorsRepresenter < Struct.new(:model) # DISCUSS: use Representable?
|
415
|
+
def to_json
|
416
|
+
JSON.generate(errors: model.errors, message: model.message)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
class DiagramRepresenter < ErrorsRepresenter
|
421
|
+
def to_json
|
422
|
+
JSON.generate({name: model.name})
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
def app_options
|
427
|
+
app_options = {
|
428
|
+
error_representer: ErrorsRepresenter,
|
429
|
+
representer: DiagramRepresenter,
|
430
|
+
errors: Errors.new,
|
431
|
+
}
|
432
|
+
end
|
433
|
+
|
434
|
+
# The idea here is to bridge a FastTrack op (without standardized ends) to the Protocol termini
|
435
|
+
it "LegacyCreate" do
|
436
|
+
# cc_check ==> FailFast
|
437
|
+
ctx = {seq: [], cc_check: false}
|
438
|
+
ctx = {domain_ctx: ctx, **app_options}
|
439
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(api_legacy_create_endpoint, [ctx, {}], failure_block: _rails_failure_block)
|
440
|
+
|
441
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:failure>} # we rewire {domain.fail_fast} to {protocol.failure}
|
442
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:authenticate, :policy, :my_policy, :model, :cc_check]}
|
443
|
+
|
444
|
+
|
445
|
+
|
446
|
+
# 1.c **404** (NO RENDERING OF BODY!!!)
|
447
|
+
ctx = {seq: [], model: false}
|
448
|
+
ctx = {domain_ctx: ctx, **app_options}
|
449
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(api_legacy_create_endpoint, [ctx, {}], failure_block: _rails_failure_block)
|
450
|
+
|
451
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:fail_fast>}
|
452
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:authenticate, :policy, :my_policy, :model]}
|
453
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>nil, :status=>404}, :failure=>true, :seq=>\"[:authenticate, :policy, :my_policy, :model]\", :signal=>\"#<Trailblazer::Activity::End semantic=:fail_fast>\"}}
|
454
|
+
|
455
|
+
# 2. **201** because the model is new.
|
456
|
+
ctx = {seq: []}
|
457
|
+
ctx = {domain_ctx: ctx, **app_options}
|
458
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(api_legacy_create_endpoint, [ctx, {}], success_block: _rails_success_block)
|
459
|
+
|
460
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:success>}
|
461
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:authenticate, :policy, :my_policy, :model, :cc_check, :validate, :save]}
|
462
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>\"{\\\"name\\\":\\\"Yo\\\"}\", :status=>201}, :failure=>nil, :seq=>\"[:authenticate, :policy, :my_policy, :model, :cc_check, :validate, :save]\", :signal=>\"#<Trailblazer::Activity::End semantic=:success>\"}}
|
463
|
+
|
464
|
+
# **403** because my_policy fails.
|
465
|
+
ctx = {seq: [], my_policy: false}
|
466
|
+
ctx = {domain_ctx: ctx, **app_options}
|
467
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(api_legacy_create_endpoint, [ctx, {}], failure_block: _rails_failure_block)
|
468
|
+
|
469
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:fail_fast>}
|
470
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:authenticate, :policy, :my_policy]}
|
471
|
+
# this calls Rails default failure block
|
472
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>\"{\\\"errors\\\":null,\\\"message\\\":\\\"Action not allowed due to a policy.\\\"}\", :status=>403}, :failure=>true, :seq=>\"[:authenticate, :policy, :my_policy]\", :signal=>\"#<Trailblazer::Activity::End semantic=:fail_fast>\"}}
|
473
|
+
# we can read {:domain_activity_return_signal} (currently only set for fails)
|
474
|
+
ctx[:_domain_activity_return_signal].inspect.must_equal %{#<Trailblazer::Activity::End semantic=:failure>}
|
475
|
+
end
|
476
|
+
# TODO: AUTOWIRE RAILWAY/FASTTRACKS
|
477
|
+
|
478
|
+
######### API #########
|
479
|
+
# FIXME: fake the controller
|
480
|
+
let(:_rails_success_block) do ->(ctx, endpoint_ctx:, seq:, signal:, model:, **) {
|
481
|
+
@failure = nil
|
482
|
+
render json: endpoint_ctx[:json], status: endpoint_ctx[:status]; @seq = seq.inspect; @signal = signal.inspect } end
|
483
|
+
let(:_rails_failure_block) do ->(ctx, endpoint_ctx:, seq:, signal:, errors:, **) {
|
484
|
+
@failure = true
|
485
|
+
render json: endpoint_ctx[:json], status: endpoint_ctx[:status]; @seq = seq.inspect; @signal = signal.inspect } end # nil-JSON with 404,
|
486
|
+
|
487
|
+
def render(options)
|
488
|
+
@render_options = options
|
489
|
+
end
|
490
|
+
def to_h
|
491
|
+
{render_options: @render_options, failure: @failure, seq: @seq, signal: @signal}
|
492
|
+
end
|
493
|
+
|
494
|
+
it do
|
495
|
+
puts "API"
|
496
|
+
puts Trailblazer::Developer.render(MyApiAdapter)
|
497
|
+
# puts
|
498
|
+
# puts Trailblazer::Developer.render(Adapter::API::Gemauth)
|
499
|
+
# exit
|
500
|
+
|
501
|
+
|
502
|
+
# 1. ops indicate outcome via termini
|
503
|
+
# 2. you can still "match"
|
504
|
+
# 3. layers
|
505
|
+
# DSL .Or on top
|
506
|
+
# use TRB's wiring API to extend instead of clumsy overriding/super. Example: failure-status
|
507
|
+
|
508
|
+
=begin
|
509
|
+
success
|
510
|
+
representer: Api::V1::Memo::Representer
|
511
|
+
status: 200
|
512
|
+
failure
|
513
|
+
representer: Api::V1::Representer::Error
|
514
|
+
status: 422
|
515
|
+
not_found
|
516
|
+
representer:
|
517
|
+
status: 404
|
518
|
+
|
519
|
+
success_representer: Api::V1::Memo::Representer,
|
520
|
+
failure_representer: Api::V1::Representer::Error,
|
521
|
+
policy: MyPolicy,
|
522
|
+
=end
|
523
|
+
|
524
|
+
|
525
|
+
# api_create_endpoint.instance_exec do
|
526
|
+
|
527
|
+
# step(Subprocess(MyTest::Protocol), patch: {[:protocol] => ->(*) { step :success?, delete: :success? }}, replace: :protocol, inherit: true, id: :protocol) end
|
528
|
+
|
529
|
+
# 1. 401 authenticate err
|
530
|
+
# RENDER an error document
|
531
|
+
ctx = {seq: []}
|
532
|
+
ctx = {domain_ctx: ctx, authenticate: false, **app_options}
|
533
|
+
# signal, (ctx, _ ) = Trailblazer::Developer.wtf?(Adapter::API, [ctx, {}])
|
534
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(api_create_endpoint, [ctx, {}], failure_block: _rails_failure_block)
|
535
|
+
|
536
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:fail_fast>}
|
537
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:authenticate, :my_401_handler]}
|
538
|
+
# DISCUSS: where to add things like headers?
|
539
|
+
# this calls Rails default failure block
|
540
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>\"{\\\"errors\\\":null,\\\"message\\\":\\\"No token\\\"}\", :status=>401}, :failure=>true, :seq=>\"[:authenticate, :my_401_handler]\", :signal=>\"#<Trailblazer::Activity::End semantic=:fail_fast>\"}}
|
541
|
+
# raise ctx.inspect
|
542
|
+
|
543
|
+
# 1.c 404 (NO RENDERING OF BODY!!!)
|
544
|
+
ctx = {seq: [], model: false}
|
545
|
+
ctx = {domain_ctx: ctx, **app_options}
|
546
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(api_create_endpoint, [ctx, {}], failure_block: _rails_failure_block)
|
547
|
+
|
548
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:fail_fast>}
|
549
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:authenticate, :policy, :model, :handle_not_found]}
|
550
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>nil, :status=>404}, :failure=>true, :seq=>\"[:authenticate, :policy, :model, :handle_not_found]\", :signal=>\"#<Trailblazer::Activity::End semantic=:fail_fast>\"}}
|
551
|
+
|
552
|
+
# `-- #<Class:0x0000000001ff5d88>
|
553
|
+
# |-- Start.default
|
554
|
+
# |-- protocol
|
555
|
+
# | |-- Start.default
|
556
|
+
# | |-- authenticate
|
557
|
+
# | |-- policy
|
558
|
+
# | |-- domain_activity
|
559
|
+
# | | |-- Start.default
|
560
|
+
# | | |-- model
|
561
|
+
# | | `-- End.not_found
|
562
|
+
# | |-- handle_not_found this is added via the block, in the PROTOCOL wiring
|
563
|
+
# | `-- End.not_found
|
564
|
+
# |-- _404_status
|
565
|
+
# |-- protocol_failure
|
566
|
+
# `-- End.fail_fast
|
567
|
+
|
568
|
+
|
569
|
+
# 1.b 422 domain error: validation failed
|
570
|
+
# RENDER an error document
|
571
|
+
ctx = {seq: [], validate: false}
|
572
|
+
ctx = {domain_ctx: ctx, **app_options}
|
573
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(api_create_endpoint, [ctx, {}], failure_block: _rails_failure_block)
|
574
|
+
|
575
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:failure>}
|
576
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:authenticate, :policy, :model, :cc_check, :validate]}
|
577
|
+
# this calls Rails default failure block
|
578
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>\"{\\\"errors\\\":null,\\\"message\\\":\\\"The submitted data is invalid.\\\"}\", :status=>422}, :failure=>true, :seq=>\"[:authenticate, :policy, :model, :cc_check, :validate]\", :signal=>\"#<Trailblazer::Activity::End semantic=:failure>\"}}
|
579
|
+
# `-- #<Class:0x0000000002e54e60>
|
580
|
+
# |-- Start.default
|
581
|
+
# |-- protocol
|
582
|
+
# | |-- Start.default
|
583
|
+
# | |-- authenticate
|
584
|
+
# | |-- policy
|
585
|
+
# | |-- domain_activity
|
586
|
+
# | | |-- Start.default
|
587
|
+
# | | |-- model
|
588
|
+
# | | |-- validate
|
589
|
+
# | | `-- End.my_validation_error
|
590
|
+
# | |-- handle_invalid_data
|
591
|
+
# | `-- End.invalid_data this is wired to the {failure} track
|
592
|
+
# |-- failure_render_config
|
593
|
+
# |-- failure_config_status
|
594
|
+
# |-- render_failure
|
595
|
+
# `-- End.failure
|
596
|
+
|
597
|
+
|
598
|
+
|
599
|
+
# 1.b2 another application error (#save), but 200 because of #failure_config_status
|
600
|
+
ctx = {seq: [], save: false}
|
601
|
+
ctx = {domain_ctx: ctx, **app_options}
|
602
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(api_create_endpoint, [ctx, {}], failure_block: _rails_failure_block)
|
603
|
+
|
604
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:failure>}
|
605
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:authenticate, :policy, :model, :cc_check, :validate, :save]}
|
606
|
+
# this calls Rails default failure block
|
607
|
+
# we set status to 200 in #failure_config_status
|
608
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>\"{\\\"errors\\\":null,\\\"message\\\":null}\", :status=>200}, :failure=>true, :seq=>\"[:authenticate, :policy, :model, :cc_check, :validate, :save]\", :signal=>\"#<Trailblazer::Activity::End semantic=:failure>\"}}
|
609
|
+
|
610
|
+
# invalid {cc_check}=>{cc_invalid}
|
611
|
+
ctx = {seq: [], cc_check: false}
|
612
|
+
ctx = {domain_ctx: ctx, **app_options}
|
613
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(api_create_endpoint, [ctx, {}], failure_block: _rails_failure_block)
|
614
|
+
|
615
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:failure>}
|
616
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:authenticate, :policy, :model, :cc_check]}
|
617
|
+
# this calls Rails default failure block
|
618
|
+
# we set status to 200 in #failure_config_status
|
619
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>\"{\\\"errors\\\":null,\\\"message\\\":null}\", :status=>200}, :failure=>true, :seq=>\"[:authenticate, :policy, :model, :cc_check]\", :signal=>\"#<Trailblazer::Activity::End semantic=:failure>\"}}
|
620
|
+
|
621
|
+
|
622
|
+
# 4. authorization error
|
623
|
+
ctx = {seq: []}
|
624
|
+
ctx = {policy: false, domain_ctx: ctx, **app_options}
|
625
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(api_create_endpoint, [ctx, {}], failure_block: _rails_failure_block)
|
626
|
+
|
627
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:fail_fast>}
|
628
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:authenticate, :policy]}
|
629
|
+
# this calls Rails default failure block
|
630
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>\"{\\\"errors\\\":null,\\\"message\\\":\\\"Action not allowed due to a policy.\\\"}\", :status=>403}, :failure=>true, :seq=>\"[:authenticate, :policy]\", :signal=>\"#<Trailblazer::Activity::End semantic=:fail_fast>\"}}
|
631
|
+
|
632
|
+
|
633
|
+
# 2. all OK
|
634
|
+
|
635
|
+
ctx = {seq: []}
|
636
|
+
ctx = {domain_ctx: ctx, **app_options}
|
637
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(api_create_endpoint, [ctx, {}], success_block: _rails_success_block)
|
638
|
+
|
639
|
+
|
640
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:success>}
|
641
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:authenticate, :policy, :model, :cc_check, :validate, :save]}
|
642
|
+
ctx[:json].must_equal %{{\"name\":\"Yo\"}}
|
643
|
+
|
644
|
+
# Rails default success block was called
|
645
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>\"{\\\"name\\\":\\\"Yo\\\"}\", :status=>200}, :failure=>nil, :seq=>\"[:authenticate, :policy, :model, :cc_check, :validate, :save]\", :signal=>\"#<Trailblazer::Activity::End semantic=:success>\"}}
|
646
|
+
|
647
|
+
|
648
|
+
# 3. 401 for API::Gemauth
|
649
|
+
# we only want to run the authenticate part!
|
650
|
+
#
|
651
|
+
# -- EndpointTest::Adapter::API::Gemauth
|
652
|
+
# |-- Start.default
|
653
|
+
# |-- protocol
|
654
|
+
# | |-- Start.default
|
655
|
+
# | |-- authenticate <this is actually gemserver_authenticate>
|
656
|
+
# | |-- handle_not_authenticated
|
657
|
+
# | `-- End.not_authenticated
|
658
|
+
# |-- my_401_handler
|
659
|
+
# |-- _401_status
|
660
|
+
# |-- render_protocol_failure_config
|
661
|
+
# |-- render_protocol_failure
|
662
|
+
# |-- protocol_failure
|
663
|
+
# `-- End.fail_fast
|
664
|
+
|
665
|
+
|
666
|
+
ctx = {seq: []}
|
667
|
+
ctx = {domain_ctx: ctx, gemserver_authenticate: false, **app_options}
|
668
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(Gemauth, [ctx, {}], failure_block: _rails_failure_block)
|
669
|
+
|
670
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:fail_fast>}
|
671
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:my_401_handler]}
|
672
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>\"{\\\"errors\\\":null,\\\"message\\\":\\\"No token\\\"}\", :status=>401}, :failure=>true, :seq=>\"[:my_401_handler]\", :signal=>\"#<Trailblazer::Activity::End semantic=:fail_fast>\"}}
|
673
|
+
|
674
|
+
# authentication works
|
675
|
+
# `-- EndpointTest::Adapter::API::Gemauth
|
676
|
+
# |-- Start.default
|
677
|
+
# |-- protocol
|
678
|
+
# | |-- Start.default
|
679
|
+
# | |-- authenticate
|
680
|
+
# | `-- End.success
|
681
|
+
# |-- success_render_config
|
682
|
+
# |-- success_render_status
|
683
|
+
# |-- render_success
|
684
|
+
# `-- End.success
|
685
|
+
|
686
|
+
ctx = {seq: [], gemserver_authenticate: true}
|
687
|
+
ctx = {domain_ctx: ctx, **app_options}
|
688
|
+
|
689
|
+
signal, (ctx, _ ) = Trailblazer::Endpoint.with_or_etc(Gemauth, [ctx, {}], success_block: _rails_success_block)
|
690
|
+
|
691
|
+
signal.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:success>}
|
692
|
+
ctx[:domain_ctx][:seq].inspect.must_equal %{[:gemserver_authenticate]}
|
693
|
+
to_h.inspect.must_equal %{{:render_options=>{:json=>\"{\\\"name\\\":\\\"Gemserver says yes.\\\"}\", :status=>200}, :failure=>nil, :seq=>\"[:gemserver_authenticate]\", :signal=>\"#<Trailblazer::Activity::End semantic=:success>\"}}
|
694
|
+
|
695
|
+
|
696
|
+
######### Controller #########
|
697
|
+
|
698
|
+
# 1. do everything automatically
|
699
|
+
# 2. override success
|
700
|
+
# 2. override failure: suppress the automatic rendering?
|
701
|
+
|
702
|
+
|
703
|
+
# class MyPolicyChain < Trailblazer::Endpoint::PolicyChain
|
704
|
+
# step :a?
|
705
|
+
# step :b?
|
706
|
+
# end
|
707
|
+
|
708
|
+
# Trailblazer::Endpoint(policy: MyPolicyChain)
|
709
|
+
|
710
|
+
# class MyEndpoint < Trailblazer::Endpoint
|
711
|
+
# step MyPolicies, replace: :policy # with or without root, we have a binary outcome?
|
712
|
+
# end
|
713
|
+
|
714
|
+
# MyEndpoint.() # with operation
|
715
|
+
# MyEndpoint.() # with workflow
|
716
|
+
|
717
|
+
# # op with > 2 ends
|
718
|
+
# {our_404: :not_found} # map ends to known ends
|
719
|
+
|
720
|
+
# # html version
|
721
|
+
# run(MyEndpoint, ) do |ctx|
|
722
|
+
# # success
|
723
|
+
# end
|
724
|
+
|
725
|
+
# # api version
|
726
|
+
# # if 404 ...
|
727
|
+
# # else default behavior
|
728
|
+
# end
|
729
|
+
|
730
|
+
|
731
|
+
|
732
|
+
|
733
|
+
end
|
734
|
+
end
|
735
|
+
|
736
|
+
# require "test_helper"
|
737
|
+
|
738
|
+
# require "reform"
|
739
|
+
# require "trailblazer"
|
740
|
+
# require "reform/form/dry"
|
741
|
+
# require "trailblazer/endpoint"
|
742
|
+
# require "trailblazer/endpoint/rails"
|
743
|
+
|
744
|
+
# class EndpointTest < Minitest::Spec
|
745
|
+
# Song = Struct.new(:id, :title, :length) do
|
746
|
+
# def self.find_by(id:nil); id.nil? ? nil : new(id) end
|
747
|
+
# end
|
748
|
+
|
749
|
+
# require "representable/json"
|
750
|
+
# class Serializer < Representable::Decorator
|
751
|
+
# include Representable::JSON
|
752
|
+
# property :id
|
753
|
+
# property :title
|
754
|
+
# property :length
|
755
|
+
|
756
|
+
# class Errors < Representable::Decorator
|
757
|
+
# include Representable::JSON
|
758
|
+
# property :messages
|
759
|
+
# end
|
760
|
+
# end
|
761
|
+
|
762
|
+
# class Deserializer < Representable::Decorator
|
763
|
+
# include Representable::JSON
|
764
|
+
# property :title
|
765
|
+
# end
|
766
|
+
|
767
|
+
# let (:my_handlers) {
|
768
|
+
# ->(m) do
|
769
|
+
# m.present { |result| _data << result["representer.serializer.class"].new(result["model"]).to_json }
|
770
|
+
# end
|
771
|
+
# }
|
772
|
+
|
773
|
+
# #---
|
774
|
+
# # present
|
775
|
+
# class Show < Trailblazer::Operation
|
776
|
+
# extend Representer::DSL
|
777
|
+
# step Model( Song, :find_by )
|
778
|
+
# representer :serializer, Serializer
|
779
|
+
# end
|
780
|
+
|
781
|
+
# # if you pass in "present"=>true as a dependency, the Endpoint will understand it's a present cycle.
|
782
|
+
# it do
|
783
|
+
# Trailblazer::Endpoint.new.(Show.({ id: 1 }, { "present" => true }), my_handlers)
|
784
|
+
# _data.must_equal ['{"id":1}']
|
785
|
+
# end
|
786
|
+
|
787
|
+
# # passing handlers directly to Endpoint#call.
|
788
|
+
# it do
|
789
|
+
# result = Show.({ id: 1 }, { "present" => true })
|
790
|
+
# Trailblazer::Endpoint.new.(result) do |m|
|
791
|
+
# m.present { |result| _data << result["representer.serializer.class"].new(result["model"]).to_json }
|
792
|
+
# end
|
793
|
+
|
794
|
+
# _data.must_equal ['{"id":1}']
|
795
|
+
# end
|
796
|
+
|
797
|
+
|
798
|
+
# class Create < Trailblazer::Operation
|
799
|
+
# step Policy::Guard ->(options) { options["user.current"] == ::Module }
|
800
|
+
|
801
|
+
# extend Representer::DSL
|
802
|
+
# representer :serializer, Serializer
|
803
|
+
# representer :deserializer, Deserializer
|
804
|
+
# representer :errors, Serializer::Errors
|
805
|
+
# # self["representer.serializer.class"] = Representer
|
806
|
+
# # self["representer.deserializer.class"] = Deserializer
|
807
|
+
|
808
|
+
|
809
|
+
# extend Contract::DSL
|
810
|
+
# contract do
|
811
|
+
# property :title
|
812
|
+
# property :length
|
813
|
+
|
814
|
+
# include Reform::Form::Dry
|
815
|
+
# validation :default do
|
816
|
+
# required(:title).filled
|
817
|
+
# end
|
818
|
+
# end
|
819
|
+
|
820
|
+
# step Model( Song, :new )
|
821
|
+
# step Contract::Build()
|
822
|
+
# step Contract::Validate( representer: self["representer.deserializer.class"] )
|
823
|
+
# step Persist( method: :sync )
|
824
|
+
# step ->(options) { options["model"].id = 9 }
|
825
|
+
# end
|
826
|
+
|
827
|
+
# let (:controller) { self }
|
828
|
+
# let (:_data) { [] }
|
829
|
+
# def head(*args); _data << [:head, *args] end
|
830
|
+
|
831
|
+
# let(:handlers) { Trailblazer::Endpoint::Handlers::Rails.new(self, path: "/songs").() }
|
832
|
+
# def render(options)
|
833
|
+
# _data << options
|
834
|
+
# end
|
835
|
+
# # not authenticated, 401
|
836
|
+
# it do
|
837
|
+
# result = Create.( { id: 1 }, "user.current" => false )
|
838
|
+
# # puts "@@@@@ #{result.inspect}"
|
839
|
+
|
840
|
+
# Trailblazer::Endpoint.new.(result, handlers)
|
841
|
+
# _data.inspect.must_equal %{[[:head, 401]]}
|
842
|
+
# end
|
843
|
+
|
844
|
+
# # created
|
845
|
+
# # length is ignored as it's not defined in the deserializer.
|
846
|
+
# it do
|
847
|
+
# result = Create.( {}, "user.current" => ::Module, "document" => '{"id": 9, "title": "Encores", "length": 999 }' )
|
848
|
+
# # puts "@@@@@ #{result.inspect}"
|
849
|
+
|
850
|
+
# Trailblazer::Endpoint.new.(result, handlers)
|
851
|
+
# _data.inspect.must_equal '[[:head, 201, {:location=>"/songs/9"}]]'
|
852
|
+
# end
|
853
|
+
|
854
|
+
# class Update < Create
|
855
|
+
# self.~ Model( :find_by )
|
856
|
+
# end
|
857
|
+
|
858
|
+
# # 404
|
859
|
+
# it do
|
860
|
+
# result = Update.({ id: nil }, "user.current" => ::Module, "document" => '{"id": 9, "title": "Encores", "length": 999 }' )
|
861
|
+
|
862
|
+
# Trailblazer::Endpoint.new.(result, handlers)
|
863
|
+
# _data.inspect.must_equal '[[:head, 404]]'
|
864
|
+
# end
|
865
|
+
|
866
|
+
# #---
|
867
|
+
# # validation failure 422
|
868
|
+
# # success
|
869
|
+
# it do
|
870
|
+
# result = Create.({}, "user.current" => ::Module, "document" => '{ "title": "" }')
|
871
|
+
# Trailblazer::Endpoint.new.(result, handlers)
|
872
|
+
# _data.inspect.must_equal '[{:json=>"{\\"messages\\":{\\"title\\":[\\"must be filled\\"]}}", :status=>422}]'
|
873
|
+
# end
|
874
|
+
|
875
|
+
|
876
|
+
# include Trailblazer::Endpoint::Controller
|
877
|
+
# #---
|
878
|
+
# # Controller#endpoint
|
879
|
+
# # custom handler.
|
880
|
+
# it do
|
881
|
+
# invoked = nil
|
882
|
+
|
883
|
+
# endpoint(Update, { id: nil }) do |res|
|
884
|
+
# res.not_found { invoked = "my not_found!" }
|
885
|
+
# end
|
886
|
+
|
887
|
+
# invoked.must_equal "my not_found!"
|
888
|
+
# _data.must_equal [] # no rails code involved.
|
889
|
+
# end
|
890
|
+
|
891
|
+
# # generic handler because user handler doesn't match.
|
892
|
+
# it do
|
893
|
+
# invoked = nil
|
894
|
+
|
895
|
+
# endpoint( Update, { id: nil }, args: {"user.current" => ::Module} ) do |res|
|
896
|
+
# res.invalid { invoked = "my invalid!" }
|
897
|
+
# end
|
898
|
+
|
899
|
+
# _data.must_equal [[:head, 404]]
|
900
|
+
# invoked.must_equal nil
|
901
|
+
# end
|
902
|
+
|
903
|
+
# # only generic handler
|
904
|
+
# it do
|
905
|
+
# endpoint(Update, { id: nil })
|
906
|
+
# _data.must_equal [[:head, 404]]
|
907
|
+
# end
|
908
|
+
# end
|