hanami 2.0.0.alpha8 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (224) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +442 -241
  3. data/FEATURES.md +30 -9
  4. data/README.md +1 -3
  5. data/hanami.gemspec +21 -11
  6. data/lib/hanami/app.rb +141 -0
  7. data/lib/hanami/assets/application_configuration.rb +10 -4
  8. data/lib/hanami/configuration/actions/content_security_policy.rb +5 -5
  9. data/lib/hanami/configuration/actions/cookies.rb +2 -2
  10. data/lib/hanami/configuration/actions.rb +10 -4
  11. data/lib/hanami/configuration/logger.rb +4 -4
  12. data/lib/hanami/configuration/router.rb +2 -6
  13. data/lib/hanami/configuration/sessions.rb +1 -1
  14. data/lib/hanami/configuration/views.rb +9 -4
  15. data/lib/hanami/configuration.rb +118 -46
  16. data/lib/hanami/constants.rb +24 -2
  17. data/lib/hanami/errors.rb +1 -1
  18. data/lib/hanami/{application → extensions}/action/slice_configured_action.rb +9 -9
  19. data/lib/hanami/extensions/action.rb +79 -0
  20. data/lib/hanami/extensions/view/context.rb +106 -0
  21. data/lib/hanami/{application → extensions}/view/slice_configured_context.rb +10 -10
  22. data/lib/hanami/{application → extensions}/view/slice_configured_view.rb +12 -6
  23. data/lib/hanami/extensions/view.rb +33 -0
  24. data/lib/hanami/extensions.rb +10 -0
  25. data/lib/hanami/providers/inflector.rb +13 -0
  26. data/lib/hanami/providers/logger.rb +13 -0
  27. data/lib/hanami/providers/rack.rb +27 -0
  28. data/lib/hanami/providers/routes.rb +33 -0
  29. data/lib/hanami/providers/settings.rb +23 -0
  30. data/lib/hanami/rake_tasks.rb +61 -0
  31. data/lib/hanami/routes.rb +51 -0
  32. data/lib/hanami/server.rb +1 -1
  33. data/lib/hanami/settings/dotenv_store.rb +58 -0
  34. data/lib/hanami/settings.rb +90 -0
  35. data/lib/hanami/setup.rb +4 -2
  36. data/lib/hanami/{application → slice}/router.rb +18 -13
  37. data/lib/hanami/slice/routes_helper.rb +37 -0
  38. data/lib/hanami/{application → slice}/routing/middleware/stack.rb +43 -5
  39. data/lib/hanami/slice/routing/resolver.rb +97 -0
  40. data/lib/hanami/{application → slice}/view_name_inferrer.rb +3 -3
  41. data/lib/hanami/slice.rb +246 -73
  42. data/lib/hanami/slice_configurable.rb +4 -17
  43. data/lib/hanami/slice_name.rb +6 -6
  44. data/lib/hanami/slice_registrar.rb +119 -0
  45. data/lib/hanami/version.rb +1 -1
  46. data/lib/hanami/web/rack_logger.rb +1 -1
  47. data/lib/hanami.rb +34 -26
  48. data/spec/integration/application_middleware_stack_spec.rb +84 -0
  49. data/spec/integration/assets/cdn_spec.rb +48 -0
  50. data/spec/integration/assets/fingerprint_spec.rb +42 -0
  51. data/spec/integration/assets/helpers_spec.rb +50 -0
  52. data/spec/integration/assets/serve_spec.rb +70 -0
  53. data/spec/integration/assets/subresource_integrity_spec.rb +54 -0
  54. data/spec/integration/body_parsers_spec.rb +50 -0
  55. data/spec/integration/cli/assets/precompile_spec.rb +147 -0
  56. data/spec/integration/cli/assets_spec.rb +14 -0
  57. data/spec/integration/cli/console_spec.rb +105 -0
  58. data/spec/integration/cli/db/apply_spec.rb +74 -0
  59. data/spec/integration/cli/db/console_spec.rb +40 -0
  60. data/spec/integration/cli/db/create_spec.rb +50 -0
  61. data/spec/integration/cli/db/drop_spec.rb +54 -0
  62. data/spec/integration/cli/db/migrate_spec.rb +108 -0
  63. data/spec/integration/cli/db/prepare_spec.rb +36 -0
  64. data/spec/integration/cli/db/rollback_spec.rb +96 -0
  65. data/spec/integration/cli/db/version_spec.rb +38 -0
  66. data/spec/integration/cli/db_spec.rb +21 -0
  67. data/spec/integration/cli/destroy/action_spec.rb +143 -0
  68. data/spec/integration/cli/destroy/app_spec.rb +118 -0
  69. data/spec/integration/cli/destroy/mailer_spec.rb +74 -0
  70. data/spec/integration/cli/destroy/migration_spec.rb +70 -0
  71. data/spec/integration/cli/destroy/model_spec.rb +113 -0
  72. data/spec/integration/cli/destroy_spec.rb +18 -0
  73. data/spec/integration/cli/generate/action_spec.rb +469 -0
  74. data/spec/integration/cli/generate/app_spec.rb +215 -0
  75. data/spec/integration/cli/generate/mailer_spec.rb +189 -0
  76. data/spec/integration/cli/generate/migration_spec.rb +72 -0
  77. data/spec/integration/cli/generate/model_spec.rb +290 -0
  78. data/spec/integration/cli/generate/secret_spec.rb +56 -0
  79. data/spec/integration/cli/generate_spec.rb +19 -0
  80. data/spec/integration/cli/new/database_spec.rb +235 -0
  81. data/spec/integration/cli/new/hanami_head_spec.rb +27 -0
  82. data/spec/integration/cli/new/template_spec.rb +118 -0
  83. data/spec/integration/cli/new/test_spec.rb +274 -0
  84. data/spec/integration/cli/new_spec.rb +970 -0
  85. data/spec/integration/cli/plugins_spec.rb +39 -0
  86. data/spec/integration/cli/routes_spec.rb +49 -0
  87. data/spec/integration/cli/server_spec.rb +626 -0
  88. data/spec/integration/cli/version_spec.rb +85 -0
  89. data/spec/integration/early_hints_spec.rb +35 -0
  90. data/spec/integration/handle_exceptions_spec.rb +244 -0
  91. data/spec/integration/head_spec.rb +89 -0
  92. data/spec/integration/http_headers_spec.rb +29 -0
  93. data/spec/integration/mailer_spec.rb +32 -0
  94. data/spec/integration/middleware_spec.rb +81 -0
  95. data/spec/integration/mount_applications_spec.rb +88 -0
  96. data/spec/integration/project_initializers_spec.rb +40 -0
  97. data/spec/integration/rackup_spec.rb +35 -0
  98. data/spec/integration/rake/with_minitest_spec.rb +67 -0
  99. data/spec/integration/rake/with_rspec_spec.rb +69 -0
  100. data/spec/integration/routing_helpers_spec.rb +61 -0
  101. data/spec/integration/security/content_security_policy_spec.rb +46 -0
  102. data/spec/integration/security/csrf_protection_spec.rb +42 -0
  103. data/spec/integration/security/force_ssl_spec.rb +29 -0
  104. data/spec/integration/security/x_content_type_options_spec.rb +46 -0
  105. data/spec/integration/security/x_frame_options_spec.rb +46 -0
  106. data/spec/integration/security/x_xss_protection_spec.rb +46 -0
  107. data/spec/integration/send_file_spec.rb +51 -0
  108. data/spec/integration/sessions_spec.rb +247 -0
  109. data/spec/integration/static_middleware_spec.rb +21 -0
  110. data/spec/integration/streaming_spec.rb +41 -0
  111. data/spec/integration/unsafe_send_file_spec.rb +52 -0
  112. data/spec/isolation/hanami/application/already_configured_spec.rb +19 -0
  113. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +10 -0
  114. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +14 -0
  115. data/spec/isolation/hanami/application/not_configured_spec.rb +9 -0
  116. data/spec/isolation/hanami/application/routes/configured_spec.rb +44 -0
  117. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +16 -0
  118. data/spec/isolation/hanami/boot/success_spec.rb +50 -0
  119. data/spec/new_integration/action/configuration_spec.rb +26 -0
  120. data/spec/new_integration/action/cookies_spec.rb +58 -0
  121. data/spec/new_integration/action/csrf_protection_spec.rb +54 -0
  122. data/spec/new_integration/action/routes_spec.rb +73 -0
  123. data/spec/new_integration/action/sessions_spec.rb +50 -0
  124. data/spec/new_integration/action/view_integration_spec.rb +165 -0
  125. data/spec/new_integration/action/view_rendering/automatic_rendering_spec.rb +247 -0
  126. data/spec/new_integration/action/view_rendering/paired_view_inference_spec.rb +115 -0
  127. data/spec/new_integration/action/view_rendering_spec.rb +107 -0
  128. data/spec/new_integration/code_loading/loading_from_app_spec.rb +152 -0
  129. data/spec/new_integration/code_loading/loading_from_slice_spec.rb +165 -0
  130. data/spec/new_integration/container/application_routes_helper_spec.rb +48 -0
  131. data/spec/new_integration/container/auto_injection_spec.rb +53 -0
  132. data/spec/new_integration/container/auto_registration_spec.rb +86 -0
  133. data/spec/new_integration/container/autoloader_spec.rb +80 -0
  134. data/spec/new_integration/container/imports_spec.rb +253 -0
  135. data/spec/new_integration/container/prepare_container_spec.rb +123 -0
  136. data/spec/new_integration/container/shutdown_spec.rb +91 -0
  137. data/spec/new_integration/container/standard_bootable_components_spec.rb +124 -0
  138. data/spec/new_integration/rack_app/middleware_spec.rb +215 -0
  139. data/spec/new_integration/rack_app/non_booted_rack_app_spec.rb +105 -0
  140. data/spec/new_integration/rack_app/rack_app_spec.rb +524 -0
  141. data/spec/new_integration/settings_spec.rb +115 -0
  142. data/spec/new_integration/slices/external_slice_spec.rb +92 -0
  143. data/spec/new_integration/slices/slice_configuration_spec.rb +40 -0
  144. data/spec/new_integration/slices/slice_routing_spec.rb +226 -0
  145. data/spec/new_integration/slices/slice_settings_spec.rb +141 -0
  146. data/spec/new_integration/slices_spec.rb +101 -0
  147. data/spec/new_integration/view/configuration_spec.rb +49 -0
  148. data/spec/new_integration/view/context/assets_spec.rb +67 -0
  149. data/spec/new_integration/view/context/inflector_spec.rb +48 -0
  150. data/spec/new_integration/view/context/request_spec.rb +61 -0
  151. data/spec/new_integration/view/context/routes_spec.rb +86 -0
  152. data/spec/new_integration/view/context/settings_spec.rb +50 -0
  153. data/spec/new_integration/view/inflector_spec.rb +57 -0
  154. data/spec/new_integration/view/part_namespace_spec.rb +96 -0
  155. data/spec/new_integration/view/path_spec.rb +56 -0
  156. data/spec/new_integration/view/template_spec.rb +68 -0
  157. data/spec/new_integration/view/views_spec.rb +103 -0
  158. data/spec/spec_helper.rb +16 -0
  159. data/spec/support/app_integration.rb +91 -0
  160. data/spec/support/coverage.rb +1 -0
  161. data/spec/support/fixtures/hanami-plugin/Gemfile +8 -0
  162. data/spec/support/fixtures/hanami-plugin/README.md +35 -0
  163. data/spec/support/fixtures/hanami-plugin/Rakefile +4 -0
  164. data/spec/support/fixtures/hanami-plugin/bin/console +15 -0
  165. data/spec/support/fixtures/hanami-plugin/bin/setup +8 -0
  166. data/spec/support/fixtures/hanami-plugin/hanami-plugin.gemspec +28 -0
  167. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/cli.rb +19 -0
  168. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/version.rb +7 -0
  169. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin.rb +8 -0
  170. data/spec/support/rspec.rb +27 -0
  171. data/spec/support/shared_examples/cli/generate/app.rb +494 -0
  172. data/spec/support/shared_examples/cli/generate/migration.rb +32 -0
  173. data/spec/support/shared_examples/cli/generate/model.rb +81 -0
  174. data/spec/support/shared_examples/cli/new.rb +97 -0
  175. data/spec/unit/hanami/configuration/actions/content_security_policy_spec.rb +102 -0
  176. data/spec/unit/hanami/configuration/actions/cookies_spec.rb +46 -0
  177. data/spec/unit/hanami/configuration/actions/csrf_protection_spec.rb +57 -0
  178. data/spec/unit/hanami/configuration/actions/default_values_spec.rb +52 -0
  179. data/spec/unit/hanami/configuration/actions/sessions_spec.rb +50 -0
  180. data/spec/unit/hanami/configuration/actions_spec.rb +78 -0
  181. data/spec/unit/hanami/configuration/base_url_spec.rb +25 -0
  182. data/spec/unit/hanami/configuration/inflector_spec.rb +35 -0
  183. data/spec/unit/hanami/configuration/logger_spec.rb +203 -0
  184. data/spec/unit/hanami/configuration/views_spec.rb +120 -0
  185. data/spec/unit/hanami/configuration_spec.rb +43 -0
  186. data/spec/unit/hanami/env_spec.rb +54 -0
  187. data/spec/unit/hanami/routes_spec.rb +25 -0
  188. data/spec/unit/hanami/settings/dotenv_store_spec.rb +119 -0
  189. data/spec/unit/hanami/settings_spec.rb +56 -0
  190. data/spec/unit/hanami/slice_configurable_spec.rb +104 -0
  191. data/spec/unit/hanami/slice_name_spec.rb +47 -0
  192. data/spec/unit/hanami/slice_spec.rb +17 -0
  193. data/spec/unit/hanami/version_spec.rb +7 -0
  194. data/spec/unit/hanami/web/rack_logger_spec.rb +78 -0
  195. metadata +353 -57
  196. data/lib/hanami/application/action.rb +0 -72
  197. data/lib/hanami/application/container/providers/inflector.rb +0 -7
  198. data/lib/hanami/application/container/providers/logger.rb +0 -7
  199. data/lib/hanami/application/container/providers/rack_logger.rb +0 -15
  200. data/lib/hanami/application/container/providers/rack_monitor.rb +0 -12
  201. data/lib/hanami/application/container/providers/routes_helper.rb +0 -9
  202. data/lib/hanami/application/container/providers/settings.rb +0 -7
  203. data/lib/hanami/application/routes.rb +0 -55
  204. data/lib/hanami/application/routes_helper.rb +0 -34
  205. data/lib/hanami/application/routing/resolver/node.rb +0 -50
  206. data/lib/hanami/application/routing/resolver/trie.rb +0 -59
  207. data/lib/hanami/application/routing/resolver.rb +0 -87
  208. data/lib/hanami/application/routing/router.rb +0 -36
  209. data/lib/hanami/application/settings/dotenv_store.rb +0 -60
  210. data/lib/hanami/application/settings.rb +0 -93
  211. data/lib/hanami/application/slice_registrar.rb +0 -106
  212. data/lib/hanami/application/view/context.rb +0 -95
  213. data/lib/hanami/application/view.rb +0 -24
  214. data/lib/hanami/application.rb +0 -273
  215. data/lib/hanami/cli/application/cli.rb +0 -40
  216. data/lib/hanami/cli/application/command.rb +0 -47
  217. data/lib/hanami/cli/application/commands/console.rb +0 -81
  218. data/lib/hanami/cli/application/commands.rb +0 -16
  219. data/lib/hanami/cli/base_command.rb +0 -48
  220. data/lib/hanami/cli/commands/command.rb +0 -171
  221. data/lib/hanami/cli/commands/server.rb +0 -88
  222. data/lib/hanami/cli/commands.rb +0 -65
  223. data/lib/hanami/configuration/middleware.rb +0 -20
  224. data/lib/hanami/configuration/source_dirs.rb +0 -42
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "hanami version", type: :integration do
4
+ context "within a project" do
5
+ it "prints current version" do
6
+ with_project do
7
+ run_cmd 'hanami version', "v#{Hanami::VERSION}"
8
+ end
9
+ end
10
+
11
+ it "prints current version with v alias" do
12
+ with_project do
13
+ run_cmd 'hanami v', "v#{Hanami::VERSION}"
14
+ end
15
+ end
16
+
17
+ it "prints current version with -v alias" do
18
+ with_project do
19
+ run_cmd 'hanami -v', "v#{Hanami::VERSION}"
20
+ end
21
+ end
22
+
23
+ it "prints current version with --version alias" do
24
+ with_project do
25
+ run_cmd 'hanami --version', "v#{Hanami::VERSION}"
26
+ end
27
+ end
28
+
29
+ it "prints help message" do
30
+ with_project do
31
+ output = <<~OUT
32
+ Command:
33
+ hanami version
34
+
35
+ Usage:
36
+ hanami version
37
+
38
+ Description:
39
+ Print Hanami version
40
+
41
+ Options:
42
+ --help, -h # Print this help
43
+ OUT
44
+
45
+ run_cmd 'hanami version --help', output
46
+ end
47
+ end
48
+ end
49
+
50
+ context "outside of a project" do
51
+ it 'prints current version' do
52
+ run_cmd 'hanami version', "v#{Hanami::VERSION}"
53
+ end
54
+
55
+ it 'prints current version with v alias' do
56
+ run_cmd 'hanami v', "v#{Hanami::VERSION}"
57
+ end
58
+
59
+ it 'prints current version with -v alias' do
60
+ run_cmd 'hanami -v', "v#{Hanami::VERSION}"
61
+ end
62
+
63
+ it 'prints current version with --version alias' do
64
+ run_cmd 'hanami --version', "v#{Hanami::VERSION}"
65
+ end
66
+
67
+ it "prints help message" do
68
+ output = <<~OUT
69
+ Command:
70
+ hanami version
71
+
72
+ Usage:
73
+ hanami version
74
+
75
+ Description:
76
+ Print Hanami version
77
+
78
+ Options:
79
+ --help, -h # Print this help
80
+ OUT
81
+
82
+ run_cmd 'hanami version --help', output
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Early Hints", type: :integration do
4
+ it "pushes assets" do
5
+ skip "curl not installed" if which("curl").nil?
6
+
7
+ with_project("bookshelf", server: :puma) do
8
+ generate "action web home#index --url=/"
9
+
10
+ write "apps/web/assets/javascripts/app.css", <<~EOF
11
+ body { margin: 0; }
12
+ EOF
13
+
14
+ inject_line_after "apps/web/templates/app.html.erb", /favicon/, %(<%= stylesheet "app" %>)
15
+ inject_line_after "config/environment.rb", /mount/, "early_hints true"
16
+
17
+ write "config/puma.rb", <<~EOF
18
+ early_hints true
19
+ EOF
20
+
21
+ port = RSpec::Support::RandomPort.call
22
+ server(port: port) do
23
+ sleep 2
24
+
25
+ # The Ruby HTTP client that we use for testing (excon), fails to connect to the server.
26
+ # It's very likely that it assumes that for each request, the server will return only one response.
27
+ # But in case of Early Hints (103) the returned response are multiple: `n` Early Hints (103) + OK (200).
28
+ #
29
+ # For this reason we fall back to cURL for this test.
30
+ system_exec("curl -i -v http://localhost:#{port}/")
31
+ expect(out).to include("Link: </assets/app.css>; rel=preload; as=style")
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "handle exceptions", type: :integration do
4
+ it "doesn't handle exceptions in development mode" do
5
+ with_project do
6
+ generate_action
7
+
8
+ server do
9
+ get "/books/1"
10
+
11
+ expect(last_response.status).to eq(500)
12
+ end
13
+ end
14
+ end
15
+
16
+ it "doesn't handle exceptions in test mode" do
17
+ with_project do
18
+ generate_action
19
+
20
+ RSpec::Support::Env["HANAMI_ENV"] = "test"
21
+
22
+ server do
23
+ get "/books/1"
24
+
25
+ expect(last_response.status).to eq(500)
26
+ end
27
+ end
28
+ end
29
+
30
+ context "when handles exceptions in production mode" do
31
+ it "it returns the expected status" do
32
+ with_project do
33
+ generate_action
34
+ setup_production_env
35
+
36
+ server do
37
+ get "/books/1"
38
+
39
+ expect(last_response.status).to eq(400)
40
+ end
41
+ end
42
+ end
43
+
44
+ context "and an exception is raised from a template" do
45
+ it "it returns a 500 and it renders a custom template if it exists" do
46
+ with_project do
47
+ generate "action web books#show --url=/books/:id"
48
+
49
+ rewrite "apps/web/templates/books/show.html.erb", <<~EOF
50
+ <%= raise ArgumentError.new("oh nooooo") %>
51
+ EOF
52
+
53
+ write "apps/web/templates/500.html.erb", <<~EOF
54
+ This is a custom template for 500 error
55
+ EOF
56
+
57
+ setup_production_env
58
+
59
+ server do
60
+ get "/books/1"
61
+
62
+ expect(last_response.status).to eq(500)
63
+ expect(last_response.body).to eq("This is a custom template for 500 error\n")
64
+ end
65
+ end
66
+ end
67
+
68
+ it "it returns a 500 and it renders the default template if custom template doesn't exist" do
69
+ with_project do
70
+ generate "action web books#show --url=/books/:id"
71
+
72
+ rewrite "apps/web/templates/books/show.html.erb", <<~EOF
73
+ <%= raise ArgumentError.new("oh nooooo") %>
74
+ EOF
75
+
76
+ setup_production_env
77
+
78
+ server do
79
+ get "/books/1"
80
+
81
+ expect(last_response.status).to eq(500)
82
+ expect(last_response.body).to include("<h2>500 - Internal Server Error</h2>")
83
+ end
84
+ end
85
+ end
86
+
87
+ it "it returns a 500 and renders backtrace error if an exception is raised from 500 custom template" do
88
+ with_project do
89
+ generate "action web books#show --url=/books/:id"
90
+
91
+ rewrite "apps/web/templates/books/show.html.erb", <<~EOF
92
+ <%= raise ArgumentError.new("oh nooooo") %>
93
+ EOF
94
+
95
+ write "apps/web/templates/500.html.erb", <<~EOF
96
+ <%= raise ArgumentError.new("Error from custom template") %>
97
+ This is a custom template for 500 error
98
+ EOF
99
+
100
+ setup_production_env
101
+
102
+ server do
103
+ get "/books/1"
104
+
105
+ expect(last_response.status).to eq(500)
106
+ expect(last_response.body).to_not include("This is a custom template for 500 error")
107
+ expect(last_response.body).to include("Error from custom template")
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ context "and an exception is raised from a view" do
114
+ it "it returns a 500 and it renders a custom template if it exists" do
115
+ with_project do
116
+ generate "action web books#show --url=/books/:id"
117
+
118
+ rewrite "apps/web/views/books/show.rb", <<~EOF
119
+ module Web::Views::Books
120
+ class Show
121
+ include Web::View
122
+
123
+ def header
124
+ raise ArgumentError.new("oh nooooo")
125
+ end
126
+ end
127
+ end
128
+ EOF
129
+
130
+ rewrite "apps/web/templates/books/show.html.erb", <<~EOF
131
+ <%= header %>
132
+ EOF
133
+
134
+ write "apps/web/templates/500.html.erb", <<~EOF
135
+ This is a custom template for 500 error
136
+ EOF
137
+
138
+ setup_production_env
139
+
140
+ server do
141
+ get "/books/1"
142
+
143
+ expect(last_response.status).to eq(500)
144
+ expect(last_response.body).to eq("This is a custom template for 500 error\n")
145
+ end
146
+ end
147
+ end
148
+
149
+ it "it returns a 500 and it renders the default template if custom template doesn't exist" do
150
+ with_project do
151
+ generate "action web books#show --url=/books/:id"
152
+
153
+ rewrite "apps/web/views/books/show.rb", <<~EOF
154
+ module Web::Views::Books
155
+ class Show
156
+ include Web::View
157
+
158
+ def header
159
+ raise ArgumentError.new("oh nooooo")
160
+ end
161
+ end
162
+ end
163
+ EOF
164
+
165
+ rewrite "apps/web/templates/books/show.html.erb", <<~EOF
166
+ <%= header %>
167
+ EOF
168
+
169
+ setup_production_env
170
+
171
+ server do
172
+ get "/books/1"
173
+
174
+ expect(last_response.status).to eq(500)
175
+ expect(last_response.body).to include("<h2>500 - Internal Server Error</h2>")
176
+ end
177
+ end
178
+ end
179
+
180
+ it "it returns a 500 and renders backtrace error if an exception is raised from 500 custom template" do
181
+ with_project do
182
+ generate "action web books#show --url=/books/:id"
183
+
184
+ rewrite "apps/web/views/books/show.rb", <<~EOF
185
+ module Web::Views::Books
186
+ class Show
187
+ include Web::View
188
+
189
+ def header
190
+ raise ArgumentError.new("oh nooooo")
191
+ end
192
+ end
193
+ end
194
+ EOF
195
+
196
+ rewrite "apps/web/templates/books/show.html.erb", <<~EOF
197
+ <%= header %>
198
+ EOF
199
+
200
+ write "apps/web/templates/500.html.erb", <<~EOF
201
+ <%= raise ArgumentError.new("Error from custom template") %>
202
+ This is a custom template for 500 error
203
+ EOF
204
+
205
+ setup_production_env
206
+
207
+ server do
208
+ get "/books/1"
209
+
210
+ expect(last_response.status).to eq(500)
211
+ expect(last_response.body).to_not include("This is a custom template for 500 error")
212
+ expect(last_response.body).to include("Error from custom template")
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ private
220
+
221
+ def generate_action
222
+ generate "action web books#show --url=/books/:id"
223
+
224
+ rewrite "apps/web/controllers/books/show.rb", <<~EOF
225
+ module Web::Controllers::Books
226
+ class Show
227
+ include Web::Action
228
+ handle_exception ArgumentError => 400
229
+
230
+ def call(params)
231
+ raise ArgumentError.new("oh nooooo")
232
+ end
233
+ end
234
+ end
235
+ EOF
236
+ end
237
+
238
+ def setup_production_env
239
+ RSpec::Support::Env["HANAMI_ENV"] = "production"
240
+ RSpec::Support::Env["DATABASE_URL"] = "sqlite://#{Pathname.new('db').join('bookshelf.sqlite')}"
241
+ RSpec::Support::Env["SMTP_HOST"] = "localhost"
242
+ RSpec::Support::Env["SMTP_PORT"] = "25"
243
+ end
244
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "HTTP HEAD", type: :integration do
4
+ it "returns empty body for HEAD requests" do
5
+ with_project do
6
+ generate "action web home#index --url=/"
7
+
8
+ server do
9
+ head "/"
10
+
11
+ expect(last_response.status).to eq(200)
12
+ expect(last_response.body).to eq("")
13
+ end
14
+ end
15
+ end
16
+
17
+ it "returns empty body for HEAD requests when body is set by the action" do
18
+ with_project do
19
+ generate "action web home#index --url=/"
20
+ rewrite "apps/web/controllers/home/index.rb", <<~EOF
21
+ module Web::Controllers::Home
22
+ class Index
23
+ include Web::Action
24
+
25
+ def call(params)
26
+ self.body = "Hello"
27
+ end
28
+ end
29
+ end
30
+ EOF
31
+
32
+ server do
33
+ head "/"
34
+
35
+ expect(last_response.status).to eq(200)
36
+ expect(last_response.body).to eq("")
37
+ end
38
+ end
39
+ end
40
+
41
+ it "returns empty body for HEAD requests when body is set by the view" do
42
+ with_project do
43
+ generate "action web home#index --url=/"
44
+ rewrite "apps/web/views/home/index.rb", <<~EOF
45
+ module Web::Views::Home
46
+ class Index
47
+ include Web::View
48
+
49
+ def render
50
+ "World"
51
+ end
52
+ end
53
+ end
54
+ EOF
55
+
56
+ server do
57
+ head "/"
58
+
59
+ expect(last_response.status).to eq(200)
60
+ expect(last_response.body).to eq("")
61
+ end
62
+ end
63
+ end
64
+
65
+ it "returns empty body for HEAD requests with send file" do
66
+ with_project do
67
+ write "public/static.txt", "Plain text file"
68
+ generate "action web home#index --url=/"
69
+ rewrite "apps/web/controllers/home/index.rb", <<~EOF
70
+ module Web::Controllers::Home
71
+ class Index
72
+ include Web::Action
73
+
74
+ def call(params)
75
+ send_file "static.txt"
76
+ end
77
+ end
78
+ end
79
+ EOF
80
+
81
+ server do
82
+ head "/"
83
+
84
+ expect(last_response.status).to eq(200)
85
+ expect(last_response.body).to eq("")
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "HTTP headers", type: :integration do
4
+ it "returns HTTP headers" do
5
+ with_project do
6
+ generate "action web home#index --url=/"
7
+
8
+ server do
9
+ get "/"
10
+
11
+ expect(last_response.status).to eq(200)
12
+ expect(last_response.headers.keys).to eq(
13
+ %w[
14
+ X-Frame-Options X-Content-Type-Options X-Xss-Protection
15
+ Content-Security-Policy Content-Type Content-Length
16
+ Server Date Connection
17
+ ]
18
+ )
19
+
20
+ expect(last_response.headers["X-Frame-Options"]).to eq("DENY")
21
+ expect(last_response.headers["X-Content-Type-Options"]).to eq("nosniff")
22
+ expect(last_response.headers["X-Xss-Protection"]).to eq("1; mode=block")
23
+ expect(last_response.headers["Content-Security-Policy"]).to eq("form-action 'self'; frame-ancestors 'self'; base-uri 'self'; default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self' https: data:; style-src 'self' 'unsafe-inline' https:; font-src 'self'; object-src 'none'; plugin-types app/pdf; child-src 'self'; frame-src 'self'; media-src 'self'")
24
+ expect(last_response.headers["Content-Type"]).to eq("text/html; charset=utf-8")
25
+ expect(Integer(last_response.headers["Content-Length"])).to be > 0
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Mailer", type: :integration do
4
+ it "use a mailer" do
5
+ with_project do
6
+ generate "mailer welcome"
7
+ write "lib/bookshelf/mailers/default_user.rb", <<~EOF
8
+ module Mailers
9
+ module DefaultUser
10
+ def user_name
11
+ "Alfonso"
12
+ end
13
+ end
14
+ end
15
+ EOF
16
+
17
+ replace "config/environment.rb", "delivery :test", <<-EOF
18
+ delivery :test
19
+
20
+ prepare do
21
+ include Mailers::DefaultUser
22
+ end
23
+ EOF
24
+
25
+ console do |input, _, _|
26
+ input.puts("Mailers::Welcome.new.user_name")
27
+ end
28
+
29
+ expect(out).to include("Alfonso")
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Project middleware", type: :integration do
4
+ it "mounts Rack middleware" do
5
+ with_project do
6
+ generate_middleware
7
+
8
+ unshift "config/environment.rb", 'require "rack/etag"'
9
+ unshift "config/environment.rb", 'require_relative "./middleware/runtime"'
10
+ unshift "config/environment.rb", 'require_relative "./middleware/custom"'
11
+
12
+ inject_line_after "config/environment.rb", "Hanami.configure", <<~EOL
13
+ middleware.use Middleware::Runtime
14
+ middleware.use Middleware::Custom, "OK"
15
+ middleware.use Rack::ETag
16
+ EOL
17
+
18
+ generate "action web home#index --url=/"
19
+ rewrite "apps/web/controllers/home/index.rb", <<~EOF
20
+ module Web::Controllers::Home
21
+ class Index
22
+ include Web::Action
23
+
24
+ def call(params)
25
+ self.body = "OK"
26
+ end
27
+ end
28
+ end
29
+ EOF
30
+
31
+ server do
32
+ get "/"
33
+
34
+ expect(last_response.status).to eq(200)
35
+
36
+ expect(last_response.headers["X-Runtime"]).to eq("1ms")
37
+ expect(last_response.headers["X-Custom"]).to eq("OK")
38
+ expect(last_response.headers["ETag"]).to_not be_nil
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def generate_middleware # rubocop:disable Metrics/MethodLength
46
+ write "config/middleware/runtime.rb", <<~EOF
47
+ module Middleware
48
+ class Runtime
49
+ def initialize(app)
50
+ @app = app
51
+ end
52
+
53
+ def call(env)
54
+ status, headers, body = @app.call(env)
55
+ headers["X-Runtime"] = "1ms"
56
+
57
+ [status, headers, body]
58
+ end
59
+ end
60
+ end
61
+ EOF
62
+
63
+ write "config/middleware/custom.rb", <<~EOF
64
+ module Middleware
65
+ class Custom
66
+ def initialize(app, value)
67
+ @app = app
68
+ @value = value
69
+ end
70
+
71
+ def call(env)
72
+ status, headers, body = @app.call(env)
73
+ headers["X-Custom"] = @value
74
+
75
+ [status, headers, body]
76
+ end
77
+ end
78
+ end
79
+ EOF
80
+ end
81
+ end
@@ -0,0 +1,88 @@
1
+ require "resolv-replace"
2
+ require "net/http"
3
+ require "uri"
4
+
5
+ RSpec.describe "mount apps", type: :integration do
6
+ before do
7
+ stub_dns_hosts("127.0.0.1 #{host} www.#{host} #{subdomain} localhost")
8
+ end
9
+
10
+ let(:host) { "bookshelf.test" }
11
+ let(:subdomain) { "beta.#{host}" }
12
+
13
+ context "with apps mounted with path" do
14
+ it "shows welcome page" do
15
+ with_project do
16
+ generate_host_middleware
17
+ generate "app admin"
18
+ generate "app beta"
19
+
20
+ replace "config/environment.rb", "Beta::App", %( mount Beta::App, at: "/", host: "#{subdomain}")
21
+
22
+ server do
23
+ # Web
24
+ visit "/"
25
+ expect(page).to have_content("bundle exec hanami generate action web 'home#index' --url=/")
26
+
27
+ # Admin
28
+ visit "/admin"
29
+ expect(page).to have_content("bundle exec hanami generate action admin 'home#index' --url=/")
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ context "when apps mounted with host: option" do
36
+ it "shows welcome page" do
37
+ with_project do
38
+ generate_host_middleware
39
+ generate "app admin"
40
+ generate "app beta"
41
+
42
+ replace "config/environment.rb", "Beta::App", %( mount Beta::App, at: "/", host: "#{subdomain}")
43
+
44
+ port = RSpec::Support::RandomPort.call
45
+ server(port: port) do
46
+ # Beta
47
+ response = raw_http_request("http://#{subdomain}:#{port}")
48
+ expect(response.body).to include("bundle exec hanami generate action beta 'home#index' --url=/")
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def generate_host_middleware
57
+ unshift "config/environment.rb", 'require_relative "./middleware/host"'
58
+
59
+ inject_line_after "config/environment.rb", "Hanami.configure", <<-EOL
60
+ middleware.use Middleware::Host
61
+ EOL
62
+
63
+ write "config/middleware/host.rb", <<-EOF
64
+ require "uri"
65
+
66
+ module Middleware
67
+ class Host
68
+ def initialize(app)
69
+ @app = app
70
+ end
71
+
72
+ def call(env)
73
+ host = URI.parse(env["REQUEST_URI"]).host
74
+ env["SERVER_NAME"] = host
75
+ env["HTTP_HOST"] = host
76
+ env["HTTP_X_FORWARDED_HOST"] = host
77
+
78
+ @app.call(env)
79
+ end
80
+ end
81
+ end
82
+ EOF
83
+ end
84
+
85
+ def raw_http_request(uri)
86
+ Net::HTTP.get_response(URI.parse(uri))
87
+ end
88
+ end