hanami 2.0.0.rc1 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/FEATURES.md +15 -21
  4. data/README.md +7 -37
  5. data/hanami.gemspec +5 -5
  6. data/lib/hanami/app.rb +2 -33
  7. data/lib/hanami/config/actions/content_security_policy.rb +1 -1
  8. data/lib/hanami/config/actions.rb +0 -3
  9. data/lib/hanami/config/logger.rb +52 -29
  10. data/lib/hanami/config.rb +22 -1
  11. data/lib/hanami/env.rb +52 -0
  12. data/lib/hanami/extensions/action/slice_configured_action.rb +5 -2
  13. data/lib/hanami/extensions/view/slice_configured_view.rb +4 -1
  14. data/lib/hanami/port.rb +45 -0
  15. data/lib/hanami/providers/rack.rb +15 -7
  16. data/lib/hanami/rake_tasks.rb +4 -5
  17. data/lib/hanami/slice.rb +3 -1
  18. data/lib/hanami/version.rb +1 -1
  19. data/lib/hanami/web/rack_logger.rb +64 -15
  20. data/lib/hanami.rb +24 -5
  21. data/spec/integration/action/format_config_spec.rb +194 -0
  22. data/spec/integration/action/slice_configuration_spec.rb +39 -42
  23. data/spec/integration/code_loading/loading_from_lib_spec.rb +34 -0
  24. data/spec/integration/dotenv_loading_spec.rb +1 -0
  25. data/spec/integration/rack_app/body_parser_spec.rb +2 -5
  26. data/spec/integration/rack_app/rack_app_spec.rb +132 -5
  27. data/spec/integration/settings/access_in_slice_class_body_spec.rb +1 -0
  28. data/spec/integration/settings/loading_from_env_spec.rb +1 -0
  29. data/spec/integration/setup_spec.rb +2 -2
  30. data/spec/spec_helper.rb +12 -0
  31. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +14 -14
  32. data/spec/unit/hanami/config/actions/default_values_spec.rb +0 -8
  33. data/spec/unit/hanami/config/actions_spec.rb +7 -10
  34. data/spec/unit/hanami/config/logger_spec.rb +4 -4
  35. data/spec/unit/hanami/port_spec.rb +117 -0
  36. data/spec/unit/hanami/version_spec.rb +1 -1
  37. data/spec/unit/hanami/web/rack_logger_spec.rb +4 -1
  38. metadata +36 -12
@@ -8,6 +8,9 @@ module Hanami
8
8
  # @api private
9
9
  # @since 2.0.0
10
10
  class RackLogger
11
+ EMPTY_PARAMS = {}.freeze
12
+ private_constant :EMPTY_PARAMS
13
+
11
14
  REQUEST_METHOD = "REQUEST_METHOD"
12
15
  private_constant :REQUEST_METHOD
13
16
 
@@ -29,10 +32,46 @@ module Hanami
29
32
  CONTENT_LENGTH = "Content-Length"
30
33
  private_constant :CONTENT_LENGTH
31
34
 
35
+ MILISECOND = "ms"
36
+ private_constant :MILISECOND
37
+
38
+ MICROSECOND = "µs"
39
+ private_constant :MICROSECOND
40
+
41
+ # Dynamic extension used in production environments
42
+ # @api private
43
+ module Production
44
+ private
45
+
46
+ # @since 1.0.0
47
+ # @api private
48
+ def data(env, status:, elapsed:)
49
+ payload = super
50
+ payload[:elapsed] = elapsed
51
+ payload[:elapsed_unit] = MICROSECOND
52
+ payload
53
+ end
54
+ end
55
+
56
+ # Dynamic extension used in non-production environments
57
+ # @api private
58
+ module Development
59
+ private
60
+
61
+ # @since 1.0.0
62
+ # @api private
63
+ def data(env, status:, elapsed:)
64
+ payload = super
65
+ payload[:elapsed] = elapsed > 1000 ? "#{elapsed / 1000}ms" : "#{elapsed}#{MICROSECOND}"
66
+ payload
67
+ end
68
+ end
69
+
32
70
  # @api private
33
71
  # @since 2.0.0
34
- def initialize(logger)
72
+ def initialize(logger, env: :development)
35
73
  @logger = logger
74
+ extend(env == :production ? Production : Development)
36
75
  end
37
76
 
38
77
  # @api private
@@ -43,36 +82,46 @@ module Hanami
43
82
  end
44
83
 
45
84
  rack_monitor.on :error do |event|
46
- log_exception event[:exception]
85
+ # TODO: why we don't provide time on error?
86
+ log_exception event[:env], event[:exception], 500, 0
47
87
  end
48
88
  end
49
89
 
50
90
  # @api private
51
91
  # @since 2.0.0
52
92
  def log_request(env, status, elapsed)
53
- data = {
54
- verb: env[REQUEST_METHOD],
55
- status: status,
56
- elapsed: "#{elapsed}ms",
57
- ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
58
- path: env[SCRIPT_NAME] + env[PATH_INFO].to_s,
59
- length: extract_content_length(env),
60
- params: env[ROUTER_PARAMS]
61
- }
62
-
63
- logger.info(data)
93
+ logger.tagged(:rack) do
94
+ logger.info(data(env, status: status, elapsed: elapsed))
95
+ end
64
96
  end
65
97
 
66
98
  # @api private
67
99
  # @since 2.0.0
68
- def log_exception(exception)
69
- logger.error(exception)
100
+ def log_exception(env, exception, status, elapsed)
101
+ logger.tagged(:rack) do
102
+ logger.error(exception, **data(env, status: status, elapsed: elapsed))
103
+ end
70
104
  end
71
105
 
72
106
  private
73
107
 
74
108
  attr_reader :logger
75
109
 
110
+ # @api private
111
+ # @since 2.0.0
112
+ def data(env, status:, elapsed:)
113
+ {
114
+ verb: env[REQUEST_METHOD],
115
+ status: status,
116
+ ip: env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR],
117
+ path: "#{env[SCRIPT_NAME]}#{env[PATH_INFO]}",
118
+ length: extract_content_length(env),
119
+ params: env.fetch(ROUTER_PARAMS, EMPTY_PARAMS)
120
+ }
121
+ end
122
+
123
+ # @api private
124
+ # @since 2.0.0
76
125
  def extract_content_length(env)
77
126
  value = env[CONTENT_LENGTH]
78
127
  !value || value.to_s == "0" ? "-" : value
data/lib/hanami.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
3
4
  require "zeitwerk"
4
5
  require_relative "hanami/constants"
5
6
 
@@ -36,7 +37,8 @@ module Hanami
36
37
  app_path = self.app_path
37
38
 
38
39
  if app_path
39
- require(app_path)
40
+ prepare_load_path
41
+ require(app_path.to_s)
40
42
  app
41
43
  elsif raise_exception
42
44
  raise(
@@ -47,6 +49,23 @@ module Hanami
47
49
  end
48
50
  end
49
51
 
52
+ # Prepare the load path as early as possible (based on the default root inferred from the location
53
+ # of `config/app.rb`), so `require` can work at the top of `config/app.rb`. This may be useful
54
+ # when external classes are needed for configuring certain aspects of the app.
55
+ #
56
+ # @api private
57
+ # @since 2.0.0
58
+ private_class_method def self.prepare_load_path
59
+ lib_path = app_path&.join("..", "..", LIB_DIR)
60
+
61
+ if lib_path&.directory?
62
+ path = lib_path.realpath.to_s
63
+ $LOAD_PATH.prepend(path) unless $LOAD_PATH.include?(path)
64
+ end
65
+
66
+ lib_path
67
+ end
68
+
50
69
  # Returns the Hamami app class.
51
70
  #
52
71
  # To ensure your Hanami app is loaded, run {.setup} (or `require "hanami/setup"`) first.
@@ -98,10 +117,10 @@ module Hanami
98
117
  # Searches within the given directory, then searches upwards through parent directories until the
99
118
  # app file can be found.
100
119
  #
101
- # @param dir [String] The directory from which to start searching. Defaults to the current
102
- # directory.
120
+ # @param dir [String, Pathname] The directory from which to start searching. Defaults to the
121
+ # current directory.
103
122
  #
104
- # @return [String, nil] the app file path, or nil if not found.
123
+ # @return [Pathname, nil] the app file path, or nil if not found.
105
124
  #
106
125
  # @api public
107
126
  # @since 2.0.0
@@ -110,7 +129,7 @@ module Hanami
110
129
  path = dir.join(APP_PATH)
111
130
 
112
131
  if path.file?
113
- path.to_s
132
+ path
114
133
  elsif !dir.root?
115
134
  app_path(dir.parent)
116
135
  end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "rack/test"
5
+
6
+ RSpec.describe "App action / Format config", :app_integration do
7
+ include Rack::Test::Methods
8
+
9
+ let(:app) { Hanami.app }
10
+
11
+ around do |example|
12
+ with_tmp_directory(Dir.mktmpdir, &example)
13
+ end
14
+
15
+ it "adds a body parser middleware for the accepted formats from the action config" do
16
+ write "config/app.rb", <<~RUBY
17
+ require "hanami"
18
+
19
+ module TestApp
20
+ class App < Hanami::App
21
+ config.logger.stream = StringIO.new
22
+
23
+ config.actions.format :json
24
+ end
25
+ end
26
+ RUBY
27
+
28
+ write "config/routes.rb", <<~RUBY
29
+ module TestApp
30
+ class Routes < Hanami::Routes
31
+ post "/users", to: "users.create"
32
+ end
33
+ end
34
+ RUBY
35
+
36
+ write "app/action.rb", <<~RUBY
37
+ # auto_register: false
38
+
39
+ module TestApp
40
+ class Action < Hanami::Action
41
+ end
42
+ end
43
+ RUBY
44
+
45
+ write "app/actions/users/create.rb", <<~RUBY
46
+ module TestApp
47
+ module Actions
48
+ module Users
49
+ class Create < TestApp::Action
50
+ def handle(req, res)
51
+ res.body = req.params[:users].join("-")
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ RUBY
58
+
59
+ require "hanami/boot"
60
+
61
+ post(
62
+ "/users",
63
+ JSON.generate("users" => %w[jane john jade joe]),
64
+ "CONTENT_TYPE" => "application/json"
65
+ )
66
+
67
+ expect(last_response).to be_successful
68
+ expect(last_response.body).to eql("jane-john-jade-joe")
69
+ end
70
+
71
+ specify "adds a body parser middleware configured to parse any custom content type for the accepted formats" do
72
+ write "config/app.rb", <<~RUBY
73
+ require "hanami"
74
+
75
+ module TestApp
76
+ class App < Hanami::App
77
+ config.logger.stream = StringIO.new
78
+
79
+ config.actions.formats.add :json, ["application/json+scim", "application/json"]
80
+ end
81
+ end
82
+ RUBY
83
+
84
+ write "config/routes.rb", <<~RUBY
85
+ module TestApp
86
+ class Routes < Hanami::Routes
87
+ post "/users", to: "users.create"
88
+ end
89
+ end
90
+ RUBY
91
+
92
+ write "app/action.rb", <<~RUBY
93
+ # auto_register: false
94
+
95
+ module TestApp
96
+ class Action < Hanami::Action
97
+ end
98
+ end
99
+ RUBY
100
+
101
+ write "app/actions/users/create.rb", <<~RUBY
102
+ module TestApp
103
+ module Actions
104
+ module Users
105
+ class Create < TestApp::Action
106
+ def handle(req, res)
107
+ res.body = req.params[:users].join("-")
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ RUBY
114
+
115
+ require "hanami/boot"
116
+
117
+ post(
118
+ "/users",
119
+ JSON.generate("users" => %w[jane john jade joe]),
120
+ "CONTENT_TYPE" => "application/json+scim"
121
+ )
122
+
123
+ expect(last_response).to be_successful
124
+ expect(last_response.body).to eql("jane-john-jade-joe")
125
+
126
+ post(
127
+ "/users",
128
+ JSON.generate("users" => %w[jane john jade joe]),
129
+ "CONTENT_TYPE" => "application/json"
130
+ )
131
+
132
+ expect(last_response).to be_successful
133
+ expect(last_response.body).to eql("jane-john-jade-joe")
134
+ end
135
+
136
+ it "does not add a body parser middleware if one is already added" do
137
+ write "config/app.rb", <<~RUBY
138
+ require "hanami"
139
+
140
+ module TestApp
141
+ class App < Hanami::App
142
+ config.logger.stream = StringIO.new
143
+
144
+ config.actions.format :json
145
+ config.middleware.use :body_parser, [json: "application/json+custom"]
146
+ end
147
+ end
148
+ RUBY
149
+
150
+ write "config/routes.rb", <<~RUBY
151
+ module TestApp
152
+ class Routes < Hanami::Routes
153
+ post "/users", to: "users.create"
154
+ end
155
+ end
156
+ RUBY
157
+
158
+ write "app/action.rb", <<~RUBY
159
+ # auto_register: false
160
+
161
+ module TestApp
162
+ class Action < Hanami::Action
163
+ end
164
+ end
165
+ RUBY
166
+
167
+ write "app/actions/users/create.rb", <<~RUBY
168
+ module TestApp
169
+ module Actions
170
+ module Users
171
+ class Create < TestApp::Action
172
+ config.formats.clear
173
+
174
+ def handle(req, res)
175
+ res.body = req.params[:users].join("-")
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ RUBY
182
+
183
+ require "hanami/boot"
184
+
185
+ post(
186
+ "/users",
187
+ JSON.generate("users" => %w[jane john jade joe]),
188
+ "CONTENT_TYPE" => "application/json+custom"
189
+ )
190
+
191
+ expect(last_response).to be_successful
192
+ expect(last_response.body).to eql("jane-john-jade-joe")
193
+ end
194
+ end
@@ -34,24 +34,25 @@ RSpec.describe "App action / Slice configuration", :app_integration do
34
34
  it "applies default actions config from the app", :aggregate_failures do
35
35
  prepare_app
36
36
 
37
- expect(TestApp::Action.config.default_request_format).to eq :html
38
- expect(TestApp::Action.config.default_response_format).to eq :html
37
+ expect(TestApp::Action.config.formats.values).to eq []
39
38
  end
40
39
 
41
40
  it "applies actions config from the app" do
42
- Hanami.app.config.actions.default_response_format = :json
41
+ Hanami.app.config.actions.format :json
43
42
 
44
43
  prepare_app
45
44
 
46
- expect(TestApp::Action.config.default_response_format).to eq :json
45
+ expect(TestApp::Action.config.formats.values).to eq [:json]
47
46
  end
48
47
 
49
48
  it "does not override config in the base class" do
50
- Hanami.app.config.actions.default_response_format = :csv
49
+ Hanami.app.config.actions.format :csv
51
50
 
52
51
  prepare_app
53
52
 
54
- TestApp::Action.config.default_response_format = :json
53
+ TestApp::Action.config.format :json
54
+
55
+ expect(TestApp::Action.config.formats.values).to eq [:json]
55
56
  end
56
57
  end
57
58
 
@@ -74,24 +75,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
74
75
  it "applies default actions config from the app", :aggregate_failures do
75
76
  prepare_app
76
77
 
77
- expect(TestApp::Actions::Articles::Index.config.default_request_format).to eq :html
78
- expect(TestApp::Actions::Articles::Index.config.default_response_format).to eq :html
78
+ expect(TestApp::Actions::Articles::Index.config.formats.values).to eq []
79
79
  end
80
80
 
81
81
  it "applies actions config from the app" do
82
- Hanami.app.config.actions.default_response_format = :json
82
+ Hanami.app.config.actions.format :json
83
83
 
84
84
  prepare_app
85
85
 
86
- expect(TestApp::Actions::Articles::Index.config.default_response_format).to eq :json
86
+ expect(TestApp::Actions::Articles::Index.config.formats.values).to eq [:json]
87
87
  end
88
88
 
89
89
  it "applies config from the base class" do
90
90
  prepare_app
91
91
 
92
- TestApp::Action.config.default_response_format = :json
92
+ TestApp::Action.config.format :json
93
93
 
94
- expect(TestApp::Actions::Articles::Index.config.default_response_format).to eq :json
94
+ expect(TestApp::Actions::Articles::Index.config.formats.values).to eq [:json]
95
95
  end
96
96
  end
97
97
 
@@ -114,24 +114,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
114
114
  it "applies default actions config from the app", :aggregate_failures do
115
115
  prepare_app
116
116
 
117
- expect(Admin::Actions::Articles::Index.config.default_request_format).to eq :html
118
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :html
117
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq []
119
118
  end
120
119
 
121
120
  it "applies actions config from the app" do
122
- Hanami.app.config.actions.default_response_format = :json
121
+ Hanami.app.config.actions.format :json
123
122
 
124
123
  prepare_app
125
124
 
126
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
125
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
127
126
  end
128
127
 
129
128
  it "applies config from the base class" do
130
129
  prepare_app
131
130
 
132
- TestApp::Action.config.default_response_format = :json
131
+ TestApp::Action.config.format :json
133
132
 
134
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
133
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
135
134
  end
136
135
  end
137
136
  end
@@ -152,24 +151,23 @@ RSpec.describe "App action / Slice configuration", :app_integration do
152
151
  it "applies default actions config from the app", :aggregate_failures do
153
152
  prepare_app
154
153
 
155
- expect(Admin::Action.config.default_request_format).to eq :html
156
- expect(Admin::Action.config.default_response_format).to eq :html
154
+ expect(Admin::Action.config.formats.values).to eq []
157
155
  end
158
156
 
159
157
  it "applies actions config from the app" do
160
- Hanami.app.config.actions.default_response_format = :json
158
+ Hanami.app.config.actions.format :json
161
159
 
162
160
  prepare_app
163
161
 
164
- expect(Admin::Action.config.default_response_format).to eq :json
162
+ expect(Admin::Action.config.formats.values).to eq [:json]
165
163
  end
166
164
 
167
165
  it "applies config from the app base class" do
168
166
  prepare_app
169
167
 
170
- TestApp::Action.config.default_response_format = :json
168
+ TestApp::Action.config.format :json
171
169
 
172
- expect(Admin::Action.config.default_response_format).to eq :json
170
+ expect(Admin::Action.config.formats.values).to eq [:json]
173
171
  end
174
172
 
175
173
  context "slice actions config present" do
@@ -178,7 +176,7 @@ RSpec.describe "App action / Slice configuration", :app_integration do
178
176
  write "config/slices/admin.rb", <<~'RUBY'
179
177
  module Admin
180
178
  class Slice < Hanami::Slice
181
- config.actions.default_response_format = :csv
179
+ config.actions.format :csv
182
180
  end
183
181
  end
184
182
  RUBY
@@ -188,24 +186,24 @@ RSpec.describe "App action / Slice configuration", :app_integration do
188
186
  it "applies actions config from the slice" do
189
187
  prepare_app
190
188
 
191
- expect(Admin::Action.config.default_response_format).to eq :csv
189
+ expect(Admin::Action.config.formats.values).to eq [:csv]
192
190
  end
193
191
 
194
192
  it "prefers actions config from the slice over config from the app-level base class" do
195
193
  prepare_app
196
194
 
197
- TestApp::Action.config.default_response_format = :json
195
+ TestApp::Action.config.format :json
198
196
 
199
- expect(Admin::Action.config.default_response_format).to eq :csv
197
+ expect(Admin::Action.config.formats.values).to eq [:csv]
200
198
  end
201
199
 
202
200
  it "prefers config from the base class over actions config from the slice" do
203
201
  prepare_app
204
202
 
205
- TestApp::Action.config.default_response_format = :csv
206
- Admin::Action.config.default_response_format = :json
203
+ TestApp::Action.config.format :csv
204
+ Admin::Action.config.format :json
207
205
 
208
- expect(Admin::Action.config.default_response_format).to eq :json
206
+ expect(Admin::Action.config.formats.values).to eq [:json]
209
207
  end
210
208
  end
211
209
  end
@@ -229,16 +227,15 @@ RSpec.describe "App action / Slice configuration", :app_integration do
229
227
  it "applies default actions config from the app", :aggregate_failures do
230
228
  prepare_app
231
229
 
232
- expect(Admin::Actions::Articles::Index.config.default_request_format).to eq :html
233
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :html
230
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq []
234
231
  end
235
232
 
236
233
  it "applies actions config from the app" do
237
- Hanami.app.config.actions.default_response_format = :json
234
+ Hanami.app.config.actions.format :json
238
235
 
239
236
  prepare_app
240
237
 
241
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
238
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
242
239
  end
243
240
 
244
241
  it "applies actions config from the slice" do
@@ -246,7 +243,7 @@ RSpec.describe "App action / Slice configuration", :app_integration do
246
243
  write "config/slices/admin.rb", <<~'RUBY'
247
244
  module Admin
248
245
  class Slice < Hanami::Slice
249
- config.actions.default_response_format = :json
246
+ config.actions.format :json
250
247
  end
251
248
  end
252
249
  RUBY
@@ -254,15 +251,15 @@ RSpec.describe "App action / Slice configuration", :app_integration do
254
251
 
255
252
  prepare_app
256
253
 
257
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
254
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
258
255
  end
259
256
 
260
257
  it "applies config from the slice base class" do
261
258
  prepare_app
262
259
 
263
- Admin::Action.config.default_response_format = :json
260
+ Admin::Action.config.format :json
264
261
 
265
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
262
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
266
263
  end
267
264
 
268
265
  it "prefers config from the slice base class over actions config from the slice" do
@@ -270,7 +267,7 @@ RSpec.describe "App action / Slice configuration", :app_integration do
270
267
  write "config/slices/admin.rb", <<~'RUBY'
271
268
  module Admin
272
269
  class Slice < Hanami::Slice
273
- config.actions.default_response_format = :csv
270
+ config.actions.format :csv
274
271
  end
275
272
  end
276
273
  RUBY
@@ -278,9 +275,9 @@ RSpec.describe "App action / Slice configuration", :app_integration do
278
275
 
279
276
  prepare_app
280
277
 
281
- Admin::Action.config.default_response_format = :json
278
+ Admin::Action.config.format :json
282
279
 
283
- expect(Admin::Actions::Articles::Index.config.default_response_format).to eq :json
280
+ expect(Admin::Actions::Articles::Index.config.formats.values).to eq [:json]
284
281
  end
285
282
  end
286
283
  end
@@ -82,6 +82,40 @@ RSpec.describe "Code loading / Loading from lib directory", :app_integration do
82
82
  end
83
83
  end
84
84
 
85
+ describe "default root with requires at top of app file" do
86
+ before :context do
87
+ with_directory(@dir = make_tmp_directory.realpath) do
88
+ write "config/app.rb", <<~'RUBY'
89
+ require "hanami"
90
+ require "external_class"
91
+
92
+ module TestApp
93
+ class App < Hanami::App
94
+ @class_from_lib = ExternalClass
95
+
96
+ def self.class_from_lib
97
+ @class_from_lib
98
+ end
99
+ end
100
+ end
101
+ RUBY
102
+
103
+ write "lib/external_class.rb", <<~'RUBY'
104
+ class ExternalClass
105
+ end
106
+ RUBY
107
+ end
108
+ end
109
+
110
+ before do
111
+ with_directory(@dir) { require "hanami/setup" }
112
+ end
113
+
114
+ specify "classes in lib/ can be required directly from the top of the app file" do
115
+ expect(Hanami.app.class_from_lib).to be ExternalClass
116
+ end
117
+ end
118
+
85
119
  context "app root reconfigured" do
86
120
  before :context do
87
121
  with_directory(@dir = make_tmp_directory.realpath) do
@@ -5,6 +5,7 @@
5
5
  RSpec.describe "Dotenv loading", :app_integration do
6
6
  before do
7
7
  @orig_env = ENV.to_h
8
+ allow(Hanami::Env).to receive(:loaded?).and_return(false)
8
9
  end
9
10
 
10
11
  after do
@@ -18,6 +18,7 @@ RSpec.describe "Hanami web app", :app_integration do
18
18
 
19
19
  module TestApp
20
20
  class App < Hanami::App
21
+ config.actions.format :json
21
22
  config.middleware.use :body_parser, :json
22
23
  config.logger.stream = StringIO.new
23
24
  end
@@ -37,8 +38,6 @@ RSpec.describe "Hanami web app", :app_integration do
37
38
  module Actions
38
39
  module Users
39
40
  class Create < Hanami::Action
40
- accept :json
41
-
42
41
  def handle(req, res)
43
42
  res.body = req.params[:users].join("-")
44
43
  end
@@ -66,7 +65,7 @@ RSpec.describe "Hanami web app", :app_integration do
66
65
 
67
66
  module TestApp
68
67
  class App < Hanami::App
69
- config.actions.formats["application/json+scim"] = :json
68
+ config.actions.formats.add :json, ["application/json+scim"]
70
69
  config.middleware.use :body_parser, [json: "application/json+scim"]
71
70
  config.logger.stream = StringIO.new
72
71
  end
@@ -86,8 +85,6 @@ RSpec.describe "Hanami web app", :app_integration do
86
85
  module Actions
87
86
  module Users
88
87
  class Create < Hanami::Action
89
- accept :json
90
-
91
88
  def handle(req, res)
92
89
  res.body = req.params[:users].join("-")
93
90
  end