hanami 2.1.0.beta1 → 2.1.0.beta2.1
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 +27 -4
- data/README.md +1 -1
- data/lib/hanami/app.rb +5 -0
- data/lib/hanami/config/actions.rb +4 -7
- data/lib/hanami/config/assets.rb +84 -0
- data/lib/hanami/config/null_config.rb +3 -0
- data/lib/hanami/config.rb +17 -5
- data/lib/hanami/extensions/action.rb +4 -2
- data/lib/hanami/extensions/view/standard_helpers.rb +4 -0
- data/lib/hanami/helpers/assets_helper.rb +772 -0
- data/lib/hanami/middleware/assets.rb +21 -0
- data/lib/hanami/middleware/render_errors.rb +4 -7
- 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 +25 -4
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami.rb +2 -2
- 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 +4 -4
- data/spec/integration/rack_app/rack_app_spec.rb +0 -221
- data/spec/integration/rake_tasks_spec.rb +107 -0
- data/spec/integration/view/context/assets_spec.rb +3 -9
- data/spec/integration/web/render_detailed_errors_spec.rb +17 -0
- data/spec/integration/web/render_errors_spec.rb +6 -4
- data/spec/support/app_integration.rb +46 -2
- 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 -2
- data/spec/unit/hanami/env_spec.rb +11 -25
- data/spec/unit/hanami/helpers/assets_helper/asset_url_spec.rb +109 -0
- data/spec/unit/hanami/helpers/assets_helper/audio_spec.rb +136 -0
- data/spec/unit/hanami/helpers/assets_helper/favicon_spec.rb +91 -0
- data/spec/unit/hanami/helpers/assets_helper/image_spec.rb +96 -0
- data/spec/unit/hanami/helpers/assets_helper/javascript_spec.rb +147 -0
- data/spec/unit/hanami/helpers/assets_helper/stylesheet_spec.rb +130 -0
- data/spec/unit/hanami/helpers/assets_helper/video_spec.rb +136 -0
- data/spec/unit/hanami/version_spec.rb +1 -1
- metadata +32 -4
- data/lib/hanami/assets/app_config.rb +0 -61
- data/lib/hanami/assets/config.rb +0 -53
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/static"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module Middleware
|
7
|
+
class Assets < Rack::Static
|
8
|
+
def initialize(app, options = {}, config: Hanami.app.config)
|
9
|
+
root = config.actions.public_directory
|
10
|
+
urls = [config.assets.path_prefix]
|
11
|
+
|
12
|
+
defaults = {
|
13
|
+
root: root,
|
14
|
+
urls: urls
|
15
|
+
}
|
16
|
+
|
17
|
+
super(app, defaults.merge(options))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -56,18 +56,15 @@ module Hanami
|
|
56
56
|
def call(env)
|
57
57
|
@app.call(env)
|
58
58
|
rescue Exception => exception
|
59
|
-
|
59
|
+
raise unless @config.render_errors
|
60
60
|
|
61
|
-
|
62
|
-
render_exception(request, exception)
|
63
|
-
else
|
64
|
-
raise exception
|
65
|
-
end
|
61
|
+
render_exception(env, exception)
|
66
62
|
end
|
67
63
|
|
68
64
|
private
|
69
65
|
|
70
|
-
def render_exception(
|
66
|
+
def render_exception(env, exception)
|
67
|
+
request = Rack::Request.new(env)
|
71
68
|
renderable = RenderableException.new(exception, responses: @config.render_error_responses)
|
72
69
|
|
73
70
|
status = renderable.status_code
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hanami
|
4
|
+
# @api private
|
5
|
+
module Providers
|
6
|
+
# Provider source to register routes helper component in Hanami slices.
|
7
|
+
#
|
8
|
+
# @see Hanami::Slice::RoutesHelper
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @since 2.0.0
|
12
|
+
class Assets < Dry::System::Provider::Source
|
13
|
+
# @api private
|
14
|
+
def self.for_slice(slice)
|
15
|
+
Class.new(self) do |klass|
|
16
|
+
klass.instance_variable_set(:@slice, slice)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def self.slice
|
22
|
+
@slice || Hanami.app
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def prepare
|
27
|
+
require "hanami/assets"
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def start
|
32
|
+
assets = Hanami::Assets.new(config: slice.config.assets)
|
33
|
+
|
34
|
+
register(:assets, assets)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def slice
|
40
|
+
self.class.slice
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/hanami/rake_tasks.rb
CHANGED
@@ -10,7 +10,7 @@ Hanami::CLI::RakeTasks.register_tasks do
|
|
10
10
|
|
11
11
|
# Ruby ecosystem compatibility
|
12
12
|
#
|
13
|
-
# Most of the SaaS automatic tasks are designed after Ruby on Rails.
|
13
|
+
# Most of the hosting SaaS automatic tasks are designed after Ruby on Rails.
|
14
14
|
# They expect the following Rake tasks to be present:
|
15
15
|
#
|
16
16
|
# * db:migrate
|
@@ -20,31 +20,32 @@ Hanami::CLI::RakeTasks.register_tasks do
|
|
20
20
|
#
|
21
21
|
# ===
|
22
22
|
#
|
23
|
-
# These Rake tasks
|
24
|
-
# want to encourage developers to use `hanami` commands.
|
23
|
+
# These Rake tasks are **NOT** listed when someone runs `rake -T`, because we
|
24
|
+
# want to encourage developers to use `hanami` CLI commands.
|
25
25
|
#
|
26
|
-
# In order to migrate the database or
|
27
|
-
# use:
|
26
|
+
# In order to migrate the database or compile assets a developer should use:
|
28
27
|
#
|
29
28
|
# * hanami db migrate
|
30
|
-
# * hanami assets
|
29
|
+
# * hanami assets compile
|
31
30
|
#
|
32
31
|
# This is the preferred way to run Hanami command line tasks.
|
33
32
|
# Please use them when you're in control of your deployment environment.
|
34
33
|
#
|
35
34
|
# If you're not in control and your deployment requires these "standard"
|
36
35
|
# Rake tasks, they are here to solve this only specific problem.
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
36
|
+
#
|
37
|
+
# namespace :db do
|
38
|
+
# task :migrate do
|
39
|
+
# # TODO(@jodosha): Enable when we'll integrate with ROM
|
40
|
+
# # run_hanami_command("db migrate")
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
|
44
|
+
if Hanami.bundled?("hanami-assets")
|
45
|
+
namespace :assets do
|
46
|
+
task :precompile do
|
47
|
+
run_hanami_command("assets compile")
|
48
|
+
end
|
48
49
|
end
|
49
50
|
end
|
50
51
|
|
@@ -53,7 +54,7 @@ Hanami::CLI::RakeTasks.register_tasks do
|
|
53
54
|
@_hanami_cli_bundler = Hanami::CLI::Bundler.new
|
54
55
|
|
55
56
|
def run_hanami_command(command)
|
56
|
-
@_hanami_cli_bundler.
|
57
|
+
@_hanami_cli_bundler.hanami_exec(command)
|
57
58
|
end
|
58
59
|
end
|
59
60
|
|
data/lib/hanami/settings.rb
CHANGED
@@ -45,7 +45,7 @@ module Hanami
|
|
45
45
|
#
|
46
46
|
# Setting values are loaded from a configurable store, which defaults to
|
47
47
|
# {Hanami::Settings::EnvStore}, which fetches the values from equivalent upper-cased keys in
|
48
|
-
# `ENV`. You can
|
48
|
+
# `ENV`. You can configure an alternative store via {Hanami::Config#settings_store}. Setting stores
|
49
49
|
# must implement a `#fetch` method with the same signature as `Hash#fetch`.
|
50
50
|
#
|
51
51
|
# [dry-c]: https://dry-rb.org/gems/dry-configurable/
|
data/lib/hanami/slice.rb
CHANGED
@@ -952,12 +952,21 @@ module Hanami
|
|
952
952
|
config = self.config
|
953
953
|
rack_monitor = self["rack.monitor"]
|
954
954
|
|
955
|
+
render_errors = render_errors?
|
956
|
+
render_detailed_errors = render_detailed_errors?
|
957
|
+
|
958
|
+
error_handlers = {}.tap do |hsh|
|
959
|
+
if render_errors || render_detailed_errors
|
960
|
+
hsh[:not_allowed] = ROUTER_NOT_ALLOWED_HANDLER
|
961
|
+
hsh[:not_found] = ROUTER_NOT_FOUND_HANDLER
|
962
|
+
end
|
963
|
+
end
|
964
|
+
|
955
965
|
Slice::Router.new(
|
956
966
|
inspector: inspector,
|
957
967
|
routes: routes,
|
958
968
|
resolver: config.router.resolver.new(slice: self),
|
959
|
-
|
960
|
-
not_found: ROUTER_NOT_FOUND_HANDLER,
|
969
|
+
**error_handlers,
|
961
970
|
**config.router.options
|
962
971
|
) do
|
963
972
|
use(rack_monitor)
|
@@ -968,19 +977,31 @@ module Hanami
|
|
968
977
|
Hanami::Middleware::PublicErrorsApp.new(slice.root.join("public"))
|
969
978
|
)
|
970
979
|
|
971
|
-
if
|
980
|
+
if render_detailed_errors
|
972
981
|
require "hanami/webconsole"
|
973
|
-
use(Hanami::Webconsole::Middleware)
|
982
|
+
use(Hanami::Webconsole::Middleware, config)
|
974
983
|
end
|
975
984
|
|
976
985
|
if Hanami.bundled?("hanami-controller") && config.actions.sessions.enabled?
|
977
986
|
use(*config.actions.sessions.middleware)
|
978
987
|
end
|
979
988
|
|
989
|
+
if Hanami.bundled?("hanami-assets") && config.assets.serve
|
990
|
+
use(Hanami::Middleware::Assets)
|
991
|
+
end
|
992
|
+
|
980
993
|
middleware_stack.update(config.middleware_stack)
|
981
994
|
end
|
982
995
|
end
|
983
996
|
|
997
|
+
def render_errors?
|
998
|
+
config.render_errors
|
999
|
+
end
|
1000
|
+
|
1001
|
+
def render_detailed_errors?
|
1002
|
+
config.render_detailed_errors && Hanami.bundled?("hanami-webconsole")
|
1003
|
+
end
|
1004
|
+
|
984
1005
|
ROUTER_NOT_ALLOWED_HANDLER = -> env, allowed_http_methods {
|
985
1006
|
raise Hanami::Router::NotAllowedError.new(env, allowed_http_methods)
|
986
1007
|
}.freeze
|
data/lib/hanami/version.rb
CHANGED
data/lib/hanami.rb
CHANGED
@@ -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,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
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/test"
|
4
|
+
|
5
|
+
RSpec.describe "Logging / Notifications", :app_integration do
|
6
|
+
include Rack::Test::Methods
|
7
|
+
|
8
|
+
let(:app) { Hanami.app }
|
9
|
+
|
10
|
+
specify "Request logging continues even when notifications bus has already been used" do
|
11
|
+
dir = Dir.mktmpdir
|
12
|
+
|
13
|
+
with_tmp_directory(dir) do
|
14
|
+
write "config/app.rb", <<~RUBY
|
15
|
+
require "hanami"
|
16
|
+
|
17
|
+
module TestApp
|
18
|
+
class App < Hanami::App
|
19
|
+
config.actions.format :json
|
20
|
+
config.logger.options = {colorize: true}
|
21
|
+
config.logger.stream = config.root.join("test.log")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
RUBY
|
25
|
+
|
26
|
+
write "config/routes.rb", <<~RUBY
|
27
|
+
module TestApp
|
28
|
+
class Routes < Hanami::Routes
|
29
|
+
post "/users", to: "users.create"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
RUBY
|
33
|
+
|
34
|
+
write "app/actions/users/create.rb", <<~RUBY
|
35
|
+
module TestApp
|
36
|
+
module Actions
|
37
|
+
module Users
|
38
|
+
class Create < Hanami::Action
|
39
|
+
def handle(req, resp)
|
40
|
+
resp.body = req.params.to_h.keys
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
RUBY
|
47
|
+
|
48
|
+
require "hanami/prepare"
|
49
|
+
|
50
|
+
# Simulate any component interacting with the notifications bus such that it creates its
|
51
|
+
# internal bus with a duplicate copy of all currently registered events. This means that the
|
52
|
+
# class-level Dry::Monitor::Notification events implicitly registered by the
|
53
|
+
# Dry::Monitor::Rack::Middleware activated via the rack provider are ignored, unless our
|
54
|
+
# provider explicitly re-registers them on _instance_ of the notifications bus.
|
55
|
+
#
|
56
|
+
# See Hanami::Providers::Rack for more detail.
|
57
|
+
Hanami.app["notifications"].instrument(:sql)
|
58
|
+
|
59
|
+
logs = -> { Pathname(dir).join("test.log").realpath.read }
|
60
|
+
|
61
|
+
post "/users", JSON.generate(name: "jane", password: "secret"), {"CONTENT_TYPE" => "application/json"}
|
62
|
+
expect(logs.()).to match %r{POST 200 \d+(µs|ms) 127.0.0.1 /}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
|