padrino-gen 0.18.0 → 0.18.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '039917b81dc2bfbb846774da7452682d6f29af8bc1f4d76609b2ec10d8392912'
4
- data.tar.gz: 451d8a83d39747bf6648d0b434f211a4571e5694f5cc70b45e9f9844ef7bfa7c
3
+ metadata.gz: 86f7ed8ab8be9172114afb80f101283607ef08a843c4914c062722ff0c1daf29
4
+ data.tar.gz: b09bcbe9c28537c856b700181370a3a75b0d2529b97f0b21de9b638c100d8585
5
5
  SHA512:
6
- metadata.gz: c0769c35ccfe2cff70e99aa0c137b825e285e831649945d18e07e5ea72b44efcc1214f3fb3ebc63661d0b0c8371e84c78dcb5fff6b7472471c83176e3dd1b40e
7
- data.tar.gz: 45b9f905e803023d7932ceb487ade75bbcd5df395754f820cc86626176de272bb422ef5696d3665027b80f7a8f1d34436f266b02c8bb27d9938fbbcce410baf1
6
+ metadata.gz: e42eba95dfc30a9687be99f7ad6ab40e5a77a2c07f24a07c565fe693e1914ac87baa6069fa586257d6d053f1f270d114bf370958e2ded3d8e31597e47ef234be
7
+ data.tar.gz: '06467684e1041ed80502f5463faf6c68163f174d3328534f7855d40701dc2f05ee0f5179ab73b6879133076db22ad0c63ab3deba71a151b23b386ed3140f6c6a'
@@ -666,6 +666,47 @@ module Padrino
666
666
  exit 1
667
667
  end
668
668
 
669
+ ##
670
+ # Returns true for an --api project: no sessions or CSRF protection. The Gemfile is the
671
+ # only record of that: padrino-core, without the padrino gem or the full set of its parts.
672
+ #
673
+ # @return [Boolean] true for an --api project.
674
+ #
675
+ def api_project?
676
+ return true if options[:api]
677
+
678
+ gemfile_gems.include?('padrino-core') && !(padrino_gem?('padrino-helpers') && padrino_gem?('padrino-mailer'))
679
+ end
680
+
681
+ ##
682
+ # Returns true when the project loads the named part of padrino, so a generated app can
683
+ # register it. A Gemfile that lists no padrino-core by name keeps the default, every part.
684
+ #
685
+ # @param [String] name
686
+ # The gem name, such as 'padrino-mailer'.
687
+ #
688
+ # @return [Boolean] true when the project loads the gem.
689
+ #
690
+ def padrino_gem?(name)
691
+ return false if options[:api]
692
+
693
+ gems = gemfile_gems
694
+ !gems.include?('padrino-core') || gems.include?('padrino') || gems.include?(name)
695
+ end
696
+
697
+ ##
698
+ # Returns the names of the gems the project's Gemfile lists literally, or an empty
699
+ # array without a readable Gemfile. The padrino-#{g} loop of a --dev Gemfile lists none.
700
+ #
701
+ # @return [Array<String>] The gem names.
702
+ #
703
+ def gemfile_gems
704
+ gemfile = destination_root('Gemfile')
705
+ return [] unless File.file?(gemfile) && File.readable?(gemfile)
706
+
707
+ File.binread(gemfile).scan(/^\s*gem[\s(]+['"]([^'"]+)['"]/).flatten
708
+ end
709
+
669
710
  ##
670
711
  # Generates standard and tiny applications within a project.
671
712
  #
@@ -684,8 +725,10 @@ module Padrino
684
725
  template 'templates/controller.rb.tt', destination_root(app, 'controllers.rb')
685
726
  @helper_name = DEFAULT_HELPER_NAME
686
727
  template 'templates/helper.rb.tt', destination_root(app, 'helpers.rb')
687
- @short_name = 'notifier'
688
- template 'templates/mailer.rb.tt', destination_root(app, 'mailers.rb')
728
+ if padrino_gem?('padrino-mailer')
729
+ @short_name = 'notifier'
730
+ template 'templates/mailer.rb.tt', destination_root(app, 'mailers.rb')
731
+ end
689
732
  else
690
733
  empty_directory destination_root(app, 'controllers')
691
734
  empty_directory destination_root(app, 'helpers')
@@ -1,13 +1,22 @@
1
1
  module <%= @project_name %>
2
2
  class <%= @app_name %> < Padrino::Application
3
- <% unless options.api? -%>
3
+ <% if padrino_gem?('padrino-mailer') -%>
4
4
  register Padrino::Mailer
5
+ <% end -%>
6
+ <% if padrino_gem?('padrino-helpers') -%>
5
7
  register Padrino::Helpers
6
8
  <% end -%>
9
+ <% if api_project? -%>
10
+ # CSRF protection follows sessions, because without a session it rejects every request.
11
+ # `enable :sessions` here turns on both, except in the test environment. For another session
12
+ # store, give the :sessions setting `use:` and `config:` options rather than a `use` line.
13
+ set :protect_from_csrf, proc { sessions? && Padrino.env != :test }
14
+ <% else -%>
7
15
  # The session cookie is Secure (sent over HTTPS only) in production. Over plain HTTP
8
16
  # the app then sends no session cookie, so logins and CSRF-protected forms fail. Behind
9
17
  # a proxy that terminates TLS, have it send X-Forwarded-Proto: https, or set secure: false.
10
18
  set :sessions, same_site: :lax, secure: Padrino.env == :production
19
+ <% end -%>
11
20
 
12
21
  ##
13
22
  # Caching support.
@@ -17,6 +17,7 @@ module Padrino
17
17
  # custom generators, so is necessary know who are.
18
18
  #
19
19
  def load_boot
20
+ Padrino::Generators.boot_error = nil
20
21
  saved_env = { 'BUNDLE_GEMFILE' => ENV['BUNDLE_GEMFILE'], 'PADRINO_LOG_LEVEL' => ENV['PADRINO_LOG_LEVEL'] }
21
22
  ENV['PADRINO_LOG_LEVEL'] ||= 'test'
22
23
  root = File.expand_path(options[:root] || '.')
@@ -33,6 +34,7 @@ module Padrino
33
34
  require 'padrino-support'
34
35
  end
35
36
  rescue StandardError => e
37
+ Padrino::Generators.boot_error = e
36
38
  puts "=> Problem loading #{boot}"
37
39
  puts ["=> #{e.message}", *e.backtrace].join("\n ")
38
40
  ensure
@@ -30,7 +30,8 @@ BACON_CONTROLLER_TEST = <<~TEST.gsub(/^ {10}/, '') unless defined?(BACON_CONTROL
30
30
  describe "!PATH!" do
31
31
  it 'returns text at root' do
32
32
  get "!EXPANDED_PATH!"
33
- last_response.body.should == "some text"
33
+ # Bacon reports a spec without requirements as missing. Add some, such as:
34
+ # last_response.body.should == "some text"
34
35
  end
35
36
  end
36
37
  TEST
@@ -74,7 +75,8 @@ BACON_HELPER_TEST = <<~TEST unless defined?(BACON_HELPER_TEST)
74
75
  end
75
76
 
76
77
  it "should return nil" do
77
- @helpers.foo.should.equal nil
78
+ # Bacon reports a spec without requirements as missing. Add some, or delete this file:
79
+ # @helpers.foo.should.equal nil
78
80
  end
79
81
  end
80
82
  TEST
@@ -49,6 +49,7 @@ MINITEST_CONTROLLER_TEST = <<~TEST.gsub(/^ {10}/, '') unless defined?(MINITEST_C
49
49
  end
50
50
 
51
51
  it "should return hello world text" do
52
+ skip "add some examples to \#{__FILE__}"
52
53
  assert_equal "Hello World", last_response.body
53
54
  end
54
55
  end
@@ -79,6 +80,7 @@ MINITEST_HELPER_TEST = <<~TEST unless defined?(MINITEST_HELPER_TEST)
79
80
  end
80
81
 
81
82
  it "should return nil" do
83
+ skip "add some examples to (or delete) \#{__FILE__}"
82
84
  assert_nil helpers.foo
83
85
  end
84
86
  end
@@ -34,6 +34,7 @@ SHOULDA_CONTROLLER_TEST = <<~TEST.gsub(/^ {10}/, '') unless defined?(SHOULDA_CON
34
34
  end
35
35
 
36
36
  should "return hello world text" do
37
+ pend "add some examples to \#{__FILE__}"
37
38
  assert_equal "Hello World", last_response.body
38
39
  end
39
40
  end
@@ -82,6 +83,7 @@ SHOULDA_HELPER_TEST = <<~TEST unless defined?(SHOULDA_HELPER_TEST)
82
83
  end
83
84
 
84
85
  should "return nil" do
86
+ pend "add some examples to (or delete) \#{__FILE__}"
85
87
  assert_equal nil, @helpers.foo
86
88
  end
87
89
  end
@@ -49,6 +49,7 @@ TESTUNIT_CONTROLLER_TEST = <<~TEST.gsub(/^ {10}/, '') unless defined?(TESTUNIT_C
49
49
  end
50
50
 
51
51
  def test_returns_hello_world_text
52
+ pend "add some examples to \#{__FILE__}"
52
53
  assert_equal "Hello World", last_response.body
53
54
  end
54
55
  end
@@ -79,6 +80,7 @@ TESTUNIT_HELPER_TEST = <<~TEST unless defined?(TESTUNIT_HELPER_TEST)
79
80
  end
80
81
 
81
82
  def test_foo_helper
83
+ pend "add some examples to (or delete) \#{__FILE__}"
82
84
  assert_equal nil, helpers.foo
83
85
  end
84
86
  end
@@ -32,9 +32,17 @@ Padrino.configure_apps do
32
32
  # a fallback, so set it in production.
33
33
  set :session_secret, ENV.fetch('SESSION_SECRET') { '<%= SecureRandom.hex(32) %>' }
34
34
  set :protection, except: :path_traversal
35
+ <% if options.api? -%>
36
+ # CSRF protection follows sessions, because without a session it rejects every request.
37
+ # `enable :sessions` above or in an app turns on both, except in the test environment.
38
+ # For another session store, give the :sessions setting `use:` and `config:` options, here
39
+ # or in an app, rather than adding a session middleware in config.ru.
40
+ set :protect_from_csrf, proc { sessions? && Padrino.env != :test }
41
+ <% else -%>
35
42
  # CSRF protection is off in the test environment so test requests don't need a
36
43
  # token. To test it, `enable :protect_from_csrf` in an app.
37
44
  set :protect_from_csrf, Padrino.env != :test
45
+ <% end -%>
38
46
  end
39
47
 
40
48
  # Mounts the core application for this project
@@ -116,7 +116,8 @@ module Padrino
116
116
  return unless defined?(controller_template_name)
117
117
 
118
118
  controller_content = instance_eval(controller_template_name).gsub(/!PATH!/, 'Controller').gsub(/!NAME!/, '').gsub(/!EXPANDED_PATH!/, '/')
119
- helper_content = instance_eval(helper_template_name).gsub(/!NAME!/, "#{@project_name}::#{@app_name}::#{DEFAULT_HELPER_NAME}")
119
+ helper_name = "#{@project_name}::#{@app_name}::#{DEFAULT_HELPER_NAME}"
120
+ helper_content = instance_eval(helper_template_name).gsub(/!NAME!/, helper_name).gsub(/!CONSTANT_NAME!/, helper_name)
120
121
 
121
122
  proc { |*args| args.map { |str| str.gsub!(/!PATH!/, recognize_path) } }.call(controller_content, helper_content)
122
123
 
@@ -242,7 +242,10 @@ if PadrinoTasks.load?(:activerecord, defined?(ActiveRecord))
242
242
  desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR.'
243
243
  task dump: :skeleton do
244
244
  require 'active_record/schema_dumper'
245
- File.open(ENV['SCHEMA'] || Padrino.root('db', 'schema.rb'), 'w') do |file|
245
+ # A project on a database server has no db/ until its first migration is generated.
246
+ schema_file = ENV['SCHEMA'] || Padrino.root('db', 'schema.rb')
247
+ FileUtils.mkdir_p(File.dirname(schema_file))
248
+ File.open(schema_file, 'w') do |file|
246
249
  ActiveRecord::SchemaDumper.dump(schema_dumper_source, file)
247
250
  end
248
251
  Rake::Task['ar:schema:dump'].reenable
@@ -261,6 +264,7 @@ if PadrinoTasks.load?(:activerecord, defined?(ActiveRecord))
261
264
  namespace :structure do
262
265
  desc 'Dump the database structure to a SQL file.'
263
266
  task dump: :skeleton do
267
+ FileUtils.mkdir_p(File.dirname(resolve_structure_sql))
264
268
  with_database(Padrino.env) do |config|
265
269
  case config[:adapter]
266
270
  when 'mysql', 'mysql2', 'em_mysql2', 'oci', 'oracle', 'jdbcmysql'
data/lib/padrino-gen.rb CHANGED
@@ -23,6 +23,11 @@ module Padrino
23
23
  DEV_PATH = File.expand_path('../../', __dir__)
24
24
 
25
25
  class << self
26
+ ##
27
+ # The error the project's config/boot.rb raised when the generator CLI loaded it, or nil.
28
+ #
29
+ attr_accessor :boot_error
30
+
26
31
  ##
27
32
  # Store our generators paths.
28
33
  #
data/padrino-gen.gemspec CHANGED
@@ -8,16 +8,21 @@ Gem::Specification.new do |s|
8
8
  s.authors = ['Padrino Team', 'Nathan Esquenazi', "Davide D'Agostino", 'Arthur Chiu']
9
9
  s.email = 'padrinorb@gmail.com'
10
10
  s.summary = 'Generators for easily creating and building padrino applications'
11
- s.homepage = 'http://www.padrinorb.com'
11
+ s.homepage = 'https://padrinorb.com'
12
12
  s.description = 'Generators for easily creating and building padrino applications from the console'
13
+ s.required_ruby_version = '>= 2.7.8'
13
14
  s.required_rubygems_version = '>= 1.3.6'
14
15
  s.version = Padrino.version
15
- s.date = Time.now.strftime('%Y-%m-%d')
16
16
  s.license = 'MIT'
17
+ s.metadata = {
18
+ 'bug_tracker_uri' => 'https://github.com/padrino/padrino-framework/issues',
19
+ 'changelog_uri' => 'https://github.com/padrino/padrino-framework/blob/master/CHANGES.rdoc',
20
+ 'documentation_uri' => 'https://padrinorb.com/guides/',
21
+ 'source_code_uri' => 'https://github.com/padrino/padrino-framework'
22
+ }
17
23
 
18
24
  s.extra_rdoc_files = Dir['*.rdoc']
19
25
  s.files = `git ls-files`.split("\n") | Dir.glob('{lib}/**/*')
20
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
26
  s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
22
27
  s.require_paths = ['lib']
23
28
  s.rdoc_options = ['--charset=UTF-8']
@@ -142,6 +142,13 @@ describe 'ActiveRecord tasks' do
142
142
  assert_match(/create_table "widgets"/, schema)
143
143
  end
144
144
 
145
+ it 'should migrate a new project on a server database, which has no db directory yet' do
146
+ FileUtils.rm_rf(File.join(@root, 'db'))
147
+ File.write(File.join(@root, 'config', 'boot.rb'), File.read(File.join(@root, 'config', 'boot.rb')).sub("Padrino.root('db', 'development.db')", "Padrino.root('development.db')"))
148
+ rake('ar:migrate')
149
+ assert_includes schema, 'ActiveRecord::Schema'
150
+ end
151
+
145
152
  it 'should dump the schema after ar:migrate:down and ar:migrate:up' do
146
153
  rake 'ar:migrate'
147
154
  rake 'ar:migrate:down', 'MIGRATION_VERSION=1'
@@ -163,6 +170,15 @@ describe 'ActiveRecord tasks' do
163
170
  assert_match(/INSERT INTO "schema_migrations" \(version\) VALUES\s*\('1'\)/, structure)
164
171
  end
165
172
 
173
+ it 'should dump the structure when there is no db directory yet' do
174
+ skip_without_sqlite_cli
175
+ File.write(File.join(@root, 'config', 'boot.rb'), File.read(File.join(@root, 'config', 'boot.rb')).sub("Padrino.root('db', 'development.db')", "Padrino.root('development.db')"))
176
+ rake 'ar:migrate'
177
+ FileUtils.rm_rf(File.join(@root, 'db'))
178
+ rake 'ar:structure:dump'
179
+ assert_file_exists File.join(@root, 'db', 'development_structure.sql')
180
+ end
181
+
166
182
  def rake_failing(*args)
167
183
  libs = %w[padrino-core padrino-gen].flat_map { |name| ['-I', File.expand_path("../../#{name}/lib", __dir__)] }
168
184
  env = { 'RACK_ENV' => 'development', 'BUNDLE_GEMFILE' => File.expand_path('../../Gemfile', __dir__) }
@@ -1,4 +1,5 @@
1
1
  require_relative 'helper'
2
+ require 'open3'
2
3
 
3
4
  describe 'AppGenerator' do
4
5
  def setup
@@ -66,6 +67,195 @@ describe 'AppGenerator' do
66
67
  assert_match_in_file(/^ set :session_secret, ENV\.fetch\('SESSION_SECRET'\) \{ '\h{64}' \}$/, "#{@apptmp}/sample_project/config/apps.rb")
67
68
  end
68
69
 
70
+ it 'should write an app without mailer, helpers, sessions or CSRF in an api project' do
71
+ capture_io { generate(:project, 'sample_project', '--api', "--root=#{@apptmp}") }
72
+ capture_io { generate(:app, 'demo', "--root=#{@apptmp}/sample_project") }
73
+ app_file = "#{@apptmp}/sample_project/demo/app.rb"
74
+ assert_no_match_in_file(/Padrino::(Mailer|Helpers)/, app_file)
75
+ assert_no_match_in_file(/set :sessions/, app_file)
76
+ # An older api project turns CSRF on for every app, so the app settles it itself.
77
+ assert_match_in_file(/^\s*set :protect_from_csrf, proc \{ sessions\? && Padrino\.env != :test \}$/, app_file)
78
+ end
79
+
80
+ it 'should write a tiny app without mailers in an api project' do
81
+ capture_io { generate(:project, 'sample_project', '--api', '--lean', "--root=#{@apptmp}") }
82
+ capture_io { generate(:app, 'demo', '--tiny', "--root=#{@apptmp}/sample_project") }
83
+ assert_no_match_in_file(/Padrino::(Mailer|Helpers)/, "#{@apptmp}/sample_project/demo/app.rb")
84
+ assert_no_file_exists("#{@apptmp}/sample_project/demo/mailers.rb")
85
+ end
86
+
87
+ it 'should keep mailer, helpers and sessions in a project with the padrino gem' do
88
+ capture_io { generate(:project, 'sample_project', "--root=#{@apptmp}") }
89
+ capture_io { generate(:app, 'demo', '--tiny', "--root=#{@apptmp}/sample_project") }
90
+ app_file = "#{@apptmp}/sample_project/demo/app.rb"
91
+ assert_match_in_file(/register Padrino::Mailer/, app_file)
92
+ assert_match_in_file(/register Padrino::Helpers/, app_file)
93
+ assert_match_in_file(/set :sessions/, app_file)
94
+ assert_no_match_in_file(/protect_from_csrf/, app_file)
95
+ assert_file_exists("#{@apptmp}/sample_project/demo/mailers.rb")
96
+ end
97
+
98
+ it 'should keep the default app in a project without a Gemfile' do
99
+ capture_io { generate(:project, 'sample_project', '--api', "--root=#{@apptmp}") }
100
+ File.delete("#{@apptmp}/sample_project/Gemfile")
101
+ capture_io { generate(:app, 'demo', "--root=#{@apptmp}/sample_project") }
102
+ app_file = "#{@apptmp}/sample_project/demo/app.rb"
103
+ assert_match_in_file(/register Padrino::Mailer/, app_file)
104
+ assert_match_in_file(/register Padrino::Helpers/, app_file)
105
+ assert_match_in_file(/set :sessions/, app_file)
106
+ end
107
+
108
+ it 'should keep the default app in a --dev project, whose Gemfile lists padrino in a loop' do
109
+ capture_io { generate(:project, 'sample_project', '--dev', "--root=#{@apptmp}") }
110
+ capture_io { generate(:app, 'demo', '--tiny', "--root=#{@apptmp}/sample_project") }
111
+ app_file = "#{@apptmp}/sample_project/demo/app.rb"
112
+ assert_match_in_file(/register Padrino::Mailer/, app_file)
113
+ assert_match_in_file(/register Padrino::Helpers/, app_file)
114
+ assert_match_in_file(/set :sessions/, app_file)
115
+ assert_file_exists("#{@apptmp}/sample_project/demo/mailers.rb")
116
+ end
117
+
118
+ it 'should keep the default app in a project that lists every padrino gem by name' do
119
+ capture_io { generate(:project, 'sample_project', '--api', "--root=#{@apptmp}") }
120
+ File.write("#{@apptmp}/sample_project/Gemfile", "gem 'padrino-helpers'\ngem 'padrino-mailer'\n", mode: 'a')
121
+ capture_io { generate(:app, 'demo', "--root=#{@apptmp}/sample_project") }
122
+ assert_match_in_file(/set :sessions/, "#{@apptmp}/sample_project/demo/app.rb")
123
+ end
124
+
125
+ # Boots the generated project in a process that loads only the gems its Gemfile names, as
126
+ # config/boot.rb would. Returns the output, ending in the status of a POST without a token.
127
+ def boot_project(project, *gems, env: 'development', path: '/demo/missing')
128
+ script = <<~RUBY
129
+ PADRINO_ROOT = #{project.inspect}
130
+ RACK_ENV = #{env.inspect}
131
+ require 'padrino-core'
132
+ #{gems.map { |gem| "require '#{gem}'" }.join("\n")}
133
+ require 'rack/mock'
134
+ require File.join(PADRINO_ROOT, 'config/apps.rb')
135
+ Padrino.load!
136
+ puts "status=\#{Rack::MockRequest.new(Padrino.application).post(#{path.inspect}).status}"
137
+ RUBY
138
+ out, = Open3.capture2e({ 'RACK_ENV' => env }, RbConfig.ruby, '-rbundler/setup', '-e', script)
139
+ out
140
+ end
141
+
142
+ { 'nothing' => [], 'padrino-helpers' => %w[padrino-helpers], 'padrino-mailer' => %w[padrino-mailer] }.each do |added, gems|
143
+ [false, true].each do |tiny|
144
+ it "should boot #{tiny ? 'a tiny' : 'an'} app in an api project with #{added} added to its Gemfile" do
145
+ project = "#{@apptmp}/sample_project"
146
+ capture_io { generate(:project, 'sample_project', '--api', "--root=#{@apptmp}") }
147
+ File.write("#{project}/Gemfile", gems.map { |gem| "gem '#{gem}'\n" }.join, mode: 'a')
148
+ capture_io { generate(:app, 'demo', *(tiny ? ['--tiny'] : []), "--root=#{project}") }
149
+ assert_match(/status=404$/, boot_project(project, *gems))
150
+ end
151
+ end
152
+ end
153
+
154
+ describe 'CSRF protection in an api project' do
155
+ def setup
156
+ super
157
+ @project = "#{@apptmp}/sample_project"
158
+ capture_io { generate(:project, 'sample_project', '--api', "--root=#{@apptmp}") }
159
+ end
160
+
161
+ def generate_app_with_route(name = 'demo')
162
+ capture_io { generate(:app, name, "--root=#{@project}") }
163
+ File.write("#{@project}/#{name}/controllers/ping.rb", "SampleProject::#{name.capitalize}.controllers { post('/ping') { 'posted' } }\n")
164
+ end
165
+
166
+ def edit(file, pattern, replacement)
167
+ File.write(file, File.read(file).sub(pattern, replacement))
168
+ end
169
+
170
+ it 'should accept a POST without a token while sessions are off' do
171
+ generate_app_with_route
172
+ assert_match(/status=200$/, boot_project(@project, path: '/demo/ping'))
173
+ end
174
+
175
+ it 'should reject a POST without a token after enabling sessions in the app' do
176
+ generate_app_with_route
177
+ edit("#{@project}/demo/app.rb", /^.*set :protect_from_csrf.*$/, "\\0\n enable :sessions")
178
+ assert_match(/status=403$/, boot_project(@project, path: '/demo/ping'))
179
+ end
180
+
181
+ it 'should reject a POST without a token after enabling sessions in config/apps.rb' do
182
+ generate_app_with_route
183
+ edit("#{@project}/config/apps.rb", '# enable :sessions', 'enable :sessions')
184
+ assert_match(/status=403$/, boot_project(@project, path: '/demo/ping'))
185
+ end
186
+
187
+ it 'should keep the test environment exemption with sessions enabled' do
188
+ generate_app_with_route
189
+ edit("#{@project}/config/apps.rb", '# enable :sessions', 'enable :sessions')
190
+ assert_match(/status=200$/, boot_project(@project, env: 'test', path: '/demo/ping'))
191
+ end
192
+
193
+ { "gem 'padrino'" => %w[padrino], "gem 'padrino-helpers'\ngem 'padrino-mailer'" => %w[padrino-helpers padrino-mailer] }.each do |lines, gems|
194
+ it "should protect an app with sessions after adding #{gems.join(' and ')} to the Gemfile" do
195
+ generate_app_with_route
196
+ File.write("#{@project}/Gemfile", "#{lines}\n", mode: 'a')
197
+ generate_app_with_route('web')
198
+ assert_match_in_file(/set :sessions/, "#{@project}/web/app.rb")
199
+ assert_match(/status=403$/, boot_project(@project, *gems, path: '/web/ping'))
200
+ assert_match(/status=200$/, boot_project(@project, *gems, path: '/demo/ping'))
201
+ end
202
+ end
203
+
204
+ it 'should accept a POST to a new app in an older project, which turns CSRF on for every app' do
205
+ edit("#{@project}/config/apps.rb", /^.*set :protect_from_csrf.*$/, ' set :protect_from_csrf, Padrino.env != :test')
206
+ generate_app_with_route
207
+ assert_match(/status=200$/, boot_project(@project, path: '/demo/ping'))
208
+ end
209
+ end
210
+
211
+ describe 'reading the Gemfile of an api project' do
212
+ def setup
213
+ super
214
+ @project = "#{@apptmp}/sample_project"
215
+ capture_io { generate(:project, 'sample_project', '--api', "--root=#{@apptmp}") }
216
+ end
217
+
218
+ def assert_api_app
219
+ app_file = "#{@project}/demo/app.rb"
220
+ assert_no_match_in_file(/Padrino::(Mailer|Helpers)/, app_file)
221
+ assert_no_match_in_file(/set :sessions/, app_file)
222
+ end
223
+
224
+ def prepend_to_gemfile(bytes)
225
+ gemfile = "#{@project}/Gemfile"
226
+ File.binwrite(gemfile, bytes.b + File.binread(gemfile))
227
+ end
228
+
229
+ it 'should read a Latin-1 Gemfile' do
230
+ prepend_to_gemfile("# Maintainer: Jos\xE9\n")
231
+ capture_io { generate(:app, 'demo', "--root=#{@project}") }
232
+ assert_api_app
233
+ end
234
+
235
+ it 'should read a UTF-8 Gemfile under an ASCII locale' do
236
+ prepend_to_gemfile("# Maintainer: Jos\u00E9\n")
237
+ external = Encoding.default_external
238
+ begin
239
+ capture_io do
240
+ Encoding.default_external = Encoding::US_ASCII
241
+ generate(:app, 'demo', "--root=#{@project}")
242
+ end
243
+ ensure
244
+ Encoding.default_external = external
245
+ end
246
+ assert_api_app
247
+ end
248
+
249
+ it 'should read a parenthesised gem declaration' do
250
+ gemfile = "#{@project}/Gemfile"
251
+ File.write(gemfile, File.read(gemfile).gsub(/^gem 'padrino-(\w+)'(.*)$/, "gem('padrino-\\1'\\2)"))
252
+ assert_match_in_file(/^gem\('padrino-core'/, gemfile)
253
+ capture_io { generate(:app, 'demo', "--root=#{@project}") }
254
+ assert_api_app
255
+ assert_match(/status=404$/, boot_project(@project))
256
+ end
257
+ end
258
+
69
259
  it 'should generate tiny app skeleton' do
70
260
  capture_io { generate(:project, 'sample_project', "--root=#{@apptmp}") }
71
261
  capture_io { generate(:app, 'demo', '--tiny', "--root=#{@apptmp}/sample_project") }
data/test/test_cli.rb CHANGED
@@ -77,6 +77,20 @@ describe 'Cli' do
77
77
  $padrino_test_boot = nil
78
78
  end
79
79
 
80
+ it 'should keep the error of a project boot that fails for the generators' do
81
+ FileUtils.mkdir_p(File.join(@apptmp, 'config'))
82
+ File.write(File.join(@apptmp, 'config/boot.rb'), "MissingBootConstant\n")
83
+ out, = capture_io { Padrino::Generators::Cli.new([], root: @apptmp).load_boot }
84
+ assert_match(/Problem loading/, out)
85
+ assert_match(/MissingBootConstant/, Padrino::Generators.boot_error.message)
86
+
87
+ File.write(File.join(@apptmp, 'config/boot.rb'), "\n")
88
+ capture_io { Padrino::Generators::Cli.new([], root: @apptmp).load_boot }
89
+ assert_nil Padrino::Generators.boot_error
90
+ ensure
91
+ Padrino::Generators.boot_error = nil
92
+ end
93
+
80
94
  it 'should give back the environment variables it sets while loading the project boot' do
81
95
  saved = { 'BUNDLE_GEMFILE' => ENV['BUNDLE_GEMFILE'], 'PADRINO_LOG_LEVEL' => ENV['PADRINO_LOG_LEVEL'] }
82
96
  ENV['BUNDLE_GEMFILE'] = '/caller/Gemfile'
@@ -164,6 +164,17 @@ describe 'ProjectGenerator' do
164
164
  assert_match('', err)
165
165
  end
166
166
 
167
+ it 'should generate an api project without sessions or CSRF protection' do
168
+ capture_io { generate(:project, 'sample_project', '--api', "--root=#{@apptmp}") }
169
+ assert_no_match_in_file(/set :sessions/, "#{@apptmp}/sample_project/app/app.rb")
170
+ assert_match_in_file(/^\s*set :protect_from_csrf, proc \{ sessions\? && Padrino\.env != :test \}$/, "#{@apptmp}/sample_project/config/apps.rb")
171
+ end
172
+
173
+ it 'should generate a tiny api project without mailers' do
174
+ capture_io { generate(:project, 'sample_project', '--api', '--tiny', "--root=#{@apptmp}") }
175
+ assert_no_file_exists("#{@apptmp}/sample_project/app/mailers.rb")
176
+ end
177
+
167
178
  it 'should generate tiny skeleton' do
168
179
  capture_io { generate(:project, 'sample_project', '--tiny', "--root=#{@apptmp}") }
169
180
  assert_dir_exists("#{@apptmp}/sample_project")
@@ -878,6 +889,30 @@ describe 'ProjectGenerator' do
878
889
  end
879
890
 
880
891
  describe 'tiny app skeleton generator for test component' do
892
+ { 'minitest' => /skip "add some examples/, 'testunit' => /pend "add some examples/, 'shoulda' => /pend "add some examples/ }.each do |test, pending|
893
+ it "should mark the tiny skeleton's #{test} placeholders pending, so a new project passes its own tests" do
894
+ capture_io { generate(:project, 'sample_project', '--tiny', "--root=#{@apptmp}", "--test=#{test}") }
895
+ %w[controllers/controllers_test.rb helpers/helpers_test.rb].each do |file|
896
+ assert_match_in_file(pending, "#{@apptmp}/sample_project/test/app/#{file}")
897
+ end
898
+ end
899
+ end
900
+
901
+ it "should leave the tiny skeleton's bacon placeholders without requirements, which bacon reports as missing" do
902
+ capture_io { generate(:project, 'sample_project', '--tiny', "--root=#{@apptmp}", '--test=bacon') }
903
+ %w[controllers/controllers_test.rb helpers/helpers_test.rb].each do |file|
904
+ assert_no_match_in_file(/^\s*[^#\s].*\.should/, "#{@apptmp}/sample_project/test/app/#{file}")
905
+ end
906
+ end
907
+
908
+ %w[bacon minitest rspec shoulda testunit].each do |test|
909
+ it "should substitute every marker in the tiny skeleton's #{test} tests" do
910
+ capture_io { generate(:project, 'sample_project', '--tiny', "--root=#{@apptmp}", "--test=#{test}") }
911
+ dir = test == 'rspec' ? 'spec' : 'test'
912
+ Dir["#{@apptmp}/sample_project/#{dir}/app/**/*.rb"].each { |file| assert_no_match_in_file(/![A-Z_]+!/, file) }
913
+ end
914
+ end
915
+
881
916
  it 'should properly generate for rspec' do
882
917
  capture_io { generate(:project, 'sample_project', '--tiny', "--root=#{@apptmp}", '--test=rspec') }
883
918
  assert_dir_exists("#{@apptmp}/sample_project/spec/")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: padrino-gen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.18.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Padrino Team
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2026-10-31 00:00:00.000000000 Z
14
+ date: 2026-11-01 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: padrino-core
@@ -19,14 +19,14 @@ dependencies:
19
19
  requirements:
20
20
  - - '='
21
21
  - !ruby/object:Gem::Version
22
- version: 0.18.0
22
+ version: 0.18.2
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - '='
28
28
  - !ruby/object:Gem::Version
29
- version: 0.18.0
29
+ version: 0.18.2
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: bundler
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -53,28 +53,28 @@ dependencies:
53
53
  requirements:
54
54
  - - '='
55
55
  - !ruby/object:Gem::Version
56
- version: 0.18.0
56
+ version: 0.18.2
57
57
  type: :development
58
58
  prerelease: false
59
59
  version_requirements: !ruby/object:Gem::Requirement
60
60
  requirements:
61
61
  - - '='
62
62
  - !ruby/object:Gem::Version
63
- version: 0.18.0
63
+ version: 0.18.2
64
64
  - !ruby/object:Gem::Dependency
65
65
  name: padrino-mailer
66
66
  requirement: !ruby/object:Gem::Requirement
67
67
  requirements:
68
68
  - - '='
69
69
  - !ruby/object:Gem::Version
70
- version: 0.18.0
70
+ version: 0.18.2
71
71
  type: :development
72
72
  prerelease: false
73
73
  version_requirements: !ruby/object:Gem::Requirement
74
74
  requirements:
75
75
  - - '='
76
76
  - !ruby/object:Gem::Version
77
- version: 0.18.0
77
+ version: 0.18.2
78
78
  description: Generators for easily creating and building padrino applications from
79
79
  the console
80
80
  email: padrinorb@gmail.com
@@ -193,10 +193,14 @@ files:
193
193
  - test/test_sql_helpers.rb
194
194
  - test/test_sql_helpers_dump.rb
195
195
  - test/test_task_generator.rb
196
- homepage: http://www.padrinorb.com
196
+ homepage: https://padrinorb.com
197
197
  licenses:
198
198
  - MIT
199
- metadata: {}
199
+ metadata:
200
+ bug_tracker_uri: https://github.com/padrino/padrino-framework/issues
201
+ changelog_uri: https://github.com/padrino/padrino-framework/blob/master/CHANGES.rdoc
202
+ documentation_uri: https://padrinorb.com/guides/
203
+ source_code_uri: https://github.com/padrino/padrino-framework
200
204
  post_install_message:
201
205
  rdoc_options:
202
206
  - "--charset=UTF-8"
@@ -206,7 +210,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
206
210
  requirements:
207
211
  - - ">="
208
212
  - !ruby/object:Gem::Version
209
- version: '0'
213
+ version: 2.7.8
210
214
  required_rubygems_version: !ruby/object:Gem::Requirement
211
215
  requirements:
212
216
  - - ">="