trailblazer-endpoint 0.0.3 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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