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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -2
- data/LICENSE.md +1 -1
- data/README.md +26 -10
- data/hanami.gemspec +2 -2
- data/lib/hanami/app.rb +5 -0
- data/lib/hanami/config/actions.rb +4 -11
- data/lib/hanami/config/assets.rb +84 -0
- data/lib/hanami/config/null_config.rb +3 -0
- data/lib/hanami/config/views.rb +0 -4
- data/lib/hanami/config.rb +71 -5
- data/lib/hanami/extensions/action/slice_configured_action.rb +15 -7
- data/lib/hanami/extensions/action.rb +8 -6
- data/lib/hanami/extensions/router/errors.rb +58 -0
- data/lib/hanami/extensions/view/context.rb +129 -60
- data/lib/hanami/extensions/view/part.rb +26 -0
- data/lib/hanami/extensions/view/scope.rb +26 -0
- data/lib/hanami/extensions/view/slice_configured_context.rb +0 -2
- data/lib/hanami/extensions/view/slice_configured_helpers.rb +44 -0
- data/lib/hanami/extensions/view/slice_configured_view.rb +106 -21
- data/lib/hanami/extensions/view/standard_helpers.rb +18 -0
- data/lib/hanami/extensions.rb +10 -3
- data/lib/hanami/helpers/assets_helper.rb +752 -0
- data/lib/hanami/helpers/form_helper/form_builder.rb +1391 -0
- data/lib/hanami/helpers/form_helper/values.rb +75 -0
- data/lib/hanami/helpers/form_helper.rb +213 -0
- data/lib/hanami/middleware/assets.rb +21 -0
- data/lib/hanami/middleware/public_errors_app.rb +75 -0
- data/lib/hanami/middleware/render_errors.rb +90 -0
- data/lib/hanami/providers/assets.rb +44 -0
- data/lib/hanami/rake_tasks.rb +19 -18
- data/lib/hanami/settings.rb +1 -1
- data/lib/hanami/slice.rb +48 -2
- data/lib/hanami/slice_configurable.rb +3 -2
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami/web/rack_logger.rb +1 -1
- data/lib/hanami.rb +3 -3
- data/spec/integration/action/view_rendering/view_context_spec.rb +221 -0
- data/spec/integration/action/view_rendering_spec.rb +0 -18
- data/spec/integration/assets/assets_spec.rb +101 -0
- data/spec/integration/assets/serve_static_assets_spec.rb +152 -0
- data/spec/integration/logging/exception_logging_spec.rb +115 -0
- data/spec/integration/logging/notifications_spec.rb +68 -0
- data/spec/integration/logging/request_logging_spec.rb +128 -0
- data/spec/integration/rack_app/middleware_spec.rb +22 -22
- data/spec/integration/rack_app/rack_app_spec.rb +3 -220
- data/spec/integration/rake_tasks_spec.rb +107 -0
- data/spec/integration/view/config/default_context_spec.rb +149 -0
- data/spec/integration/view/{inflector_spec.rb → config/inflector_spec.rb} +1 -1
- data/spec/integration/view/config/part_class_spec.rb +147 -0
- data/spec/integration/view/config/part_namespace_spec.rb +103 -0
- data/spec/integration/view/config/paths_spec.rb +119 -0
- data/spec/integration/view/config/scope_class_spec.rb +147 -0
- data/spec/integration/view/config/scope_namespace_spec.rb +103 -0
- data/spec/integration/view/config/template_spec.rb +38 -0
- data/spec/integration/view/context/assets_spec.rb +3 -9
- data/spec/integration/view/context/request_spec.rb +3 -7
- data/spec/integration/view/helpers/form_helper_spec.rb +174 -0
- data/spec/integration/view/helpers/part_helpers_spec.rb +124 -0
- data/spec/integration/view/helpers/scope_helpers_spec.rb +84 -0
- data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +162 -0
- data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +119 -0
- data/spec/integration/view/slice_configuration_spec.rb +9 -9
- data/spec/integration/web/render_detailed_errors_spec.rb +107 -0
- data/spec/integration/web/render_errors_spec.rb +242 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/app_integration.rb +46 -2
- data/spec/support/matchers.rb +32 -0
- data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +24 -36
- data/spec/unit/hanami/config/actions/csrf_protection_spec.rb +4 -3
- data/spec/unit/hanami/config/actions/default_values_spec.rb +3 -6
- data/spec/unit/hanami/config/render_detailed_errors_spec.rb +25 -0
- data/spec/unit/hanami/config/render_errors_spec.rb +25 -0
- data/spec/unit/hanami/config/views_spec.rb +0 -18
- data/spec/unit/hanami/env_spec.rb +11 -25
- data/spec/unit/hanami/extensions/view/context_spec.rb +59 -0
- data/spec/unit/hanami/helpers/assets_helper/asset_url_spec.rb +109 -0
- data/spec/unit/hanami/helpers/assets_helper/audio_tag_spec.rb +132 -0
- data/spec/unit/hanami/helpers/assets_helper/favicon_link_tag_spec.rb +91 -0
- data/spec/unit/hanami/helpers/assets_helper/image_tag_spec.rb +92 -0
- data/spec/unit/hanami/helpers/assets_helper/javascript_tag_spec.rb +143 -0
- data/spec/unit/hanami/helpers/assets_helper/stylesheet_link_tag_spec.rb +126 -0
- data/spec/unit/hanami/helpers/assets_helper/video_tag_spec.rb +132 -0
- data/spec/unit/hanami/helpers/form_helper_spec.rb +2826 -0
- data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +27 -0
- data/spec/unit/hanami/router/errors/not_found_error_spec.rb +22 -0
- data/spec/unit/hanami/slice_configurable_spec.rb +18 -0
- data/spec/unit/hanami/version_spec.rb +1 -1
- data/spec/unit/hanami/web/rack_logger_spec.rb +1 -1
- metadata +95 -35
- data/lib/hanami/assets/app_config.rb +0 -61
- data/lib/hanami/assets/config.rb +0 -53
- data/spec/integration/action/view_integration_spec.rb +0 -165
- data/spec/integration/view/part_namespace_spec.rb +0 -96
- data/spec/integration/view/path_spec.rb +0 -56
- data/spec/integration/view/template_spec.rb +0 -68
- data/spec/isolation/hanami/application/already_configured_spec.rb +0 -19
- data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +0 -10
- data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +0 -14
- data/spec/isolation/hanami/application/not_configured_spec.rb +0 -9
- data/spec/isolation/hanami/application/routes/configured_spec.rb +0 -44
- data/spec/isolation/hanami/application/routes/not_configured_spec.rb +0 -16
- data/spec/isolation/hanami/boot/success_spec.rb +0 -50
data/lib/hanami/version.rb
CHANGED
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
|
-
|
|
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
|