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
data/lib/hanami.rb CHANGED
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "hanami/application"
4
- require_relative "hanami/errors"
5
- require_relative "hanami/version"
6
-
7
-
8
3
  # A complete web framework for Ruby
9
4
  #
10
5
  # @since 0.1.0
@@ -12,39 +7,36 @@ require_relative "hanami/version"
12
7
  # @see http://hanamirb.org
13
8
  module Hanami
14
9
  @_mutex = Mutex.new
10
+ @_bundled = {}
15
11
 
16
- def self.application
12
+ def self.app
17
13
  @_mutex.synchronize do
18
- unless defined?(@_application)
19
- raise ApplicationLoadError,
20
- "Hanami.application is not yet configured. " \
21
- "You may need to `require \"hanami/setup\"` to load your config/application.rb file."
14
+ unless defined?(@_app)
15
+ raise AppLoadError,
16
+ "Hanami.app is not yet configured. " \
17
+ "You may need to `require \"hanami/setup\"` to load your config/app.rb file."
22
18
  end
23
19
 
24
- @_application
20
+ @_app
25
21
  end
26
22
  end
27
23
 
28
- def self.application?
29
- defined?(@_application)
24
+ def self.app?
25
+ defined?(@_app)
30
26
  end
31
27
 
32
- def self.application=(klass)
28
+ def self.app=(klass)
33
29
  @_mutex.synchronize do
34
- if defined?(@_application)
35
- raise ApplicationLoadError, "Hanami.application is already configured."
30
+ if defined?(@_app)
31
+ raise AppLoadError, "Hanami.app is already configured."
36
32
  end
37
33
 
38
- @_application = klass unless klass.name.nil?
34
+ @_app = klass unless klass.name.nil?
39
35
  end
40
36
  end
41
37
 
42
- def self.rack_app
43
- application.rack_app
44
- end
45
-
46
38
  def self.env
47
- (ENV["HANAMI_ENV"] || "development").to_sym
39
+ ENV.fetch("HANAMI_ENV") { ENV.fetch("RACK_ENV", "development") }.to_sym
48
40
  end
49
41
 
50
42
  def self.env?(*names)
@@ -52,22 +44,38 @@ module Hanami
52
44
  end
53
45
 
54
46
  def self.logger
55
- application[:logger]
47
+ app[:logger]
56
48
  end
57
49
 
58
50
  def self.prepare
59
- application.prepare
51
+ app.prepare
60
52
  end
61
53
 
62
54
  def self.boot
63
- application.boot
55
+ app.boot
64
56
  end
65
57
 
66
58
  def self.shutdown
67
- application.shutdown
59
+ app.shutdown
60
+ end
61
+
62
+ def self.bundled?(gem_name)
63
+ @_mutex.synchronize do
64
+ @_bundled[gem_name] ||= begin
65
+ gem(gem_name)
66
+ true
67
+ rescue Gem::LoadError
68
+ false
69
+ end
70
+ end
68
71
  end
69
72
 
70
73
  def self.bundler_groups
71
74
  [:plugins]
72
75
  end
76
+
77
+ require_relative "hanami/version"
78
+ require_relative "hanami/errors"
79
+ require_relative "hanami/extensions"
80
+ require_relative "hanami/app"
73
81
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "App middleware stack", type: :integration do
4
+ it "mounts Rack middleware" do
5
+ with_project do
6
+ generate "action web home#index --url=/"
7
+ generate_middleware
8
+
9
+ # Add apps/web/middleware to the load paths
10
+ replace "apps/web/app.rb", "load_paths << [", "load_paths << ['middleware',"
11
+
12
+ # Require Rack::ETag
13
+ unshift "apps/web/app.rb", "require 'rack/etag'"
14
+
15
+ # Mount middleware
16
+ replace "apps/web/app.rb", "# middleware.use", "middleware.use 'Web::Middleware::Runtime'\nmiddleware.use 'Web::Middleware::Custom', 'OK'\nmiddleware.use Rack::ETag"
17
+
18
+ rewrite "apps/web/controllers/home/index.rb", <<~EOF
19
+ module Web::Controllers::Home
20
+ class Index
21
+ include Web::Action
22
+
23
+ def call(params)
24
+ self.body = "OK"
25
+ end
26
+ end
27
+ end
28
+ EOF
29
+
30
+ server do
31
+ get "/"
32
+
33
+ expect(last_response.status).to eq(200)
34
+
35
+ expect(last_response.headers["X-Runtime"]).to eq("1ms")
36
+ expect(last_response.headers["X-Custom"]).to eq("OK")
37
+ expect(last_response.headers["ETag"]).to_not be_nil
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def generate_middleware # rubocop:disable Metrics/MethodLength
45
+ write "apps/web/middleware/runtime.rb", <<~EOF
46
+ module Web
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
+ end
62
+ EOF
63
+
64
+ write "apps/web/middleware/custom.rb", <<~EOF
65
+ module Web
66
+ module Middleware
67
+ class Custom
68
+ def initialize(app, value)
69
+ @app = app
70
+ @value = value
71
+ end
72
+
73
+ def call(env)
74
+ status, headers, body = @app.call(env)
75
+ headers["X-Custom"] = @value
76
+
77
+ [status, headers, body]
78
+ end
79
+ end
80
+ end
81
+ end
82
+ EOF
83
+ end
84
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "assets", type: :integration do
4
+ describe "CDN mode" do
5
+ it "servers assets with CDN url" do
6
+ with_project do
7
+ generate "action web home#index --url=/"
8
+
9
+ write "apps/web/assets/javascripts/app.css", <<~EOF
10
+ body { color: #333 };
11
+ EOF
12
+ rewrite "apps/web/templates/app.html.erb", <<~EOF
13
+ <!DOCTYPE html>
14
+ <html>
15
+ <head>
16
+ <title>Web</title>
17
+ <%= favicon %>
18
+ <%= stylesheet 'app' %>
19
+ </head>
20
+ <body>
21
+ <%= yield %>
22
+ </body>
23
+ </html>
24
+ EOF
25
+
26
+ replace_last "apps/web/app.rb", "# scheme 'https'", "scheme 'https'"
27
+ replace "apps/web/app.rb", "# host 'cdn.example.org'", "host 'cdn.example.org'"
28
+ replace_last "apps/web/app.rb", "# port 443", "port 443"
29
+
30
+ #
31
+ # Precompile
32
+ #
33
+ RSpec::Support::Env["HANAMI_ENV"] = "production"
34
+ RSpec::Support::Env["DATABASE_URL"] = "sqlite://#{Pathname.new('db').join('bookshelf.sqlite')}"
35
+ RSpec::Support::Env["SMTP_HOST"] = "localhost"
36
+ RSpec::Support::Env["SMTP_PORT"] = "25"
37
+
38
+ hanami "assets precompile"
39
+
40
+ server do
41
+ visit "/"
42
+
43
+ expect(page.body).to include(%(<link href="https://cdn.example.org/assets/app-5df86b4e9cbd733a027762b2f6bf8693.css" type="text/css" rel="stylesheet" integrity="sha256-LxaTcWkL8TAWFQWeHJ7OqoSoEXXaYapNIS+TCvGNf48=" crossorigin="anonymous">))
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "assets", type: :integration do
4
+ describe "fingerprint mode" do
5
+ it "servers assets with fingerprint url" do
6
+ with_project do
7
+ generate "action web home#index --url=/"
8
+
9
+ write "apps/web/assets/javascripts/app.css", <<~EOF
10
+ body { color: #333 };
11
+ EOF
12
+ rewrite "apps/web/templates/app.html.erb", <<~EOF
13
+ <!DOCTYPE html>
14
+ <html>
15
+ <head>
16
+ <title>Web</title>
17
+ <%= favicon %>
18
+ <%= stylesheet 'app' %>
19
+ </head>
20
+ <body>
21
+ <%= yield %>
22
+ </body>
23
+ </html>
24
+ EOF
25
+ #
26
+ # Precompile
27
+ #
28
+ RSpec::Support::Env["HANAMI_ENV"] = "production"
29
+ RSpec::Support::Env["DATABASE_URL"] = "sqlite://#{Pathname.new('db').join('bookshelf.sqlite')}"
30
+ RSpec::Support::Env["SMTP_HOST"] = "localhost"
31
+ RSpec::Support::Env["SMTP_PORT"] = "25"
32
+ hanami "assets precompile"
33
+
34
+ server do
35
+ visit "/"
36
+
37
+ expect(page.body).to include(%(<link href="/assets/app-5df86b4e9cbd733a027762b2f6bf8693.css" type="text/css" rel="stylesheet" integrity="sha256-LxaTcWkL8TAWFQWeHJ7OqoSoEXXaYapNIS+TCvGNf48=" crossorigin="anonymous">))
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "assets", type: :integration do
4
+ describe "helpers" do
5
+ it "renders assets tags" do
6
+ with_project do
7
+ generate "action web home#index --url=/"
8
+
9
+ rewrite "apps/web/templates/app.html.erb", <<~EOF
10
+ <!DOCTYPE html>
11
+ <html>
12
+ <head>
13
+ <title>Web</title>
14
+ <%= favicon %>
15
+ <%= stylesheet 'app' %>
16
+ </head>
17
+ <body>
18
+ <%= yield %>
19
+ <%= javascript 'app' %>
20
+ </body>
21
+ </html>
22
+ EOF
23
+
24
+ rewrite "apps/web/templates/home/index.html.erb", <<~EOF
25
+ <%= image('app.jpg') %>
26
+ <%= video('movie.mp4') %>
27
+ <%=
28
+ video do
29
+ text "Your browser does not support the video tag"
30
+ source src: view.asset_path('movie.mp4'), type: 'video/mp4'
31
+ source src: view.asset_path('movie.ogg'), type: 'video/ogg'
32
+ end
33
+ %>
34
+ EOF
35
+
36
+ server do
37
+ visit "/"
38
+
39
+ expect(page.body).to include(%(<link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">))
40
+ expect(page.body).to include(%(<link href="/assets/app.css" type="text/css" rel="stylesheet">))
41
+ expect(page.body).to include(%(<script src="/assets/app.js" type="text/javascript"></script>))
42
+
43
+ expect(page.body).to include(%(<img src="/assets/app.jpg" alt="App">))
44
+ expect(page.body).to include(%(<video src="/assets/movie.mp4"></video>))
45
+ expect(page.body).to include(%(<video>\nYour browser does not support the video tag\n<source src="/assets/movie.mp4" type="video/mp4">\n<source src="/assets/movie.ogg" type="video/ogg">\n</video>))
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "assets", type: :integration do
4
+ describe "serve" do
5
+ it "compiles and serves assets in development mode" do
6
+ project = "bookshelf_serve_assets"
7
+
8
+ with_project(project, gems: ['sassc']) do
9
+ generate "action web home#index --url=/"
10
+
11
+ write "apps/web/assets/javascripts/app.css.sass", <<~EOF
12
+ $font-family: Helvetica, sans-serif
13
+
14
+ body
15
+ font: 100% $font-family
16
+ EOF
17
+ rewrite "apps/web/templates/app.html.erb", <<~EOF
18
+ <!DOCTYPE html>
19
+ <html>
20
+ <head>
21
+ <title>Web</title>
22
+ <%= favicon %>
23
+ <%= stylesheet 'app' %>
24
+ </head>
25
+ <body>
26
+ <%= yield %>
27
+ </body>
28
+ </html>
29
+ EOF
30
+
31
+ server do
32
+ visit "/"
33
+ expect(page.body).to include(%(<link href="/assets/app.css" type="text/css" rel="stylesheet">))
34
+
35
+ visit "/assets/app.css"
36
+ expect(page.body).to include(%(body {\n font: 100% Helvetica, sans-serif; }\n))
37
+ end
38
+ end
39
+ end
40
+
41
+ it "serve assets with prefixes" do
42
+ with_project do
43
+ generate "action web home#index --url=/"
44
+
45
+ replace(
46
+ "apps/web/app.rb",
47
+ "# Specify sources for assets",
48
+ "prefix '/library/assets'\n# Specify sources for assets"
49
+ )
50
+
51
+ replace(
52
+ "apps/web/config/routes.rb",
53
+ "/",
54
+ "namespace :library { get '/', to: 'home#index' }"
55
+ )
56
+
57
+ write "apps/web/assets/javascripts/app.js", <<~EOF
58
+ console.log('test');
59
+ EOF
60
+
61
+ hanami "assets precompile"
62
+
63
+ server do
64
+ visit "/library/assets/app.js"
65
+ expect(page).to have_content("console.log('test');")
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ RSpec.describe "assets", type: :integration do
6
+ describe "subresource integrity" do
7
+ it "precompiles assets with checksums calculated with given algorithms" do
8
+ with_project do
9
+ generate "action web home#index --url=/"
10
+
11
+ write "apps/web/assets/javascripts/app.css", <<~EOF
12
+ body { font: Helvetica; }
13
+ EOF
14
+ rewrite "apps/web/templates/app.html.erb", <<~EOF
15
+ <!DOCTYPE html>
16
+ <html>
17
+ <head>
18
+ <title>Web</title>
19
+ <%= favicon %>
20
+ <%= stylesheet 'app' %>
21
+ </head>
22
+ <body>
23
+ <%= yield %>
24
+ </body>
25
+ </html>
26
+ EOF
27
+
28
+ replace "apps/web/app.rb", "subresource_integrity :sha256", "subresource_integrity :sha256, :sha512"
29
+
30
+ #
31
+ # Precompile
32
+ #
33
+ RSpec::Support::Env["HANAMI_ENV"] = "production"
34
+ # FIXME: database connection shouldn't be required for `assets precompile`
35
+ RSpec::Support::Env["DATABASE_URL"] = "sqlite://#{Pathname.new('db').join('bookshelf.sqlite')}"
36
+ RSpec::Support::Env["SMTP_HOST"] = "localhost"
37
+ RSpec::Support::Env["SMTP_PORT"] = "25"
38
+
39
+ hanami "assets precompile"
40
+
41
+ manifest = File.read("public/assets.json")
42
+ expect(JSON.parse(manifest)).to be_kind_of(Hash) # assert it's a well-formed JSON
43
+
44
+ expect(manifest).to include(%("/assets/app.css":{"target":"/assets/app-068e7d97b3671a14824bc20437ac5d06.css","sri":["sha256-cHgODQTP2nNk9ER+ViDGp+lC+ddHRmuLgJk05glJ4+w=","sha512-TDyxo1Ow7UjBib6ykALJh7J1OHEcE0yX4X21s1714ZBAhwdOz7k9t8+QQDAwWAmeH97bNaZGY7oTfVrwyTQ3cw=="]}))
45
+ expect(manifest).to include(%("/assets/favicon.ico":{"target":"/assets/favicon-b0979f93c7f7246ac70949a80f7cbdfd.ico","sri":["sha256-PLEDhpDsTBpxl1KtXjzBjg+PUG67zpf05B1z2db4iJU=","sha512-9NvaW7aVAywbLPPi3fw5s4wtWwd37i8VpzgfEo9uCOyr/mDT2dbYJtGNjfTJa4R8TOw69yHr4NhazPQsLt1WHw=="]}))
46
+
47
+ server do
48
+ visit "/"
49
+ expect(page.body).to include(%(<link href="/assets/app-068e7d97b3671a14824bc20437ac5d06.css" type="text/css" rel="stylesheet" integrity="sha256-cHgODQTP2nNk9ER+ViDGp+lC+ddHRmuLgJk05glJ4+w= sha512-TDyxo1Ow7UjBib6ykALJh7J1OHEcE0yX4X21s1714ZBAhwdOz7k9t8+QQDAwWAmeH97bNaZGY7oTfVrwyTQ3cw==" crossorigin="anonymous">))
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "body parsers", type: :integration do
4
+ it "parses JSON payload for non-GET requests" do
5
+ with_project do
6
+ generate_action
7
+ enable_json_body_parser
8
+
9
+ server do
10
+ post "/books", %({"book": {"title": "CLI apps with Ruby"}}), "CONTENT_TYPE" => "app/json", "HTTP_ACCEPT" => "app/json"
11
+ expect(last_response.body).to eq(%({"book":{"title":"CLI apps with Ruby"}}))
12
+ end
13
+ end
14
+ end
15
+
16
+ it "doesn't eval untrusted JSON" do
17
+ with_project do
18
+ generate_action
19
+ enable_json_body_parser
20
+
21
+ server do
22
+ post "/books", %({"json_class": "Foo"}), "CONTENT_TYPE" => "app/json", "HTTP_ACCEPT" => "app/json"
23
+ expect(last_response.body).to eq(%({"json_class":"Foo"}))
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def generate_action
31
+ generate "action web books#create --url=/books --method=POST"
32
+
33
+ rewrite "apps/web/controllers/books/create.rb", <<~EOF
34
+ require 'hanami/utils/json'
35
+ module Web::Controllers::Books
36
+ class Create
37
+ include Web::Action
38
+
39
+ def call(params)
40
+ self.body = Hanami::Utils::Json.generate(params.to_hash)
41
+ end
42
+ end
43
+ end
44
+ EOF
45
+ end
46
+
47
+ def enable_json_body_parser
48
+ inject_line_after "apps/web/app.rb", "configure do", "body_parsers :json"
49
+ end
50
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ RSpec.describe "hanami assets", type: :integration do
6
+ describe "precompile" do
7
+ it "precompiles assets" do
8
+ gems = %w[sassc coffee-script]
9
+
10
+ Platform.match do
11
+ os(:linux).engine(:ruby) { gems.push("therubyracer") }
12
+ os(:linux).engine(:jruby) { gems.push("therubyrhino") }
13
+ end
14
+
15
+ with_project("bookshelf_assets_precompile", gems: gems) do
16
+ #
17
+ # Web assets
18
+ #
19
+ write "apps/web/assets/javascripts/app.js.coffee", <<~EOF
20
+ class App
21
+ constructor: () ->
22
+ @init = true
23
+ EOF
24
+ write "apps/web/assets/stylesheets/_colors.scss", <<~EOF
25
+ $background-color: #f5f5f5;
26
+ EOF
27
+
28
+ write "apps/web/assets/stylesheets/app.css.scss", <<~EOF
29
+ @import 'colors';
30
+
31
+ body {
32
+ background-color: $background-color;
33
+ }
34
+ EOF
35
+ #
36
+ # Admin assets
37
+ #
38
+ generate "app admin"
39
+ write "apps/admin/assets/javascripts/dashboard.js.coffee", <<~EOF
40
+ class Dashboard
41
+ constructor: (@data) ->
42
+ EOF
43
+
44
+ #
45
+ # Precompile
46
+ #
47
+ RSpec::Support::Env["HANAMI_ENV"] = "production"
48
+ hanami "assets precompile"
49
+
50
+ # rubocop:disable Lint/ImplicitStringConcatenation
51
+
52
+ #
53
+ # Verify manifest
54
+ #
55
+ manifest = retry_exec(Errno::ENOENT) do
56
+ File.read("public/assets.json")
57
+ end
58
+
59
+ expect(JSON.parse(manifest)).to be_kind_of(Hash) # assert it's a well-formed JSON
60
+
61
+ expect(manifest).to include(%("/assets/admin/dashboard.js":{"target":"/assets/admin/dashboard-39744f9626a70683b6c2d46305798883.js","sri":["sha256-1myPVWoqrq+uAVP2DSkmAown+5dm0x61+E3AjlGOKEc="]}))
62
+ expect(manifest).to include(%("/assets/admin/favicon.ico":{"target":"/assets/admin/favicon-b0979f93c7f7246ac70949a80f7cbdfd.ico","sri":["sha256-PLEDhpDsTBpxl1KtXjzBjg+PUG67zpf05B1z2db4iJU="]}))
63
+ expect(manifest).to include(%("/assets/app.css":{"target":"/assets/app-adb4104884aadde9abfef0bd98ac461e.css","sri":["sha256-S6V565W2In9pWE0uzMASpp58xCg32TN3at3Fv4g9aRA="]}))
64
+ expect(manifest).to include(%("/assets/app.js":{"target":"/assets/app-bb8f10498d83d401db238549409dc4c5.js","sri":["sha256-9m4OTbWigbDPp4oCe1LZz9isqidvW1c3jNL6mXMj2xs="]}))
65
+ expect(manifest).to include(%("/assets/favicon.ico":{"target":"/assets/favicon-b0979f93c7f7246ac70949a80f7cbdfd.ico","sri":["sha256-PLEDhpDsTBpxl1KtXjzBjg+PUG67zpf05B1z2db4iJU="]}))
66
+
67
+ #
68
+ # Verify web assets (w/ checksum)
69
+ #
70
+ expect("public/assets/app-adb4104884aadde9abfef0bd98ac461e.css").to have_file_content <<~EOF
71
+ body {background-color: #f5f5f5}
72
+ EOF
73
+
74
+ expect("public/assets/app-bb8f10498d83d401db238549409dc4c5.js").to have_file_content \
75
+ """
76
+ (function(){var App;App=(function(){function App(){this.init=true;}
77
+ return App;})();}).call(this);
78
+ """
79
+
80
+ expect("public/assets/favicon-b0979f93c7f7246ac70949a80f7cbdfd.ico").to be_an_existing_file
81
+
82
+ #
83
+ # Verify web assets (w/o checksum)
84
+ #
85
+ expect("public/assets/app.css").to have_file_content <<~EOF
86
+ body {background-color: #f5f5f5}
87
+ EOF
88
+
89
+ expect("public/assets/app.js").to have_file_content \
90
+ """
91
+ (function(){var App;App=(function(){function App(){this.init=true;}
92
+ return App;})();}).call(this);
93
+ """
94
+
95
+ expect("public/assets/favicon.ico").to be_an_existing_file
96
+
97
+ #
98
+ # Verify admin assets (w/ checksum)
99
+ #
100
+ expect("public/assets/admin/dashboard-39744f9626a70683b6c2d46305798883.js").to have_file_content \
101
+ """
102
+ (function(){var Dashboard;Dashboard=(function(){function Dashboard(data){this.data=data;}
103
+ return Dashboard;})();}).call(this);
104
+ """
105
+
106
+ expect("public/assets/admin/favicon-b0979f93c7f7246ac70949a80f7cbdfd.ico").to be_an_existing_file
107
+
108
+ #
109
+ # Verify admin assets (w/o checksum)
110
+ #
111
+ expect("public/assets/admin/dashboard.js").to have_file_content \
112
+ """
113
+ (function(){var Dashboard;Dashboard=(function(){function Dashboard(data){this.data=data;}
114
+ return Dashboard;})();}).call(this);
115
+ """
116
+
117
+ expect("public/assets/admin/favicon.ico").to be_an_existing_file
118
+
119
+ # rubocop:enable Lint/ImplicitStringConcatenation
120
+ end
121
+ end
122
+
123
+ it "prints help message" do
124
+ with_project do
125
+ output = <<~OUT
126
+ Command:
127
+ hanami assets precompile
128
+
129
+ Usage:
130
+ hanami assets precompile
131
+
132
+ Description:
133
+ Precompile assets for deployment
134
+
135
+ Options:
136
+ --help, -h # Print this help
137
+
138
+ Examples:
139
+ hanami assets precompile # Basic usage
140
+ hanami assets precompile HANAMI_ENV=production # Precompile assets for production environment
141
+ OUT
142
+
143
+ run_cmd "hanami assets precompile --help", output
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "hanami assets", type: :integration do
4
+ it "prints subcommands" do
5
+ with_project do
6
+ output = <<~OUT
7
+ Commands:
8
+ hanami assets precompile # Precompile assets for deployment
9
+ OUT
10
+
11
+ run_cmd "hanami assets", output, exit_status: 1
12
+ end
13
+ end
14
+ end