hanami 2.0.3 → 2.1.0.beta2

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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -2
  3. data/LICENSE.md +1 -1
  4. data/README.md +26 -10
  5. data/hanami.gemspec +2 -2
  6. data/lib/hanami/app.rb +5 -0
  7. data/lib/hanami/config/actions.rb +4 -11
  8. data/lib/hanami/config/assets.rb +84 -0
  9. data/lib/hanami/config/null_config.rb +3 -0
  10. data/lib/hanami/config/views.rb +0 -4
  11. data/lib/hanami/config.rb +71 -5
  12. data/lib/hanami/extensions/action/slice_configured_action.rb +15 -7
  13. data/lib/hanami/extensions/action.rb +8 -6
  14. data/lib/hanami/extensions/router/errors.rb +58 -0
  15. data/lib/hanami/extensions/view/context.rb +129 -60
  16. data/lib/hanami/extensions/view/part.rb +26 -0
  17. data/lib/hanami/extensions/view/scope.rb +26 -0
  18. data/lib/hanami/extensions/view/slice_configured_context.rb +0 -2
  19. data/lib/hanami/extensions/view/slice_configured_helpers.rb +44 -0
  20. data/lib/hanami/extensions/view/slice_configured_view.rb +106 -21
  21. data/lib/hanami/extensions/view/standard_helpers.rb +18 -0
  22. data/lib/hanami/extensions.rb +10 -3
  23. data/lib/hanami/helpers/assets_helper.rb +752 -0
  24. data/lib/hanami/helpers/form_helper/form_builder.rb +1391 -0
  25. data/lib/hanami/helpers/form_helper/values.rb +75 -0
  26. data/lib/hanami/helpers/form_helper.rb +213 -0
  27. data/lib/hanami/middleware/assets.rb +21 -0
  28. data/lib/hanami/middleware/public_errors_app.rb +75 -0
  29. data/lib/hanami/middleware/render_errors.rb +90 -0
  30. data/lib/hanami/providers/assets.rb +44 -0
  31. data/lib/hanami/rake_tasks.rb +19 -18
  32. data/lib/hanami/settings.rb +1 -1
  33. data/lib/hanami/slice.rb +48 -2
  34. data/lib/hanami/slice_configurable.rb +3 -2
  35. data/lib/hanami/version.rb +1 -1
  36. data/lib/hanami/web/rack_logger.rb +1 -1
  37. data/lib/hanami.rb +3 -3
  38. data/spec/integration/action/view_rendering/view_context_spec.rb +221 -0
  39. data/spec/integration/action/view_rendering_spec.rb +0 -18
  40. data/spec/integration/assets/assets_spec.rb +101 -0
  41. data/spec/integration/assets/serve_static_assets_spec.rb +152 -0
  42. data/spec/integration/logging/exception_logging_spec.rb +115 -0
  43. data/spec/integration/logging/notifications_spec.rb +68 -0
  44. data/spec/integration/logging/request_logging_spec.rb +128 -0
  45. data/spec/integration/rack_app/middleware_spec.rb +22 -22
  46. data/spec/integration/rack_app/rack_app_spec.rb +3 -220
  47. data/spec/integration/rake_tasks_spec.rb +107 -0
  48. data/spec/integration/view/config/default_context_spec.rb +149 -0
  49. data/spec/integration/view/{inflector_spec.rb → config/inflector_spec.rb} +1 -1
  50. data/spec/integration/view/config/part_class_spec.rb +147 -0
  51. data/spec/integration/view/config/part_namespace_spec.rb +103 -0
  52. data/spec/integration/view/config/paths_spec.rb +119 -0
  53. data/spec/integration/view/config/scope_class_spec.rb +147 -0
  54. data/spec/integration/view/config/scope_namespace_spec.rb +103 -0
  55. data/spec/integration/view/config/template_spec.rb +38 -0
  56. data/spec/integration/view/context/assets_spec.rb +3 -9
  57. data/spec/integration/view/context/request_spec.rb +3 -7
  58. data/spec/integration/view/helpers/form_helper_spec.rb +174 -0
  59. data/spec/integration/view/helpers/part_helpers_spec.rb +124 -0
  60. data/spec/integration/view/helpers/scope_helpers_spec.rb +84 -0
  61. data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +162 -0
  62. data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +119 -0
  63. data/spec/integration/view/slice_configuration_spec.rb +9 -9
  64. data/spec/integration/web/render_detailed_errors_spec.rb +107 -0
  65. data/spec/integration/web/render_errors_spec.rb +242 -0
  66. data/spec/spec_helper.rb +1 -1
  67. data/spec/support/app_integration.rb +46 -2
  68. data/spec/support/matchers.rb +32 -0
  69. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +24 -36
  70. data/spec/unit/hanami/config/actions/csrf_protection_spec.rb +4 -3
  71. data/spec/unit/hanami/config/actions/default_values_spec.rb +3 -6
  72. data/spec/unit/hanami/config/render_detailed_errors_spec.rb +25 -0
  73. data/spec/unit/hanami/config/render_errors_spec.rb +25 -0
  74. data/spec/unit/hanami/config/views_spec.rb +0 -18
  75. data/spec/unit/hanami/env_spec.rb +11 -25
  76. data/spec/unit/hanami/extensions/view/context_spec.rb +59 -0
  77. data/spec/unit/hanami/helpers/assets_helper/asset_url_spec.rb +109 -0
  78. data/spec/unit/hanami/helpers/assets_helper/audio_tag_spec.rb +132 -0
  79. data/spec/unit/hanami/helpers/assets_helper/favicon_link_tag_spec.rb +91 -0
  80. data/spec/unit/hanami/helpers/assets_helper/image_tag_spec.rb +92 -0
  81. data/spec/unit/hanami/helpers/assets_helper/javascript_tag_spec.rb +143 -0
  82. data/spec/unit/hanami/helpers/assets_helper/stylesheet_link_tag_spec.rb +126 -0
  83. data/spec/unit/hanami/helpers/assets_helper/video_tag_spec.rb +132 -0
  84. data/spec/unit/hanami/helpers/form_helper_spec.rb +2826 -0
  85. data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +27 -0
  86. data/spec/unit/hanami/router/errors/not_found_error_spec.rb +22 -0
  87. data/spec/unit/hanami/slice_configurable_spec.rb +18 -0
  88. data/spec/unit/hanami/version_spec.rb +1 -1
  89. data/spec/unit/hanami/web/rack_logger_spec.rb +1 -1
  90. metadata +95 -35
  91. data/lib/hanami/assets/app_config.rb +0 -61
  92. data/lib/hanami/assets/config.rb +0 -53
  93. data/spec/integration/action/view_integration_spec.rb +0 -165
  94. data/spec/integration/view/part_namespace_spec.rb +0 -96
  95. data/spec/integration/view/path_spec.rb +0 -56
  96. data/spec/integration/view/template_spec.rb +0 -68
  97. data/spec/isolation/hanami/application/already_configured_spec.rb +0 -19
  98. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +0 -10
  99. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +0 -14
  100. data/spec/isolation/hanami/application/not_configured_spec.rb +0 -9
  101. data/spec/isolation/hanami/application/routes/configured_spec.rb +0 -44
  102. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +0 -16
  103. data/spec/isolation/hanami/boot/success_spec.rb +0 -50
@@ -7,7 +7,7 @@ module Hanami
7
7
  # @api private
8
8
  module Version
9
9
  # @api public
10
- VERSION = "2.0.3"
10
+ VERSION = "2.1.0.beta2"
11
11
 
12
12
  # @since 0.9.0
13
13
  # @api private
@@ -29,7 +29,7 @@ module Hanami
29
29
  ROUTER_PARAMS = "router.params"
30
30
  private_constant :ROUTER_PARAMS
31
31
 
32
- CONTENT_LENGTH = "Content-Length"
32
+ CONTENT_LENGTH = "CONTENT_LENGTH"
33
33
  private_constant :CONTENT_LENGTH
34
34
 
35
35
  MILISECOND = "ms"
data/lib/hanami.rb CHANGED
@@ -18,7 +18,7 @@ module Hanami
18
18
  def self.loader
19
19
  @loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
20
20
  loader.ignore(
21
- "#{loader.dirs.first}/hanami/{constants,boot,errors,prepare,rake_tasks,setup}.rb"
21
+ "#{loader.dirs.first}/hanami/{constants,boot,errors,extensions/router/errors,prepare,rake_tasks,setup}.rb"
22
22
  )
23
23
  end
24
24
  end
@@ -144,8 +144,8 @@ module Hanami
144
144
  #
145
145
  # @api public
146
146
  # @since 2.0.0
147
- def self.env
148
- ENV.fetch("HANAMI_ENV") { ENV.fetch("RACK_ENV", "development") }.to_sym
147
+ def self.env(e: ENV)
148
+ e.fetch("HANAMI_ENV") { e.fetch("RACK_ENV", "development") }.to_sym
149
149
  end
150
150
 
151
151
  # Returns true if {.env} matches any of the given names
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App action / View rendering / View context", :app_integration do
4
+ subject(:context) {
5
+ # We capture the context during rendering via our view spies; see the view classes below
6
+ action.call("REQUEST_METHOD" => "GET", "QUERY_STRING" => "/mock_request")
7
+ action.view.called_with[:context]
8
+ }
9
+
10
+ before do
11
+ with_directory(make_tmp_directory) do
12
+ write "config/app.rb", <<~RUBY
13
+ module TestApp
14
+ class App < Hanami::App
15
+ end
16
+ end
17
+ RUBY
18
+
19
+ write "app/action.rb", <<~RUBY
20
+ # auto_register: false
21
+
22
+ require "hanami/view"
23
+
24
+ module TestApp
25
+ class Action < Hanami::Action
26
+ end
27
+ end
28
+ RUBY
29
+
30
+ before_prepare if respond_to?(:before_prepare)
31
+ require "hanami/prepare"
32
+ end
33
+ end
34
+
35
+ describe "app action" do
36
+ let(:action) { TestApp::App["actions.posts.show"] }
37
+
38
+ def before_prepare
39
+ write "app/actions/posts/show.rb", <<~RUBY
40
+ module TestApp
41
+ module Actions
42
+ module Posts
43
+ class Show < TestApp::Action
44
+ end
45
+ end
46
+ end
47
+ end
48
+ RUBY
49
+
50
+ # Custom view class as a spy for `#call` args
51
+ write "app/views/posts/show.rb", <<~RUBY
52
+ module TestApp
53
+ module Views
54
+ module Posts
55
+ class Show
56
+ attr_reader :called_with
57
+
58
+ def call(**args)
59
+ @called_with = args
60
+ ""
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ RUBY
67
+ end
68
+
69
+ context "no context class defined" do
70
+ it "defines and uses a context class" do
71
+ expect(context).to be_an_instance_of TestApp::Views::Context
72
+ expect(context.class).to be < Hanami::View::Context
73
+ end
74
+
75
+ it "includes the request" do
76
+ expect(context.request).to be_an_instance_of Hanami::Action::Request
77
+ expect(context.request.env["QUERY_STRING"]).to eq "/mock_request"
78
+ end
79
+ end
80
+
81
+ context "context class defined" do
82
+ def before_prepare
83
+ super()
84
+
85
+ write "app/views/context.rb", <<~RUBY
86
+ # auto_register: false
87
+
88
+ module TestApp
89
+ module Views
90
+ class Context < Hanami::View::Context
91
+ def concrete_app_context?
92
+ true
93
+ end
94
+ end
95
+ end
96
+ end
97
+ RUBY
98
+ end
99
+
100
+ it "uses the defined context class" do
101
+ expect(context).to be_an_instance_of TestApp::Views::Context
102
+ expect(context).to be_a_concrete_app_context
103
+ end
104
+ end
105
+
106
+ context "hanami-view not bundled" do
107
+ before do
108
+ allow(Hanami).to receive(:bundled?).and_call_original
109
+ expect(Hanami).to receive(:bundled?).with("hanami-view").and_return false
110
+ end
111
+
112
+ it "does not provide a context" do
113
+ expect(context).to be nil
114
+ end
115
+
116
+ context "context class defined" do
117
+ def before_prepare
118
+ super()
119
+
120
+ write "app/views/context.rb", <<~RUBY
121
+ module TestApp
122
+ module Views
123
+ class Context
124
+ def initialize(**)
125
+ end
126
+
127
+ def with(**)
128
+ self
129
+ end
130
+
131
+ def concrete_app_context?
132
+ true
133
+ end
134
+ end
135
+ end
136
+ end
137
+ RUBY
138
+ end
139
+
140
+ it "uses the defined context class" do
141
+ expect(context).to be_an_instance_of TestApp::Views::Context
142
+ expect(context).to be_a_concrete_app_context
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ describe "slice action" do
149
+ let(:action) { Main::Slice["actions.posts.show"] }
150
+
151
+ def before_prepare
152
+ write "slices/main/action.rb", <<~RUBY
153
+ module Main
154
+ class Action < TestApp::Action
155
+ end
156
+ end
157
+ RUBY
158
+
159
+ write "slices/main/actions/posts/show.rb", <<~RUBY
160
+ module Main
161
+ module Actions
162
+ module Posts
163
+ class Show < Main::Action
164
+ end
165
+ end
166
+ end
167
+ end
168
+ RUBY
169
+
170
+ # Custom view class as a spy for `#call` args
171
+ write "slices/main/views/posts/show.rb", <<~RUBY
172
+ module Main
173
+ module Views
174
+ module Posts
175
+ class Show
176
+ attr_reader :called_with
177
+
178
+ def call(**args)
179
+ @called_with = args
180
+ ""
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ RUBY
187
+ end
188
+
189
+ context "no context class defined" do
190
+ it "defines and uses a context class" do
191
+ expect(context).to be_an_instance_of Main::Views::Context
192
+ expect(context.class).to be < Hanami::View::Context
193
+ end
194
+ end
195
+
196
+ context "context class defined" do
197
+ def before_prepare
198
+ super()
199
+
200
+ write "slices/main/views/context.rb", <<~RUBY
201
+ # auto_register: false
202
+
203
+ module Main
204
+ module Views
205
+ class Context < Hanami::View::Context
206
+ def concrete_slice_context?
207
+ true
208
+ end
209
+ end
210
+ end
211
+ end
212
+ RUBY
213
+ end
214
+
215
+ it "uses the defined context class" do
216
+ expect(context).to be_an_instance_of Main::Views::Context
217
+ expect(context).to be_a_concrete_slice_context
218
+ end
219
+ end
220
+ end
221
+ end
@@ -32,24 +32,6 @@ RSpec.describe "App action / View rendering", :app_integration do
32
32
  end
33
33
  RUBY
34
34
 
35
- write "app/views/context.rb", <<~RUBY
36
- require "hanami/view/context"
37
-
38
- module TestApp
39
- module Views
40
- class Context < Hanami::View::Context
41
- def request
42
- _options.fetch(:request)
43
- end
44
-
45
- def response
46
- _options.fetch(:response)
47
- end
48
- end
49
- end
50
- end
51
- RUBY
52
-
53
35
  write "app/actions/users/show.rb", <<~RUBY
54
36
  module TestApp
55
37
  module Actions
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/test"
4
+ require "stringio"
5
+
6
+ RSpec.describe "Assets", :app_integration do
7
+ include Rack::Test::Methods
8
+ let(:app) { Hanami.app }
9
+ let(:root) { make_tmp_directory }
10
+
11
+ before do
12
+ with_directory(root) do
13
+ write "config/app.rb", <<~RUBY
14
+ module TestApp
15
+ class App < Hanami::App
16
+ config.logger.stream = StringIO.new
17
+ end
18
+ end
19
+ RUBY
20
+
21
+ write "config/routes.rb", <<~RUBY
22
+ module TestApp
23
+ class Routes < Hanami::Routes
24
+ get "posts/:id/edit", to: "posts.edit"
25
+ put "posts/:id", to: "posts.update"
26
+ end
27
+ end
28
+ RUBY
29
+
30
+ write "app/action.rb", <<~RUBY
31
+ # auto_register: false
32
+
33
+ require "hanami/action"
34
+
35
+ module TestApp
36
+ class Action < Hanami::Action
37
+ end
38
+ end
39
+ RUBY
40
+
41
+ write "app/view.rb", <<~RUBY
42
+ # auto_register: false
43
+
44
+ require "hanami/view"
45
+
46
+ module TestApp
47
+ class View < Hanami::View
48
+ config.layout = nil
49
+ end
50
+ end
51
+ RUBY
52
+
53
+ write "app/views/posts/show.rb", <<~RUBY
54
+ module TestApp
55
+ module Views
56
+ module Posts
57
+ class Show < TestApp::View
58
+ end
59
+ end
60
+ end
61
+ end
62
+ RUBY
63
+
64
+ write "app/templates/posts/show.html.erb", <<~ERB
65
+ <%= stylesheet_link_tag("app") %>
66
+ <%= css("app") %>
67
+ <%= javascript_tag("app") %>
68
+ <%= js("app") %>
69
+ ERB
70
+
71
+ write "app/assets/js/app.ts", <<~TS
72
+ import "../css/app.css";
73
+
74
+ console.log("Hello from index.ts");
75
+ TS
76
+
77
+ write "app/assets/css/app.css", <<~CSS
78
+ .btn {
79
+ background: #f00;
80
+ }
81
+ CSS
82
+
83
+ before_prepare if respond_to?(:before_prepare)
84
+ require "hanami/prepare"
85
+ end
86
+ end
87
+
88
+ specify "assets are available in helpers and in `assets` component" do
89
+ compile_assets!
90
+
91
+ output = Hanami.app["views.posts.show"].call.to_s
92
+
93
+ expect(output).to match(%r{<link href="/assets/app-[A-Z0-9]{8}.css" type="text/css" rel="stylesheet">})
94
+ expect(output).to match(%r{<script src="/assets/app-[A-Z0-9]{8}.js" type="text/javascript"></script>})
95
+
96
+ assets = Hanami.app["assets"]
97
+
98
+ expect(assets["app.css"].to_s).to match(%r{/assets/app-[A-Z0-9]{8}.css})
99
+ expect(assets["app.js"].to_s).to match(%r{/assets/app-[A-Z0-9]{8}.js})
100
+ end
101
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/test"
4
+ require "stringio"
5
+
6
+ RSpec.describe "Serve Static Assets", :app_integration do
7
+ include Rack::Test::Methods
8
+ let(:app) { Hanami.app }
9
+ let(:root) { make_tmp_directory }
10
+ let!(:env) { ENV.to_h }
11
+
12
+ before do
13
+ with_directory(root) do
14
+ write "config/app.rb", <<~RUBY
15
+ module TestApp
16
+ class App < Hanami::App
17
+ config.logger.stream = StringIO.new
18
+ end
19
+ end
20
+ RUBY
21
+
22
+ write "config/routes.rb", <<~RUBY
23
+ module TestApp
24
+ class Routes < Hanami::Routes
25
+ root to: ->(env) { [200, {}, ["Hello from root"]] }
26
+ end
27
+ end
28
+ RUBY
29
+
30
+ write "public/assets/app.js", <<~JS
31
+ console.log("Hello from app.js");
32
+ JS
33
+ end
34
+ end
35
+
36
+ after do
37
+ ENV.replace(env)
38
+ end
39
+
40
+ context "with default configuration" do
41
+ before do
42
+ with_directory(root) do
43
+ require "hanami/prepare"
44
+ end
45
+ end
46
+
47
+ it "serves static assets" do
48
+ get "/assets/app.js"
49
+
50
+ expect(last_response.status).to eq(200)
51
+ expect(last_response.body).to match(/Hello/)
52
+ end
53
+
54
+ it "returns 404 for missing asset" do
55
+ get "/assets/missing.js"
56
+
57
+ expect(last_response.status).to eq(404)
58
+ expect(last_response.body).to match(/Not Found/i)
59
+ end
60
+
61
+ it "doesn't escape from root directory" do
62
+ get "/assets/../../config/app.rb"
63
+
64
+ expect(last_response.status).to eq(404)
65
+ expect(last_response.body).to match(/Not Found/i)
66
+ end
67
+ end
68
+
69
+ context "when configuration is set to false" do
70
+ before do
71
+ with_directory(root) do
72
+ write "config/app.rb", <<~RUBY
73
+ module TestApp
74
+ class App < Hanami::App
75
+ config.logger.stream = StringIO.new
76
+ config.assets.serve = false
77
+ end
78
+ end
79
+ RUBY
80
+
81
+ require "hanami/boot"
82
+ end
83
+ end
84
+
85
+ it "doesn't serve static assets" do
86
+ get "/assets/app.js"
87
+
88
+ expect(last_response.status).to eq(404)
89
+ end
90
+ end
91
+
92
+ context "when env var is set to true" do
93
+ before do
94
+ with_directory(root) do
95
+ ENV["HANAMI_SERVE_ASSETS"] = "true"
96
+ require "hanami/boot"
97
+ end
98
+ end
99
+
100
+ it "serves static assets" do
101
+ get "/assets/app.js"
102
+
103
+ expect(last_response.status).to eq(200)
104
+ end
105
+ end
106
+
107
+ context "when env var is set to false" do
108
+ before do
109
+ with_directory(root) do
110
+ ENV["HANAMI_SERVE_ASSETS"] = "false"
111
+ require "hanami/boot"
112
+ end
113
+ end
114
+
115
+ it "doesn't serve static assets" do
116
+ get "/assets/app.js"
117
+
118
+ expect(last_response.status).to eq(404)
119
+ end
120
+ end
121
+
122
+ context "when Hanami.env is not :development or :test" do
123
+ before do
124
+ with_directory(root) do
125
+ ENV["HANAMI_ENV"] = "production"
126
+ require "hanami/boot"
127
+ end
128
+ end
129
+
130
+ it "doesn't serve static assets" do
131
+ get "/assets/app.js"
132
+
133
+ expect(last_response.status).to eq(404)
134
+ end
135
+ end
136
+
137
+ context "when Hanami.env is not :development or :test, but env var is set to true" do
138
+ before do
139
+ with_directory(root) do
140
+ ENV["HANAMI_ENV"] = "production"
141
+ ENV["HANAMI_SERVE_ASSETS"] = "true"
142
+ require "hanami/boot"
143
+ end
144
+ end
145
+
146
+ it "serves static assets" do
147
+ get "/assets/app.js"
148
+
149
+ expect(last_response.status).to eq(200)
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/test"
4
+ require "stringio"
5
+
6
+ RSpec.describe "Logging / Exception logging", :app_integration do
7
+ include Rack::Test::Methods
8
+
9
+ let(:app) { Hanami.app }
10
+
11
+ let(:logger_stream) { StringIO.new }
12
+
13
+ def configure_logger
14
+ Hanami.app.config.logger.stream = logger_stream
15
+ end
16
+
17
+ def logs
18
+ @logs ||= (logger_stream.rewind and logger_stream.read)
19
+ end
20
+
21
+ before do
22
+ with_directory(make_tmp_directory) do
23
+ write "config/app.rb", <<~RUBY
24
+ module TestApp
25
+ class App < Hanami::App
26
+ # Disable framework-level error rendering so we can test the raw action behavior
27
+ config.render_errors = false
28
+ config.render_detailed_errors = false
29
+ end
30
+ end
31
+ RUBY
32
+
33
+ write "config/routes.rb", <<~RUBY
34
+ module TestApp
35
+ class Routes < Hanami::Routes
36
+ root to: "test"
37
+ end
38
+ end
39
+ RUBY
40
+
41
+ require "hanami/setup"
42
+ configure_logger
43
+
44
+ before_prepare if respond_to?(:before_prepare)
45
+ require "hanami/prepare"
46
+ end
47
+ end
48
+
49
+ describe "unhandled exceptions" do
50
+ def before_prepare
51
+ write "app/actions/test.rb", <<~RUBY
52
+ module TestApp
53
+ module Actions
54
+ class Test < Hanami::Action
55
+ UnhandledError = Class.new(StandardError)
56
+
57
+ def handle(request, response)
58
+ raise UnhandledError, "unhandled"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ RUBY
64
+ end
65
+
66
+ it "logs a 500 error and full exception details when an exception is raised" do
67
+ # Make the request with a rescue so the raised exception doesn't crash the tests
68
+ begin
69
+ get "/"
70
+ rescue TestApp::Actions::Test::UnhandledError # rubocop:disable Lint/SuppressedException
71
+ end
72
+
73
+ expect(logs.lines.length).to be > 10
74
+ expect(logs).to match %r{GET 500 \d+(µs|ms) 127.0.0.1 /}
75
+ expect(logs).to include("unhandled (TestApp::Actions::Test::UnhandledError)")
76
+ expect(logs).to include("app/actions/test.rb:7:in `handle'")
77
+ end
78
+
79
+ it "re-raises the exception" do
80
+ expect { get "/" }.to raise_error(TestApp::Actions::Test::UnhandledError)
81
+ end
82
+ end
83
+
84
+ describe "errors handled by handle_exception" do
85
+ def before_prepare
86
+ write "app/actions/test.rb", <<~RUBY
87
+ module TestApp
88
+ module Actions
89
+ class Test < Hanami::Action
90
+ NotFoundError = Class.new(StandardError)
91
+
92
+ handle_exception NotFoundError => :handle_not_found_error
93
+
94
+ def handle(request, response)
95
+ raise NotFoundError
96
+ end
97
+
98
+ private
99
+
100
+ def handle_not_found_error(request, response, exception)
101
+ halt 404
102
+ end
103
+ end
104
+ end
105
+ end
106
+ RUBY
107
+ end
108
+
109
+ it "does not log an error" do
110
+ get "/"
111
+
112
+ expect(logs).to match %r{GET 404 \d+(µs|ms) 127.0.0.1 /}
113
+ end
114
+ end
115
+ end