hanami 2.0.0.alpha8 → 2.0.0.beta1

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.
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,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/configuration"
4
+ require "hanami/configuration/views"
5
+ require "saharspec/matchers/dont"
6
+
7
+ RSpec.describe Hanami::Configuration, "#views" do
8
+ let(:configuration) { described_class.new(app_name: app_name, env: :development) }
9
+ let(:app_name) { "MyApp::app" }
10
+
11
+ subject(:views) { configuration.views }
12
+
13
+ context "Hanami::View available" do
14
+ it "exposes Hanami::Views's app configuration" do
15
+ is_expected.to be_an_instance_of(Hanami::Configuration::Views)
16
+
17
+ is_expected.to respond_to(:finalize!)
18
+ is_expected.to respond_to(:layouts_dir)
19
+ is_expected.to respond_to(:layouts_dir=)
20
+ end
21
+
22
+ it "includes base view configuration" do
23
+ expect(views).to respond_to(:paths)
24
+ expect(views).to respond_to(:paths=)
25
+ end
26
+
27
+ it "does not include the inflector setting" do
28
+ expect(views).not_to respond_to(:inflector)
29
+ expect(views).not_to respond_to(:inflector=)
30
+ end
31
+
32
+ describe "#settings" do
33
+ it "includes locally defined settings" do
34
+ expect(views.settings).to include :parts_path
35
+ end
36
+
37
+ it "includes all view settings apart from inflector" do
38
+ expect(views.settings).to include (Hanami::View.settings - [:inflector])
39
+ end
40
+ end
41
+
42
+ it "preserves default values from the base view configuration" do
43
+ expect(views.layouts_dir).to eq Hanami::View.config.layouts_dir
44
+ end
45
+
46
+ it "allows settings to be configured independently of the base view configuration" do
47
+ expect { views.layouts_dir = "custom_layouts" }
48
+ .to change { views.layouts_dir }.to("custom_layouts")
49
+ .and(dont.change { Hanami::View.config.layouts_dir })
50
+ end
51
+
52
+ describe "specialised default values" do
53
+ describe "paths" do
54
+ it 'is ["templates"]' do
55
+ expect(views.paths).to match [
56
+ an_object_satisfying { |path| path.dir.to_s == "templates" }
57
+ ]
58
+ end
59
+ end
60
+
61
+ describe "template_inference_base" do
62
+ it 'is "views"' do
63
+ expect(views.template_inference_base).to eq "views"
64
+ end
65
+ end
66
+
67
+ describe "layout" do
68
+ it 'is "app"' do
69
+ expect(views.layout).to eq "app"
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "finalized configuration" do
75
+ before do
76
+ views.finalize!
77
+ end
78
+
79
+ it "is frozen" do
80
+ expect(views).to be_frozen
81
+ end
82
+
83
+ it "does not allow changes to locally defined settings" do
84
+ expect { views.parts_path = "parts" }.to raise_error(Dry::Configurable::FrozenConfig)
85
+ end
86
+
87
+ it "does not allow changes to base view settings" do
88
+ expect { views.paths = [] }.to raise_error(Dry::Configurable::FrozenConfig)
89
+ end
90
+ end
91
+ end
92
+
93
+ context "Hanami::View not available" do
94
+ before do
95
+ load_error = LoadError.new.tap do |error|
96
+ error.instance_variable_set :@path, "hanami/view"
97
+ end
98
+
99
+ allow_any_instance_of(described_class)
100
+ .to receive(:require)
101
+ .with(anything)
102
+ .and_call_original
103
+
104
+ allow_any_instance_of(described_class)
105
+ .to receive(:require)
106
+ .with("hanami/view")
107
+ .and_raise load_error
108
+ end
109
+
110
+ it "does not expose any settings" do
111
+ is_expected.not_to be_an_instance_of(Hanami::Configuration::Views)
112
+ is_expected.not_to respond_to(:layouts_dir)
113
+ is_expected.not_to respond_to(:layouts_dir=)
114
+ end
115
+
116
+ it "can be finalized" do
117
+ is_expected.to respond_to(:finalize!)
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,43 @@
1
+ require "hanami/configuration"
2
+
3
+ RSpec.describe Hanami::Configuration do
4
+ let(:config) { described_class.new(app_name: app_name, env: env) }
5
+ let(:app_name) { "MyApp::app" }
6
+ let(:env) { :development }
7
+
8
+ describe "environment-specific configuration" do
9
+ before do
10
+ config.logger.level = :debug__set_without_env
11
+ end
12
+
13
+ before do
14
+ config.environment :production do |c|
15
+ c.logger.level = :info__set_for_production_env
16
+ end
17
+ end
18
+
19
+ context "settings configured for current env" do
20
+ let(:env) { :production }
21
+
22
+ it "applies the settings" do
23
+ expect(config.logger.level).to eq :info__set_for_production_env
24
+ end
25
+
26
+ it "leaves the settings in place when finalizing" do
27
+ expect { config.finalize! }.not_to(change { config.logger.level })
28
+ end
29
+ end
30
+
31
+ context "settings configured for a different env" do
32
+ let(:env) { :development }
33
+
34
+ it "does not apply the settings" do
35
+ expect(config.logger.level).to eq :debug__set_without_env
36
+ end
37
+
38
+ it "does not apply the settings when finalizing" do
39
+ expect { config.finalize! }.not_to(change { config.logger.level })
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Hanami, ".env" do
4
+ subject(:env) { described_class.env }
5
+
6
+ before do
7
+ @orig_env = ENV.to_h
8
+ end
9
+
10
+ after do
11
+ ENV.replace(@orig_env)
12
+ end
13
+
14
+ context "HANAMI_ENV in ENV" do
15
+ before do
16
+ ENV["HANAMI_ENV"] = "test"
17
+ end
18
+
19
+ it "is the value of HANAMI_ENV" do
20
+ is_expected.to eq :test
21
+ end
22
+ end
23
+
24
+ context "RACK_ENV in ENV" do
25
+ before do
26
+ ENV["RACK_ENV"] = "test"
27
+ end
28
+
29
+ it "is the value of RACK_ENV" do
30
+ is_expected.to eq :test
31
+ end
32
+ end
33
+
34
+ context "both HANAMI_ENV and RACK_ENV in ENV" do
35
+ before do
36
+ ENV["HANAMI_ENV"] = "test"
37
+ ENV["RACK_ENV"] = "production"
38
+ end
39
+
40
+ it "is the value of HANAMI_ENV" do
41
+ is_expected.to eq :test
42
+ end
43
+ end
44
+
45
+ context "no ENV vars set" do
46
+ before do
47
+ ENV.delete("HANAMI_ENV")
48
+ end
49
+
50
+ it "defaults to \"development\"" do
51
+ is_expected.to eq :development
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/routes"
4
+
5
+ RSpec.describe Hanami::Routes do
6
+ describe ".define" do
7
+ it "sets routes block" do
8
+ routes_class = Class.new(described_class)
9
+
10
+ routes_class.define { "Dummy routes" }
11
+
12
+ expect(routes_class.routes.call).to eq("Dummy routes")
13
+ end
14
+ end
15
+
16
+ describe ".routes" do
17
+ context "when called before the routes have been defined" do
18
+ it "raises an error" do
19
+ routes_class = Class.new(described_class)
20
+
21
+ expect { routes_class.routes }.to raise_error(RuntimeError)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/settings/dotenv_store"
4
+ require "dotenv"
5
+
6
+ RSpec.describe Hanami::Settings::DotenvStore do
7
+ def mock_dotenv(store)
8
+ dotenv = spy(:dotenv)
9
+ allow(store).to receive(:require).and_call_original
10
+ stub_const "Dotenv", dotenv
11
+ end
12
+
13
+ describe "#with_dotenv_loaded" do
14
+ context "dotenv available and environment other than test" do
15
+ it "requires and loads a range of dotenv files, specific to the current environment" do
16
+ store = described_class.new(store: {}, hanami_env: :development)
17
+ dotenv = mock_dotenv(store)
18
+
19
+ store.with_dotenv_loaded
20
+
21
+ expect(store).to have_received(:require).with("dotenv").ordered
22
+ expect(dotenv).to have_received(:load).ordered.with(
23
+ ".env.development.local",
24
+ ".env.local",
25
+ ".env.development",
26
+ ".env"
27
+ )
28
+ end
29
+
30
+ it "returns self" do
31
+ store = described_class.new(store: {}, hanami_env: :development)
32
+
33
+ expect(store.with_dotenv_loaded).to be(store)
34
+ end
35
+ end
36
+
37
+ context "dotenv available and test environment" do
38
+ it "does not load .env.local (which is intended for non-test settings only)" do
39
+ store = described_class.new(store: {}, hanami_env: :test)
40
+ dotenv = mock_dotenv(store)
41
+
42
+ store.with_dotenv_loaded
43
+
44
+ expect(store).to have_received(:require).with("dotenv").ordered
45
+ expect(dotenv).to have_received(:load).ordered.with(
46
+ ".env.test.local",
47
+ ".env.test",
48
+ ".env"
49
+ )
50
+ end
51
+
52
+ it "returns self" do
53
+ store = described_class.new(store: {}, hanami_env: :test)
54
+
55
+ expect(store.with_dotenv_loaded).to be(store)
56
+ end
57
+ end
58
+
59
+ context "dotenv unavailable" do
60
+ let(:store) { described_class.new(store: {}) }
61
+
62
+ before do
63
+ allow(store).to receive(:require).with("dotenv").and_raise LoadError
64
+ end
65
+
66
+ it "attempts to require dotenv" do
67
+ store.with_dotenv_loaded
68
+
69
+ expect(store).to have_received(:require).with("dotenv")
70
+ end
71
+
72
+ it "gracefully ignores load errors" do
73
+ expect { store.with_dotenv_loaded }.not_to raise_error
74
+ end
75
+
76
+ it "returns self" do
77
+ expect(store.with_dotenv_loaded).to be(store)
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "#fetch" do
83
+ it "fetches from ENV" do
84
+ store = described_class.new(store: { "FOO" => "bar" })
85
+
86
+ expect(store.fetch("FOO")).to eq("bar")
87
+ end
88
+
89
+ it "capitalizes name" do
90
+ store = described_class.new(store: { "FOO" => "bar" })
91
+
92
+ expect(store.fetch("foo")).to eq("bar")
93
+ end
94
+
95
+ it "coerces name to string" do
96
+ store = described_class.new(store: { "FOO" => "bar" })
97
+
98
+ expect(store.fetch(:foo)).to eq("bar")
99
+ end
100
+
101
+ it "returns default when value is not found" do
102
+ store = described_class.new(store: { "FOO" => "bar" })
103
+
104
+ expect(store.fetch("BAZ", "qux")).to eq("qux")
105
+ end
106
+
107
+ it "returns the block execution when value is not found" do
108
+ store = described_class.new(store: { "FOO" => "bar" })
109
+
110
+ expect(store.fetch("BAZ") { "qux" }).to eq("qux")
111
+ end
112
+
113
+ it "raises KeyError when value is not found and no default is given" do
114
+ store = described_class.new(store: { "FOO" => "bar" })
115
+
116
+ expect{ store.fetch("BAZ") }.to raise_error(KeyError)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/settings"
4
+
5
+ RSpec.describe Hanami::Settings do
6
+ describe "#initialize" do
7
+ it "uses values from the store when present" do
8
+ config_class = Class.new(described_class) do
9
+ setting :database_url, default: "postgres://localhost/test_app_development"
10
+ end
11
+ store = { database_url: "mysql://localhost/test_app_development" }.freeze
12
+
13
+ instance = config_class.new(store)
14
+
15
+ expect(instance.config.database_url).to eq("mysql://localhost/test_app_development")
16
+ end
17
+
18
+ it "uses defaults when values are not present in the store" do
19
+ config_class = Class.new(described_class) do
20
+ setting :database_url, default: "postgres://localhost/test_app_development"
21
+ end
22
+ store = {}.freeze
23
+
24
+ instance = config_class.new(store)
25
+
26
+ expect(instance.config.database_url).to eq("postgres://localhost/test_app_development")
27
+ end
28
+
29
+ it "collects error for all failed settings" do
30
+ config_class = Class.new(described_class) do
31
+ setting :database_url, constructor: ->(_v) { raise "nope to database" }
32
+ setting :redis_url, constructor: ->(_v) { raise "nope to redis" }
33
+ end
34
+ store = {
35
+ database_url: "postgres://localhost/test_app_development",
36
+ redis_url: "redis://localhost:6379"
37
+ }.freeze
38
+
39
+ expect { config_class.new(store) }.to raise_error(
40
+ described_class::InvalidSettingsError,
41
+ /(database_url: nope to database).*(redis_url: nope to redis)/m,
42
+ )
43
+ end
44
+ end
45
+
46
+ it "delegates unknown methods to config" do
47
+ config_class = Class.new(described_class) do
48
+ setting :foo, default: "bar"
49
+ end
50
+ store = {}.freeze
51
+
52
+ instance = config_class.new(store)
53
+
54
+ expect(instance.foo).to eq("bar")
55
+ end
56
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/app"
4
+ require "hanami/slice_configurable"
5
+
6
+ RSpec.describe Hanami::SliceConfigurable, :app_integration do
7
+ before do
8
+ module TestApp
9
+ class App < Hanami::App
10
+ register_slice :main
11
+ register_slice :admin
12
+ end
13
+
14
+ class BaseClass
15
+ extend Hanami::SliceConfigurable
16
+
17
+ def self.configure_for_slice(slice)
18
+ traces << slice
19
+ end
20
+
21
+ def self.traces
22
+ @traces ||= []
23
+ end
24
+
25
+ def self.inherited(subclass)
26
+ subclass.instance_variable_set(:@traces, traces.dup)
27
+ super
28
+ end
29
+ end
30
+ end
31
+
32
+ Hanami.app.prepare
33
+ end
34
+
35
+ context "subclass inside slice namespace" do
36
+ before do
37
+ module Main
38
+ class MySubclass < TestApp::BaseClass; end
39
+ end
40
+ end
41
+
42
+ subject(:subclass) { Main::MySubclass }
43
+
44
+ it "calls `configure_for_slice` with the slice" do
45
+ expect(subclass.traces).to eq [Main::Slice]
46
+ end
47
+
48
+ context "further subclass, within same slice" do
49
+ before do
50
+ module Main
51
+ class MySubSubclass < Main::MySubclass; end
52
+ end
53
+ end
54
+
55
+ subject(:subclass) { Main::MySubSubclass }
56
+
57
+ it "calls `configure_for_slice` again for the same slice" do
58
+ expect(subclass.traces).to eq [Main::Slice, Main::Slice]
59
+ end
60
+ end
61
+
62
+ context "further subclass, within another slice namespace" do
63
+ before do
64
+ module Admin
65
+ class MySubSubclass < Main::MySubclass; end
66
+ end
67
+ end
68
+
69
+ subject(:subclass) { Admin::MySubSubclass }
70
+
71
+ it "calls `configure_for_slice` with the other slice" do
72
+ expect(subclass.traces).to eq [Main::Slice, Admin::Slice]
73
+ end
74
+ end
75
+ end
76
+
77
+ context "class inside app" do
78
+ before do
79
+ module TestApp
80
+ class MySubclass < TestApp::BaseClass; end
81
+ end
82
+ end
83
+
84
+ subject(:subclass) { TestApp::MySubclass }
85
+
86
+ it "calls `configure_for_slice` with the app instance" do
87
+ expect(subclass.traces).to eq [TestApp::App]
88
+ end
89
+
90
+ context "further subclass, within another slice namespace" do
91
+ before do
92
+ module Main
93
+ class MySubSubclass < TestApp::MySubclass; end
94
+ end
95
+ end
96
+
97
+ subject(:subclass) { Main::MySubSubclass }
98
+
99
+ it "calls `configure_for_slice` with the other slice" do
100
+ expect(subclass.traces).to eq [TestApp::App, Main::Slice]
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/slice_name"
4
+
5
+ require "dry/inflector"
6
+
7
+ RSpec.describe Hanami::SliceName do
8
+ subject(:slice_name) { described_class.new(slice, inflector: -> { inflector }) }
9
+ let(:slice) { double(name: "Main::Slice") }
10
+ let(:inflector) { Dry::Inflector.new }
11
+
12
+ let(:slice_module) { Module.new }
13
+
14
+ before do
15
+ stub_const "Main", slice_module
16
+ end
17
+
18
+ describe "#name" do
19
+ it "returns the downcased, underscored string name of the module containing the slice" do
20
+ expect(slice_name.name).to eq "main"
21
+ end
22
+ end
23
+
24
+ describe "#to_s" do
25
+ it "returns the downcased, underscored string name of the module containing the slice" do
26
+ expect(slice_name.to_s).to eq "main"
27
+ end
28
+ end
29
+
30
+ describe "#to_sym" do
31
+ it "returns the downcased, underscored, symbolized name of the module containing the slice" do
32
+ expect(slice_name.to_sym).to eq :main
33
+ end
34
+ end
35
+
36
+ describe "#namespace_name" do
37
+ it "returns the string name of the module containing the slice" do
38
+ expect(slice_name.namespace_name).to eq "Main"
39
+ end
40
+ end
41
+
42
+ describe "#namespace_const" do
43
+ it "returns the module containing the slice" do
44
+ expect(slice_name.namespace).to be slice_module
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ require "hanami/slice"
2
+
3
+ RSpec.describe Hanami::Slice, :app_integration do
4
+ before do
5
+ module TestApp
6
+ class App < Hanami::App
7
+ end
8
+ end
9
+ end
10
+
11
+ describe ".prepare" do
12
+ it "raises an error if the slice class is anonymous" do
13
+ expect { Class.new(described_class).prepare }
14
+ .to raise_error Hanami::SliceLoadError, /Slice must have a class name/
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Hanami::VERSION" do
4
+ it "returns current version" do
5
+ expect(Hanami::VERSION).to eq("2.0.0.beta1")
6
+ end
7
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/web/rack_logger"
4
+ require "hanami/logger"
5
+ require "stringio"
6
+ require "rack/mock"
7
+
8
+ RSpec.describe Hanami::Web::RackLogger do
9
+ subject { described_class.new(logger) }
10
+ let(:logger) { Hanami::Logger.new(app_name, stream: stream, level: Hanami::Logger::DEBUG, filter: filters) }
11
+ let(:stream) { StringIO.new }
12
+ let(:filters) { ["user.password"] }
13
+ let(:app_name) { "my_app" }
14
+
15
+ describe "#initialize" do
16
+ it "returns an instance of #{described_class}" do
17
+ expect(subject).to be_kind_of(described_class)
18
+ end
19
+ end
20
+
21
+ describe "#log_request" do
22
+ it "logs current request" do
23
+ time = Time.parse("2022-02-04 11:38:25.218816 +0100")
24
+ expect(Time).to receive(:now).at_least(:once).and_return(time)
25
+
26
+ path = "/users"
27
+ ip = "127.0.0.1"
28
+ status = 200
29
+ elapsed = 0.0001
30
+ content_length = 23
31
+ verb = "POST"
32
+
33
+ env = Rack::MockRequest.env_for(path, method: verb)
34
+ env["Content-Length"] = content_length
35
+ env["REMOTE_ADDR"] = ip
36
+
37
+ params = {"user" => {"password" => "secret"}}
38
+ env["router.params"] = params
39
+
40
+ subject.log_request(env, status, elapsed)
41
+
42
+ stream.rewind
43
+ actual = stream.read
44
+
45
+ expect(actual).to include(%([#{app_name}] [INFO] [#{time}] #{verb} #{status} #{elapsed}ms #{ip} #{path} #{content_length} {"user"=>{"password"=>"[FILTERED]"}}))
46
+ end
47
+
48
+ context "ip" do
49
+ it "takes into account HTTP proxy forwarding" do
50
+ env = Rack::MockRequest.env_for("/")
51
+ env["REMOTE_ADDR"] = remote = "127.0.0.1"
52
+ env["HTTP_X_FORWARDED_FOR"] = forwarded = "::1"
53
+
54
+ subject.log_request(env, 200, 0.1)
55
+
56
+ stream.rewind
57
+ actual = stream.read
58
+
59
+ expect(actual).to include(forwarded)
60
+ expect(actual).to_not include(remote)
61
+ end
62
+ end
63
+
64
+ context "path prefix" do
65
+ it "logs full referenced relative path" do
66
+ env = Rack::MockRequest.env_for(path = "/users")
67
+ env["SCRIPT_NAME"] = script_name = "/v1"
68
+
69
+ subject.log_request(env, 200, 0.1)
70
+
71
+ stream.rewind
72
+ actual = stream.read
73
+
74
+ expect(actual).to include(script_name + path)
75
+ end
76
+ end
77
+ end
78
+ end