trailblazer-endpoint 0.0.3 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -0
  3. data/Appraisals +5 -0
  4. data/CHANGES.md +27 -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 +50 -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 +217 -1
  12. data/lib/trailblazer/endpoint/dsl.rb +8 -3
  13. data/lib/trailblazer/endpoint/options.rb +16 -69
  14. data/lib/trailblazer/endpoint/protocol.rb +7 -11
  15. data/lib/trailblazer/endpoint/protocol/cipher.rb +27 -0
  16. data/lib/trailblazer/endpoint/protocol/controller.rb +102 -0
  17. data/lib/trailblazer/endpoint/protocol/find_process_model.rb +15 -0
  18. data/lib/trailblazer/endpoint/version.rb +1 -1
  19. data/test/adapter/api_test.rb +6 -11
  20. data/test/adapter/web_test.rb +2 -5
  21. data/test/config_test.rb +25 -0
  22. data/test/docs/controller_test.rb +220 -73
  23. data/test/endpoint_test.rb +1 -1
  24. data/test/rails-app/.gitignore +8 -2
  25. data/test/rails-app/.ruby-version +1 -0
  26. data/test/rails-app/Gemfile +21 -9
  27. data/test/rails-app/Gemfile.lock +174 -118
  28. data/test/rails-app/app/concepts/app/api/v1/representer/errors.rb +16 -0
  29. data/test/rails-app/app/concepts/auth/jwt.rb +35 -0
  30. data/test/rails-app/app/concepts/auth/operation/authenticate.rb +32 -0
  31. data/test/rails-app/app/concepts/auth/operation/policy.rb +9 -0
  32. data/test/rails-app/app/concepts/song/cell/create.rb +4 -0
  33. data/test/rails-app/app/concepts/song/cell/new.rb +4 -0
  34. data/test/rails-app/app/concepts/song/operation/create.rb +17 -0
  35. data/test/rails-app/app/concepts/song/operation/show.rb +10 -0
  36. data/test/rails-app/app/concepts/song/representer.rb +5 -0
  37. data/test/rails-app/app/concepts/song/view/create.erb +1 -0
  38. data/test/rails-app/app/concepts/song/view/new.erb +1 -0
  39. data/test/rails-app/app/controllers/api/v1/songs_controller.rb +41 -0
  40. data/test/rails-app/app/controllers/application_controller.rb +8 -1
  41. data/test/rails-app/app/controllers/application_controller/api.rb +107 -0
  42. data/test/rails-app/app/controllers/application_controller/web.rb +46 -0
  43. data/test/rails-app/app/controllers/auth_controller.rb +44 -0
  44. data/test/rails-app/app/controllers/home_controller.rb +5 -0
  45. data/test/rails-app/app/controllers/songs_controller.rb +254 -13
  46. data/test/rails-app/app/models/song.rb +6 -0
  47. data/test/rails-app/app/models/user.rb +7 -0
  48. data/test/rails-app/bin/bundle +114 -0
  49. data/test/rails-app/bin/rails +4 -0
  50. data/test/rails-app/bin/rake +4 -0
  51. data/test/rails-app/bin/setup +33 -0
  52. data/test/rails-app/config/application.rb +26 -3
  53. data/test/rails-app/config/credentials.yml.enc +1 -0
  54. data/test/rails-app/config/database.yml +2 -2
  55. data/test/rails-app/config/environments/development.rb +7 -17
  56. data/test/rails-app/config/environments/production.rb +28 -23
  57. data/test/rails-app/config/environments/test.rb +10 -12
  58. data/test/rails-app/config/initializers/application_controller_renderer.rb +6 -4
  59. data/test/rails-app/config/initializers/cors.rb +16 -0
  60. data/test/rails-app/config/initializers/trailblazer.rb +2 -0
  61. data/test/rails-app/config/locales/en.yml +11 -1
  62. data/test/rails-app/config/master.key +1 -0
  63. data/test/rails-app/config/routes.rb +27 -4
  64. data/test/rails-app/db/schema.rb +15 -0
  65. data/test/rails-app/test/controllers/api_songs_controller_test.rb +87 -0
  66. data/test/rails-app/test/controllers/songs_controller_test.rb +152 -145
  67. data/test/rails-app/test/test_helper.rb +7 -1
  68. data/test/test_helper.rb +0 -2
  69. data/trailblazer-endpoint.gemspec +2 -1
  70. metadata +69 -24
  71. data/test/rails-app/config/initializers/cookies_serializer.rb +0 -5
  72. data/test/rails-app/config/initializers/new_framework_defaults.rb +0 -24
  73. data/test/rails-app/config/initializers/session_store.rb +0 -3
  74. data/test/rails-app/config/secrets.yml +0 -22
  75. data/test/rails-app/test/helpers/.keep +0 -0
  76. data/test/rails-app/test/integration/.keep +0 -0
  77. data/test/rails-app/test/mailers/.keep +0 -0
  78. data/test/rails-app/vendor/assets/javascripts/.keep +0 -0
  79. data/test/rails-app/vendor/assets/stylesheets/.keep +0 -0
@@ -0,0 +1,2 @@
1
+ require "trailblazer/operation"
2
+ require "representable/json"
@@ -16,8 +16,18 @@
16
16
  #
17
17
  # This would use the information in config/locales/es.yml.
18
18
  #
19
+ # The following keys must be escaped otherwise they will not be retrieved by
20
+ # the default I18n backend:
21
+ #
22
+ # true, false, on, off, yes, no
23
+ #
24
+ # Instead, surround them with single quotes.
25
+ #
26
+ # en:
27
+ # 'true': 'foo'
28
+ #
19
29
  # To learn more, please read the Rails Internationalization guide
20
- # available at http://guides.rubyonrails.org/i18n.html.
30
+ # available at https://guides.rubyonrails.org/i18n.html.
21
31
 
22
32
  en:
23
33
  hello: "Hello world"
@@ -0,0 +1 @@
1
+ fee3e51bb20a824ec13dc5912f3548f2
@@ -1,6 +1,29 @@
1
1
  Rails.application.routes.draw do
2
- # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
3
- resources :songs
4
- post "update_with_user", to: "songs#update_with_user"
5
- post "create_with_custom_handlers", to: "songs#create_with_custom_handlers"
2
+ # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
3
+ post "/songs/create_with_options", to: "songs_controller/create_with_options#create"
4
+ post "/songs/create_or", to: "songs_controller/create_or#create"
5
+ post "/songs/endpoint_ctx", to: "songs_controller/create_endpoint_ctx#create"
6
+ post "/songs/create_with_or", to: "songs#create"
7
+ post "/songs", to: "songs#create_without_block"
8
+ post "/songs/create_with_protocol_failure", to: "songs_controller/create_with_protocol_failure#create_with_protocol_failure"
9
+ post "/songs/create_with_options_for_domain_ctx", to: "songs_controller/create_with_options_for_domain_ctx#create"
10
+ post "/auth/sign_in", to: "auth#sign_in"
11
+
12
+ post "/v1/songs", to: "api/v1/songs#create"
13
+ get "/v1/songs/:id", to: "api/v1/songs#show"
14
+
15
+ get "/v1/songs_with_options/:id", to: "api/v1/songs_controller/with_options#show"
16
+
17
+ get "/", to: "home#dashboard"
18
+
19
+ post "/songs/serialize", to: "songs_controller/serialize#create"
20
+ post "/songs/copy_from_domain_ctx", to: "songs_controller/create1#create"
21
+ post "/songs/serialize1", to: "songs_controller/serialize1#create"
22
+ post "/songs/serialize2", to: "songs_controller/serialize2#create"
23
+ post "/songs/serialize3", to: "songs_controller/serialize3#create"
24
+ post "/songs/serialize4", to: "songs_controller/serialize4#create"
25
+ post "/songs/serialize5", to: "songs_controller/serialize5#create"
26
+ post "/songs/serialize6", to: "songs_controller/serialize6#create"
27
+ post "/songs/serialize7", to: "songs_controller/serialize7#create"
28
+ post "/songs/serialize72", to: "songs_controller/serialize7#new"
6
29
  end
@@ -0,0 +1,15 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `rails
6
+ # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define(version: 0) do
14
+
15
+ end
@@ -0,0 +1,87 @@
1
+ require "test_helper"
2
+
3
+ class ApiSongsControllerTest < ActionDispatch::IntegrationTest
4
+
5
+ def jwt(user_id)
6
+ Auth::Jwt.generate('user_id', user_id, {}
7
+ # 'email': options['current_user'].email
8
+ )
9
+ end
10
+
11
+ def post_json(endpoint, params_hash, api_token = nil, headers={})
12
+ post endpoint, params: params_hash.to_json, headers: request_headers(api_token).merge(headers)
13
+ end
14
+ def get_json(endpoint, params = nil, api_token = nil)
15
+ get endpoint, params: params, headers: request_headers(api_token)
16
+ end
17
+
18
+ def request_headers(api_token = nil)
19
+ headers = {
20
+ 'Content-Type' => 'application/json'
21
+ }
22
+ unless api_token.nil?
23
+ headers.merge!(authorization_header(api_token))
24
+ end
25
+ headers
26
+ end
27
+ def authorization_header(api_token)
28
+ { 'Authorization' => "Bearer #{api_token}"}
29
+ end
30
+
31
+
32
+ test "API interface" do
33
+ yogi_jwt = jwt(1)
34
+
35
+ # default {success}
36
+ #:success
37
+ post_json "/v1/songs", {id: 1}, yogi_jwt
38
+
39
+ assert_response 200
40
+ assert_equal "{\"id\":1}", response.body
41
+ #:success end
42
+
43
+ # no proper input/params
44
+ post_json "/v1/songs", {}, yogi_jwt
45
+ # default {failure}
46
+ assert_response 422
47
+ assert_equal "{\"errors\":{\"message\":\"The submitted data is invalid.\"}}", response.body
48
+
49
+ # 401
50
+ #:not_authenticated
51
+ post_json "/v1/songs", {} # no token
52
+ assert_response 401
53
+ assert_equal "{\"errors\":{\"message\":\"Authentication credentials were not provided or are invalid.\"}}", response.body
54
+ #:not_authenticated end
55
+
56
+ # 403
57
+ post_json "/v1/songs", {id: 1, policy: false}, yogi_jwt
58
+ assert_response 403
59
+ assert_equal "{\"errors\":{\"message\":\"Action not allowed due to a policy setting.\"}}", response.body
60
+
61
+ # 200 / GET
62
+ get_json "/v1/songs/1", {}, yogi_jwt
63
+ assert_response 200
64
+ assert_equal "{\"id\":\"1\"}", response.body
65
+
66
+ # 404
67
+ get_json "/v1/songs/0", {}, yogi_jwt
68
+ assert_response 404
69
+ assert_equal "{\"errors\":{}}", response.body
70
+
71
+ # TODO: CHANGE/customize block
72
+ end
73
+
74
+ test "allows overriding {:success_block} and friends" do
75
+ yogi_jwt = jwt(1)
76
+
77
+ # Not authenticated, 401, overridden {:protocol_failure_block} kicks in
78
+ get_json "/v1/songs_with_options/1"
79
+ assert_response 402
80
+
81
+ # All good, default block
82
+ get_json "/v1/songs_with_options/1", yogi_jwt
83
+
84
+ end
85
+ end
86
+
87
+ # TODO: test 404 with NotFound config
@@ -1,156 +1,163 @@
1
1
  require "test_helper"
2
2
 
3
- module Reform
4
- Form = nil
5
- end # FIXME
6
-
7
-
8
- Song = Struct.new(:id, :title, :length) do
9
- def self.find_by(id:1); id=="0" ? nil : new(id, "A Song") end
10
- end
11
-
12
- require "trailblazer"
13
- require "trailblazer/operation/model"
14
- # require "reform/form/active_model/validations"
15
- require "trailblazer/operation/contract"
16
- require "trailblazer/operation/representer"
17
- require "trailblazer/operation/guard"
18
- require "trailblazer/endpoint"
19
-
20
-
21
- require "representable/json"
22
- class Serializer < Representable::Decorator
23
- include Representable::JSON
24
- property :id
25
- property :title
26
- property :length
27
-
28
- class Errors < Representable::Decorator
29
- include Representable::JSON
30
- property :messages
31
- end
32
- end
33
-
34
- class Deserializer < Representable::Decorator
35
- include Representable::JSON
36
- property :title
37
- end
38
-
39
-
40
- class Create < Trailblazer::Operation
41
- include Policy::Guard
42
- policy ->(*) { self["user.current"] == ::Module }
43
-
44
- extend Representer::DSL
45
- representer :serializer, Serializer
46
- representer :deserializer, Deserializer
47
- representer :errors, Serializer::Errors
48
- # self["representer.serializer.class"] = Representer
49
- # self["representer.deserializer.class"] = Deserializer
50
-
51
- include Model
52
- model Song, :create
53
-
54
- include Contract::Step
55
- include Representer::Deserializer::JSON
56
- # contract do
57
- # property :title
58
- # property :length
59
-
60
- # include Reform::Form::ActiveModel::Validations
61
- # validates :title, presence: true
62
- # end
63
-
64
- # FIXME: use trb-rails and reform
65
- class FakeContract
66
- def initialize(model,*); @model = model end
67
- def call(params)
68
- if params[:title]
69
- @title, @length = params[:title],params[:length]
70
- return Trailblazer::Operation::Result.new(true, {})
71
- end
72
-
73
- return Struct.new(:errors, :success?).new(Struct.new(:messages).new({"title": ["must be set"]}), false)
74
- end
75
- def sync; @model.title = @title; @model.length = @length; end
76
- end
77
-
78
- contract FakeContract
79
-
80
- def process(params)
81
- validate(params) do |f|
82
- self["contract"].sync
83
- self["model"].id = 9
84
- end
85
- end
86
- end
87
-
88
- class Update < Create
89
- action :find_by
90
- end
91
-
92
- # TODO: test present.
93
- class Show < Trailblazer::Operation
94
- include Policy::Guard
95
- policy ->(*) { self["user.current"] == ::Module }
96
-
97
- extend Representer::DSL
98
- representer :serializer, Serializer
99
-
100
- include Model
101
- model Song, :find_by
102
-
103
- self.> ->(input, options) { options["present"] = true }, before: "operation.result"
104
- end
105
-
106
- class SongsControllerTest < ActionController::TestCase
107
- # 404
108
- test "update 404" do
109
- post :update, params: { id: 0 }
110
- assert_equal 404, response.status
111
- end
112
-
3
+ class SongsControllerTest < ActionDispatch::IntegrationTest
4
+ test "all possible outcomes with {Create}" do
113
5
  # 401
114
- test "update 401" do
115
- post :update, params: { id: 1 }
116
- assert_equal 401, response.status
117
- end
118
-
119
- # 422
120
- # TODO: test when no error repr set.
121
- test "update 422" do
122
- post :create, params: { id: 1 }
123
- assert_equal 422, response.status
124
- assert_equal %{{\"messages\":{\"title\":[\"must be set\"]}}}, response.body
6
+ post "/songs", params: {}
7
+ assert_response 401
8
+ assert_equal "", response.body
9
+
10
+ # sign in
11
+ post "/auth/sign_in", params: {username: "yogi@trb.to", password: "secret"}
12
+ assert_equal 1, session[:user_id]
13
+ # follow_redirect!
14
+ assert_equal 1, session[:user_id]
15
+
16
+ post "/songs", params: {id: 1}
17
+ # default {success} block doesn't do anything
18
+ assert_response 200
19
+ assert_equal "", response.body
20
+
21
+ post "/songs", params: {}
22
+ # default {failure} block doesn't do anything
23
+ assert_response 422
24
+ assert_equal "", response.body
25
+
26
+ # 403
27
+ post "/songs", params: {policy: false}
28
+ assert_response 403
29
+ assert_equal "", response.body
30
+
31
+ post "/songs/create_with_options", params: {id: 1}
32
+ # {success} block renders model
33
+ assert_response 200
34
+ assert_equal "<div>#<struct Song id=\"1\">#<struct User id=2, email=\"seuros@trb.to\"></div>\n", response.body
35
+
36
+ post "/songs/create_with_options", params: {}
37
+ # default {failure} block doesn't do anything
38
+ assert_response 422
39
+ assert_equal "", response.body
40
+
41
+ post "/songs/create_with_or", params: {id: 1}
42
+ # {success} block renders model
43
+ assert_response 200
44
+ assert_equal "<div>#<struct Song id=\"1\">#<struct User id=1, email=\"yogi@trb.to\"></div>\n", response.body
45
+
46
+ post "/songs/create_with_or", params: {}
47
+ assert_response 200
48
+ assert_equal %{<div>#<struct errors=nil></div>\n}, response.body
49
+
50
+ # Or { render status: 422 }
51
+ post "/songs/create_or", params: {}
52
+ assert_response 422
53
+ assert_equal %{null}, response.body
54
+
55
+ # {:endpoint_ctx} is available in blocks
56
+ post "/songs/endpoint_ctx", params: {id: 1}
57
+ assert_response 200
58
+ assert_equal "Created", response.body
59
+ # assert_equal "[\"domain_ctx\",\"session\",\"controller\",\"config_source\",\"current_user\",\"domain_activity_return_signal\"]", response.body
60
+
61
+ # {:options_for_domain_ctx} overrides domain_ctx
62
+ post "/songs/create_with_options_for_domain_ctx", params: {id: 1} # params get overridden
63
+ assert_response 200
64
+ assert_equal "<div>#<struct Song id=999></div>\n", response.body
125
65
  end
126
66
 
127
- # 201
128
- test "create 201" do
129
- post :create, params: { id: 1, title: "AVH" }
130
- assert_equal 201, response.status
131
- assert_equal %{}, response.body
132
- assert_equal "/songs/9", response.header["Location"]
67
+ test "override protocol_failure" do
68
+ post "/songs/create_with_protocol_failure", params: {}
69
+ assert_response 500
70
+ assert_equal "wrong login, app crashed", response.body
133
71
  end
134
72
 
135
- # 200 present
136
- test "show 200" do
137
- get :show, params: { id: 1 }
138
- assert_equal 200, response.status
139
- assert_equal %{{"id":"1","title":"A Song"}}, response.body
73
+ test "serializing/deserializing/find_process_model_from_resume_data" do
74
+ # # 401
75
+ # post "/songs/serialize/"
76
+ # assert_response 401
77
+
78
+ # sign in
79
+ post "/auth/sign_in", params: {username: "yogi@trb.to", password: "secret"}
80
+ assert_equal 1, session[:user_id]
81
+
82
+
83
+ # When {:encrypted_resume_data} is {nil} the entire deserialize cycle is skipped.
84
+ # Nothing gets serialized.
85
+ # This is considered an error since we're expecting resume data.
86
+ post "/songs/serialize1/", params: {} # encrypted_resume_data: nil
87
+ assert_response 500
88
+ assert_equal "xxx", response.body # DISCUSS: is this an application or a protocol error?
89
+
90
+ # Nothing deserialized, but {:remember} serialized
91
+ # "confirm_delete form"
92
+ post "/songs/serialize2/", params: {} # {:remember} serialized
93
+ assert_response 200
94
+ encrypted_string = "0109C4E535EDA2CCE8CD69E50C179F5950CC4A2A898504F951C995B6BCEAFE1DFAB02894854B96B9D11C23E25DB5FB03"
95
+ assert_equal "false/nil/#{encrypted_string}", response.body
96
+
97
+ # {:remember} deserialized
98
+ # "submit confirm_delete"
99
+ # We're expecting serialized data and don't serialize anything.
100
+ post "/songs/serialize3/", params: {encrypted_resume_data: encrypted_string} # {:remember} serialized
101
+ assert_response 200
102
+ assert_equal "false/{\"remember\"=>\"#<OpenStruct id=1>\", \"id\"=>9}/", response.body
103
+
104
+ # retrieve process_model via id in {:resume_data}
105
+ # we can see {endpoint_ctx[:process_model_id]}
106
+ # We're expecting serialized data and don't serialize anything.
107
+ post "/songs/serialize4/", params: {encrypted_resume_data: encrypted_string}
108
+ assert_response 200
109
+ assert_equal "9/{\"remember\"=>\"#<OpenStruct id=1>\", \"id\"=>9}/", response.body
110
+
111
+ # retrieve process_model via {:resume_data}'s serialized id.
112
+ # We're expecting serialized data and don't serialize anything.
113
+ post "/songs/serialize5/", params: {encrypted_resume_data: encrypted_string}
114
+ assert_response 200
115
+ assert_equal "#<struct Song id=9>/{\"remember\"=>\"#<OpenStruct id=1>\", \"id\"=>9}/", response.body
116
+ # model not found
117
+ post "/songs/serialize5/", params: {encrypted_resume_data: "36C61CCE30E6CFDE637DF0DA9257CC49"}
118
+ assert_response 404
119
+ assert_equal "", response.body
120
+
121
+ # find model without serialize from params, no serialized stuff passed
122
+ # DISCUSS: this could be a different test
123
+ post "/songs/serialize6/", params: {id: 1}
124
+ assert_response 200
125
+ assert_equal "#<struct Song id=\"1\">/#<struct Song id=\"1\">/", response.body
126
+ # model not found
127
+ post "/songs/serialize6/", params: {id: nil}
128
+ assert_response 404
129
+ assert_equal "", response.body
130
+
131
+ # {find_process_model: true} set on controller level, for all endpoints.
132
+ post "/songs/serialize7/", params: {id: 1}
133
+ assert_response 200
134
+ assert_equal "#<struct Song id=\"1\">/#<struct Song id=\"1\">/", response.body
135
+ # {find_process_model: false} overrides controller setting
136
+ post "/songs/serialize72/", params: {id: 1}
137
+ assert_response 200
138
+ assert_equal "false/false/", response.body
139
+
140
+
141
+ # {#insert_copy_from_domain_ctx!} provides {:process_model}
142
+ post "/songs/copy_from_domain_ctx", params: {id: 1}
143
+ assert_response 200
144
+ assert_equal %{#<struct Song id="1">}, response.body
140
145
  end
141
146
 
142
- # 201 update
143
- test "update 200" do
144
- post :update_with_user, params: { id: 1, title: "AVH" }
145
- assert_equal 200, response.status
146
- assert_equal %{}, response.body
147
- assert_equal "/songs/9", response.header["Location"]
148
- end
149
-
150
- # custom 999
151
- test "custom 999" do
152
- post :create_with_custom_handlers, params: { id: 1, title: "AVH" }
153
- assert_equal 999, response.status
154
- assert_equal %{{"id":9,"title":"AVH"}}, response.body
147
+ test "sign_in" do
148
+ # wrong credentials
149
+ post "/auth/sign_in", params: {}
150
+ assert_response 401
151
+ assert_equal "", response.body
152
+ assert_nil session[:user_id]
153
+
154
+ # valid signin
155
+ post "/auth/sign_in", params: {username: "yogi@trb.to", password: "secret"}
156
+ assert_response 302
157
+ # assert_equal "", response.body
158
+ assert_equal 1, session[:user_id]
159
+ assert_redirected_to "/"
155
160
  end
156
161
  end
162
+
163
+ # TODO: test 404 with NotFound config