hanami 2.0.0.rc1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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