hanami 2.0.0.beta4 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/hanami.gemspec +8 -7
  4. data/lib/hanami/app.rb +47 -36
  5. data/lib/hanami/assets/app_config.rb +7 -15
  6. data/lib/hanami/assets/config.rb +5 -6
  7. data/lib/hanami/config/actions/content_security_policy.rb +1 -1
  8. data/lib/hanami/config/actions/cookies.rb +27 -0
  9. data/lib/hanami/config/actions/sessions.rb +42 -5
  10. data/lib/hanami/config/actions.rb +81 -17
  11. data/lib/hanami/config/logger.rb +112 -23
  12. data/lib/hanami/config/router.rb +0 -1
  13. data/lib/hanami/config/views.rb +6 -10
  14. data/lib/hanami/config.rb +235 -73
  15. data/lib/hanami/constants.rb +4 -0
  16. data/lib/hanami/errors.rb +17 -0
  17. data/lib/hanami/extensions/action/slice_configured_action.rb +9 -5
  18. data/lib/hanami/extensions/action.rb +59 -7
  19. data/lib/hanami/extensions/view/context.rb +3 -4
  20. data/lib/hanami/extensions/view/slice_configured_view.rb +4 -4
  21. data/lib/hanami/extensions/view.rb +7 -5
  22. data/lib/hanami/providers/inflector.rb +6 -0
  23. data/lib/hanami/providers/logger.rb +8 -0
  24. data/lib/hanami/providers/rack.rb +12 -0
  25. data/lib/hanami/providers/routes.rb +14 -4
  26. data/lib/hanami/routes.rb +36 -1
  27. data/lib/hanami/settings/env_store.rb +1 -1
  28. data/lib/hanami/settings.rb +102 -36
  29. data/lib/hanami/slice/router.rb +38 -16
  30. data/lib/hanami/slice/routing/middleware/stack.rb +66 -42
  31. data/lib/hanami/slice/routing/resolver.rb +10 -17
  32. data/lib/hanami/slice/view_name_inferrer.rb +1 -1
  33. data/lib/hanami/slice.rb +553 -14
  34. data/lib/hanami/slice_registrar.rb +20 -15
  35. data/lib/hanami/version.rb +2 -3
  36. data/lib/hanami/web/rack_logger.rb +14 -4
  37. data/lib/hanami.rb +122 -23
  38. data/spec/integration/action/csrf_protection_spec.rb +1 -1
  39. data/spec/integration/container/application_routes_helper_spec.rb +3 -1
  40. data/spec/integration/container/provider_lifecycle_spec.rb +61 -0
  41. data/spec/integration/container/standard_providers/rack_provider_spec.rb +44 -0
  42. data/spec/integration/container/{standard_bootable_components_spec.rb → standard_providers_spec.rb} +3 -3
  43. data/spec/integration/rack_app/body_parser_spec.rb +3 -0
  44. data/spec/integration/rack_app/middleware_spec.rb +427 -3
  45. data/spec/integration/rack_app/non_booted_rack_app_spec.rb +2 -1
  46. data/spec/integration/rack_app/rack_app_spec.rb +39 -11
  47. data/spec/integration/setup_spec.rb +4 -4
  48. data/spec/integration/slices/external_slice_spec.rb +2 -1
  49. data/spec/integration/slices/slice_configuration_spec.rb +3 -1
  50. data/spec/integration/slices/slice_loading_spec.rb +4 -4
  51. data/spec/integration/slices/slice_routing_spec.rb +4 -3
  52. data/spec/integration/slices_spec.rb +100 -0
  53. data/spec/isolation/hanami/boot/success_spec.rb +1 -1
  54. data/spec/support/app_integration.rb +2 -10
  55. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +7 -7
  56. data/spec/unit/hanami/config/actions/default_values_spec.rb +1 -1
  57. data/spec/unit/hanami/config/actions/sessions_spec.rb +1 -3
  58. data/spec/unit/hanami/config/actions_spec.rb +1 -12
  59. data/spec/unit/hanami/config/logger_spec.rb +38 -55
  60. data/spec/unit/hanami/config/router_spec.rb +1 -1
  61. data/spec/unit/hanami/config/views_spec.rb +3 -13
  62. data/spec/unit/hanami/settings_spec.rb +1 -1
  63. data/spec/unit/hanami/slice_configurable_spec.rb +5 -5
  64. data/spec/unit/hanami/slice_spec.rb +32 -0
  65. data/spec/unit/hanami/version_spec.rb +1 -1
  66. data/spec/unit/hanami/web/rack_logger_spec.rb +13 -2
  67. metadata +54 -45
  68. data/lib/hanami/config/sessions.rb +0 -50
  69. data/spec/unit/hanami/config_spec.rb +0 -43
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "constants"
4
- require_relative "slice"
5
4
 
6
5
  module Hanami
7
6
  # @api private
8
7
  class SliceRegistrar
8
+ VALID_SLICE_NAME_RE = /^[a-z][a-z0-9_]+$/
9
9
  SLICE_DELIMITER = CONTAINER_KEY_DELIMITER
10
10
 
11
11
  attr_reader :parent, :slices
@@ -17,14 +17,16 @@ module Hanami
17
17
  end
18
18
 
19
19
  def register(name, slice_class = nil, &block)
20
+ unless name.to_s =~ VALID_SLICE_NAME_RE
21
+ raise ArgumentError, "slice name #{name.inspect} must be lowercase alphanumeric text and underscores only"
22
+ end
23
+
20
24
  return unless filter_slice_names([name]).any?
21
25
 
22
26
  if slices.key?(name.to_sym)
23
27
  raise SliceLoadError, "Slice '#{name}' is already registered"
24
28
  end
25
29
 
26
- # TODO: raise error unless name meets format (i.e. single level depth only)
27
-
28
30
  slice = slice_class || build_slice(name, &block)
29
31
 
30
32
  configure_slice(name, slice)
@@ -44,8 +46,6 @@ module Hanami
44
46
  end
45
47
 
46
48
  def load_slices
47
- return self unless root
48
-
49
49
  slice_configs = Dir[root.join(CONFIG_DIR, SLICES_DIR, "*#{RB_EXT}")]
50
50
  .map { |file| File.basename(file, RB_EXT) }
51
51
 
@@ -77,7 +77,9 @@ module Hanami
77
77
 
78
78
  def with_nested
79
79
  to_a.flat_map { |slice|
80
- [slice] + slice.slices.with_nested
80
+ # Return nested slices first so that their more specific namespaces may be picked up first
81
+ # by SliceConfigurable#slice_for
82
+ slice.slices.with_nested + [slice]
81
83
  }
82
84
  end
83
85
 
@@ -91,36 +93,39 @@ module Hanami
91
93
  parent.inflector
92
94
  end
93
95
 
94
- # Runs when a slice file has been found at `config/slices/[slice_name].rb`, or a slice
95
- # directory at `slices/[slice_name]`. Attempts to require the slice class, if defined,
96
- # or generates a new slice class for the given slice name.
96
+ def parent_slice_namespace
97
+ parent.eql?(parent.app) ? Object : parent.namespace
98
+ end
99
+
100
+ # Runs when a slice file has been found at `config/slices/[slice_name].rb`, or a slice directory
101
+ # at `slices/[slice_name]`. Attempts to require the slice class, if defined, before registering
102
+ # the slice. If a slice class is not found, registering the slice will generate the slice class.
97
103
  def load_slice(slice_name)
98
- slice_const_name = inflector.camelize(slice_name)
99
104
  slice_require_path = root.join(CONFIG_DIR, SLICES_DIR, slice_name).to_s
100
-
101
105
  begin
102
106
  require(slice_require_path)
103
107
  rescue LoadError => e
104
108
  raise e unless e.path == slice_require_path
105
109
  end
106
110
 
111
+ slice_module_name = inflector.camelize("#{parent_slice_namespace.name}#{PATH_DELIMITER}#{slice_name}")
107
112
  slice_class =
108
113
  begin
109
- inflector.constantize("#{slice_const_name}::Slice")
114
+ inflector.constantize("#{slice_module_name}#{MODULE_DELIMITER}Slice")
110
115
  rescue NameError => e
111
- raise e unless e.name.to_s == slice_const_name || e.name.to_s == :Slice
116
+ raise e unless e.name.to_s == inflector.camelize(slice_name) || e.name.to_s == :Slice
112
117
  end
113
118
 
114
119
  register(slice_name, slice_class)
115
120
  end
116
121
 
117
122
  def build_slice(slice_name, &block)
123
+ slice_module_name = inflector.camelize("#{parent_slice_namespace.name}#{PATH_DELIMITER}#{slice_name}")
118
124
  slice_module =
119
125
  begin
120
- slice_module_name = inflector.camelize(slice_name.to_s)
121
126
  inflector.constantize(slice_module_name)
122
127
  rescue NameError
123
- Object.const_set(inflector.camelize(slice_module_name), Module.new)
128
+ parent_slice_namespace.const_set(inflector.camelize(slice_name), Module.new)
124
129
  end
125
130
 
126
131
  slice_module.const_set(:Slice, Class.new(Hanami::Slice, &block))
@@ -6,9 +6,8 @@ module Hanami
6
6
  # @since 0.9.0
7
7
  # @api private
8
8
  module Version
9
- # @since 0.9.0
10
- # @api private
11
- VERSION = "2.0.0.beta4"
9
+ # @api public
10
+ VERSION = "2.0.0.rc1"
12
11
 
13
12
  # @since 0.9.0
14
13
  # @api private
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hanami
4
+ # @api private
4
5
  module Web
5
6
  # Rack logger for Hanami apps
7
+ #
8
+ # @api private
9
+ # @since 2.0.0
6
10
  class RackLogger
7
11
  REQUEST_METHOD = "REQUEST_METHOD"
8
12
  private_constant :REQUEST_METHOD
@@ -25,10 +29,14 @@ module Hanami
25
29
  CONTENT_LENGTH = "Content-Length"
26
30
  private_constant :CONTENT_LENGTH
27
31
 
32
+ # @api private
33
+ # @since 2.0.0
28
34
  def initialize(logger)
29
35
  @logger = logger
30
36
  end
31
37
 
38
+ # @api private
39
+ # @since 2.0.0
32
40
  def attach(rack_monitor)
33
41
  rack_monitor.on :stop do |event|
34
42
  log_request event[:env], event[:status], event[:time]
@@ -39,6 +47,8 @@ module Hanami
39
47
  end
40
48
  end
41
49
 
50
+ # @api private
51
+ # @since 2.0.0
42
52
  def log_request(env, status, elapsed)
43
53
  data = {
44
54
  verb: env[REQUEST_METHOD],
@@ -47,16 +57,16 @@ module Hanami
47
57
  ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
48
58
  path: env[SCRIPT_NAME] + env[PATH_INFO].to_s,
49
59
  length: extract_content_length(env),
50
- params: env[ROUTER_PARAMS],
51
- time: Time.now,
60
+ params: env[ROUTER_PARAMS]
52
61
  }
53
62
 
54
63
  logger.info(data)
55
64
  end
56
65
 
66
+ # @api private
67
+ # @since 2.0.0
57
68
  def log_exception(exception)
58
- logger.error exception.message
59
- logger.error exception.backtrace.join("\n")
69
+ logger.error(exception)
60
70
  end
61
71
 
62
72
  private
data/lib/hanami.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "zeitwerk"
4
+ require_relative "hanami/constants"
5
+
3
6
  # A complete web framework for Ruby
4
7
  #
5
8
  # @since 0.1.0
@@ -9,11 +12,21 @@ module Hanami
9
12
  @_mutex = Mutex.new
10
13
  @_bundled = {}
11
14
 
15
+ # @api private
16
+ # @since 2.0.0
17
+ def self.loader
18
+ @loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
19
+ loader.ignore(
20
+ "#{loader.dirs.first}/hanami/{constants,boot,errors,prepare,rake_tasks,setup}.rb"
21
+ )
22
+ end
23
+ end
24
+
12
25
  # Finds and loads the Hanami app file (`config/app.rb`).
13
26
  #
14
27
  # Raises an exception if the app file cannot be found.
15
28
  #
16
- # @return [Hanami::App] the loaded app class
29
+ # @return [app] the loaded app class
17
30
  #
18
31
  # @api public
19
32
  # @since 2.0.0
@@ -34,32 +47,18 @@ module Hanami
34
47
  end
35
48
  end
36
49
 
37
- # Finds and returns the absolute path for the Hanami app file (`config/app.rb`).
50
+ # Returns the Hamami app class.
38
51
  #
39
- # Searches within the given directory, then searches upwards through parent directories until the
40
- # app file can be found.
52
+ # To ensure your Hanami app is loaded, run {.setup} (or `require "hanami/setup"`) first.
41
53
  #
42
- # @param dir [String] The directory from which to start searching. Defaults to the current
43
- # directory.
54
+ # @return [Hanami::App] the app class
44
55
  #
45
- # @return [String, nil] the app file path, or nil if not found.
56
+ # @raise [AppLoadError] if the app has not been loaded
57
+ #
58
+ # @see .setup
46
59
  #
47
60
  # @api public
48
61
  # @since 2.0.0
49
- def self.app_path(dir = Dir.pwd)
50
- dir = Pathname(dir).expand_path
51
- path = dir.join(APP_PATH)
52
-
53
- if path.file?
54
- path.to_s
55
- elsif !dir.root?
56
- app_path(dir.parent)
57
- end
58
- end
59
-
60
- APP_PATH = "config/app.rb"
61
- private_constant :APP_PATH
62
-
63
62
  def self.app
64
63
  @_mutex.synchronize do
65
64
  unless defined?(@_app)
@@ -72,10 +71,18 @@ module Hanami
72
71
  end
73
72
  end
74
73
 
74
+ # Returns true if the Hanami app class has been loaded.
75
+ #
76
+ # @return [Boolean]
77
+ #
78
+ # @api public
79
+ # @since 2.0.0
75
80
  def self.app?
76
81
  instance_variable_defined?(:@_app)
77
82
  end
78
83
 
84
+ # @api private
85
+ # @since 2.0.0
79
86
  def self.app=(klass)
80
87
  @_mutex.synchronize do
81
88
  if instance_variable_defined?(:@_app)
@@ -86,30 +93,117 @@ module Hanami
86
93
  end
87
94
  end
88
95
 
96
+ # Finds and returns the absolute path for the Hanami app file (`config/app.rb`).
97
+ #
98
+ # Searches within the given directory, then searches upwards through parent directories until the
99
+ # app file can be found.
100
+ #
101
+ # @param dir [String] The directory from which to start searching. Defaults to the current
102
+ # directory.
103
+ #
104
+ # @return [String, nil] the app file path, or nil if not found.
105
+ #
106
+ # @api public
107
+ # @since 2.0.0
108
+ def self.app_path(dir = Dir.pwd)
109
+ dir = Pathname(dir).expand_path
110
+ path = dir.join(APP_PATH)
111
+
112
+ if path.file?
113
+ path.to_s
114
+ elsif !dir.root?
115
+ app_path(dir.parent)
116
+ end
117
+ end
118
+
119
+ # Returns the Hanami app environment as loaded from the `HANAMI_ENV` environment variable.
120
+ #
121
+ # @example
122
+ # Hanami.env # => :development
123
+ #
124
+ # @return [Symbol] the environment name
125
+ #
126
+ # @api public
127
+ # @since 2.0.0
89
128
  def self.env
90
129
  ENV.fetch("HANAMI_ENV") { ENV.fetch("RACK_ENV", "development") }.to_sym
91
130
  end
92
131
 
132
+ # Returns true if {.env} matches any of the given names
133
+ #
134
+ # @example
135
+ # Hanami.env # => :development
136
+ # Hanami.env?(:development, :test) # => true
137
+ #
138
+ # @param names [Array<Symbol>] the environment names to check
139
+ #
140
+ # @return [Boolean]
141
+ #
142
+ # @api public
143
+ # @since 2.0.0
93
144
  def self.env?(*names)
94
145
  names.map(&:to_sym).include?(env)
95
146
  end
96
147
 
148
+ # Returns the app's logger.
149
+ #
150
+ # Direct global access to the logger via this method is not recommended. Instead, consider
151
+ # accessing the logger via the app or slice container, in most cases as an dependency using the
152
+ # `Deps` mixin.
153
+ #
154
+ # @example
155
+ # # app/my_component.rb
156
+ #
157
+ # module MyApp
158
+ # class MyComponent
159
+ # include Deps["logger"]
160
+ #
161
+ # def some_method
162
+ # logger.info("hello")
163
+ # end
164
+ # end
165
+ # end
166
+ #
167
+ # @return [Dry::Logger::Dispatcher]
168
+ #
169
+ # @api public
170
+ # @since 1.0.0
97
171
  def self.logger
98
172
  app[:logger]
99
173
  end
100
174
 
175
+ # Prepares the Hanami app.
176
+ #
177
+ # @see App::ClassMethods#prepare
178
+ #
179
+ # @api public
180
+ # @since 2.0.0
101
181
  def self.prepare
102
182
  app.prepare
103
183
  end
104
184
 
185
+ # Boots the Hanami app.
186
+ #
187
+ # @see App::ClassMethods#boot
188
+ #
189
+ # @api public
190
+ # @since 2.0.0
105
191
  def self.boot
106
192
  app.boot
107
193
  end
108
194
 
195
+ # Shuts down the Hanami app.
196
+ #
197
+ # @see App::ClassMethods#shutdown
198
+ #
199
+ # @api public
200
+ # @since 2.0.0
109
201
  def self.shutdown
110
202
  app.shutdown
111
203
  end
112
204
 
205
+ # @api private
206
+ # @since 2.0.0
113
207
  def self.bundled?(gem_name)
114
208
  @_mutex.synchronize do
115
209
  @_bundled[gem_name] ||= begin
@@ -120,12 +214,17 @@ module Hanami
120
214
  end
121
215
  end
122
216
 
217
+ # Returns an array of bundler group names to be eagerly loaded by hanami-cli and other CLI
218
+ # extensions.
219
+ #
220
+ # @api private
221
+ # @since 2.0.0
123
222
  def self.bundler_groups
124
223
  [:plugins]
125
224
  end
126
225
 
127
- require_relative "hanami/version"
226
+ loader.setup
227
+
128
228
  require_relative "hanami/errors"
129
229
  require_relative "hanami/extensions"
130
- require_relative "hanami/app"
131
230
  end
@@ -35,7 +35,7 @@ RSpec.describe "App action / CSRF protection", :app_integration do
35
35
  context "CSRF protection explicitly disabled" do
36
36
  let(:app_hook) {
37
37
  proc do
38
- config.sessions = :cookie, {secret: "abc123"}
38
+ config.actions.sessions = :cookie, {secret: "abc123"}
39
39
  config.actions.csrf_protection = false
40
40
  end
41
41
  }
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "stringio"
4
+
3
5
  RSpec.describe "App routes helper", :app_integration do
4
6
  specify "Routing to actions based on their container identifiers" do
5
7
  with_tmp_directory(Dir.mktmpdir) do
@@ -8,7 +10,7 @@ RSpec.describe "App routes helper", :app_integration do
8
10
 
9
11
  module TestApp
10
12
  class App < Hanami::App
11
- config.logger.stream = File.new("/dev/null", "w")
13
+ config.logger.stream = StringIO.new
12
14
  end
13
15
  end
14
16
  RUBY
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Container / Provider lifecycle", :app_integration do
4
+ let!(:slice) {
5
+ module TestApp
6
+ class App < Hanami::App
7
+ register_provider :connection do
8
+ prepare do
9
+ module ::TestApp
10
+ class Connection
11
+ def initialize
12
+ @connected = true
13
+ end
14
+
15
+ def disconnect
16
+ @connected = false
17
+ end
18
+
19
+ def connected?
20
+ @connected
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ start do
27
+ register("connection", TestApp::Connection.new)
28
+ end
29
+
30
+ stop do
31
+ container["connection"].disconnect
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ TestApp::App
38
+ }
39
+
40
+ before do
41
+ require "hanami/setup"
42
+ end
43
+
44
+ specify "individual providers can be prepared, started and stopped" do
45
+ expect { TestApp::Connection }.to raise_error NameError
46
+
47
+ slice.prepare :connection
48
+
49
+ expect(TestApp::Connection).to be
50
+ expect(slice.container.registered?("connection")).to be false
51
+
52
+ slice.start :connection
53
+
54
+ expect(slice.container.registered?("connection")).to be true
55
+ expect(slice["connection"]).to be_connected
56
+
57
+ slice.stop :connection
58
+
59
+ expect(slice["connection"]).not_to be_connected
60
+ end
61
+ end
@@ -0,0 +1,44 @@
1
+ RSpec.describe "Container / Standard providers / Rack", :app_integration do
2
+ specify "Rack provider is loaded when rack is bundled" do
3
+ with_tmp_directory(Dir.mktmpdir) do
4
+ write "config/app.rb", <<~RUBY
5
+ require "hanami"
6
+
7
+ module TestApp
8
+ class App < Hanami::App
9
+ end
10
+ end
11
+ RUBY
12
+
13
+ write "slices/main/.keep", ""
14
+
15
+ require "hanami/prepare"
16
+
17
+ expect(Hanami.app["rack.monitor"]).to be_a_kind_of(Dry::Monitor::Rack::Middleware)
18
+ expect(Main::Slice["rack.monitor"]).to be_a_kind_of(Dry::Monitor::Rack::Middleware)
19
+ end
20
+ end
21
+
22
+ specify "Rack provider is not loaded when rack is not bundled" do
23
+ allow(Hanami).to receive(:bundled?).and_call_original
24
+ allow(Hanami).to receive(:bundled?).with("rack").and_return false
25
+
26
+ with_tmp_directory(Dir.mktmpdir) do
27
+ write "config/app.rb", <<~RUBY
28
+ require "hanami"
29
+
30
+ module TestApp
31
+ class App < Hanami::App
32
+ end
33
+ end
34
+ RUBY
35
+
36
+ write "slices/main/.keep", ""
37
+
38
+ require "hanami/prepare"
39
+
40
+ expect(Hanami.app.key?("rack.monitor")).to be false
41
+ expect(Main::Slice.key?("rack.monitor")).to be false
42
+ end
43
+ end
44
+ end
@@ -1,4 +1,4 @@
1
- RSpec.describe "Container / Standard bootable components", :app_integration do
1
+ RSpec.describe "Container / Standard providers", :app_integration do
2
2
  specify "Standard components are available on booted container" do
3
3
  with_tmp_directory(Dir.mktmpdir) do
4
4
  write "config/app.rb", <<~RUBY
@@ -15,7 +15,7 @@ RSpec.describe "Container / Standard bootable components", :app_integration do
15
15
 
16
16
  expect(Hanami.app.key?(:settings)).to be false
17
17
  expect(Hanami.app["inflector"]).to eql Hanami.app.inflector
18
- expect(Hanami.app["logger"]).to be_a_kind_of(Hanami::Logger)
18
+ expect(Hanami.app["logger"]).to be_a_kind_of(Dry::Logger::Dispatcher)
19
19
  expect(Hanami.app["rack.monitor"]).to be_a_kind_of(Dry::Monitor::Rack::Middleware)
20
20
  end
21
21
  end
@@ -36,7 +36,7 @@ RSpec.describe "Container / Standard bootable components", :app_integration do
36
36
 
37
37
  expect(Hanami.app.key?(:settings)).to be false
38
38
  expect(Hanami.app["inflector"]).to eql Hanami.app.inflector
39
- expect(Hanami.app["logger"]).to be_a_kind_of(Hanami::Logger)
39
+ expect(Hanami.app["logger"]).to be_a_kind_of(Dry::Logger::Dispatcher)
40
40
  expect(Hanami.app["rack.monitor"]).to be_a_kind_of(Dry::Monitor::Rack::Middleware)
41
41
  end
42
42
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rack/test"
4
+ require "stringio"
4
5
 
5
6
  RSpec.describe "Hanami web app", :app_integration do
6
7
  include Rack::Test::Methods
@@ -18,6 +19,7 @@ RSpec.describe "Hanami web app", :app_integration do
18
19
  module TestApp
19
20
  class App < Hanami::App
20
21
  config.middleware.use :body_parser, :json
22
+ config.logger.stream = StringIO.new
21
23
  end
22
24
  end
23
25
  RUBY
@@ -66,6 +68,7 @@ RSpec.describe "Hanami web app", :app_integration do
66
68
  class App < Hanami::App
67
69
  config.actions.formats["application/json+scim"] = :json
68
70
  config.middleware.use :body_parser, [json: "application/json+scim"]
71
+ config.logger.stream = StringIO.new
69
72
  end
70
73
  end
71
74
  RUBY