hanami 2.0.0.rc1 → 2.0.0

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.
@@ -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
@@ -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
@@ -42,6 +42,8 @@ RSpec.describe "Hanami web app", :app_integration do
42
42
 
43
43
  module TestApp
44
44
  class App < Hanami::App
45
+ config.actions.format :json
46
+ config.logger.options = {colorize: true}
45
47
  config.logger.stream = config.root.join("test.log")
46
48
  end
47
49
  end
@@ -61,11 +63,26 @@ RSpec.describe "Hanami web app", :app_integration do
61
63
  end
62
64
  RUBY
63
65
 
66
+ write "app/actions/users/create.rb", <<~RUBY
67
+ module TestApp
68
+ module Actions
69
+ module Users
70
+ class Create < Hanami::Action
71
+ def handle(req, resp)
72
+ resp.body = req.params.to_h.keys
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ RUBY
79
+
64
80
  write "config/routes.rb", <<~RUBY
65
81
  module TestApp
66
82
  class Routes < Hanami::Routes
67
83
  root to: ->(env) { [200, {}, ["OK"]] }
68
84
  get "/users", to: "users.index"
85
+ post "/users", to: "users.create"
69
86
  end
70
87
  end
71
88
  RUBY
@@ -78,7 +95,11 @@ RSpec.describe "Hanami web app", :app_integration do
78
95
 
79
96
  logs = -> { Pathname(dir).join("test.log").realpath.read }
80
97
 
81
- expect(logs.()).to match %r{GET 200 \d+ms 127.0.0.1 /}
98
+ expect(logs.()).to match %r{GET 200 \d+(µs|ms) 127.0.0.1 /}
99
+
100
+ post "/users", JSON.generate(name: "jane", password: "secret"), {"CONTENT_TYPE" => "application/json"}
101
+
102
+ expect(logs.()).to match %r{POST 200 \d+(µs|ms) 127.0.0.1 /}
82
103
 
83
104
  begin
84
105
  get "/users"
@@ -86,7 +107,10 @@ RSpec.describe "Hanami web app", :app_integration do
86
107
  raise unless e.to_s == "OH NOEZ"
87
108
  end
88
109
 
89
- expect(logs.()).to match %r{OH NOEZ}
110
+ err_log = logs.()
111
+
112
+ expect(err_log).to include("OH NOEZ")
113
+ expect(err_log).to include("app/actions/users/index.rb:6:in `handle'")
90
114
  end
91
115
  end
92
116
 
@@ -121,7 +145,54 @@ RSpec.describe "Hanami web app", :app_integration do
121
145
 
122
146
  logs = -> { Pathname(dir).join("test.log").realpath.read }
123
147
 
124
- expect(logs.()).to match %r{GET 200 \d+ms 127.0.0.1 /}
148
+ expect(logs.()).to match %r{GET 200 \d+(µs|ms) 127.0.0.1 /}
149
+ end
150
+ end
151
+ end
152
+
153
+ describe "Request logging on production" do
154
+ let(:app) { Main::Slice.rack_app }
155
+
156
+ around do |example|
157
+ ENV["HANAMI_ENV"] = "production"
158
+ example.run
159
+ ensure
160
+ ENV["HANAMI_ENV"] = "test"
161
+ end
162
+
163
+ specify "Has rack monitor preconfigured with default request logging (when used via a slice)" do
164
+ dir = Dir.mktmpdir
165
+
166
+ with_tmp_directory(dir) do
167
+ write "config/app.rb", <<~RUBY
168
+ require "hanami"
169
+
170
+ module TestApp
171
+ class App < Hanami::App
172
+ config.logger.stream = config.root.join("test.log")
173
+ end
174
+ end
175
+ RUBY
176
+
177
+ write "slices/main/config/routes.rb", <<~RUBY
178
+ module Main
179
+ class Routes < Hanami::Routes
180
+ root to: ->(env) { [200, {}, ["OK"]] }
181
+ end
182
+ end
183
+ RUBY
184
+
185
+ require "hanami/boot"
186
+
187
+ get "/"
188
+
189
+ logs = -> { Pathname(dir).join("test.log").realpath.read }
190
+
191
+ log_content = logs.()
192
+
193
+ expect(log_content).to match(%r["verb":"GET"])
194
+ expect(log_content).to match(%r["path":"/"])
195
+ expect(log_content).to match(%r[elapsed":\d+,"elapsed_unit":"µs"])
125
196
  end
126
197
  end
127
198
  end
@@ -126,7 +126,7 @@ RSpec.describe "Hanami setup", :app_integration do
126
126
  with_tmp_directory(Dir.mktmpdir) do
127
127
  write "config/app.rb"
128
128
 
129
- expect(app_path).to match(%r{^/.*/config/app.rb$})
129
+ expect(app_path.to_s).to match(%r{^/.*/config/app.rb$})
130
130
  end
131
131
  end
132
132
  end
@@ -138,7 +138,7 @@ RSpec.describe "Hanami setup", :app_integration do
138
138
  write "lib/foo/bar/.keep"
139
139
 
140
140
  Dir.chdir("lib/foo/bar") do
141
- expect(app_path).to match(%r{^/.*/config/app.rb$})
141
+ expect(app_path.to_s).to match(%r{^/.*/config/app.rb$})
142
142
  end
143
143
  end
144
144
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
4
+
3
5
  SPEC_ROOT = File.expand_path(__dir__).freeze
6
+ LOG_DIR = Pathname(SPEC_ROOT).join("..").join("log")
4
7
 
5
8
  require_relative "support/coverage" if ENV["COVERAGE"].eql?("true")
6
9
 
@@ -14,3 +17,12 @@ Hanami::Utils::FileList["./spec/support/**/*.rb"].each do |file|
14
17
 
15
18
  require file
16
19
  end
20
+
21
+ RSpec.configure do |config|
22
+ config.after(:suite) do
23
+ # TODO: Find out what causes logger to create this dir when running specs.
24
+ # There's probably a test app class being created somewhere with root
25
+ # not pointing to a tmp dir.
26
+ FileUtils.rm_r(LOG_DIR) if LOG_DIR.exist?
27
+ end
28
+ end
@@ -26,14 +26,6 @@ RSpec.describe Hanami::Config::Actions, "default values" do
26
26
  end
27
27
 
28
28
  describe "new default values applied to base action settings" do
29
- describe "default_request_format" do
30
- specify { expect(config.default_request_format).to eq :html }
31
- end
32
-
33
- describe "default_response_format" do
34
- specify { expect(config.default_response_format).to eq :html }
35
- end
36
-
37
29
  describe "content_security_policy" do
38
30
  specify { expect(config.content_security_policy).to be_kind_of(Hanami::Config::Actions::ContentSecurityPolicy) }
39
31
  end